Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait for Data Before Rendering View in Angular 5

Tags:

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 ?

like image 979
user2304483 Avatar asked Mar 27 '18 04:03

user2304483


2 Answers

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.

like image 178
Pandiyan Cool Avatar answered Oct 06 '22 17:10

Pandiyan Cool


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.

like image 41
a2345sooted Avatar answered Oct 06 '22 15:10

a2345sooted