未加星标

Resolving Route Data in Angular 2

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二03 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

Not long ago, we’ve written aboutNavigation 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? 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 [email protected]/router'; import { ContactsListComponent } from './contacts-list'; import { ContactsDetailComponent } from './contacts-detail'; export const AppRoutes: 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 [email protected]/core'; import { BrowserModule } from [email protected]/platform-browser'; import { RouterModule } from [email protected]/router'; import { AppRoutes } from './app.routes'; @NgModule({ imports: [ BrowserModule, RouterModule.forRoot(AppRoutes) ], ... }) export class AppModule {}

Nothing special going on here. However, if this is all new to you, you might want to read ourarticle on routing.

Let’s take a look at the ContactsDetailComponent . 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 2, we’ve learned that we can easily access route parameters using the ActivatedRoute like this:

import { Component, OnInit } from [email protected]/core'; import { ActivatedRoute } from [email protected]/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.params['id']; this.contactsService.getContact(id) .subscribe(contact => this.contact = contact); } }

Okay, cool. So the only thing ContactsDetailComponent does, is it fetches a contact object by the given id and assigns 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:

<h2>{{contact?.name}}</h2> <dl> <dt>Phone</dt> <dd>{{contact?.phone}}</dd> <dt>Website</dt> <dd>{{contact?.website}}</dd> </dl>

We 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 fetch the data asynchronously. The Safe Navigation Operator ensures that Angular won’t throw 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 [email protected]/core'; @Injectable() export class ContactsService { getContact(id) { return Observable.of({ id: id, name: 'Pascal Precht', website: 'http://thoughtram.io', }).delay(3000); } }

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.

Defining resolvers

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<any> , Promise<any> or just data. This is great, because our ContactsService#getContact() method returns an Observable<Contact> .

Resolvers need to be registered via providers. Our article on Dependency Injection in Angular 2 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', useValue: () => { return { 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 he 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 to 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 = [ ... { path: 'contact/:id', component: ContactsDetailComponent, resolve: { 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 anOpaqueToken, 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 off 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?

Resolvers with dependencies

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 { Resolve, ActivatedRouteSnapshot } from [email protected]/router'; import { ContactsService } from './contacts.service'; export class ContactResolve implements Resolve<Contact> { constructor(private contactsService: ContactsService) {} resolve(route: ActivatedRouteSnapshot) { return this.contactsService.getContact(route.params['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 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.

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

分页:12
转载请注明
本文标题:Resolving Route Data in Angular 2
本站链接:http://www.codesec.net/view/480846.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(36)