Telerik blogs
ngmigrate_header

This guide was written for Angular 2 version: 2.0.0-rc.4

With component architecture in Angular 2, it's important to design components that contain what we call inputs and outputs. The data enters a component via an input, and leaves the component through an output. This is a small but powerful conceptual change to Angular 1.x's two-way data-binding in which changes automatically propagate to all listeners for that particular binding.

Angular 1.x introduced one-way data-flow in the Angular 1.5.x branch, which mirrors the Angular 2 way of building components. For this guide, we'll be using Angular 1.x's .component() method to compare to Angular 2.

This guide continues on from the previous guide of passing data into components, which is a recommended prerequisite.

Table of contents

Angular 1.x

In Angular 1.x, we have multiple ways to emit data via event binding from a "component". Before Angular 1.5.x, this was always done through the .directive() API, which contains scope and bindToController properties for bindings. In Angular 1.5.x the .component() API was introduced and we use a single bindings property. To emit an event from a component, we need to use attribute binding.

// "Component Event Binding with @Output() in Angular" is one of our top 5 JavaScript articles of 2017. See the full list here.

Attribute binding

Following on the previous article, we'll be using our <counter> component. We'll keep the attribute bindings in place for passing data into our component, but add a controller with a function to let us know when the component updates the count number.

To use the Component we declare it inside a template and use a custom attribute on the element itself. In this case, the count attribute exists from the previous article, so the new addition here is on-update with the registered callback from the controller:

const app = {
  template: `
    <div>
      My Counter:
      <counter
       count="$ctrl.count"
       on-update="$ctrl.countUpdated($event);"></counter>
    </div>
  `,
  controller() {
    this.count = 2;
    this.countUpdated = (event) => {
      this.count = event.count;
    };
  }
};

angular
  .module('app')
  .component('app', app);

The number 2 is hardcoded here, however a real world application would be data-driven. We call this "attribute binding" because Angular 1.x grabs existing HTML and extends it, therefore we use a custom attribute.

Directive attribute bindings

With Directives we have two ways to pass in event callbacks, scope or bindToController. Both use the '&' syntax, which allows us to delegate a function for this purpose.

Let's take the counter directive and demonstrate event bindings through accessing the on-update attribute via bindToController (which converts to camelCase in the bindings Object):

const counter = () => ({
  scope: {},
  bindToController: {
    count: '<',
    onUpdate: '&'
  },
  controllerAs: '$ctrl',
  controller() {
    this.increment = () => this.count++;
    this.decrement = () => this.count--;
  },
  template: `
    <div>
      <button ng-click="$ctrl.decrement()">-</button>
      <input ng-model="$ctrl.count">
      <button ng-click="$ctrl.increment()">+</button>
    </div>
  `
});

In directives, we can either use the bindToController property and specify an object of bindings, or use the scope property to declare the bindings and alternative bindToController syntax:

const counter = () => ({
  ...
  scope: {
    count: '<',
    onUpdate: '&'
  },
  bindToController: true
  ...
});

Both of these make the onUpdate property specified as an event binding to be available in the template and controller for calling the function.

Component attribute bindings

With the .component() API, things are similar to the directive but are much simpler:

const counter = {
  bindings: {
    count: '<',
    onUpdate: '&'
  },
  controller() {
    this.increment = () => this.count++;
    this.decrement = () => this.count--;
  },
  template: `
    <div>
      <button ng-click="$ctrl.decrement()">-</button>
      <input ng-model="$ctrl.count">
      <button ng-click="$ctrl.increment()">+</button>
    </div>
  `
};

angular
  .module('app')
  .component('counter', counter);

Note the changes from scope and bindToController to the new bindings property, as well as dropping the controllerAs property as $ctrl is the new default for .component(). Component definitions are also objects, not functions like directives are.

Custom attribute binding names

Let's assume we want to create an internal component property called onUpdate, yet want the attribute we bind to be called something different. If we declare an attribute of updates instead of on-update, we end up with <counter updates="$ctrl.fn($event);"> instead, and things would look like this:

const counter = {
  bindings: {
    ...
    onUpdate: '&updates'
  },
  ...
};

angular
  .module('app')
  .component('counter', counter);

We use count as the internal component reference, but explicitly tell Angular 1.x that the property is coming from init and we want one-way data-flow with the < syntax prefix.

Calling delegate methods

Calling these functions is easy, as they map directly across to the bindings property:

const counter = {
  bindings: {
    count: '<',
    onUpdate: '&'
  },
  controller() {
    this.increment = () => {
      this.count++;
      this.onUpdate({
        $event: {
          count: this.count
        }
      });
    }
    this.decrement = () => {
      this.count--;
      this.onUpdate({
        $event: {
          count: this.count
        }
      });
    }
  },
  template: `
    <div>
      <button ng-click="$ctrl.decrement()">-</button>
      <input ng-model="$ctrl.count">
      <button ng-click="$ctrl.increment()">+</button>
    </div>
  `
};

angular
  .module('app')
  .component('counter', counter);

Here we pass this Object { $event: {} } into the function's callback, this is to mirror Angular 2's $event syntax when being passed data back. So when this.onUpdate is invoked, it actually passes the data back up to the parent. This is where $ctrl.countUpdated($event); is called and passed the data, which is the parent component. Let's move on to the Angular 2 implementation.

Angular 2

In Angular 2, this concept still applies and we use property binding instead of attributes. There is little difference in the physical appearance of the two, however Angular 2 pre-compiles the templates and accesses JavaScript properties, rather than fetching data from existing HTML attributes - it's a different compile phase.

Angular 1 uses attribute binding, Angular 2 uses property binding

Property binding

We can jump to the CounterComponent we saw from the previous article:

import {Component} from '@angular/core';
import CounterComponent from './counter';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <counter
        [count]="counterValue"
        (update)="counterUpdate($event)"></counter>
    </div>
  `,
  directives: [CounterComponent]
})
export default class App {
  public counterValue: number;
  constructor() {
    this.counterValue = 2;
  }
  counterUpdate(event: object) {
    this.counterValue = event.count;
  }
}

Notice here how we are using <counter (update)="counterUpdate($event)">, where counterUpdate is driven from the ES2015 Class. We use on-update in Angular 1.x to denote the binding is some kind of an event callback. In Angular 2, the syntax lets us know this as it's different from input binding square brackets. The normal style brackets are a part of Angular 2's template syntax that means we are providing event binding.

Component property bindings

In Angular 2, we have a more explicit API for defining inputs and outputs for components. For outputs, we have a TypeScript decorator named @Output(), which is extremely readable and easy to use. Before we can begin using the decorator we need to import the Output and EventEmitter APIs from @angular:

import {Component, Input, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'counter',
  template: `
    <div>
      <button (click)="decrement()">-</button>
      <input [ngModel]="count">
      <button (click)="increment()">+</button>
    </div>
  `
})
export default class CounterComponent {
  constructor() {}
  increment() {
    this.count++;
  }
  decrement() {
    this.count--;
  }
}

The next stage of this is defining the component output via the @Output() decorator and invoking a new instance of EventEmitter. We can then declare this inside the ES2015 Class next to @Input():

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

@Component({
  ...
})
export default class CounterComponent {
  @Input() count: number = 0;
  @Output() update = new EventEmitter<any>();
  constructor() {}
  ...
}

Now, if you think back to the Angular 1.x example where we used bindings: { onUpdate: '&' }, this is actually doing the exact same thing and telling Angular 2 where the event output will be coming from.

Using EventEmitter

To use the EventEmitter instance, we need to then reference update and then call the emit method inside increment and decrement just like with the Angular 1.x example:

import {Component, Input, Output, EventEmitter} from '@angular/core';

@Component({
  ...
})
export default class CounterComponent {
  @Input() count: number = 0;
  @Output() update = new EventEmitter<any>();
  constructor() {}
  increment() {
    this.count++;
    this.update.emit({
      count: this.count
    });
  }
  decrement() {
    this.count--;
    this.update.emit({
      count: this.count
    });
  }
}

We pass in an Object with a count property, just like in the Angular 1.x code, which is also made available to the parent component via counterUpdate($event):

import {Component} from '@angular/core';
import CounterComponent from './counter';

@Component({
  ...
})
export default class App {
  ...
  counterUpdate(event: object) {
    this.counterValue = event.count;
  }
}

Alternative @Output() syntax

There is also an alternative syntax to using @Output() as a decorator, and that's using it as an outputs property inside the @Component() decorator:

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

@Component({
  selector: 'counter',
  ...
  outputs: ['update']
})
export default class CounterComponent {
  ...
}

This is however the least favored approach. I'd stick with using TypeScript decorators to make use of types and readability.

Custom property binding names

In Angular 1.x we can use bindings: { foo: '&bar' } syntax to change the binding name to a different internal mapping - in this case bar becomes foo. We can also do the same with Angular 2's @Output() by passing in a string to the decorator defining the name:

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

@Component({
  ...
})
export default class CounterComponent {
  @Input('init') count: number = 0;
  @Output('change') update = new EventEmitter<any>();
  constructor() {}
  ...
}

This would be the equivalent of <counter (change)="fn($event)"> mapped internally to update. Also the outputs: [] array is set by using : to separate the mapped name and the property binding:

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

@Component({
  selector: 'counter',
  ...
  outputs: ['update:change']
})
export default class CounterComponent {
  ...
}

These aren't typically advised either. You're best sticking with TypeScript decorators in this case to keep things string-less and dynamic.

Final code

You can see in the final code below that incrementing/decrementing the counter also updates the parent through the @Output() event:


todd-motto
About the Author

Todd Motto

Todd Motto (@toddmotto) is a Google Developer Expert from England, UK. He's taught millions of developers world-wide through his blogs, videos, conferences and workshops. He focuses on teaching Angular and JavaScript courses over on Ultimate Courses.

Related Posts

Comments

Comments are disabled in preview mode.