DaGLiMiOuX
DaGLiMiOuX

Reputation: 889

Assign class instance property a value based on its type in Typescript Angular

I don't know if this is allowed in Typescript, but I'm working in an Angular 7 project and I want to instantiate a Page class fullfilling all his properties from DB object. These are my classes:

export class User {
    id: number;
    name: string;
    created_at: string;

    constructor(obj?: any) {
        Object.assign(this, obj);
    }

    getName(): string {
        return this.name;
    }
}

export class Page {
    id: number;
    title: string;
    author: User;


    constructor(obj?: any) {
        Object.assign(this, obj);
    }

    showTitle(): string {
        return this.title;
    }
}

Here is an example of my service method to retrieve the data:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { Page } from '../models/page';

@Injectable()
export class PageService {

    constructor(httpClient: HttpClient) {}

    getPage(id: number): Observable<Page> {
        return this.httpClient
                   .get<Page>('http://<my-server-ip>:<port>/api/pages')
                   .pipe(
                       map((page: Page) => {
                           console.log('retrieved', page);
                           return new Page(page);
                       })
                   );
    }
}

And here is an example of this function call in my component

export class MyCustomComponent implements OnInit {

    constructor(pageService: PageService) {}

    ngOnInit() {
        this.pageService.getPage()
            .subscribe((page: Page) => {
                console.log(page.showTitle());
            });
    }
}

This example works, but when I want to access to User methods, like:

console.log(page.author.getName());

I don't have access to them because it is not an instantiation of User class.

The same would happen with Page if I do not return a new instance of page class as an observable, thats why I use return new Page(page) after retrieving the data.

The problem is that I want to keep my constructors as generic as possible, so creating a constructor to assign the value manually (e.g.: this.author = new User(obj.author);) is not a valid workaround, as I want to implement it in every model or create a GenericModel then extend all my models.

Is there a way to fill a property with defined type in a instantiated class depending in its type?

This is what I tried so far, but it doesn't work:

export class Page {
    // ...
    constructor(obj?: any) {
        Object.keys(obj).forEach((key: string, index: number) => {
            if (typeof(obj[key]) === 'object' && obj[key] !== null) {
                this[key] = new (this[key].constructor)(obj[key]);
            } else {
                this[key] = obj[key]
            }
        });
    }
}
ERROR TypeError: Cannot read property 'author' of null
ERROR TypeError: Cannot read property 'constructor' of undefined

I understand that this is null when constructor is called, but I couldn't find another way to fill author property with a new instance to access to methods. Also, if I get a standard/default object like { ... }, the if will trigger and probably will throw an error too, as it does not have a constructor.

Upvotes: 1

Views: 4122

Answers (1)

DeborahK
DeborahK

Reputation: 60518

You could use Object.assign like this:

getPage(id: number): Observable<Page> {
    return this.httpClient
               .get<Page>('http://<my-server-ip>:<port>/api/pages')
               .pipe(
                   map((page: Page) => {
                       console.log('retrieved', page);
                       return Object.assign(new Page(), page);
                   })
               );
}

This code creates a new Page instance and then copies over all of the properties from the returned response (page in this example).

Then you don't need to modify your constructors.

UPDATE

NOTE: The spread syntax only copies over the properties, so I changed to use Object.assign instead.

Upvotes: 1

Related Questions