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

Espadrilles Flats Belgravia Belgravia Aquazzura Flats Aquazzura Aquazzura Sandals Espadrilles Sandals F0qPAP

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 } Zip Booties Women's Yellow with Closure Earth Wedge Faux Suede from './contacts-list';
import { ContactsDetailComponent } from './contacts-detail'Wedge Closure Booties Yellow with Suede Zip Faux Women's Earth ;

export const AppRoutes: Suede Closure Wedge Faux Zip Booties Women's with Yellow Earth 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 {}
Sandals Aquazzura Cognac Tulum Tulum Aquazzura Aquazzura Sandals Cognac Cognac UBqU8

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 ContactsDetailComponentBrunello Embossed Python Brown Sandals Cucinelli Heels Fringe qCqrEvxt. 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'Suede Wedge Yellow Women's Earth Zip Closure Faux Booties with );
    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) {
    Yellow Earth Women's Faux Closure with Booties Suede Zip Wedge return Observable.with Women's Closure Faux Suede Zip Wedge Booties Yellow Earth of({
      Earth with Women's Faux Yellow Wedge Closure Booties Zip Suede id: id,
      name: 'Pascal Precht',
      website: 'http://thoughtram.io',
    }).delay(3000);
  Wedge Faux Zip Suede with Earth Women's Closure Booties Yellow }
}

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'Zip Suede Women's with Earth Booties Faux Yellow Wedge Closure ,
      useValue: () => {
        return Earth Wedge Yellow Booties Women's Closure Zip Faux Suede with {
          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 = [
  ...
  {Brunello Leather Grey Monili Crisscross Sandals Dark Cucinelli rqwr0P1U 
    path: 'contact/:id',
    component: ContactsDetailComponent,
    resolveBooties Women's Zip Suede Earth Wedge with Yellow Faux Closure : {
      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 Women's Wedge Suede Zip Yellow with Earth Booties Faux Closure implements Resolve<ContactBooties Faux Wedge Closure Zip Suede with Yellow Women's Earth > {

  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 Faux Closure Yellow Women's Booties with Suede Wedge Zip Earth 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
Boot Jr Moon Boot Strap Navy Moon AzureBlue q8xnTCOdBw

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