how define computed property in typescript - javascript

I want to add an object which has dynamic property name into an array but I don't know how to define the array
class Driver {
public id: string;
public name: string;
constructor(id , name) {
this.id = id;
this.name = name;
}
}
let drivers: Driver[];
let driver = new Driver("1111","tom");
drivers.push({[driver.id]:driver})

Since you don't know what the key for those objects will be beforehand, you'll want to use an index signature. So for your example, you would define the type like so:
let drivers: {[id: string]: Driver}[];

Related

Using Classes to define JSON structure

In my project, I have a handful of data model classes that take a response from an API in the constructor.
Here's an example, with an arbitrary toJSON method.
class Document {
id: number
name: string
constructor(init: DocumentStructure) {
this.id = init.id
this.name = init.name
}
toJSON() {
return {
id: this.id,
name: this.name
}
}
}
It's coming into the constructor as an object, so for correct typing I also have a type definition for the object structure that is separate from the class. Assume that it must come in as an object due to requirements further up the chain.
type DocumentStructure = {
id: number
handle: string
}
My question is: is there any way to use the class as a structural definition? Could I ever do something like the following, where the incoming init object is a JSON structure that matches the attributes of Document, but is not actual an instance of the class Document?
class Document {
id: number
name: string
constructor(init: Document) {
this.id = init.id
this.name = init.name
}
toJSON() {
return {
id: this.id,
name: this.name
}
}
}
If this is an impossible/bad idea, what are the TS best practices for dealing with this?
I would suggest separating the classes and input structures, e.g. class Document and interface DocumentDTO:
interface DocumentDTO {
id: number
name: string
}
class Document extends DocumentDTO {
constructor(init: DocumentDTO) {
this.id = init.id
this.name = init.name
}
toJSON() {
return {
id: this.id,
name: this.name
}
}
}
If though you are in some way restricted, you can also use the following approach:
// A helper type for extractig all non-function properties from type C
type SerializableProperties<C> = {
[K in keyof C]: C[K] extends Function ? never : K;
}[keyof C];
// This type will be the same as C but it will not have any function properties
//
// If you don't need the extra generality you can also just say
//
// type Serializable<C> = Omit<C, 'toJSON'>;
//
// But that might give you some headaches when you start adding methods to those classes :)
type Serializable<C> = Pick<C, SerializableProperties<C>>;
class DocumentClass {
id: string;
name: string;
constructor(init: Serializable<DocumentClass>) {
this.id = init.id
this.name = init.name
}
toJSON(): Serializable<DocumentClass> {
return {
id: this.id,
name: this.name
}
}
}
enter link description hereTypeScript Playground link here
If you're okay with keeping all of the class parameters, you can skip the toJSON method and use the built-in
// { id: 'foo', name: 'bar' }
JSON.stringify(new Document(...))

Generic way of validating the structure of javascript class

So I have a provider class
export class Provider {
activeAccount: boolean;
description: String;
name: String;
providerType: any;
publicUrl: String;
uuid: String;
isValidStructure(obj){
let keys = Object.keys(obj), valid = true,
properties = ['activeAccount', 'description', 'name', 'providerType', 'publicUrl', 'uuid']
keys.forEach((key)=>{
if(!properties.includes(key)){
valid = false
}
})
return valid
}
}
I have a method to validate that the data used to create the model is a property of the model, it works but is there a generic way of doing this
The properties array is hardcoded in the function isValidStructure
let validData: any = {name: 'some name'}
let invalidData: any = {namexx: 'the key is invalid'}
let provider = Object.assign(new Provider(), validData)
let provider2 = Object.assign(new Provider(), invalidData)
provider.isValidStructure(validData)//true
provider2.isValidStructure(invalidData)//false
Above is some examples of the function and its output
Is there some way of getting the properties of the class
There is no way to get a list of properties of the defined-class in javascript or out-of-the-box solution for typescript, you have to manually inject it.
export class Provider {
static properties = [...]
}
/// or
Provider.properties = []
You could define an interface that has all the common properties between the class and your passed in config obj and then have your class implement it and your constructor take that type as a param.
export interface IProvider {
activeAccount?: boolean;
description?: String;
name?: String;
providerType?: any;
publicUrl?: String;
uuid?: String;
}
export class Provider implements IProvider {
activeAccount: boolean;
description: String;
name: String;
providerType: any;
publicUrl: String;
uuid: String;
constructor(obj?: IProvider) {
if (obj) {
this.activeAccount = obj.activeAccount;
...
}
}
}
const provider = new Provider({...});
UPDATE
If none/any of the properties are required, you could add a ? in the interface between the prop name and colon. That way you can allow the constructor obj have as many of the valid props as it needs, but it would still raise an error if you try to pass in:
const provider = new Provider({ invalid: true });
Take a look at Typescript Interfaces for more info
UPDATE 2
To put this back into your original example:
let validData: any = {name: 'some name'}
let invalidData: any = {namexx: 'the key is invalid'}
let provider = Object.assign(new Provider(), validData)
let provider2 = Object.assign(new Provider(), invalidData)
Defining both validData and invalidData as any will force Typescript not to yell at you with red squigglies because it basically says "this might be an IProvider". So you should set validData: IProvider instead of any.
Also, the same thing happens with Object.assign() when adding props to a class instance. It appears that Typescript doesn't mind that you're attempting to put in invalid props if you do this:
const validData: IProvider = { name: 'some name' };
const invalidData: any = { namexx: 'the key is invalid' };
const provider: Provider = Object.assign(new Provider(validData), invalidData);
// Provider { name: 'some name', namexx: 'the key is invalid' }
However, if you again change any to IProvider on invalidData: IProvider, it'll yell at you for the namexx prop in there.
That all said, since typing and validating seems to be important to you, then you should try to ensure that all types are explicitly declared to allow Typescript to do what it does.

Reference equality for container objects

For a specific application we are storing id's of objects in specific classes. For example, a "product" object would have it's string id stored in a "ProductId" object. Likewise a "user" object would have it's string id stored in a UserId object (see example code below).
class Product {
id: ProductId;
price: number;
...
}
class User {
id: UserId;
name: string;
...
constructor(id: UserId, name: string) {
this.id = id;
this.name = name;
...
}
}
class ProductId {
id: string;
constructor(id: string) {
this.id = id;
}
}
class UserId {
id: string;
constructor(id: string) {
this.id = id;
}
}
One issue with this approach is that storing objects in a Map and then trying to retrieve them (see below code) does not work because two UserId's with the same underlying id do not compare equal with ===.
const users = new Map<UserId, User>();
const user = new User(new UserId('8753098'), 'John');
users.set(user.id, user);
console.log(users.get(new UserId('8753098')); //undefined
It seems that javascript does not have operator overloading, or has no way of overriding the equality function.
I have also thought of working with a global map, and create Id's with a static method :
class UserId {
private id: string;
constructor(id: string) {
this.id = id;
}
static userIds = new Map<string, UserId>();
static fromString(id: string) {
let userId = userIds.get(id);
if (userId === undefined) {
userId = new UserId(id);
userIds.set(id, userId);
}
return userId;
}
}
But that has a potential memory leak because all objects are retained in the map and never released.
Does anyone have a solution for this ?
Does anyone have a solution for this ?
Instead of class UserId just do a type type UserId = string.
More
If you are concerned about structural equality and would prefer nominal typing you can add a brand using an enum as shown here
enum UserIdBrand {}
type UserId = UserIdBrand & string;

Angular2 & Typescript - Property name does not exist on type

The suggested answer to this question using find does not work in Typescript, it cannot compile. I've looked at other similar questions, but they all seem a little different (context) in some way.
This is the array:
categories: Category[] = [];
This is Category object:
export class Category{
constructor(
id: string,
name: string,
category_types: Object[]
) {}
}
and I am trying to find like this (value is a string, eg 'Wood'):
let a = this.categories.find(v => v.name === value);
It says Property name does not exist on type 'Category'.
That is because your class Category does not have any properties. You can define parameter properties to create properties directly out of constructor parameters:
export class Category{
constructor(
public id: string,
public name: string,
public category_types: Object[]
) {}
}
Now all the parameters of Category are also its public properties.

How to properly set object properties in TypeScript with setters and getters?

How do you set each property of an object with a setter in TypeScript?
export class AuthService {
private _user:User; // User is my model
constructor(...){}
public get user()
{
return this._user;
}
public set user(value){
this._user = value;
}
...
Then setting anywhere gives errors when:
this.authService.user.id = data.userId;
this.authService.user.isLoggedIn = 'true';
MORE:
The user model:
export class User {
constructor(
public email: string,
public pass: string,
public id?: string,
public fname?: string,
public lname?: string,
public isLoggedIn?: string){}
}
The error: Cannot set property 'id' of undefined
You need to pass the whole user object to the setter, but you would need access to all other attributes of the user
this.authService.user = {
id: data.userId,
isLoggedIn: true
};
Alternatively, have individual setters for each property
public set id(value){
this._user.id = value;
}
public set isLoggedIn(value){
this._user.isLoggedIn = value;
}
Which you would call as thus
this.authService.id = data.userId;
this.authService.isLoggedIn = 'true';
The error message seems clear, you're trying to set attributes on an object that doesn't exist.
if this.authService.user === null you cannot set it's properties.
You have first to create a new User(...) somewhere and assign it to this.authService.user then you can go and change it's properties as needed.

Categories