TypeScript basic programming : interface issue - javascript

Just trying to learn TypeScript.
Not sure why I'm still getting below error.
Note: I have tried meeting all the requirements of interface
interface Greet {
greet(name?: Greet): string;
(val: string): string;
}
class Person implements Greet {
greet(name?: Greet): string {
return 'Hello ' + name;
}
obj(val: string):string {
return 'Hello';
};
}
Error
TsFiles/OopsTest.ts(8,7): error TS2420: Class 'Person' incorrectly implements interface 'Greet'.
Type 'Person' provides no match for the signature '(val: string): string'
8:26:50 PM - Compilation complete. Watching for file changes.

If you're trying to create a hybrid type then, per the documentation, the implementation should look something like:
function getPerson(): Greet {
let person = <Greet>function(val: string) { return '' };
person.greet = function(name?: Greet) { return '' };
return person;
}
Having (val: string): string; in the interface requires the implementation of Greet to be a function, not a class.

interface Greet {
greet(name?: Greet): string;
obj(val: string): string; // <<<=== `obj` missing
}

Related

TypeScript Mixins and Constructor Names

I have this sample of code experimenting with mixins in TypeScript. However, it is not returning what I am expecting.
It should give me: User ({"id":3,"name":"Lorenzo Delaurentis"}).
Instead, I am getting: Function ({"id":3,"name":"Lorenzo Delaurentis"}).
The line let Name = Class.constructor.name should give me User, but it is not. Am I missing something obvious here?
type ClassConstructor<T> = new(...args: any[]) => T
function withDebug<C extends ClassConstructor<{
getDebugValue(): object
}>>(Class: C) {
return class extends Class {
constructor(...args: any[]) {
super(...args)
}
debug() {
let Name = Class.constructor.name
let value = this.getDebugValue()
return `${Name} (${JSON.stringify(value)})`
}
}
}
class DebugUser {
constructor(
private id: number,
private firstName: string,
private lastName: string
) {}
getDebugValue() {
return {
id: this.id,
name: `${this.firstName} ${this.lastName}`
}
}
}
let User = withDebug(DebugUser)
let user = new User(3, 'Lorenzo', "Delaurentis")
console.log(user.debug())
P.S. I compiled with tsc mixins --target ES6. Otherwise, I get an error: error TS2339: Property 'name' does not exist on type 'Function'.
You want just Class.name. The Class.constructor is Function.

How to implement a Typescript interface that allows additional properties?

Ok, I have been struggling with this one as all information I find is about how to define interfaces that allow other properties, but not how to create a class that can implement the interface.
I have (or want to have) the following interface:
export interface IEnvironment {
value: string;
names: string[];
[x: string | 'value' | 'names']: (() => boolean) | string | string[]
};
Then I want a class that implements said interface, but I only want to implement the value and names properties.
For full disclosure, I want to create an environment object with value, names and one function per name in names. Like this:
export class Environment implements IEnvironment {
value: string;
names: Array<string>;
static defaultNames: string[] = ['Development', 'PreProduction', 'Production'];
constructor(value: string, names?: Array<string>) {
this.value = value;
this.names = names ?? Environment.defaultNames;
let currEnvFound = false;
this.names.forEach((name) => {
// Look at all the hoops I had to jump so TypeScript would not complain. Suggestions welcome.
(this as unknown as { [x: string]: () => boolean })[`is${name}`] = function () { return (this as unknown as Environment).value === name; };
currEnvFound = currEnvFound || name === value;
});
// Throw if the current environment value was not found.
if (!currEnvFound) {
throw new Error(`The provided environment value "${value}" was not found among the provided list of environments.`);
}
}
};
Now this works except for one error I get:
Class 'Environment' incorrectly implements interface 'IEnvironment'.
Index signature for type 'string' is missing in type 'Environment'.
So how can I do this? I'm a noob in the TypeScript arena, so I'd rather ask the experts.
If no solution, could this be worked around with another interface that extends IEnvironment? Like remove the extra properties thing and move it to another interface that I would use as consumer of the object so I get the correct Intellisense.
Thank you very much in advance.
You just need to declare this dynamic x property from interface as class property.
Add this line as your class property: [x: string]: string|(() => boolean)|string[];
Finally, your class looks like this:
class Environment implements IEnvironment {
value: string;
names: Array<string>;
static defaultNames: string[] = ['Development', 'PreProduction', 'Production'];
//ADD THIS
[x: string]: string|(() => boolean)|string[];
constructor(value: string, names?: Array<string>) {
this.value = value;
this.names = names ?? Environment.defaultNames;
let currEnvFound = false;
this.names.forEach((name) => {
// Look at all the hoops I had to jump so TypeScript would not complain. Suggestions welcome.
(this as unknown as { [x: string]: () => boolean })[`is${name}`] = function () { return (this as unknown as Environment).value === name; };
currEnvFound = currEnvFound || name === value;
});
// Throw if the current environment value was not found.
if (!currEnvFound) {
throw new Error(`The provided environment value "${value}" was not found among the provided list of environments.`);
}
}
};

Typescript complaining about not assigning a get property

I have this code stackblitz
export class Student {
id: number;
name: string;
age?:number;
get studentType():string {
return 'fullTime'
}
constructor(params: Student) {
Object.assign(this, params);
}
}
const student = new Student({id:1, name: 'Jon'}); //ts error here
I get the below error
Argument of type '{ id: number; name: string; }' is not assignable to parameter of type 'Student'.
Property 'studentType' is missing in type '{ id: number; name: string; }'.
While studentType is a get only property and can't bet set.
What is the reason for that and how can I solve it?
ps. (I don't want to make it nullable like studentType? or convert it to just a function)
Getters / Setters are exactly like regular properties, thats why Typescript can't distinguish between a getter / setter and a regular property. You could make all properties optional though, with that you can omit the studentType:
constructor(params: Partial<Student>) {
Object.assign(this, params);
}
However other properties (e.g. name) could also be omitted now. To make that more typesafe you could introduce an additional interface:
export interface StudentData {
id: number;
name: string;
age?:number;
}
export class Student implements StudentData {
get studentType():string {
return 'fullTime'
}
constructor(params: StudentData) {
Object.assign(this, params);
}
}
That is a more controversial topic in TypeScript.
For class, TypeScript consider the overall shape of the class to be the type.
This includes private variables and methods, and in this case, including the getter/setter.
One solution to your problem is you can use Partial<>
constructor(params: Partial<Student>) { ... }
or Pick<>
constructor(params: Pick<Student, 'id' | 'name' | 'age'>) { ... }
Another way is to create an interface yourself:
interface IStudent { id: number, name: string, age?:number }
class Student implements IStudent {
constructor(params: IStudent) { ... }
}
What is the reason for that?
Basically, {id:1, name: 'Jon'} is not a student, since that object lacks a studentType property. This seems obvious and idiotic but makes sense, since typescript cannot know wether you're gonna rely on that property of the argument or not.
In your constructor, you just call Object.assign and let it go. But you could be calling some function that actually relies on the argument having that property, which could led to a runtime error if not pointed out by typescript.
and how can I solve it?
Well, there are several answers already. I would just type the constructor parameter properly. If you expect an object that has id, name and/or age I would type it accordingly:
export class Student {
id: number;
name: string;
age?:number;
get studentType():string {
return 'fullTime'
}
constructor(params: {id: number, name: string, age?: number}) {
Object.assign(this, params);
}
}
const student = new Student({id:1, name: 'Jon'}); //ts error here
This is because you are giving the type in constructor as Student.
This can be done:
export class Student {
id: number;
name: string;
age?: number;
constructor(id:number, name:string,age?:number) {
this.age = age;
this.name = name;
this.id = id;
}
get studentType():string {
return 'fullTime'
}
}
const student = new Student(1, 'Jon');

Difference between the static and instance sides of classes

I am trying to understand interface topic in Typescript
when I came across Class type, I got this code from official docs
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
I can understand that Clock has no match for the signature new (hour: number, minute: number); that's why we get an error there.
But in docs the explaination is something which I am unable to understand. It goes in this way :
This is because when a class implements an interface, only the instance side of the class is checked. Since the constructor sits in the static side, it is not included in this check.
Any explanation would be appreciated.
The interface declares the method/members that the instances have, and not what the implementing class has.
For example check the Array and ArrayConstructor declarations:
interface Array<T> {
length: number;
toString(): string;
toLocaleString(): string;
push(...items: T[]): number;
pop(): T | undefined;
...
[n: number]: T;
}
interface ArrayConstructor {
new (arrayLength?: number): any[];
new <T>(arrayLength: number): T[];
new <T>(...items: T[]): T[];
(arrayLength?: number): any[];
<T>(arrayLength: number): T[];
<T>(...items: T[]): T[];
isArray(arg: any): arg is Array<any>;
readonly prototype: Array<any>;
}
As you can see, the Array has method/members which exist on any instance of array:
let a = [];
a.push(1, 2, 3);
console.log(a.length);
But the ArrayConstructor has the members/methods which exist on the Array itself:
console.log(Array. prototype);
console.log(Array.isArray(9));
The constructors are part of the "static" part which is why they are declared in the ArrayConstructor.
If you declare a constructor on an interface for example you'll have a problem implementing that interface:
interface MyInterface {
constructor();
getName(): string;
}
class MyClass implements MyInterface {
constructor() {}
getName() { return "name" };
}
Error:
Class 'MyClass' incorrectly implements interface 'MyInterface'. Types
of property 'constructor' are incompatible. Type 'Function' is not
assignable to type '() => void'. Type 'Function' provides no match for
the signature '(): any'.
Before you can get an instance you need to use the static side, the constructor, to get an instance. You don't need new in your interface anyway, your class is typed itself so typescript knows whatever arguments it has to pass along the constructor.
You can make benefits of an interface with type new if you want to pass a function or class that has to meet certain constructor requirements before it can be instantiated.
interface IFoo {
new(title: string);
}
function MyFunction(ctor: IFoo, title:string) {
return new ctor(title);
}
class MyClass {
constructor(public title: string) {}
}
class MySecondClass {
constructor(public title: string) {}
}
var myClass = MyFunction(MyClass, 'title');
var mySecondClass = MyFunction(MySecondClass, 'title');
console.log(myClass.title, mySecondClass.title);
In fact, a TypeScript class is a regular function in JavaScript which is static when you don't use new in front of it. This is were the docs are referring to.
// static side
function Person() {
}
Person.SayHi = function () {
return 'Hello';
}
console.log(Person.SayHi()); // static function..
var person = new Person() // instance side
See also this answer

Unable to assign value to typescript dictionary

I created a typescript dictionary. When I assign value to it, I am getting the following error.
TypeError: Cannot set property 'v1/items/someItemType/someItemId/EVENT/some
DataTypeId' of undefined
at MainClass.subscribe (G:\Projects\src\main\MainClass
.js:9:3522)
at Context.<anonymous> (G:\Projects\test\main\Wish.
spec.js:45:18)
at callFn (G:\Projects\node_modules\mocha\lib\runna
ble.js:315:21)
at Hook.Runnable.run (G:\Projects\node_modules\moch
a\lib\runnable.js:308:7)
at next (G:\Projects\node_modules\mocha\lib\runner.
js:298:10)
at Immediate._onImmediate (G:\Projects\node_modules
\mocha\lib\runner.js:320:5)
Below is how I declared the dictionary.
interface StringTopicToMyTopicMap {
[topic: string]: MyTopic;
};
Below is the interface for MyTopic.
export interface MyTopic {
dataTypeID: string;
itemID: string;
itemType: string;
dataType: MyDataType;
qos: number;
}
Then I tried assign value to this dictionary as shown below.
private subscriptionTopicsMap: StringTopicToMyTopicMap;
subscribe(myTopic: MyTopic) {
if (_.isEmpty(myTopic)) {
throw new MyError('myTopic cannot be empty');
} else {
let topic: string;
topic = 'v1/items/' + myTopic.itemType + '/' + myTopic.itemID + '/'
+ myTopic.dataType + '/' + myTopic.dataTypeID;
this.subscriptionTopicsMap[topic] = myTopic;
this.client.subscribe(topic);
}
}
Error is thrown from the line,
this.subscriptionTopicsMap[topic] = myTopic;
I am new to typescript things. What am I doing wrong here? Please advice.
I usually do this in an easier way, no need for the second interface.
Also as the answer before, you gonna need ={} as below:
private subscriptionTopicsMap: { [id: string]: MyTopic; } = {};
this.subscriptionTopicsMap[topic] = myTopic;
Just initialize your property when declared
//private subscriptionTopicsMap: StringTopicToMyTopicMap;
private subscriptionTopicsMap: StringTopicToMyTopicMap = {};
you have not initialized your subscriptionTopicsMap dictionary,
Also you dont have any class which implements StringTopicToMyTopicMap, and may be instantiated
class StringTopicToMyTopicMapClass implements StringTopicToMyTopicMap {
[topic: string]: MyTopic;
};
private subscriptionTopicsMap: StringTopicToMyTopicMap = new StringTopicToMyTopicMapClass ();

Categories

Resources