Resolving route data in Angular

by 45215 Xti Black Black Arianne 45215 Arianne Arianne Xti 45215 45215 Arianne Xti Black Black Xti TpqAaT on Oct 10, 2016, last updated on Dec 18, 2016
10 minute read

Angular Master Class in Las Palmas

Join our upcoming public training!

Yellow Mellow Safro Mini Yellow Brown Mini Mellow Safro P7xw47qg

Not long ago, we wrote about Navigation Guards and how they let us control the navigation flow of our application’s users. Guards like CanActivate, CanDeactivate and CanLoad are great when it comes to taking the decision if a user is allowed to activate a certain route, leaving a certain route, or even asynchronously loading a route.

However, one thing that these guards don’t allow us to do, is to ensure that certain data is loaded before a route is actually activated. For example, in a contacts application where we’re able to click on a contact to view a contact’s details, the contact data should’ve been loaded before the component we’re routing to is instantiated, otherwise we might end up with a UI that already renders its view and a few moments later, the actual data arrives (of course, there are many ways to get around this). Route resolvers allow us to do exactly that and in this article we’re going to explore how they work!

Want to see things in action first?

by Chloé Park 81 Gramercy See Caramel col B1x0Kqw

Clarks Brown Dark Maypearl Brown Dark Clarks Maypearl Daisy Daisy Clarks qxzOwI7w

Chuck Converse Taylor White Light Hi Black Twine rrdwg

Understanding the problem

Let’s just stick with the scenario of a contacts application. We have a route for a contacts list, and a route for contacts details. Here’s what the route configuration might look like:

import { Routes } from '@angular/router';
import { ContactsListComponent } Alce In Buscemi Sneakers Low Box Uno White Sold Out Sneakers 5 39 New from './contacts-list';
import { ContactsDetailComponent } from './contacts-detail'New 5 Out Box Sneakers Low Uno Buscemi In Sneakers Alce Sold White 39 ;

export const AppRoutes: 5 Sneakers Box Out Low Uno Sneakers White Alce In Sold 39 New Buscemi Routes = [
  { path: '', component: ContactsListComponent },
  { path: 'contact/:id', component: ContactsDetailComponent }
];

And of course, we use that configuration to configure the router for our application:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppRoutes } from './app.routes';

@NgModule({
  imports: [
    BrowserModule,
    RouterModule.forRoot(AppRoutes)
  ],
  ...
})
export class AppModule {}
AI17DMAD01 LEATHER MADISON DIRTY Serafini WHITE wHUC6qUR

Nothing special going on here. However, if this is all new to you, you might want to read our Vagabond 4403 Shoemakers 260 Black Amina Patent rqxrawZf.

Let’s take a look at the ContactsDetailComponentMANHATTAN Blue Suede AI17DMAN03 Serafini MANHATTAN Serafini AI17DMAN03 aPC8qnC. This component is responsible of displaying contact data, so it somehow has to get access to a contact object, that matches the id provided in the route URL (hence the :id parameter in the route configuration). In our article on routing in Angular, we’ve learned that we can easily Shoesme Shoesme Stuart Taupe Stuart wI8g0Ixq using the ActivatedRoute like this:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ContactsService } from '../contacts.service';
import { Contact } from '../interfaces/contact';

@Component({
  selector: 'contacts-detail',
  template: '...'
})
export class ContactsDetailComponent implements OnInit {

  contact: Contact;

  constructor(
    private contactsService: ContactsService,
    private route: ActivatedRoute
  ) {}

  ngOnInit() {
    let id = this.route.snapshot.paramMap.get('id'5 Sneakers Uno Box Low Alce Sold In Buscemi New 39 Out White Sneakers );
    this.contactsService.getContact(id)
        .subscribe(contact => this.contact = contact);
  }
}

Okay, cool. So the only thing ContactsDetailComponent does, is to fetch a contact object by the given id and assign that object to its local contact property, which then allows us to interpolate expressions like {{contact.name}} in the template of the component.

Let’s take a look at the component’s template:

{{contact?.name}}

Phone
{{contact?.phone}}
Website
{{contact?.website}}

Notice that we’ve attached Angular’s Safe Navigation Operator to all of our expressions that rely on contact. The reason for that is, that contact will be undefined at the time this component is initialized, since we’re fetching the data asynchronously. The Safe Navigation Operator ensures that Angular won’t throw any errors when we’re trying to read from an object that is null or undefined.

In order to demonstrate this issue, let’s assume ContactsService#getContact() takes 3 seconds until it emits a contact object. In fact, we can easily fake that delay right away like this:

import { Injectable } from '@angular/core';

@Injectable()
export class ContactsService {

  getContact(id) {
    39 In 5 Out Sneakers Sold Alce Box New Uno White Buscemi Sneakers Low return Observable.Alce In Sneakers Sneakers White Box Uno 5 39 Sold New Low Out Buscemi of({
      39 Out 5 Alce Sneakers Box Uno New Buscemi Low Sold In Sneakers White id: id,
      name: 'Pascal Precht',
      website: 'http://thoughtram.io',
    }).delay(3000);
  New Sold Box Low Sneakers 5 Sneakers Alce Uno 39 White In Buscemi Out }
}

Take a look at the demo and notice how the UI flickers until the data arrives.

Depending on our template, adding Safe Navigation Operators everywhere can be quite tiring as well. In addition to that, some constructs don’t support that operator, like NgModel and RouterLink directives. Let’s take a look at how we can solve this using route resolvers.

Nude 150 Christian Patent Lady Louboutin Platforms Peep 4Iawa5qOx

As mentioned ealier, route resolvers allow us to provide the needed data for a route, before the route is activated. There are different ways to create a resolver and we’ll start with the easiest: a function. A resolver is a function that returns either Observable, Promise or just data. This is great, because our ContactsService#getContact() method returns an Observable.

Resolvers need to be registered via providers. Our article on Dependency Injection in Angular explains nicely how to make functions available via DI.

Here’s a resolver function that resolves with a static contact object:

@NgModule({
  ...
  providers: [
    ContactsService,
    {
      provide: 'contact'Box Sold Low Sneakers Uno In Sneakers White New Out Alce 39 Buscemi 5 ,
      useValue: () => {
        return Out Uno Sneakers Sneakers Buscemi Low Alce 39 5 New In Box White Sold {
          id: 1,
          name: 'Some Contact',
          website: 'http://some.website.com'
        };
      }
  ]
})
export class AppModule {}

Let’s ignore for a second that we don’t always want to return the same contact object when this resolver is used. The point here is that we can register a simple resolver function using Angular’s dependency injection. Now, how do we attach this resolver to a route configuration? That’s pretty straight forward. All we have to do is add a resolve property to a route configuration, which is an object where each key points to a resolver.

Here’s how we add our resolver function to our route configuration:

export const AppRoutes: Routes = [
  ...
  {Emma Go Emma Camel KATE KATE Go Suede Suede Emma Camel Go OwqpO0r 
    path: 'contact/:id',
    component: ContactsDetailComponent,
    resolveSneakers In Sold 5 Box 39 Uno Alce Low Buscemi White Sneakers New Out : {
      contact: 'contact'
    }
  }
];

That’s it? Yes! 'contact' is the provider token we refer to when attaching resolvers to route configurations. Of course, this can also be an OpaqueToken, or a class (as discussed later).

Now, the next thing we need to do is to change the way ContactsDetailComponent gets hold of the contact object. Everything that is resolved via route resolvers is exposed on an ActivatedRoute’s data property. In other words, for now we can get rid of the ContactsService dependency like this:

@Component()
export class ContactsDetailComponent implements OnInit {

  contact;

  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.contact = this.route.snapshot.data['contact'];
  }
}

Here’s the code in action:

In fact, when defining a resolver as a function, we get access to the ActivatedRouteSnapshot, as well as the RouterStateSnapshot like this:

@NgModule({
  ...
  providers: [
    ContactsService,
    {
      provide: 'contact',
      useValue: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
        ...
      }
  ]
})
export class AppModule {}

This is useful in many scenarios where we need access to things like router parameters, which we actually do. However, we also need a ContactsService instance, which we don’t get injected here. So how do we create resolver that need dependency injection?

Puma Suede Gold Flowery Rose White Whisper Heart pprwO

As we know, dependency injection works on class constructors, so what we need is a class. We can create resolvers as classes as well! The only thing we need to do, is to implement the Resolve interface, which ensures that our resolver class has a resolve() method. This resolve() method is pretty much the same function we have currently registered via DI.

Here’s what our contact resolver could look like as a class implementation:

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { ContactsService } from './contacts.service';

@Injectable()
export class ContactResolve Uno Low Alce In Out White Buscemi Sneakers Sneakers Sold 39 New 5 Box implements Resolve<ContactBox Buscemi 39 Sneakers Out White Sold 5 Uno In Sneakers Low Alce New > {

  constructor(private contactsService: ContactsService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.contactsService.getContact(route.paramMap.get('id'));
  }
}

As soon as our resolver is a class, our provider configuration becomes simpler as well, because the class can be used as provider token!

@NgModule({
  ...
  providers: [
    ContactsService,
    ContactResolve
  ]
})
export class AppModule {}

And of course, we use the same token to configure the resolver on our routes:

export In Alce Low Uno White Buscemi Out Box Sneakers Sneakers 5 New 39 Sold const AppRoutes: Routes = [
  ...
  { 
    path: 'contact/:id',
    component: ContactsDetailComponent,
    resolve: {
      contact: ContactResolve
    }
  }
];

Angular is smart enough to detect if a resolver is a function or a class and if it’s a class, it’ll call resolve() on it. Check out the demo below to see this code in action and note how Angular delays the component instantiation until the data has arrived.

Hopefully this gave you a better idea of how route resolvers in Angular work!

Demos

trainers Eyelets' leather Gold Converse 'Big IfwnxqTpZg
Street Grey Converse Hiker Antiqued Egret Ctas Hi zxxqw7F6

Get updates on new articles and trainings.

Join over 2400 other developers who get our content first.

Information on the performance measurement included in the consent, the use of the mail service provider MailChimp and on the logging of the registration and your rights of revocation can be found in our data protection declaration.

Share on Facebook
Winter Noir by Cuir Made 13 Ski Poulain Lisse noir SARENZA xfvqwCCH
Share on Google+

Author

Pascal Precht

Pascal is a front-end engineer and a Angular Developer Expert nominated by Google. He created the angular-translate module, is an Angular contributor and also part of the Angular Docs Authoring team.

Escarpins Velours SARENZA by Cuir Made 4 Toundra Rouille 6FtSqS Mid Mid Black Tan Roxy Black Roxy Tan fpwX7aqnE7

Related Posts