Thursday, June 30, 2016

Learning Angular 2: Tour of Heroes Tutorial, Lesson 5

Lesson 5 of the Tour of Heroes tutorial introduces services.

On a recent episode of the Adventures in Angular podcast, guest Pascal Precht made the recommendation that developers should get in the habit of always applying the @Injectable decorator to a service. The reason behind that recommendation is because the decorators (@Component, @Injectable, etc.) cause the emission of metdata needed to work out the dependency injection hierarchy, and there's no negative side-effects to adding the @Injectable decorator to a service even if that service doesn't itself have any dependencies.  The tutorial essentially makes the same recommendation.

The dependency injection (DI) mechanism described in the lesson involves three parts:

  • Using an import statement to import the code of the service module/file

  • Using a constructor function in the component to assign the module variable defined with the import statement as a property of the component.

  • Adding the module variable defined with the import statement to the list of providers in the component's "providers" metadata property, "providing" a working instance of the service.

At first glance, it appears to be a more verbose process than the Angular 1 approach of listing the dependencies as arguments to the module (though if you wanted to do any minification you needed to document the dependencies in either an inline array or a separate $inject property statement).  But you also had to load your modules properly, and of course all of your JavaScript files containing the dependency modules had to be pulled in via <script> tag upfront.  With this syntax the import statements let you only load the dependent modules you need for this particular component.

I think it's a more explicit syntax that takes some of the mystery out of the DI process.  As someone who does a lot of server-side coding, I'm familiar with constructor functions, so for me it's a matter of thinking of the "providers" property of the component as the arguments that will become part of the call to the constructor function generated by the compiler.  You can look in the ES5 output to see the resulting call to the constructor given a provider of "heroService":

var AppComponent = (function () {
    function AppComponent(heroService) {
        this.heroService = heroService;
        this.title = 'Tour of Heroes';
    }

Having hooks into the component lifecycle like ngOnInit provides developers with additional control over their components.  With Angular 1 I would end up working with controllers where it was sometimes hard to distinguish the code that would be executed when the controlller loaded from the code that defined event handlers or watched for certain data changes:  ngOnInit will provide a container for that initialization code, and run it at a point in the lifecycle where the component is "fully engaged."

Given the discussion in lesson 3 about how ngIf destroys/removes the content it contains rather than just hiding it, I was curious to see if that meant that any sub-component in that ngIf-wrapped content would fire off ngOnInit every time it was restored to the DOM.  So I made a really simple HeroSubDetailComponent:

import { Hero } from './hero';
import { Component, Input, OnInit } from '@angular/core';

@Component({
    selector: 'my-hero-subdetail',
    template: `
        <div>
            <span>{{hero.name}} is kinda cool.</span>
        </div>
    `
})

export class HeroSubDetailComponent implements OnInit {
    @Input()
    hero: Hero;

    ngOnInit() {
        console.log( "sub-detail-component initialized" );
    } 
}

...and then added it to the existing HeroDetailComponent within an ngIf block that would only evaluate to true for the first hero:


import { HeroSubDetailComponent } from './hero-sub-detail.component';

@Component({
    selector: 'my-hero-detail',
    directives: [ HeroSubDetailComponent ],
    template: `
    <div>
      <h2>{{hero.name}} details!</h2>
      <div>
        <label>id: </label>
        {{hero.id}}
      </div>
      <div>
        <label>name: </label>
        {{hero.name}}
      </div>
      <div *ngIf="hero.id == 11">
        <my-hero-subdetail [hero]="hero"></my-hero-subdetail>
      </div>
  </div>
    `
})

...and as I suspected, every time the HeroSubDetailComponent was removed then re-added to the DOM as I clicked through different heroes, it would fire ngOnInit on the re-add.

I wasn't aware that Promises were added as native constructs in ES2015 (and thus in the latest implementation of TypeScript), so that was something I learned during this lesson.  I was momentarily confused by how it was used without instantiating an instance of it:  apparently the resolve() method is a static method, so in this case where the data is hard-coded in the application and immediately available it makes sense to go this route.  But a note about the fact that in a more traditional use case you would instantiate a Promise instance might be warranted.

No comments:

Post a Comment