75

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.

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
  • 1
    You can read [lazy loading article](http://www.syntaxsuccess.com/viewarticle/lazy-loading-in-angular-2.0) from @TGH – Eric Martinez Jan 11 '16 at 22:03
  • 1
    @shannon the whole router just got deprecated and re-written. Hopefully they talk about this today at NgConf. – Langley May 05 '16 at 14:39
  • @Langley you mean they will completely re-write the angular 2 router? – Pascal May 11 '16 at 19:45
  • @Pascal it seems that way, take a look at this: https://angular.io/docs/ts/latest/guide/router-deprecated.html – Langley May 11 '16 at 21:01
  • well... than they could also write the 'deprected beta angular 2' replaced by the 'RC angular 2' Lets see wether the RC router can handle infinite child routers like aureliajs ;-) – Pascal May 11 '16 at 21:11
  • I would be happy with the capabilities of UI-Router :D. Although, it may not be renamed because it's completely getting re-specced. It may just be that they plan some smaller degree of breaking changes that they weren't comfortable calling "RC". One we know they planned to include but is not complete yet is this `Resolve` feature. – shannon May 11 '16 at 21:39
  • FYI, in case you haven't seen it yet, here is the most current and authoritative information on this. https://github.com/angular/angular/issues/4015 Note it is committed for RC. – shannon May 11 '16 at 21:41

6 Answers6

86

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

tehaaron
  • 2,250
  • 10
  • 31
  • 54
  • Thank you. This works well as a workaround, but ultimately I think it makes more sense to treat the object and all of its children as mandatory and just wait for it to be available to render, as suggested by other answers (if I can get them to work). Otherwise this decoration will be on every value in all of my pages, and I'm back to Angular 1.x functionality in this regard. It probably has performance issues (extra dirty checks) as well. – shannon Jan 14 '16 at 19:11
  • 3
    @shannon I don't believe that to be the case. Please see my update answer and the ng2 docs. – tehaaron Jan 14 '16 at 23:03
  • The reason for my suspicion regarding performance is that the `Observable` (same would hold for a `Promise`) has already been unwrapped. Now the only way to determine when it has changed is with our trusty Angular 1.x mechanism, the dirty check. Compare this to the use of an `Observable`, which is supported by the `async` pipe in the template itself. Angular internally can (I suspect it does) wire up the pipe to so that no watchers are necessary, and the node is rendered only when an observable arrives. It is effectively a message bus with event-driven actions. – shannon Jan 14 '16 at 23:56
  • Another minor issue is that putting Elvis operators everywhere defeats to some degree the goal of the Angular team's change of heart, regarding silently swallowing errors. You've probably experienced issues with this yourself, finding that templates (and the application as a whole) are harder to debug when functions are expected to handle null values. Further, if I have to make a choice of what to display, for example in an A:B scenario, now I *also* have to implement additional `ngIf` logic. I would really like to get the "wait to render component" approach working. – shannon Jan 15 '16 at 00:16
  • 3
    This operator is now called ["safe navigation operator"](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#safe-navigation-operator). – ssc-hrep3 Jul 10 '16 at 10:03
  • This operator doesn't work for two way binding, which severely limits its utility for this purpose. – Mud Nov 04 '16 at 17:35
  • Sure. I agree that if you want to display the empty data, then this is the right thing to do. But as the "safe navigation operator" link you provided mentions, if the content should never be viewed uninitialized, then this approach doesn't make sense. In keeping with my original question, which specifies that I wish to wait to display the interaction until after the model is resolved, I've selected the other answer. Thank you for this one, it is not quite what I asked for, but obviously very useful to many other people here. – shannon Jun 22 '17 at 18:48
38

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.

TetraDev
  • 16,074
  • 6
  • 60
  • 61
7

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

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
  • 1
    Your reffered plnker: http://run.plnkr.co/BAqA98lphi4rQZAd/#/resolved isn't available anymore. Thank you for pushing this feature. Maybe you can add the current feature which is scheduled for the RC1 release issue to your answer: https://github.com/angular/angular/issues/4015 – Philip Mar 21 '16 at 10:58
  • RouteData is immutable. – Hristo Venev May 30 '16 at 10:06
  • @HristoVenev: Does your comment (and associated downvote) relate to the applicability of the solution, while the Angular 2 Beta router is still current? – shannon May 30 '16 at 17:43
  • @shannon `RouteData` is immutable even for the beta router (`router-deprecated`). – Hristo Venev May 30 '16 at 19:08
  • Yes, I'm aware that RouteData is documented as "immutable". Let's ignore for a moment what that means to a JavaScript property set. Let's get right to the heart of your point. Are saying the solution does not work, or that there is a better solution available? Last I checked, my application shows no defects in this area, this solution was suggested by the Angular team, no competing solution has been offered by anyone else. Although it would be nice to follow the documentation's guidance, Resolve is not yet implemented, so that guidance doesn't merit a downvote. – shannon May 30 '16 at 21:30
4

Set a local value with the observer

...also, don't forget to initialize the value with dummy data to avoid uninitialized errors.

export class ModelService {
    constructor() {
      this.mode = new Model();

      this._http.get('/api/v1/cats')
      .map(res => res.json())
      .subscribe(
        json => {
          this.model = new Model(json);
        },
        error => console.log(error);
      );
    }
}

This assumes Model, is a data model representing the structure of your data.

Model with no parameters should create a new instance with all values initialized (but empty). That way, if the template renders before the data is received it won't throw an error.

Ideally, if you want to persist the data to avoid unnecessary http requests you should put this in an object that has its own observer that you can subscribe to.

brandonscript
  • 68,675
  • 32
  • 163
  • 220
Evan Plaice
  • 13,944
  • 6
  • 76
  • 94
  • This doesn't make sense to me, Evan. Doesn't this circumvent the better-performing Observable pattern of Angular 2, by flattening the observable sequence into a plain object that needs dirty checks? Also I'd really prefer not to have to maintain a bunch of empty initializers, one for each model in my client. I'm hoping there is another way. – shannon Jan 14 '16 at 07:42
  • I totally agree than an `ngIf` is also a functional workaround - and it requires less duplicated code than your other suggestion in cases where result objects are expected to be complex. However, it has the same weakness in that it unwraps the `Observable` and requires the template to perform dirty checks (or re-render unnecessarily). I know you disagreed with this in your last comment, but if you think further about it, I suspect you'll agree. See also my comment to @tehaaron, whose answer had a similar effect. – shannon Jan 15 '16 at 00:08
  • @shannon Dirty checking in Angular2 is cheap but that's irrelevant because it doesn't answer the question. What you want is a prewarmed cache of data prior to page load so there's no re-render flash. To do that you'll need to have your data loaded as a separate service and you'll need a way to trigger the data to load prior to the page loading. – Evan Plaice Jan 15 '16 at 02:46
  • (cont) Services aren't constructed until the first time they're injected. So some other component has to trigger the construction. One way would be to inject the service into a parent component. Another would be to lazy load the route and somehow trigger the service to start prior to loading the component. – Evan Plaice Jan 15 '16 at 02:48
  • Dirty checking is always cheap at small volumes, and I have nothing against the usage of it in Angular 1.x. I agree the benefit of simplicity outweighs the cost. However, a fundamental architectural decision was made to support an `Observable`-driven mechanism in Angular 2 and the performance improvement on pages with large numbers of items was one reason. It is higher performance and still very easy to get "right" as a consumer. So, I'd like to use it. – shannon Jan 15 '16 at 08:24
  • A pre-warmed cache is a good idea, but is not actually what I need in my case. Users are selecting to navigate to a leaf on a potentially large tree. I do not plan to preload the entire object graph over time before allowing the first navigation click. – shannon Jan 15 '16 at 08:26
  • From Angular 2 code: `ObservableStrategy.prototype.createSubscription = function(async, updateLatestValue) { return async_1.ObservableWrapper.subscribe(async, updateLatestValue, function(e) { throw e; }); };` – shannon Jan 15 '16 at 09:54
  • @shannon Here is actual working code incl a service for caching. https://github.com/evanplaice/evanplaice.com/blob/master/app/vitae/components/vitae.js. https://github.com/evanplaice/evanplaice.com/blob/master/app/vitae/services/vitae.js. – Evan Plaice Jan 15 '16 at 10:05
  • @shannon Dirty checking in AngularJS is -- by no means -- comparable to dirty checking in Angular2. I'm not sure of a better way to describe it. To prevent dirty checking of deeply nested data structures, immutability is the best option. Create a new object using the data from the old so the comparator only has to check references. https://egghead.io/lessons/angular-2-refactoring-mutations-to-enforce-immutable-data-in-angular-2. – Evan Plaice Jan 15 '16 at 10:09
  • Ultimately, though, dirty-checking is still dirty-checking. Using the node-specific notification offered by Observables makes sense to me if it's available. Another example of where it plays nicer, is in error handling. What happens when the resource doesn't load? Now you have to "un-navigate". – shannon Jan 15 '16 at 10:40
  • Re: your code example, I hear what you are saying. As a side note, I think the code you shared exposes some likely misuse scenarios resulting in defects. I'd be happy to discuss if you are interested. You could reduce your workload in addressing misuse by simply initializing an Observable as a concatenated value: `http.get("etc").map(res => new FRESHModel(res.json())).startWith(new FRESHModel())`, if I understand your intent. – shannon Jan 15 '16 at 10:54
  • @shannon I'm all ears. Observables is a new concept to me so in many cases I don't know what I don't know. Would you mind filing an issue on my project so we can continue the discussion there. I'd prefer not to flood the comments here any more than we have already. – Evan Plaice Jan 16 '16 at 01:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100839/discussion-between-shannon-and-evan-plaice). – shannon Jan 16 '16 at 11:22
3

A nice solution that I've found is to do on UI something like:

<div *ngIf="vendorServicePricing && quantityPricing && service">
 ...Your page...
</div

Only when: vendorServicePricing, quantityPricing and service are loaded the page is rendered.

Radu Linu
  • 1,143
  • 13
  • 29
  • This should be on first place because it is the simplest solution. No need of Resolve or `?.` "notnull-Operator" – Javan R. Aug 25 '17 at 07:30
  • What if you have a really big template? Store everything in a big IF clause? It'd be more nice to have a means of intercepting pre-render like in React and just return null until you have some data. – Raul Rene Jun 05 '18 at 11:09
2

Implement the routerOnActivate in your @Component and return your promise:

https://angular.io/docs/ts/latest/api/router/OnActivate-interface.html

EDIT: This explicitly does NOT work, although the current documentation can be a little hard to interpret on this topic. See Brandon's first comment here for more information: https://github.com/angular/angular/issues/6611

EDIT: The related information on the otherwise-usually-accurate Auth0 site is not correct: https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-depth/

EDIT: The angular team is planning a @Resolve decorator for this purpose.

shannon
  • 8,664
  • 5
  • 44
  • 74
Langley
  • 5,326
  • 2
  • 26
  • 42
  • Does returning a promise in `ngOnInit` have the same effect? – agconti Jan 11 '16 at 22:16
  • 1
    I don't think so, but it wouldn't help if it does because that function gets called until after. From: https://angular.io/docs/ts/latest/api/core/OnInit-interface.html#!#sts=ngOnInit() , "ngOnInit is called right after the directive's data-bound properties have been checked for the first time" – Langley Jan 11 '16 at 22:19
  • Interesting: It seems silly that they're using it in all of there demos if you can't initialize data with it. – agconti Jan 11 '16 at 22:21
  • you can, to empty objects, the idea is that you show the view as quickly as possible, and render the details later, but I prefer to load the model first too. – Langley Jan 11 '16 at 22:23
  • This didn't fly on our first attempt. The object was not resolved when the page loads. I didn't inspect carefully yet, will try again in the morning. – shannon Jan 12 '16 at 03:26
  • If the request doesn't complete prior to the template rendering phase, it'll still crash. I don't think routerOnActivate is intended as a workaround for setting template values. – Evan Plaice Jan 13 '16 at 21:29
  • 2
    The doc says > * If `routerOnActivate` returns a promise, the route change will wait until the promise settles to * instantiate and activate child components. – Günter Zöchbauer Jan 14 '16 at 17:17
  • @Langley I believe this is unnecessarily complicated compared to the Elvis operator for this specific use case. Do you know if there is an advantage to routerOnActivate for simply waiting for data to become available? – tehaaron Jan 14 '16 at 23:08
  • 2
    The elvis operator is not allowed on two way bindings. – Langley Jan 14 '16 at 23:22
  • @tehaaron: Also, if you consider how the Elvis operator must determine when an object of unknown type is resolved, vs. how an `Observer` is notified of updates, you'll see an inherent design intent and performance difference. – shannon Jan 15 '16 at 00:21
  • @Langley: I understand I must resolve it. We've done some more digging, and the exception while attempting to access the property is raised before the promise is resolved. I've returned the promise from `onRouterActivate`, and verified the hook is being called. The promise is returned, and is still unresolved after the exception. I see in Angular RouterOutlet code comments where the render is *supposed* to be deferred until the promise is fulfilled, but this is not the case. – shannon Jan 20 '16 at 19:39