Angular 2 Tutorial: Upgrading to Angular 2 RC5

Angular 2 Tutorial: Upgrading to Angular 2 RC5

  • 2016-08-24
  • 1393

Version 0.0.3 of my GuildRunner sandbox Angular 2 application is now available.  All of the differences between this version and the previous version (minus the updates to the version number and the README file) are changes made to upgrade the application to use Angular 2 RC5 (release candidate 5).

While there were some changes to the router/routing syntax, the biggest change that comes with RC5 is the introduction of Angular modules and the @ngModule decorator.  There is a long documentation page about Angular modules in the official developer guide, but essentially Angular modules allow you to bundle sets of shared injectable dependencies into a single file that provides those dependencies “downstream”.

For example, prior to the upgrade my MainNavigationComponent received the directives needed for routing (like RouterLink) via the “directives” metadata property (which also meant it had to be imported):


//main-navigation.component.ts (previous version)
import { ROUTER_DIRECTIVES } from [email protected]/router';
...
@Component({
  ...
  directives: [ ROUTER_DIRECTIVES ]
})

As another example, the GuildsMasterComponent received the GuildService for its constructor method via the “providers” metadata property:


//guilds-master.component.ts (previous version)
import { GuildService } from '../guild.service';
...
@Component({
  ...
  providers: [ GuildService ]
})

Now both of those dependencies are declared in the new application-level Angular module - app.module.ts:


//app.module.ts
...
import { routing } from './app.routing';
import { GuildService } from './guild.service';
...
import { SandboxModule } from './sandbox/sandbox.module';
...
@NgModule({
  imports:      [
    BrowserModule,
    HttpModule,
    routing,  //Provides the routing directives as well as the route definitions
    SandboxModule
  ],

  declarations: [
    AppComponent,
    VersionComponent,
    MainNavigationComponent,
    HomeComponent,
    GuildsMasterComponent
  ],

  providers: [
    { provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server
    { provide: SEED_DATA,  useClass: InMemoryDataService },     // in-mem server data
    VersionService,
    GuildService //Provides the GuildService downstream
  ],

  bootstrap:    [
    AppComponent
  ],
})

Because the MainNavigationComponent and GuildsMasterComponent are included in the module via the “declarations” block, they are part of the feature bundle of this module, and so they have access, via dependency injection, to the routing and GuildService dependencies without the need for the “directives” or “providers” metadata properties of the @Component.

Note the four properties in this @ngModule decorator. The “declarations” property is where you list all of the components, directives, and custom pipes used in your module: anything your templates need in order to operate (example: the inclusion of the MainNavigationComponent in the declarations allows the AppComponent template to understand how to render the “” tag in the AppComponent HTML). The “providers” property is where you list your module services (things that would be injected into your component as a constructor argument).

The “imports” property is where you list other modules that provide functionality (services, directives, etc.) to your feature module. Some of the modules may be Angular library modules, such as the required BrowserModule or the HttpModule needed for performing HTTP operations. But it can also include other modules in your application. Note the inclusion of the “SandboxModule” in this example:


//app.module.ts
import { SandboxModule } from './sandbox/sandbox.module';
...
@NgModule({
  imports:      [
    ...
    SandboxModule
  ],

That is a separate Angular module file dedicated to the “sandbox” feature area of my application:


//sandbox/sandbox.module.ts
import { NgModule }       from [email protected]/core';
import { sandboxRouting } from './sandbox.routing'
import { GuildListComponent } from "./guild-list/guild-list.component";
import { SandboxService } from './sandbox.service';

@NgModule({
  imports: [
    sandboxRouting
  ],

  declarations: [
    GuildListComponent
  ],

  providers: [
    SandboxService
  ]
})

export class SandboxModule {}

This module encompasses the components, services, and the routing related to the sandbox feature area of the application, and it’s integrated with the rest of the application simply by the fact that it’s declared in the “imports” property of the main application Angular module. Note that it doesn’t contain the fourth property seen in app.module.ts: the “bootstrap” property is mainly for declaring the top-level component of a given module, something the sandbox feature area doesn’t have.

So the introduction of Angular modules adds a new organizational construct to Angular 2 and cuts down on typing since there’s no need to add “directives” and “providers” properties to your components in order to perform dependency injection.

However, there is one small caveat, best explained by example. Even though my app.module.ts file declares the GuildService in its array of providers, and I no longer need to use the “providers” metadata property on my GuildsMasterComponent, I still need to import the GuildService:


//guilds-master.component.ts (new version)
import { GuildService } from '../guild.service';
...
export class GuildsMasterComponent implements OnInit {

  guilds: Guild[] = [];

  constructor( private guildService: GuildService ) { }

This puzzled me, and in perusing some of the updated documentation and tutorial examples I couldn’t find an explanation for why that import was still necessary if the GuildsMasterComponent was getting its instance of the GuildService from the application Angular module.

But then I looked at the JavaScript being generated from the guilds-master.component.ts file. Here are the relevant lines from that JavaScript file, with the significant lines followed by comments:


//guilds-master.component.js
...
var guild_service_1 = require('../guild.service');  //Significant line #1
...
GuildsMasterComponent = __decorate([
        core_1.Component({
            moduleId: module.id,
            selector: 'app-guilds-master',
            templateUrl: 'guilds-master.component.html',
            styleUrls: ['guilds-master.component.css']
        }), 
        __metadata('design:paramtypes', [guild_service_1.GuildService]) //Significant line #2
    ], GuildsMasterComponent);
    return GuildsMasterComponent;

Those two significant lines are generated by Angular compiler based on the argument declaration of the GuildsMasterComponent constructor. If you try to type the “guildService” argument of the constructor as another data type (like “any”), the compiler won’t know what object/export it’s supposed to use. And you can’t set the “guildService” argument to type GuildService without importing GuildService so that the TypeScript compiler can recognize the data type.

Suggest

Angular 2 and NodeJS - The Practical Guide to MEAN Stack 2.0

Learn Angular 2 Development By Building 10 Apps

Angular 2 Crash Course with TypeScript

Angular 2 - The Complete Guide (Updated to RC4!)

Angular 2 with TypeScript for Beginners: The Pragmatic Guide