I am new to Angular 5 and was able to create view with access to service methods using subscribe but encounter problem in delay in render. The user see first "undefined" string and after 0.5 sec it change to the correct value.
Here is the .ts file for the component:
public trips: Trip[]; public numberOfUsers : Map<string, number> = new Map<string, number>(); constructor(private tripService: TripService) { } ngOnInit() { this.getTrips(); } getTrips() { this.tripService.getTripForMe().subscribe( data => { this.trips = data;}, err => console.error(err), () => this.initNumberOfUsers() ); } initNumberOfUsers() { for (let i = 0; i < this.trips.length; i++) { let count; this.tripService.getNumberOfUsers().subscribe( data => { count = data}, err => console.error(err), () => this.numberOfUsers.set(this.trips[i].id, count) ); ; } } getNumberOfUsers(data) { return this.numberOfUsers.get(data.id); }
The method initNumberOfUsers is called after the first subscribe finish which is good, but the getNumberOfUsers(data) called probably before and get "undefined".
Here is the relevant part from the template ("data" is current value of a ngFor loop):
<span [innerText]="getNumberOfUsers(data)"></span>
Is there a way to make sure that both subscribe methods are sequentially finalized before rendering ?
UPDATE
I was able to create a resolver and the concole show the object but, I have difficulties to pass the data to the component.
Reolver code:
import { Injectable } from '@angular/core'; import { Router, Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { Trip} from .... import { TripService } from ..... import { Observable } from 'rxjs/Rx'; @Injectable() export class TripsResolve implements Resolve<Trip[]> { public trips: Trip[]; constructor(private service: TripService) {} resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { this.service.getTrips().subscribe( data => { this.trips = data }, err => console.error(err), () => console.log(this.trips) ); return this.trips; } }
From route
resolve: { trips: TripsResolve }
From module
providers: [ ... TripsResolve, ...
Data is not pass/not avilavle in the component:
import { Component, OnInit } from '@angular/core'; import { Trip } from ... import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: ... templateUrl: ... styleUrls: ... }) export class MyComponent implements OnInit { public trips: Trip[]; constructor(private route: ActivatedRoute, private router: Router) { } ngOnInit() { this.route.data.forEach(data => { this.trips = data.trips; console.log(this.trips); //can't see it }); } }
Any idea how can I access/retrieve the data in the component ?
I have faced similar issues when I'm dealing with data through API calls
I have tried two solutions
One is using *ngIf
The DOM will populate only when the data is ready.
<div *ngIf="isLoaded">Text to show</div>
Another one is using resolver, before rendering view itself it will prepare the data. But sometimes resolver will keep your screen as blank if data loads takes too much time.
@Injectable() export class HeroDetailResolve implements Resolve { constructor(private heroService: HeroService, private router: Router) { } resolve(route: ActivatedRouteSnapshot): Promise | boolean { let id = +route.params['id']; return this.heroService.getHero(id).then(hero => { if (hero) { return hero; } else { // id not found this.router.navigate(['/dashboard']); return false; } }); } }
So you have two options now.
I would recommend *ngIf. While loading of data is in progress, display some loader gif or some message to let the user know that something happening in the background.
Check out resolvers in Angular.
Here is a link: https://angular.io/api/router/Resolve
Much like guards, they run before the component is fully created and prevent you from having to have if/else logic in your template for waits while loading.
If you don't want to use a resolver, you can also do something like:
<span *ngIf="myVariable; else stillLoading" [innerText]="getNumberOfUsers(data)"></span> <ng-template #stillLoading><p>Loading...</p></ng-template>
myVariable
would be whatever you want to be defined before you render the <span>
tag. I just didn't want to look that closely into your map :)
The choice on whether to use a resolver or if/else in templates depends on what you want your UX to be like. There's probably other reasons too, but that one comes to mind first.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With