1

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.

2
  • Maybe you should use generics on that class to pass the type of one argument in particular and access that methods. Generics typescript Commented Jan 25, 2019 at 12:43
  • @PauloGaldoSandoval That is what I was thinking, but I dunno how can I achieve that. When I receive the json data from the server, I need to convert it in class instantiations and, if I have an object property, I need to instantiate that object property into a class somehow (based on its property name or doing a relationship aka mapping author => 'Models\author.ts'). Any ideas? Sorry for the late reply. Commented Feb 1, 2019 at 11:31

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Sorry for the late reply. I'm actually doing that in the constructor of Page class, as I mentioned in my Q. The problem is that inside that class I have a property that has its own class type author: Author. Using your example, which is the one I'm using, does not fill the author property instantiating the author class like new Author();, it only fills the data as a generic object. Filling it like a new instantiation is a must, because I need to access to Author class methods. Sorry if it wasn't clear enough in my Q.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.