Search code examples
angularpromiseobservabledependency-resolver

Wait for Angular 2 to load/resolve model before rendering view/template


In Angular 1.x, UI-Router was my primary tool for this. By returning a promise for "resolve" values, the router would simply wait for the promise to complete before rendering directives.

Alternately, in Angular 1.x, a null object will not crash a template - so if I don't mind a temporarily incomplete render, I can just use $digest to render after the promise.then() populates an initially empty model object.

Of the two approaches, if possible I'd prefer to wait to load the view, and cancel route navigation if the resource cannot be loaded. This saves me the work of "un-navigating". EDIT: Note this specifically means this question requests an Angular 2 futures-compatible or best-practice method to do this, and asks to avoid the "Elvis operator" if possible! Thus, I did not select that answer.

However, neither of these two methods work in Angular 2.0. Surely there is a standard solution planned or available for this. Does anyone know what it is?

@Component() {
    template: '{{cats.captchans.funniest}}'
}
export class CatsComponent {

    public cats: CatsModel;

    ngOnInit () {
        this._http.get('/api/v1/cats').subscribe(response => cats = response.json());
    }
}

The following question may reflect the same issue: Angular 2 render template after the PROMISE with data is loaded . Note that question has no code or accepted answer in it.


Solution

  • The package @angular/router has the Resolve property for routes. So you can easily resolve data before rendering a route view.

    See: https://angular.io/docs/ts/latest/api/router/index/Resolve-interface.html

    Example from docs as of today, August 28, 2017:

    class Backend {
      fetchTeam(id: string) {
        return 'someTeam';
      }
    }
    
    @Injectable()
    class TeamResolver implements Resolve<Team> {
      constructor(private backend: Backend) {}
    
      resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
        return this.backend.fetchTeam(route.params.id);
      }
    }
    
    @NgModule({
      imports: [
        RouterModule.forRoot([
          {
            path: 'team/:id',
            component: TeamCmp,
            resolve: {
              team: TeamResolver
            }
          }
        ])
      ],
      providers: [TeamResolver]
    })
    class AppModule {}
    

    Now your route will not be activated until the data has been resolved and returned.

    Accessing Resolved Data In Your Component

    To access the resolved data from within your component at runtime, there are two methods. So depending on your needs, you can use either:

    1. route.snapshot.paramMap which returns a string, or the
    2. route.paramMap which returns an Observable you can .subscribe() to.

    Example:

      // the no-observable method
      this.dataYouResolved= this.route.snapshot.paramMap.get('id');
      // console.debug(this.licenseNumber);
    
      // or the observable method
      this.route.paramMap
         .subscribe((params: ParamMap) => {
            // console.log(params);
            this.dataYouResolved= params.get('id');
            return params.get('dataYouResolved');
            // return null
         });
      console.debug(this.dataYouResolved);
    

    I hope that helps.