Tuesday 9 August 2016

Angular 2 RC5 - NgModules, Lazy Loading and AoT compilation

Today we’re publishing Angular 2 RC5 - including:
  • Support for @NgModule decorators
  • FormsModule, RouterModule, and Material Design modules make it easier to use these libraries
  • Ahead-of-time (AoT) compilation for components and services
  • Lazy-loading support for the router

NgModules

@NgModule is a new decorator added in RC5 that provide a number of useful features for both Angular’s core and developer ergonomics. Check out the new docs here.

Basic NgModule usage looks like this:
@NgModule({
imports: [ BrowserModule ],
declarations: [ MyComponent ],
bootstrap: [ MyComponent ]
})
class MyAppModule {}

This decorator tells Angular two important things about your application:
declarations tell angular that `MyComponent` belongs to the `MyAppModule`
bootstrap advises angular that when it creates this module at startup, we want to automatically bootstrap `MyComponent` into the DOM.

To really understand what `NgModules` provide though, it’s worth understanding a bit deeper how Angular works under the hood, specifically in regards to compilation.

Compilation

At the heart of Angular 2 is our compiler. The compiler’s job is to take components and services written by developers and turn them into instructions to the browser to interact with your application.

At a high level, the compiler takes a Component like this:

@Component({
  selector: 'my-component',
  template: '<div>Hello {{name}}</div>'
})
class MyComponent {
name = ‘Sally’
}

and produces a ComponentFactory that looks (simplified) like this:

class MyComponentFactory {
  //creates the initial UI state
createInternal(){
    const parentRenderNode = this.renderer.createViewRoot(nativeDOMElement);
    this._text_0 = this.renderer.createText(parentRenderNode,'\n   ',null);
    this._el_1 = this.renderer.createElement(parentRenderNode,'div',null);
    this._text_2 = this.renderer.createText(this._el_1,'',null);
    this._text_3 = this.renderer.createText(parentRenderNode,'\n\n ',null);
  }
//updates the UI state
detectChangesInternal(){
    const currVal_0:any = utils.interpolate(1,'Hello ',this.context.name,'');
    if (utils.checkBinding(throwOnChange,this._expr_0,currVal_0)) {
      this.renderer.setText(this._text_2,currVal_0);
this._expr_0 = currVal_0;
}
  }
}

A ComponentFactory then, is simply the wrapper around the instructions that update the DOM based on your data. In pretty much every Angular 2 application written so far, this has happened behind the scenes transparently, right before your application starts up. We’ve referred to this as “dynamic” compilation in the past - our official term is “Just in Time (JIT)” compilation.
From the beginning, part of the design for Angular 2 was to enable this process to happen Ahead of Time (AoT) - that is, as a build step, when building your application. Roughly 60% of Angular’s code size is the compiler which does this work, so enabling AoT compilation means you don’t have to ship that code to your users, which gives a huge savings in bytes over the wire. Additionally, because the work is happening ahead of time, your users see dramatically decreased startup times for your app, because the compilation work doesn’t have to be done before the app can start.

So how does this work? For Angular to assemble a factory for each component, it examines your templates and looks for custom components (`my-foo-widget`), directives ( *ngFor, *ngIf, etc ), pipes ( someObservable | async ). It does this by looking up tags and selectors against a known list of these features.

We refer to this environment as the compiler’s context -  the suite of components, directives, and pipes that are available to angular as it is parsing your applications templates. Context is the reason, historically, you’ve had to add the components, directives and pipes to every component you’ve written:
@Component({
  selector: ‘my-component’,
  template: ‘my-component.html’,
  directives: [ SomeComponent, SomeOtherComponent ],
  pipes: [ MyMagicPipe ]
})
class MyComponent {}

If you’ve written any Angular 2 code at all though, you’ve probably asked yourself “but WHY do I have to list all these things!?” - especially if you’ve noticed that certain directives and pipes in Angular 2 are “special” - they’re available to your entire application without you doing anything ( *ngFor / *ngIf / *ngSwitch, for example). Good news for people who’ve asked that question - NgModules solve this confusion and significantly reduce the amount of boilerplate it takes to write an Angular application. For example - an application that wanted to use the router and Material Design might look like this:

import {Component} from ‘@angular/core’
import {MD_BUTTON_DIRECTIVES} from ‘@angular-material2/button’
import {MD_SIDENAV_DIRECTIVES} from ‘@angular-material2/sidenav’
import {MD_CARD_DIRECTIVES} from ‘@angular-material2/card’
import {provideRouter, ROUTER_DIRECTIVES} from ‘@angular/router’
@Component({
  selector: ‘my-component’,
  providers: [ provideRouter(routeConfig) ],
  directives: [
    MD_BUTTON_DIRECTIVES,
    MD_SIDENAV_DIRECTIVES,
    MD_CARD_DIRECTIVES,
    ROUTER_DIRECTIVES
  ]
})
class MyComponent {}

Quite a bit of boilerplate here, which would then need to be repeated in every component you want to use those directives in - which can get quite repetitive. By contrast, the same application using @NgModules would look like this:
import {Component} from ‘@angular/core’
import {MdButtonModule} from ‘@angular-material2/button’
import {MdSideNavModule} from ‘@angular-material2/sidenav’
import {MdCardModule} from ‘@angular-material2/card’
import {RouterModule} from ‘@angular/router’
@NgModule({
  imports: [
    MdButtonModule,
    MdSideNavModule,
    MdCardModule,
    RouterModule.forRoot(routeConfig)
  ]
})

class MyAppModule {}

At first glance, this might not seem all that different. The important difference is that now, *any* Component that belongs to this module now has access to *everything* imported into the Module, so building a Component that uses the router and material design now looks like
@Component({
  selector: ‘my-component’,
  templateUrl: ‘my-component.html’
})
class MyComponent {}

Note the distinct lack of repeated declarations of Components, Pipes, and Directives. This gets better and better as your application grows, and makes the entire Angular 2 experience much nicer. As your application grows, `NgModules` allow you to organize and pass around chunks of functionality at the module level, without large amounts of overhead and unofficial conventions (MY_RANDOM_THING_PROVIDERS).

 Essentially, NgModules provide the context the compiler needs to do its’ work, without the overhead of repeatedly declaring that context in every place you want to make it available. AoT Compile - because an NgModule now makes it easy to provide the context necessary for compilation, Angular’s AoT compiler works well - this functionality is included in RC5, and documentation on how to use it is forthcoming.

At a high level, @angular/compiler-cli provides a wrapper around Typescript’s `tsc` compiler, and both AoT compiles your application’s code, and then transpiles your application’s Typescript to Javascript:
$ ngc -p src

This generates a new file for each component and module ( called an NgFactory ), and to run your app in AoT mode, all that’s required is changing your main.ts file from
import {platformBrowserDynamic} from ‘@angular/platform-browser-dynamic’
import {MyAppModule} from ‘./app’
platformBrowserDynamic().bootstrapModule(MyAppModule);

to
import {platformBrowser} from ‘@angular/platform-browser’
import {MyAppModuleNgFactory} from ‘./app.ngfactory’ //generated code
platformBrowser().bootstrapModuleFactory(MyAppModuleNgFactory);

Lazy Loading In addition to enabling AoT compilation and generally improving the developer experience of Angular2, NgModules enable a simple way to lazy load pieces of your application via the router. A simple example illustrates this:
import {RouterModule} from ‘@angular/router’
import {NgModule} from ‘@angular/core’
@NgModule({
declarations: [ MyComponent, MyHomeRoute ],
bootstrap: [ MyComponent ],
imports: [
  RouterModule.forRoot([
{ path: ‘home’, component: MyHomeRoute },
{ path: ‘lazy’, loadChildren: ‘./my-lazy-module’ }
])
})
class MyAppModule {}

You simply define a `loadChildren` property on a route, and Angular will go fetch the module at that location and load the routes defined in it into the router config.
import {RouterModule} from ‘@angular/router’
import {NgModule} from ‘@angular/core’

@NgModule({
declarations: [ MyLazyHome, MyLazyChild ],
imports: [
  RouterModule.forChild([
  { path: ‘’, component: MyLazyHome },
{ path: ‘a’, component: MyLazyChild }
])
]
})
class MyLazyModule {}

You can additionally define a guard to protect the loading of child modules, which makes it perfect for controlling access to application functionality via user authentication or configuration.

RC5 deprecations 

While we made some significant changes in RC5, we’ve been careful to make sure it shouldn’t break any existing applications - most apps should just work with an upgrade to RC5. Because NgModules now provide a much cleaner and simpler way to declare the things your applications needs to run, we’ve deprecated and will remove for 2.0.0-final the need to declare directives, pipes and components in individual components, in favor of doing so at the NgModule level. For simple applications, migrating to NgModules syntax will generally involve tweaking your bootstrap file, and deleting a significant amount of repetitive boilerplate code in your components.

We’ve provided a migration guide here to guide you through it.

Upcoming

RC5 represents our expected public API for our 2.0.0 release, including router and forms APIs, and introduces some major improvements in how angular applications are written and compiled. We plan to spend the next few weeks optimizing Angular’s core for build tooling and tree-shakeablility, providing documentation and guidance on building for production, and tackling any issues that surface with RC5. Stay tuned - we’re almost there!
Share:

0 comments:

Post a Comment