Saturday, July 9, 2016

Learning Angular 2: Tour of Heroes Tutorial, Lesson 6

The next lesson in the Tour of Heroes tutorial covers an important topic: routing.  I've been looking forward to this particular lesson because I was curious as to how routing would work given the new component-structured architecture.

The first step of the lesson involves renaming the current AppComponent to HeroesComponent (renaming the file, renaming the exported class) and then creating a new AppComponent in preparation for adding routing.  The most interesting part of this step is the relocation of the HeroService from the HeroesComponent providers array to the new AppComponent providers array, and that the use of the service still works in the HeroesComponent (note that both components still import the service module).  The lesson describes this move as the HeroService being "promoted," and it results in the HeroService becoming a singleton shared with the entire app.

Worth noting that the <base> tag for defining the base route in the header is a single, unclosed tag like the <meta> tags.

As I was entering the routes, I noted that IntelliJ provided suggestions for the name of the component.  

Even better, when I selected the component from the suggestion list, IntelliJ automatically added the needed import statement at the top of the app.routes.ts file: very handy.

The first time I looked at this lesson a few days ago, it was written to utilized the recently deprecated version of the router.  It's since been revised to use the latest version of the router, referred to as the Component Router.  In the instructions for setting up the first route to the HeroesComponent, the lesson doesn't explicitly mention a few of the needed modifications to app.component.ts:

  • The import of ROUTER_DIRECTIVES
  • The removal of the HeroesComponent from the directives array and the removal of the HeroesComponent import
  • The addition of ROUTER_DIRECTIVES to the component directives array

...
import { ROUTER_DIRECTIVES } from '@angular/router';
...
directives: [ ROUTER_DIRECTIVES ],
...

...but those modifications do appear in the "v2" code example for app.component.ts somewhat after the fact.

With the browser console open during the reloading of the app, I see error messages generated about the fact that there is no route for '' - that there is no default route defined yet.  Glancing ahead, that is addressed as part of the next step in the lesson with the instructions for setting up the default route to redirect to the DashboardComponent.  Briefly visited the documentation link for redirects and read about how the pathMatch property for the redirect can be set to a value of "prefix" that is useful in other circumstances.

That's when I ran into a little trouble.  I noticed that IntelliJ did not recognized pathMatch as a valid property for a route (it drew a red line under it).  I put the cursor on the "RouterConfig" in the RouterConfig import statement and used the IntelliJ keyboard shortcut Command-B (Control-B on Windows) to jump to the RouterConfig declaration, which lead me to the TypeScript interface definition for a Route object in the RouteConfig array:


export interface Route {
    path?: string;
    terminal?: boolean;
    component?: Type | string;
    outlet?: string;
    canActivate?: any[];
    canDeactivate?: any[];
    redirectTo?: string;
    children?: Route[];
}

Seeing no "pathMatch" property in the interface (interesting that all of the interface properties, including the "path" property, are optional), I decided to check to see what the latest version of the npm "@angular/router" module was:  turned out I had previously updated package.json to install the 3.0.0-alpha.8 version when I first looked over the lesson, but the latest version was now 3.0.0-beta.2.  So I did the upgrade, and the IntelliJ warning disappeared, but when I tried to load Tour of Heroes, I suddenly had errors in the console and the app wouldn't go past the loading screen.  

I revisted the output from "npm-install" and saw warnings about needing the "rc.4" version of other Angular npm modules (core, common, compiler, platform, platform-browser):  my package.json file was configured to pull down version "rc.2" of those files.  

So I updated my package.json dependencies like so...


 "@angular/common":  "2.0.0-rc.4",
 "@angular/compiler":  "2.0.0-rc.4",
 "@angular/core":  "2.0.0-rc.4",
 "@angular/http":  "2.0.0-rc.2",
 "@angular/platform-browser":  "2.0.0-rc.4",
 "@angular/platform-browser-dynamic":  "2.0.0-rc.4",
 "@angular/router":  "3.0.0-beta.2",

...deleted the node_modules folder in my project to install clean, and ran "npm install" again. I got a different set of warnings at the end of the install (so I'll probably be revising my package.json again in the near future), but Tour of Heroes was up and running again, and "pathMatch" was now a property of the Route interface:


pathMatch?: 'full' | 'prefix';

I had already explored how to pull in the HTML of my component from another file using the "templateUrl" property in a previous post, but this lesson officially introduced that technique. Even better, it provided a link to a documentation page about using component-relative paths: if you're using CommonJS modules and certain module loader systems like SystemJS or webpack, you can add a "moduleId" property to your @Component decorator properties and then reference your HTML or CSS files for the component relative to the path, like so:


@Component({
    moduleId: module.id,
    selector: 'my-dashboard',
    templateUrl: 'dashboard.component.html'
})

I like that approach: it makes it a bit more consistent with the path declarations for the imports and it means you can move the component and its HTML/CSS files to another area of your app and not have to update the templateUrl and styleUrl properties.

The second route defined in the lesson demonstrates the use of route parameters, where the route parameters are defined in a route using the colon (:) in the path just like in Angular 1.  I found the fact that components subscribe to route parameter changes on init and then should unsubscribe to them when the component is destroyed (ngOnDestroy) very interesting.

The section of the lesson that adds the mini-detail introduces one of the built-in pipes for Angular 2.  I remember hearing in a recent Adventures in Angular podcast that pipes in Angular 2 are good for doing some - but not all - of the things one used to do in Angular 1 with filters.  They are primarily output formatters, but are not really designed to perform data manipulation functions like sorting.

The router service's navigate() method replaces the old $location service path() method in Angular 1, but rather than taking a single string argument that represents the path to navigate to, it takes an array of values that starts with the route.  It'll be interesting to see how that plays out in larger applications with deep, multi-parameter routes.

No comments:

Post a Comment