Typescript - Why should I rewrite all members to implement an interface? - oop

I have an interface with some optional variables like:
interface A {
id: string;
name?: string;
email?: string;
...
}
What I want to do is that
class B implements A {
constructor(x: string, y: string, ...) {
this.id = x;
this.name = y;
...
}
getName(): string {
return this.name;
}
}
I don't want to rewrite all members that I will use and I need some members to stay optional. Each interface will be implemented with only one class, so if I rewrite all the members in class B than the interface A becomes useless.
You may ask "Why you need interface A anyway?". I need it because I am using it from some other project, and I have to extend and implement it with some functions.
Any solution or different idea about that implementation?

One option is to use Object.assign like so:
interface A {
id: string;
name?: string;
email?: string;
}
class B implements A {
id: string;
name: string;
email: string;
constructor(data: A) {
Object.assign(this, data);
}
getName(): string {
return this.name;
}
}
(code in playground)

Related

How do I create an instance of a Class passed as parameter to a function?

I'm building a library in Kotlin and here's my usecase
I have a base class
abstract class Component(...) {
// ... class body
}
I want the users of my library to define their own sub-classes like say:
class MyComponent() : Component() {
// .. class body
}
How can I write a helper function that takes in this derived class as a param and create an instance out of it. Something like:
fun helper(component: Class, props: HashMap<String, String>) : Component {
// somehow create a new instance of Class and return it?
}
Thanks!
You can have users pass a constructor reference:
fun helper(componentConstructor: ()->Component, props: Map<String, String>) : Component {
val component = componentConstructor()
// set it up and return it.
}
// usage:
val component = helper(::MyComponent, emptyMap())
Better for props not to require a specific type of map since it doesn’t matter here. Needless burden for users of your library.
abstract class Component(val prop1: String, val prop2: String) {
// ... class body
}
class MyComponent(prop1: String, prop2: String) : Component (prop1, prop2) {
// ... class body
}
fun helper(component: Class<MyComponent>, props: Map<String, String>): Component {
val constructor = component.constructors.first { it.parameterCount == props.size }
val arguments = props.values.toTypedArray()
return constructor.newInstance(*arguments) as Component
}
val instance = helper(MyComponent::class.java, mapOf("prop1" to "value1", "prop2" to "value2"))
println(instance.prop1 + ", " + instance.prop2) // Prints: value1, value2

Typescript how to declare a function that returns a lowest common denominator type?

i want declare a function that returns a common type or its extended type
interface Common {
id: number;
}
interface AdditionalInformation extends Common {
myname: string;
}
Surely the function returns an object containing the id property
and wishing it could also return the myname property
I tried to declare the function like this:
export class Lib {
public static lowestCommonDenominator <T extends Common>(): Common {
const a: Common = { id: 1 };
return a;
}
public static firstCaseFunction(): Common {
const ok: Common = this.lowestCommonDenominator();
return ok;
}
public static secondCaseFunction(): AdditionalInformation {
// Property 'myname' is missing in type 'Common' but required in type 'AdditionalInformation'.ts(2741)
const ko: AdditionalInformation = this.lowestCommonDenominator();
return ko;
}
}
But when I assign the function to an extended type, I get the error:
Property 'myname' is missing in type 'Common' but required in type
'AdditionalInformation'.ts(2741)
Is it possible to implement what I want?
This code snippet removes the error
export class Lib {
public static lowestCommonDenominator <T extends Common>(): T {
const a: Common = { id: 1 };
return a as T;
}
public static firstCaseFunction(): Common {
const ok: Common = this.lowestCommonDenominator();
return ok;
}
public static secondCaseFunction(): AdditionalInformation {
const ko: AdditionalInformation = this.lowestCommonDenominator<AdditionalInformation>();
return ko;
}
}

Supplied parameters do not match any signature of call target in typeScript using with Angular 2

Object structure look like as below:
export class Recipe {
public name: string;
public description: string;
public imagePath: string;
constructorn(name: string, desc: string, imagePath: string) {
this.name = name;
this.description = desc;
this.imagePath = imagePath;
}
}
And my call statement:
export class RecipeListComponent implements OnInit {
recipes: Recipe[] = [
new Recipe('Test Recipe', 'This is simply a test',
'https://cdn.pixabay.com/photo/2016/06/15/19/09/food-
1459693_960_720.jpg')
];
}
Though I am passing all the parameter but still I am getting the error "Supplied parameters do not match any signature of call target"
You misspelled constructor and might want to use parameter properties.
export class Recipe {
constructor(public name: string, public desc: string, public imagePath: string) {
// Insert logic..
}
}
This should do the job.

Using Typescript object spread operator with this keyword

In my code I often have to copy data from json to instantiate class in constructor.
function append(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key) {
dst[key] = src[key];
}
}
};
export class DataClass {
id: number;
title: string;
content: string;
img: null | string;
author: string;
// no methods, just raw data from API
}
export class AdoptedClass1 extends DataClass {
// has same fields as DataClass
showcase: string;
constructor (data: DataClass) {
append(data, this);
// do some stuff
}
}
// similar code for AdoptedClass2
I'm wondering if I can replace append function call in constructor with object spread operator
For your need I'll prefer to use Object.assign(this, data) over your custom made append function. Nevertheless have a look at the documentation to understand the limitation of it.
Back to your main question: it is not possible to use the spread operator to do what you want. Many people are interested in that feature but it has been put on hold as you can see here.
To get closer of what you ask we can refactor your code a little:
export class DataClass {
id: number
title: string
content: string
img: null | string
author: string
constructor(data: DataClass) {
Object.assign(this, data)
}
}
export class AdoptedClass1 extends DataClass {
showcase: string
constructor (data: DataClass) {
super(data)
// do some stuff
}
}
By simply adding the constructor to the data class you will be allowed to use super(data) in children and IMHO the code will be a lot cleaner.
You can use object spread operator by replacing this line:
append(data,this)
with this line
data = {...data, ...this};

How to take a subset of an object using an interface?

Suppose I have this class and interface
class User {
name: string;
age: number;
isAdmin: boolean;
}
interface IUser {
name: string;
age: number;
}
And then I get this json object from somewhere
const data = {
name: "John",
age: 25,
isAdmin: true
}
I want to subset data using IUser and remove the isAdmin property like this
let user = subset<IUser>(data);
// user is now { name: "John", age: 25 }
// can safely insert user in the db
My question is how do I implement that function in TypeScript?
function subset<T>(obj: object) {
// keep all properties of obj that are in T
// keep, all optional properties in T
// remove any properties out of T
}
There's no way to do that which is better than:
function subset(obj: IUser) {
return {
name: obj.name,
age: obj.age
}
}
The typescript interfaces don't exist at runtime (which is when subset is invoked) so you cannot use the IUser interface to know which properties are needed and which aren't.
You can use a class which does "survive" the compilation process but:
class IUser {
name: string;
age: number;
}
Compiles to:
var IUser = (function () {
function IUser() {
}
return IUser;
}());
As you can see, the properties aren't part of the compiled output, as the class members are only added to the instance and not to the class, so even a class won't help you.
You can use decorator and metadata (more on that here) but that sounds like an overkill for your scenario.
Another option for a more generic subset function is:
function subset<T>(obj: T, ...keys: (keyof T)[]) {
const result = {} as T;
keys.forEach(key => result[key] = obj[key]);
return result;
}
let user1 = subset(data, "name", "age");
let user2 = subset(data, "name", "ag"); // error: Argument of type '"ag"' is not assignable to parameter of type '"name" | "age" | "isAdmin"'