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

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


Try {{model?.person.name}} this should wait for model to not be undefined and then render.

Angular 2 refers to this ?. syntax as the Elvis operator. Reference to it in the documentation is hard to find so here is a copy of it in case they change/move it:

The Elvis Operator ( ?. ) and null property paths

The Angular “Elvis” operator ( ?. ) is a fluent and convenient way to guard against null and undefined values in property paths. Here it is, protecting against a view render failure if the currentHero is null.

The current hero's name is {{currentHero?.firstName}}

Let’s elaborate on the problem and this particular solution.

What happens when the following data bound title property is null?

The title is {{ title }}

The view still renders but the displayed value is blank; we see only "The title is" with nothing after it. That is reasonable behavior. At least the app doesn't crash.

Suppose the template expression involves a property path as in this next example where we’re displaying the firstName of a null hero.

The null hero's name is {{nullHero.firstName}}

JavaScript throws a null reference error and so does Angular:

TypeError: Cannot read property 'firstName' of null in [null]

Worse, the entire view disappears.

We could claim that this is reasonable behavior if we believed that the hero property must never be null. If it must never be null and yet it is null, we've made a programming error that should be caught and fixed. Throwing an exception is the right thing to do.

On the other hand, null values in the property path may be OK from time to time, especially when we know the data will arrive eventually.

While we wait for data, the view should render without complaint and the null property path should display as blank just as the title property does.

Unfortunately, our app crashes when the currentHero is null.

We could code around that problem with NgIf

<!--No hero, div not displayed, no error --><div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>

Or we could try to chain parts of the property path with &&, knowing that the expression bails out when it encounters the first null.

The null hero's name is {{nullHero && nullHero.firstName}}

These approaches have merit but they can be cumbersome, especially if the property path is long. Imagine guarding against a null somewhere in a long property path such as a.b.c.d.

The Angular “Elvis” operator ( ?. ) is a more fluent and convenient way to guard against nulls in property paths. The expression bails out when it hits the first null value. The display is blank but the app keeps rolling and there are no errors.

<!-- No hero, no problem! -->The null hero's name is {{nullHero?.firstName}}

It works perfectly with long property paths too:

a?.b?.c?.d


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.


EDIT: The angular team has released the @Resolve decorator. It still needs some clarification, in how it works, but until then I'll take someone else's related answer here, and provide links to other sources:


EDIT: This answer works for Angular 2 BETA only. Router is not released for Angular 2 RC as of this edit. Instead, when using Angular 2 RC, replace references to router with router-deprecated to continue using the beta router.

The Angular2-future way to implement this will be via the @Resolve decorator. Until then, the closest facsimile is CanActivate Component decorator, per Brandon Roberts. see https://github.com/angular/angular/issues/6611

Although beta 0 doesn't support providing resolved values to the Component, it's planned, and there is also a workaround described here: Using Resolve In Angular2 Routes

A beta 1 example can be found here: http://run.plnkr.co/BAqA98lphi4rQZAd/#/resolved . It uses a very similar workaround, but slightly more accurately uses the RouteData object rather than RouteParams.

@CanActivate((to) => {    return new Promise((resolve) => {        to.routeData.data.user = { name: 'John' }

Also, note that there is also an example workaround for accessing nested/parent route "resolved" values as well, and other features you expect if you've used 1.x UI-Router.

Note you'll also need to manually inject any services you need to accomplish this, since the Angular Injector hierarchy is not currently available in the CanActivate decorator. Simply importing an Injector will create a new injector instance, without access to the providers from bootstrap(), so you'll probably want to store an application-wide copy of the bootstrapped injector. Brandon's second Plunk link on this page is a good starting point: https://github.com/angular/angular/issues/4112