DEV Community

EronAlves1996
EronAlves1996

Posted on

Communication between components in Angular

In Angular, any component is a live unit of information, style, UI and logic. Angular follows strictly the Object Orientation Paradigm, and, that way, every component is a object. With this in mind, that are some ways we can make components talk to each another.

Basic Rendering in Angular

Angular have a intuitive way to render things in the UI, and some ways to integrate data with cool graphics. The most common way is data binding. You have two directions to go, which you can choose one or both. Let's see some code in it.

As a general example, we gonna make two simple counters who communicates between them, and a parent component to see some "parental conversation".

$ ng n example-proj
$ ng g c counterOne
$ ng g c counterTwo
$ ng g c parent
Enter fullscreen mode Exit fullscreen mode

In this project, I'll not use any routing nor any fancy lib.

By the way, with our components generated, we will start some coding. First of all, we gonna set the Counter One in place and adjust a one-way data binding. The variable that the UI gonna listen contain a number, who will increment by a click maded by the user.

<!-- app.component.html -->

<app-counter-one></app-counter-one>


<!-- counter-one.component.html -->

<h3>Counter One</h3>
<p>This counter is in {{actualNumber}}</p>
<button>Increment</button>
Enter fullscreen mode Exit fullscreen mode
// counter-one.component.ts

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

@Component({
  selector: 'app-counter-one',
  templateUrl: './counter-one.component.html',
  styleUrls: ['./counter-one.component.css']
})
export class CounterOneComponent implements OnInit {

  actualNumber: number = 0;

  constructor() { }

  ngOnInit(): void {
  }

}
Enter fullscreen mode Exit fullscreen mode

Everything is in place, this is the UI resulted by the code:

Basic generated UI in Angular

Well, let's explain some things. The first way you can bind some data in your UI is with the double mustache ({{ }}). This is the way you do interpolation in Angular and reference some variable in your component. Angular will compile it and deliver the final UI with the value for the final user. We gonna increment the counter using event binding, sending an event back to the code, who will trigger a function and update the value on counter:

<!-- counter-one.component.html -->

<button (click)="increment()">Increment</button>
Enter fullscreen mode Exit fullscreen mode
// counter-one.component.ts

 increment(){
    this.actualNumber++;
  }
Enter fullscreen mode Exit fullscreen mode

With this modification, the internal state of actualNumber is automatically actualized and send to UI. You can see your counter increment every time you click on "Increment".

If you see it on the inspector, only the p element (who is receiving the interpolation) is gonna get actualized.

Communication with parent component

Let's edit it a little, to introducing some inherithance and hierarchy between components:

<!-- app.component.html -->

<app-parent></app-parent>

<!-- parent.component.html -->

<h2>I am a parent component!</h2>
<app-counter-one></app-counter-one>
Enter fullscreen mode Exit fullscreen mode

This is the UI generated by this code:

Angular parent-sibling UI

Now, let's see some ways for communicating between components.
First of all, a parent component can communicate with a child via property binding:

<!-- parent.component.html -->

<h2>I am a parent component!</h2>
<app-counter-one injectedProp="Some shit"></app-counter-one>

<!-- counter-one.component.html -->

<p>{{injectedProp}}</p>
Enter fullscreen mode Exit fullscreen mode
// counter-one.component.ts

@Input() injectedProp:string = "";
Enter fullscreen mode Exit fullscreen mode

That way, I set the message passed to the Counter One by Parent with props. We able the prop with an internal variable in the child component with an @Input annotation. Another way to set communication is making it in the opposite flow by event binding:

<!-- parent.component.html -->

<p>{{bubbled}}</p>
<app-counter-one injectedProp="Some shit" (bubbleProp)="listen($event)"></app-counter-one>

Enter fullscreen mode Exit fullscreen mode
// counter-one.component.ts

@Output() bubbleProp = new EventEmitter<string>();

// ...

increment(){
    this.actualNumber++;
    this.bubbleProp.emit("Counted " + this.actualNumber);
  }

// parent.component.ts

bubbled:string = "";

// ...

listen(ev: string){
    this.bubbled = ev;
  }

Enter fullscreen mode Exit fullscreen mode

By the way, in the Counter One we set a variable with an EventEmitter object and annotated it with @Output to expose it as an event to the parent. When we click in the "Increment" button, it will emit the event with an internal value. In the parent, we set an event in the Counter One with $event as argument. This variable contains the value that are send by the .emit method. Internally, the listen method will take care by the value received from the event.

Communication between siblings

Let's edit the Counter Two and make it react the counting from Counter One. We can make it in two ways. The first is by bubbling the event to the parent and make the parent inject it in the sibling with property binding:

<!-- parent.component.html -->

<h2>I am a parent component!</h2>
<app-counter-one (bubbleProp)="listen($event)"></app-counter-one>
<app-counter-two [injectedProp]="bubbled"></app-counter-two>

<!-- counter-one.component.html -->

<h3>Counter One</h3>
<p>This counter is in {{actualNumber}}</p>
<button (click)="increment()">Increment</button>

<!-- counter-two.component.html -->

<h3>Counter Two</h3>
<p>This counter is in {{injectedProp}}</p>

Enter fullscreen mode Exit fullscreen mode
// parent.component.ts

bubbled:number = 0;

// ...

listen(ev: number){
    this.bubbled = ev * 2;
  }

// counter-one.component.ts

@Output() bubbleProp = new EventEmitter<number>();

// ...

increment(){
    this.actualNumber++;
    this.bubbleProp.emit(this.actualNumber);
  }

// counter-two.component.ts

@Input() injectedProp:number = 0;

Enter fullscreen mode Exit fullscreen mode

This way seems more complex, because the way to send the information to the targeted component can be long and this can be hard to manage.

Services + Observables to rescue

The second way to pass messages or to make a component communicate with a sibling is creating a service and put an observable in it. You inject value from the sender in the observable and subscribe to the observable in the target component. That way, you can keep the information secret and protected from any other component, despite the component hierarchy.

$ ng g s message
Enter fullscreen mode Exit fullscreen mode

This command will create a Service class. We can create a Subject (which is a type of Observable) in it and methods to allow access to others components for it:

// message.service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MessageService {

  container: Subject<number> =  new Subject<number>();

  constructor() { }

  getContainer(): Subject<number>{
    return this.container;
  }

  setContainerValue(value: number){
    this.container.next(value);
  }
}
Enter fullscreen mode Exit fullscreen mode

With this set, we can make a component communicate with other. We gonna make Dependency Injection to able the service between the components and access its methods, and make the necessary operations:


// counter-one.component.ts

constructor(private messageService: MessageService) { }

// ...

increment(){
    this.actualNumber++;
    this.messageService.setContainerValue(this.actualNumber * 2);
  }

// counter-two.component.ts

constructor(private messageService: MessageService) { }

  ngOnInit(): void {
    this.messageService.getContainer().subscribe(numb => this.injectedProp = numb);
  }

Enter fullscreen mode Exit fullscreen mode

That way, we pass a value to the message, and any Component who subscribes to it immediately receives the value and automatically handles it internally.

Top comments (0)