Angular 2: How Do Components Communicate

Previously on Angular 2: How to Build a Container Component I explained how to create structural components that marks up the way a view is rendered by providing grid directives with a container, title, rows and items.

But before I move on and work in more advanced examples; I want to explain how do components communicate between each other in Angular 2.

Types of Communication

If you have some experience working with Angular 1, then you will know that there are some ways to communicate between controllers by using either the $rootScope, $scope, $emit, $broadcast or services. But now using Angular 2 we have slightly different ways to communicate between components.

When I first started to work with Angular 2 I quickly learned about the @Input and @Output decorators, I was also able to figure out that services will keep doing some of the job in this version, but I also found there are other interesting ways to communicate between components, so lets review these.

Services

The very first method I want to talk about is by using services, why? Well because I know many people will find familiar working with this and is a good way to feel you are in home.

The following snippet is an example of the most basic service form in Angular 2:

1
2
3
4
5
import {Injectable} from '@angular/core';
@Injectable()
export class MyService {
constructor() { }
}

Basically, as you can see this is a regular class that will be exported as a module.

The only questionable feature in here is the @Injectable() decorator, which basically is a way to say angular that this service is good to be used in Dependency Injection (DI).

The following is an example of how would you call this service from any other component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {Component, OnInit} from '@angular/core';
import {MyService} from './shared/my-service'; // see angular.io/styleguide

@Component({
selector: '<my-component></my-component>',
templateUrl: 'app/name.component.html',
// This is unnecessary when installing MyService within Root NgModule
providers: [MyService]
})

export class MyComponent implements OnInit {

constructor(private ms: MyService) {}

ngOnInit() { this.ms.doSomething(); }
}

To use our service within any component is really simple, we import the module from whatever directory we may store it, then we say to Angular 2 that we want to use MyService as provider, this way Angular can create an instance for use and inject it in our constructor.

Then we are able to use our service instance by using this.ms anywhere in our component.

OK, so we know how to build a service so what? well we can build setters and getters to maintain some states of the application, or even some sort of information like when a user is logged, you can get the logged user anywhere in your application components.

Services emitting Events using RXJS

This may be a more advanced way to use services, let me continue with the logged example.

Within your Application you have 1 of many components living at the same time, one of these components is in charge to either communicate the user is logged in to other components or logged out.

Since a standard getter and setter won’t work because changes may occur after all components are already loaded, we need to set a listener that is watching for changes, but how?

Complete Example in GitHub

RJXS Subject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { Logged } from './declarations/logged.d';

@Injectable()
export class LoggedService {

private logged: Logged;
private subject: Subject<Logged> = new Subject<Logged>();

setLogged(logged: Logged): void {
this.logged = logged;
this.subject.next(logged);
}

getLogged(): Observable<Logged> {
return this.subject.asObservable();
}
}

This example seems to be a little more complex, but it is not other than a way to emit and listen events, so we do not only have a setter and a getter that stores a local reference of logged but it will also send messages to any component that is register and listens for changes.

Note: This post has been updated thanks to the wonderful community response on twitter, please keep commenting :D

Now we would listen for changes as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {Component, OnInit} from '@angular/core';
import {LoggedService} from './shared/logged-service';

@Component({
selector: '<my-component></my-component>',
templateUrl: 'app/name.component.html',
// This is unnecessary when installing LoggedService within Root NgModule
providers: [LoggedService]
})

export class MyComponent implements OnInit {

constructor(private ls: LoggedService) {}

ngOnInit() {
// Will fire everytime other component use the setter this.ls.setLogged()
this.ls.getLogged().subscribe((logged: Logged) => {
console.log('Welcome %s', logged.displayname);
});
}
}

If we get our Injectable service and then we subscribe to the getLogged method within any of our components living in our application, we would be able to know when a user has been logged, when we could for example change the NavigationComponent View and display the user displayname instead of a sign in button.

I know this last example is a little more complex… So please leave a comment below for anything you need to be clarified.

Input Properties (Parent -> Child)

The very first cool way to communicate between parents and children is by using @Input properties, which basically is type of communication Parent -> Child.

The following snippet explains how to set a input property in a child component that will receive messages from the parent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {Component, Input, OnInit} from '@angular/core';

@Component({
selector: 'child-component',
template: `I'm {{ name }}`
})

export class ChildComponent implements OnInit {

@Input() name: string;

constructor() { }

ngOnInit() { }
}

If we consider the following example, we will see how to implement the ChildComponent and how to communicate by using the @Input property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {Component, OnInit} from '@angular/core';
import {ChildComponent} from './child-component.ts';

@Component({
selector: 'parent-component',
template: `<child-component [name]="childName"></child-component>`,
// This is unnecessary when installing ChildComponent within Root NgModule
directives: [ChildComponent]
})

export class ParentComponent implements OnInit {

private childName: string;

constructor() { }

ngOnInit() {
this.childName = 'Boo';
}
}

When implementing the ChildComponent within the ParentComponent and set the childName as name by using the input property name, we communicate top to bottom a message that the result should be the ChildComponent saying something like: I’m Boo.

In this order of ideas we can also know when for some reason the ParentComponent changes ChildComponent name, we can listen for those changes by using the component life cycle hook ngOnChanges.

1
2
3
4
5
6
7
8
9
10
11
12
13
import {Component, Input, SimpleChange} from '@angular/core';

@Component({
selector: 'child-component',
template: `I'm {{ name }}`
})

export class ChildComponent {

@Input() name: string;

ngOnChanges(changes: SimpleChange) { }
}

Output Properties (Child -> Parent)

The following method is how ChildComponent communicates with ParentComponent by emitting events.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component({
selector: 'child-component',
template: `I'm {{ name }}`
})

export class ChildComponent {

@Input() name: string;
@Output() cry: EventEmitter<boolean> = new EventEmitter<boolean>();

ngOnChanges(changes: SimpleChange) { }

isHungry() {
this.cry.next(true);
}
}

Since the ChildComponent is now able to emit the cry event when is hungry, now as ParentComponent we need to set a listener for that event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {Component, OnInit} from '@angular/core';
import {ChildComponent} from './child-component.ts';

@Component({
selector: 'parent-component',
template: `<child-component [name]="childName" (cry)="isChildCrying($event)"></child-component>`,
// This is unnecessary when installing ChildComponent within Root NgModule
directives: [ChildComponent]
})

export class ParentComponent implements OnInit {

private childName: string;

constructor() { }

ngOnInit() {
this.childName = 'Boo';
}

isChildCrying(isIt: boolean) {
console.log('The child %s crying', isIt ? 'is' : 'is not');
}
}

Now every time the ChildComponent is hungry and emits the ‘cry’ event, the ParentComponent now is able to listen for those events.

Of course we could create more methods to feed the ChildComponent and listen when it stops crying.

Component Template Reference

Another way to share information between ParentComponent and ChildComponent is by using a component reference and then access to properties or methods of that component.

1
2
3
4
5
6
7
8
9
10
11
12
import {Component} from '@angular/core';

@Component({
selector: 'child-component',
template: `I'm {{ name }}`
})

export class ChildComponent {

public name: string;

}

Now if we implement this ChildComponent within the ParentCompnent and get a reference, we will be able to get access to properties and methods from the parent template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import {Component, OnInit} from '@angular/core';
import {ChildComponent} from './child-component.ts';

@Component({
selector: 'parent-component',
template: `
<child-component #child></child-component>
<button (click)="child.name = childName"></button>
`,

// This is unnecessary when installing ChildComponent within Root NgModule
directives: [ChildComponent]
})

export class ParentComponent implements OnInit {

private childName: string;

constructor() { }

ngOnInit() {
this.childName = 'Boo';
}
}

ViewChild Injection

I know this may sound scary, but the truth is that if you already understood the communication by Component Template References then you may find that you may need to have access from the ParentComponent and not from the parent component template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {Component, AfterViewInit, ViewChild, ElementRef} from '@angular/core';
import {ChildComponent} from './child-component.ts';

@Component({
selector: 'parent-component',
template: `<child-component #child></child-component>`,
// This is unnecessary when installing ChildComponent within Root NgModule
directives: [ChildComponent]
})

export class ParentComponent implements AfterViewInit {

@ViewChild('child') cc: ElementRef;

constructor() { }

ngAfterViewInit() {
this.cc.name = 'Boo';
}
}

Actually, I think this is a pretty neat way to communicate because is really clean, but I would use this method when the ChildComponent is part of a same module, otherwise I would choice between @Input @Outputor Services because you don’t really want to internally work/mess with 3rd party libraries.

What is next?

In my next blog posts I will bring some hot new topics by using horizon.io which just has been launched and of course I will talk about how to build a native realtime application using Angular 2 and NativeScript 2.

If you like this series and want be aware of next releases and new packages, follow me on Twitter @johncasarrubias and if you feel it leave a comment here.

Thanks for reading.


Comments:

...