Reputation: 15
I am calling a http Get service method from a component with the aim of mapping the response back to a Person object within my component for display on the front end.
my component:
export class DisplayPersonComponent implements OnInit {
personId: number;
person: Person;
constructor(private route: ActivatedRoute, private service : PersonService) { }
ngOnInit() {
this.route.params.subscribe(params => {
this.personId = params['person-id']});
this.getPerson(this.personId);
}
getPerson(id: number)
{
this.service.getPerson(id).subscribe((data: Person) => { console.log(data);
this.person = data
});
}
}
my service method:
getPerson(personId : number): Observable<Person> {
let urlParams = new HttpParams().set("personId", personId.toString());
return this.http.get<Person>(this.apiUrl, { params: urlParams})
.pipe(map((data: Person ) => { return data }),
catchError(error => { return throwError(' Something went wrong! ');
})
);
}
}
I can inspect the data object in the component when it returns and it looks like json ie { PersonID: 1, Name: 'Name'} etc But this.Person is always undefined and there is no output/error to explain why. It's also worth mentioning I use the same objects for a POST method and it works fine and maps perfectly from client to server without any specified mapping.
Upvotes: 1
Views: 1279
Reputation: 31125
The variable this.personId
is assigned asynchronously. So by the time getPerson()
is called, it's still undefined.
Instead of nested subscriptions, you could use RxJS higher order mapping operator like switchMap
to map from one observable to another.
ngOnInit() {
this.route.params.pipe(
switchMap(params => this.service.getPerson(params['person-id']))
).subscribe((data: Person) => {
console.log(data);
this.person = data;
})
}
async
pipeIf you aren't using this.person
in the controller, you could skip the subscription there and use async
pipe in the template
Controller
import { Observable } from 'rxjs';
export class DisplayPersonComponent implements OnInit {
person$: Observable<Person>; // <-- type `Observable`
constructor(private route: ActivatedRoute, private service : PersonService) { }
ngOnInit() {
this.person$ = this.route.params.pipe(
switchMap(params => this.service.getPerson(params['person-id']))
);
}
}
Template
<ng-container *ngIf="(person$ | async) as person">
{{ person }}
<some-comp [person]="person">...</some-comp>
</ng-container>
Cannot read property
The error is thrown because the person
variable is undefined until it's assigned a value inside the subscription. The second option using async
should mitigate the issue. In any case, you could use safe navigation operator ?.
to check if a variable is defined before trying to access it's properties.
<div>
{{ person?.personId }}
{{ person?.someProp }}
{{ person?.someOtherProp }}
</div>
You could learn more about asynchronous data here.
Upvotes: 1
Reputation: 113
Your service method returns an observable, not the person directly. So if you are inspecting the person object before the observable actually returned an object then it will be undefined. It's not a bug, it's just how async code works.
In your markup, simply make sure to check if the object is undefined before rendering it :
<div *ngIf="person">...</div>
The reason why your POST method will work is because you are probably not actually doing anything with the response. If you were to do something like show a toast notification on POST success for example then the same principles would apply, you would have to observe the POST response and do something on success/error.
Upvotes: 0
Reputation: 535
Observables are async
, by the moment you're evaluating person
, the method inside subscribe that is called when the observable completes hasn't been called yet.
I don't know where you're using the person
object, but it seems like you need to rethink your logic a bit.
Fortunately, angular has the async pipe which can be used with *ngIf as such:
<div *ngIf="(getPerson(personId) | async) as person">
This pipe takes in an observable and "resolves" it to the person object in the html template. It also only renders what's inside of it after this request completes, so you can use properties of the object freely inside!
Source and some more info: https://ultimatecourses.com/blog/angular-ngif-async-pipe
Upvotes: 0