Reputation: 889
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
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