1. 程式人生 > >Angular Router Series: Pillar 2 — Navigation

Angular Router Series: Pillar 2 — Navigation

These events are very useful for studying or debugging the router. You could easily tap into them to display a loading message during navigation as well.

excerpt from app.component.ts. Displays a loading message at the start of navigation, and clears the message once navigation has ended

Let’s run through a navigation to /users

.

Navigation Start

events: NavigationStart

In our sample application, the user starts by clicking on the following link:

Navigate to /users, and pass along the query parameters login=1 (see the section on router guards)

Whenever the router detects a click on a router link directive, it starts the navigation cycle. There are imperative means of starting a navigation as well, such as the Router Service’s navigate

and navigateByUrl methods.

Previously, there could be multiple navigations running simultaneously (hence the need for a navigation id), but with this change, there can be only one navigation at a time.

URL Matching, and Redirects

events: RoutesRecognized

redirects and matching are steps one and two of the cycle

The router starts by doing a depth-first search through the array of router configurations ( ROUTES in our example), and trying to match the URL /users to one of the path properties in the router configurations, while applying any redirects along the way. If you’d like to know about this process in detail, I’ve written about it here.

In our case, there are no redirects to worry about, and the URL /users will match the following configuration in ROUTES:

{ path: 'users', component: UsersComponent, ... }

If the matched path requires a lazy loaded module, it will be loaded at this point.

The router emits a RoutesRecognized event to signal that it has found a match for the URL, and a component to navigate to (UsersComponent). This answers the router’s first question, “What should I navigate to?”. But not so fast, the router has to make sure that it is allowed to navigate to this new component.

Enter route guards.

Route Guards

events: GuardsCheckStart, GuardsCheckEnd

Route guards are boolean functions that the router uses to determine if it can perform a navigation. As developers, we use guards to control whether a navigation can occur or not. In our sample application, we use a canActivate guard to check if a user is logged in by specifying it in the route configuration.

{ path: 'users', ..., canActivate: [CanActivateGuard] }

The guard function is shown below.

isAuthorized will return true when the query parameter login=1

This guard passes the login query parameter to an auth service (auth.service.ts in the example app).

If the call to isAuthorized(route.queryParams.login) returns true, the guard passes successfully. Otherwise, the guard fails, and the router emits a NavigationCancel event, and aborts the entire navigation.

Other guards include canLoad (should a module be lazily-loaded or not), canActivateChild, and canDeactivate (which is useful for preventing a user from navigating away from a page, for instance, when filling out a form).

Guards are similar to services, they are registered as providers and are injectable. The router will run guards every time there is a change to the URL.

The canActivate guard is run before any data is fetched for the route, since there is no reason to fetch data for a route that shouldn’t be activated. Once the guard has passed, the router has answered its second question, “Should I perform this navigation?”. The router can now prefetch any data using route resolvers.

Route Resolvers

events: ResolveStart, ResolveEnd

Route resolvers are functions that we can use to prefetch data during navigation, before the router has rendered anything. Similar to guards, we specify a resolver in the route configuration, using the resolve property:

{ path: 'users', ..., resolve: { users: UserResolver } }

Once the router has matched a URL to a path, and all guards have passed, it will call the resolve method defined in the UserResolver class to fetch data. The router stores the results on the ActivatedRoute service’s data object, under the key users. This information can be read by subscribing to the ActivatedRoute service’s data observable.

activatedRouteService.data.subscribe(data => data.users);

The ActivatedRoute service is used inside of the UsersComponent, to retrieve data from the resolver.

Resolvers let us prefetch component data during routing. This technique can be used to avoid displaying partially loaded templates to the user by prefetching any data. Remember, a component’s template will be visible to the user during OnInit, so fetching any data that needs to be rendered in that lifecycle hook can lead to a partial page load.

However, it is often better to just let a page partially load. When done well, it will increase the user’s perceived performance of the site. The decision of whether or not to prefetch data is up to you, but it’s usually best to have a partial page load with a nice loading animation instead of using resolvers.

Internally, the router has a runResolve method which will execute the resolver, and store its results on the ActivatedRoute snapshot.

“future” is as ActivatedRouteSnapshot

Once the router has processed all resolvers, the next step is to start rendering components using the appropriate router outlets.

Activating Routes

events: ActivationStart, ActivationEnd, ChildActivationStart, ChildActivationEnd

Now it’s time to activate the components, and display them using a<router-outlet>. The router can extract the information it needs about the component from the tree of ActivatedRouteSnapshots that it built during the previous steps of the navigation cycle.

The component property tells the router to create and activate an instance of UsersComponent. We can also see the users we fetched under the data.users property.

If you are unfamiliar with the process of creating dynamic components in Angular, there are great explanations here and here.All of the magic happens within the router’s activateWith function:

Don’t stress the details, I’ll summarize the main points of the code here:

  • On Line 9, A ComponentFactoryResolver is used to create an instance of the UsersComponent. The router pulls this information off of the ActivatedRouteSnapshot in line 7.
  • On line 12, the component is actually created. location is a ViewContainerRef for the <router-outlet> that is being targeted. If you’ve ever wondered why the rendered content is placed as a sibling to the <router-outlet> as opposed to inside of it, the details can be found by following the details inside of .
  • After the component is created and activated, activateChildRoutes (not shown) is called. This is done to account for any nested <router-outlet>, known as child routes.

The router will render a component on the screen. If the rendered component has any nested <router-outlet> elements, the router will go through and render those as well.

Updating the URL

The last step in the navigation cycle is to update the URL to /users.

The router is now ready to listen for another URL change, and start the cycle all over again.