8 July 2016

Understanding Angular 2 Components for AngularJS Devs

What is it like to build an Angular 2 app, and how is the experience different from AngularJS?

Angular 2 is component based. Components combine concepts that we are already familiar with from AngularJS. The Angular 2 Component combines the AngularJS Directive, Controller, and Scope. My article will attempt to make you more comfortable with components by comparing them to what you already know from AngularJS.

Here are some tutorials that I worked through to prepare this. I will periodically link to code examples from them: Quickstart & Tour of Heroes

TypeScript is recommended for Angular 2. You will see the .ts file extension. I will point out TypeScript syntax where needed.

Bootstrapping the Top Level Component

Here is an example of an Angular 2 component, app.component.ts, that I’ve copied below from the Angular 2 Tutorial.

import { Component, OnInit } from '@angular/core';

import { Hero } from './hero';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-app',
  template: '
    <h1></h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge"></span> 
      </li>
    </ul>
    <my-hero-detail [hero]="selectedHero"></my-hero-detail>
  ',
  styles: ['
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
  '],
  directives: [HeroDetailComponent],
  providers: [HeroService]
})

export class AppComponent implements OnInit {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private heroService: HeroService) { }

  getHeroes() {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit() {
    this.getHeroes();
  }

  onSelect(hero: Hero) { this.selectedHero = hero; }
}

Before we delve into the details, let me describe what is happening here at a high level. First we import any referenced classes using TypeScript modules at the top. Next we configure the component using the @Component decorator. The decorator is a feature of TypeScript. Finally, we define the component’s properties and behavior then export it.

Angular 2 apps will have one main, top-level Component. Using the ES6 module system (supported by TypeScript), we can kick-off the application by importing the AppComponent and bootstrapping it as the main component like this:

// main.ts
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';

bootstrap(AppComponent);

Loading main.js in a <script> tag will start the app on the page through it’s top-level component.

The @Component Decorator

TypeScript decorators, like @Component, are functions that modify the class defined below. The Angular 2 @Component decorator is a configuration much like the AngularJS Directive. Compare the following AngularJS Directive to the Angular 2 @Component decorator above.

directive('myHero', function() {
  return {
    restrict: 'E',
    scope: {
      customerInfo: '=info'
    },
    templateUrl: 'my-hero.html',
    controller: 'HeroController',
    controllerAs: 'heroCtrl'
  };
});

Components and directives define the HTML markup to use. Components also define the styles attribute where directives do not. In components, styles are encapsulated. They do not “leak” to elements in the outer HTML or to other Components. This is enabled by some improvements in HTML itself.

Styles and HTML markup need not be defined inline. @Component provides the templateUrl and styleUrl properties all alternative to template and style.

@Component({
  selector: 'my-dashboard',
  templateUrl: 'app/dashboard.component.html',
  styleUrls: ['app/dashboard.component.css']
})

Components Continued

Angular 2 components make some decisions for you that directives didn’t. For example, Angular 2 components:

  • Always isolate scope (instead of sharing a parent scope)
  • Always restrict ‘E’ (load into custom elements in the DOM)
  • Always bind to a controller (as opposed to $scope)

AngularJS applications that adhere to the John Papa Style Guide already use these conventions. I would strongly recommend referencing the guide while developing your AngularJS apps.

Component Class Definition

Angular 2 component class definitions do the same job as our AngularJS controllers. At the bottom of app.component.ts we export the AppComponent component class definition. It’s where we define and initialize the state of our component and define the component’s behaviour as functions that events on the page bind to.

import {Component, OnInit} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroService} from './hero.service';

@Component({
  selector: 'my-app',
  template:'
    <h1></h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="#hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge"></span> 
      </li>
    </ul>
    <my-hero-detail [hero]="selectedHero"></my-hero-detail>
  ',
  directives: [HeroDetailComponent],
  providers: [HeroService]
})

export class AppComponent implements OnInit {
  title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private _heroService: HeroService) { }

  getHeroes() {
    this._heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit() {
    this.getHeroes();
  }

  onSelect(hero: Hero) { this.selectedHero = hero; }
}

Angular 2 provides the ngOnInit lifecycle hook for you to step in and initialize the component. We initialize data through the ngOnInit hook instead of through the constructor to limit side effects in our tests. Notice we only use the constructor function to wire things up.

Component Interactions

AppComponent will render itself into <my-app>, by setting its own element selector to my-app in the @Component decorator. It will also render a HeroDetailComponent into <my-hero-detail>. It does a few things to make this happen:

  • Imports the HeroDetailComponent class
  • Registers the HeroDetailComponent in its directives metadata array
  • Provides the <my-hero-detail> element in its template markup
  • Sets the values of HeroDetailComponent’s input properties through <my-hero-detail>’s element attributes

Here is the implementation of the HeroDetailComponent:

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

@Component({
  selector: 'my-hero-detail',
  template: '
    <div *ngIf="hero">
      <h2> details!</h2>
      <div>
        <label>id: </label>
      </div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  '
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}

When the app is bootstrapped, AppComponent is directed through its selector property to seek out a <my-app> element on the page to render itself into. Its sub-component, HeroDetailComponent, will similarly seek out a <my-hero-detail> element through its selector property.

The <my-hero-detail> element is provided in the markup of the HeroDetail’s parent component, AppComponent, and the <my-app> element is provided in index.html. Users of angular ui-router will recognize this pattern of rendering nested views into <ui-view> elements.

When the user selects a hero, AppComponent is responsible for passing that information along to the HeroDetailComponent.

<my-hero-detail [hero]="selectedHero"></my-hero-detail>

In the <my-hero-detail> element, the AppComponent binds the hero property of the HeroDetailComponent to the current value of its selectedHero property. We also declare hero as an @Input property HeroDetailComponent’s class definition (see HeroDetailComponent code above). The interesting side-effect of this is that data-binding on selectedHero, and the <div *ngIf="hero"> in HeroDetailComponent’s template is the trigger that renders the my-hero-detail component on the page.

A property that is bound in this way, through a sub-component’s element, must be declared as an @Input() in the sub-component. Failing to do so will force Angular 2 to reject the binding and throw an error. In Angular 2, we explicitly declare bindable properties as @Input(), so that we can protect internal properties from being munged from the outside.

The bracket syntax on [hero] represents a property data binding. This is a one-way binding of currentHero from the component to an element. Angular 2 adds new template syntax to represent different bindings in the markup.

One-way binding means the value of currentHero cannot be updated by hero-detail. A two-way binding to currentHero would be represented like [(hero)] = "currentHero". This example doesn’t touch all the types of data-binding in Angular 2. You can read more about event bindings, etc, here.

Dependency Injection

AppComponent depends on the HeroService in order to access the application’s hero data. We register the HeroService as a dependency in the @Component decorator’s providers property. In our providers array we define the services we are interested in, and they are made available in our component’s constructor.

Notice that we do not re-declare the HeroService as a dependency of the HeroDetail component. Dependencies need not be re-declared in sub-components, and doing so could be detrimental. Redeclaring HeroService in this way would give HeroDetail a new instance of the HeroService. This defeats the purpose of the service, which is to share the same data amongst different components.

Conclusion

I wrote this article to demonstrate some differences in the development experience between Angular 2 and AngularJS by looking at one specific new feature, Components. I hope components provide you with a new way of thinking about your applications, simultaneously making them more flexible, extendable, and maintainable.

Angular 2 introduces many other improvements, both in the code and in areas like performance and mobile support. I invite you to explore and discover them for yourself. Happy Angularing!

Heads up! This article may make reference to the Gaslight team—that's still us! We go by Launch Scout now, this article was just written before we re-introduced ourselves. Find out more here.

Related Posts

Want to learn more about the work we do?

Explore our work

Ready to start your software journey with us?

Contact Us