April 13, 2015
  • All
  • angularjs
  • Ionic
  • Top Posts

Angular 2 Series: Components

Max Lynch

CEO

README: Angular 2 has changed significantly since this post was written. As such, please do not use this code verbatim. Instead, focus on the concepts below and then map them to the new syntax and API of Angular 2.0.0.

Welcome to the second post in our series on Angular 2. If you missed the first post, check out the quick Intro to Angular 2.

In this post, we are going to talk about Components, which elegantly replace the mix of controllers, scopes, and directives from Angular 1.

The death of scope

If you missed Igor and Tobias’ great talk at ngEurope in October, they quite dramatically proclaimed the death of a variety of Angular 1 concepts, including controllers and $scope. I was there, I and remember the gasps and nervous laughter from the crowd as a series of ominous gravestones flashed on the screen:

RIP Angular 1

The uproar from the broader community was immediate, with scathing comments, high-profile departures, and plenty of overly-dramatic banter to make it really exciting.

Though it made for good TV, the reaction was unwarranted. Those new to Angular 2 quickly see how the new component model is a lot easier to use than what we had in v1.

The “Component”

Angular 1 wasn’t built around the concept of components. Instead, we’d attach controllers to various parts of the page with our custom logic. Scopes would be attached or flow through, based on how our custom directives encapsulated themselves (isolate scope, anyone?).

I don’t really know how to describe the model in v1, but it wasn’t like any other hierarchical system I’ve ever used. We’d just “attach” behaviors to various parts of our page and then have to deal with the complicated meta-structure created by our directives, controllers, and scopes.

To add logic to the page, we’d have to decide between building a controller or a custom directive.

In version 2, Angular drops all of this for a much cleaner, more object-oriented Component model.

Our first component

If you’ve ever written Java, C#, or any other language with strong OO patterns, you’ll immediately understand a component. It’s just a class that represents an element on the screen, with member-data that influences the way it looks and behaves.

Just like a JComponent subclass in Java (Swing), or a Control subclass in Windows Forms development.

Let’s see an example:


import {Component} from '@angular/core'

@Component({
  selector: 'my-component',
  template: '<div>Hello my name is {{name}}</div>'
})

export class MyComponent {
  constructor() {
    this.name = 'Max'
  }
  sayMyName() {
    console.log('My name is', this.name)
  }
}

This creates a new component called MyComponent, which will be identified in markup as <my-component>, just like restrict: 'E' did in v1.

We can specify a template for the component with the template property in @Component annotation (annotations are an es6 extension added by both AtScript and TypeScript, though only TypeScript will be relevant going forward).

Member data

If we look closer at the example above, we don’t see $scope anywhere. Instead, we see this.name = 'Max', and a member function (“method”) called sayMyName(). If you’re familiar with Controller As syntax in v1, the use of this will look familiar to you.

Even so, this is different. When our component is “instantiated” and rendered on the page, we have an instance of our component. We can modify the instance data of that component, call methods on it, and pass it around to other components. It’s just an object!

More tangibly, imagine we have a Button component. The component has a this.title for the text of the button, and a click handler. Previously, we’d either build a new directive for the button, maybe add an isolate scope with some events and two-way data bindings, or get lazy and just attach a controller to it (or one of its parents). With Angular 2 components, we get a natural combination of all of these concepts: our Component has encapsulated instance data (much like isolate scopes), event handlers (much like methods attached to scopes), and a template (much like directives).

We can even inherit from other components or attach components through attributes, and access parents and siblings (more on that in a future post). This is a far easier, less error-prone way to replace require from directives in v1.

ngApp?

In the previous post, we briefly mentioned the bootstrap process, the process by which an Angular 2 app powers itself up. In v1, we had ng-app, or we could manually bootstrap. In v2, there isn’t an ng-app. Instead, we provide our own root Component from which we want to start the app:

<app></app>

which would look like this as a v2 component:


@Component({ 
  selector: 'app',
  templateUrl: 'main.html'
 })

class MyApp {
  constructor() {
    console.log('App Start')
  }
}

bootstrap(MyApp)

What we’d often see in Angular 1 was ng-app and ng-controller used on the same element, like this:

<body ng-app="myspace" ng-controller="AppCtrl">

Then, our AppCtrl would create some root scope data that our child controllers would access and/or extend.

In v2, we get all of that in one Component.

I really like this change. If you think about your document layout, it’s really just a tree. The root shouldn’t be any different than every other element, even though it was in v1.

With the change to the component model, we need to change our mindset a bit. Instead of attaching our custom logic to the page with controllers, we put that logic into components. If we want a certain part of the page needs business logic, we create a new component for it. If we are using an existing component but want to attach behavior to it, we can extend it.

Annotations

Let’s dig in a bit to the @Component annotation above. If you recall, these are extensions on ES6, which are becoming part of TypeScript which Angular 2 will use.

Basically, these annotations allow us to attach information to our component. We can configure the selector our Component will look for to instantiate itself (like <my-component>), and also set the template.

@Component is the core annotation, and has a few important parameters:

@Component({
  selector: 'my-component',
  providers: [MyService],
  // Url based template
  templateUrl: 'main.html',

  // Inline template
  template: `
    <div>
      <button></button>
      <ng-content></ng-content>
    </div>
  `,
  directives: [Button],
})

The selector param is the replacement for the auto-naming system of directives in v1 and is now explicit: you must set it to exactly what you want the new component to be named in markup. It works much like querySelector.

The providers param is part of the new Dependency Injection system, and configures which services we want to inject into our component (more on this in a later post).

We can use provide a template either inline, using template, or point to an external file using templateUrl. This similar to how we defined templates for custom directives in Angular 1. Notice the use of the awesome new backtick Template strings in ES6 that let us easily create multi-line templates!

We also reference the child directives we want to enable so they are compiled and instantiated (I hope this changes in the future so you don’t have to explicitly specify child directives).

Finally, we should note the <ng-content></ng-content> tag in our template. This is the new “ngTransclude” and specifies where child content will be injected. You can have multiple ones and target different spots to include child content (more on this in the future!)

Conclusion

A lot is changing in Angular 2, and few things as dramatically as the way we build and specify components on the page.

Though it’s scary to move on from our beloved scopes, controllers, and directives (concepts that we worked so hard to learn and learn to love), it’s really a wonderful thing. With the new component model, Angular is moving into much more familiar OO territory, and is setting itself up to work with the future of web standards, like Web Components and Shadow DOM.

Once you get used to the new model, it’s hard to go back. I think the community will learn to embrace the changes and appreciate how they make Angular 2 so much better.

In our next post, we will talk about “New-way” data binding: the untimely death of 2-way data binding! Stay tuned.


Max Lynch

CEO