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.
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.
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.
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.
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.
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 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.
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
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.
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.
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;
}
}
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.
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.
You can see in the final code below that incrementing/decrementing the counter also updates the parent through the @Output()
event:
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.