Update (August 2018): Still valid for Angular 6
It’s written for beginners — if you are an advanced or intermediate Angular developer you probably already know all these techniques.
Edit: Although, the title of this article is a bit misleading because we do not really want to communicate between components directly. Our components should be isolated and encapsulated. I chose this title because I think developers struggling with this will be googling it this way.
How to communicate between components? This is the topic I’ve seen many new Angular developers to struggle with. I will show you the three most common approaches, with examples that fits different use cases.
There is also the “redux way” which I could cover in another article in the future.
Imagine the use case that you have the sidebar in your application. Sidebar is either open or closed. You have the side-bar component and then you have component (or multiple components) that can open/close it or ask for its state.
Each one of these examples has the demo application with code inspections on StackBlitz and github repository.
This solution should be used when components have dependency between them. For example dropdown, and dropdown toggle. They usually can’t exist without each other.
We will create the side-bar-toggle component which will have the side-bar component as input and by clicking on the toggle button we will open/close the side-bar component.
Here is the relevant code:
app.component.html
app component
<app-side-bar-toggle [sideBar]="sideBar"></app-side-bar-toggle>
<app-side-bar #sideBar></app-side-bar>
side-bar-toggle.component.ts
@Component({
selector: 'app-side-bar-toggle',
templateUrl: './side-bar-toggle.component.html',
styleUrls: ['./side-bar-toggle.component.css']
})
export class SideBarToggleComponent {
@Input() sideBar: SideBarComponent;
@HostListener('click')
click() {
this.sideBar.toggle();
}
}
side-bar.component.ts
@Component({
selector: 'app-side-bar',
templateUrl: './side-bar.component.html',
styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent {
@HostBinding('class.is-open')
isOpen = false;
toggle() {
this.isOpen = !this.isOpen;
}
}
Can be used when it’s easy to control shared state between components through their parent component and you don’t feel like you want to create new service or create some boilerplate code, because of one variable.
Implementation of this approach is almost the same as the previous one, however side-bar-toggle doesn’t receive side-bar component. Instead parent component holds sideBarIsOpened property which is passed to the side-bar component.
app.component.html
<app-side-bar-toggle (toggle)="toggleSideBar()"></app-side-bar-toggle>
<app-side-bar [isOpen]="sideBarIsOpened"></app-side-bar>
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
sideBarIsOpened = false;
toggleSideBar(shouldOpen: boolean) {
this.sideBarIsOpened = !this.sideBarIsOpened;
}
}
side-bar-toggle.component.ts
@Component({
selector: 'app-side-bar-toggle',
templateUrl: './side-bar-toggle.component.html',
styleUrls: ['./side-bar-toggle.component.css']
})
export class SideBarToggleComponent {
@Output() toggle: EventEmitter<null> = new EventEmitter();
@HostListener('click')
click() {
this.toggle.emit();
}
}
side-bar.component.ts
@Component({
selector: 'app-side-bar',
templateUrl: './side-bar.component.html',
styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent {
@HostBinding('class.is-open') @Input()
isOpen = false;
}
Finally this option is useful and should be used when you have component that is controlled or its state is asked from multiple instances.
Now we have multiple places across the application that will need to access our side-bar component. Let’s see how we do it.
We will now create side-bar.service.ts so we will have:
Side bar services will have toggle method and change event so every component that will inject this service can be notified that panel was opened or can toggle it.
In this example neither side-bar component or side-bar-toggle component has input parameters, because they communicate through service.
Now the code:
app.component.html
<app-side-bar-toggle></app-side-bar-toggle>
<app-side-bar></app-side-bar>
side-bar-toggle.component.ts
@Component({
selector: 'app-side-bar-toggle',
templateUrl: './side-bar-toggle.component.html',
styleUrls: ['./side-bar-toggle.component.css']
})
export class SideBarToggleComponent {
constructor(
private sideBarService: SideBarService
) { }
@HostListener('click')
click() {
this.sideBarService.toggle();
}
}
side-bar.component.ts
@Component({
selector: 'app-side-bar',
templateUrl: './side-bar.component.html',
styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent {
@HostBinding('class.is-open')
isOpen = false;
constructor(
private sideBarService: SideBarService
) { }
ngOnInit() {
this.sideBarService.change.subscribe(isOpen => {
this.isOpen = isOpen;
});
}
}
side-bar.service.ts
@Injectable()
export class SideBarService {
isOpen = false;
@Output() change: EventEmitter<boolean> = new EventEmitter();
toggle() {
this.isOpen = !this.isOpen;
this.change.emit(this.isOpen);
}
}
If you have another way of doing this or you have any problems with examples above let me know in the comments.
30s ad
☞ Angular 5 Advanced MasterClass & FREE E-Book
☞ Angular 5 NgRx Store Masterclass & FREE E-Book
☞ Ultimate Angular 5 with TypeScript and Bootstrap 4
☞ Angular 2 Step by Step: From Beginner to Advanced
☞ Angular Tutorial - Learn Angular from Scratch
☞ Web Development Tutorial - JavaScript, HTML, CSS
☞ Javascript Project Tutorial: Budget App
☞ Test Driven Development with Angular
☞ JavaScript Programming Tutorial Full Course for Beginners
☞ E-Commerce JavaScript Tutorial - Shopping Cart from Scratch