Reputation: 4081
I have a problem mapping my Angular 5 HttpClient responses to concrete TypeScript classes.
I have a following Angular 5 class:
export class MaintainableUser {
constructor(public id: string, public name: string, public privileges: string[]) {
}
public hasPrivilege(privilege: string): boolean {
return this.privileges.indexOf(privilege) != -1;
}
public togglePrivilege(privilege: string) {
if (this.hasPrivilege(privilege)) {
this.privileges.splice(this.privileges.indexOf(privilege), 1);
} else {
this.privileges.push(privilege);
}
console.log(this.privileges);
}
}
I have a following Angular5 service:
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {MaintainableUser} from './maintain-users/maintainable-user';
import {Observable} from 'rxjs/Observable';
import {catchError} from 'rxjs/operators';
import {ApiErrorHandler} from '../api-error-handler';
@Injectable()
export class AdminService {
constructor(private http: HttpClient, private errors: ApiErrorHandler) {
}
getAllUsers(): Observable<MaintainableUser[]> {
return this.http.get<MaintainableUser[]>('api/admin/users').pipe(catchError(this.errors.handle));
}
}
That's nice and clean and now I can use my service like this:
import {Component, OnInit} from '@angular/core';
import {MaintainableUser} from './maintainable-user';
import {AdminService} from '../admin.service';
@Component({
selector: 'app-maintain-users',
templateUrl: './maintain-users.component.html',
styleUrls: ['./maintain-users.component.scss']
})
export class MaintainUsersComponent implements OnInit {
constructor(private adminService: AdminService) {
}
ngOnInit() {
this.reloadMaintainableUsers();
}
private reloadMaintainableUsers() {
this.adminService.getAllMaintainableUsers().subscribe(
data => {
console.log(data);
console.log(data[0] instanceof MaintainableUser);
data[0].hasPrivilege('ADMIN');
},
err => {
console.log('Service error: ' + err);
}
);
}
}
Problem is that the data I receive is not actually a list of MaintainableUser. The response block from my MaintainableUsersComponent has the following output:
{"id": "john", "name": "John Doe", "privileges": ["ADMIN"]}
false
ERROR TypeError: _v.context.$implicit.hasPrivilege is not a function
Why is that? Why is my data[0] not instanceof MaintainableUser? I'm very new with Angular but I understand from different tutorials that since version 4 I should be able to auto map exactly like that to my classes/interfaces. Or is it just a fake thing so you could be safe that your object is strongly typed but you can not be sure it is instance of the actual object itself? It would be really nice if I could have some helper methods in my response classes so I could use them in view's for example, but currently I have not found a clean way to do it.
Upvotes: 2
Views: 1811
Reputation: 136
It is rather problem with understanding how classes/objects work. What is returned from API is just a plain object (parsed JSON) which doesn't have any methods of MaintainableUser
because it is not instance of your class. It only has same properties as your class.
You need to manually pass values from object into constructor and create instance of your class. Angular doesn't do it automatically. I often do something like this:
export class MaintainableUser {
public static createInstance({ id, name, privileges }): MaintainableUser {
return new MaintainableUser(id, name, privileges);
}
constructor(public id: string, public name: string, public privileges: string[]) {}
public hasPrivilege(privilege: string): boolean {
return this.privileges.indexOf(privilege) != -1;
}
public togglePrivilege(privilege: string) {
if (this.hasPrivilege(privilege)) {
this.privileges.splice(this.privileges.indexOf(privilege), 1);
} else {
this.privileges.push(privilege);
}
console.log(this.privileges);
}
}
And then:
getAllUsers(): Observable<MaintainableUser[]> {
return this.http.get<MaintainableUser[]>.('api/admin/users').pipe(
map(MaintainableUser.createInstance)
catchError(this.errors.handle)
);
}
Upvotes: 3
Reputation: 7427
Your MaintainableUser
type is being mapped just fine, so the HttpResponse is correctly mapping that generic type and the Typescript compiler isn't complaining, but your response is not an instance of MaintainableUser
. It's still just an object that was parsed from JSON.
Your MaintainableUser
class has a method property, which is equivalent to adding that method to MaintainableUser.prototype
. In fact, that's exactly what the Typescript compiler does, when targeted to ES5.
Remember that Typescript types have no effect on the transpiled Javascript. Passing that MaintainableUser[]
type to the HttpClient
simply adds strong typing to the response. Essentially, you're just telling your IDE and the compiler that the Response is a MaintainableUser[]
, but it's not actually an instance of MaintainableUser
. You need to use the new
keyword to pass on that prototype.
I'd refactor MaintainableUser
to accept that Response as a constructor parameter and map the response to a new instance of MaintainableUser
, or move the hasPrivelage
method to a service.
Upvotes: 1
Reputation: 1431
instanceof
checks the prototype chain, so unless your getAllMaintainableUsers()
is creating new MaintainableUser
objects, it will return false. My guess (from your getAllUsers()
call above) is that you are directly returning an object from a service. Even though you are annotating the type as MaintainableUser[]
, since it is not created with the prototype of MaintainableUser
, instanceof will not return true for data[0] instanceof MaintainableUser
.
Upvotes: 0