Communication between components in Angular

How to communicate between components

We distinguish several ways of communication between components: parent to child, child to parent and any to any.

In this post I will show an example of the implentation of each of the ways.

Example

The application has three components nav and profile which has a child profile-edit.
1. I would like the name from the profile to be send to the profile edit.
2. When changing name in profile-edit, I would like to pass this value to the parent (profile component).
3. When changing name in profile-edit, I would like to refresh name value in the nav component.

Parent to child

Passing data from parent to child is possible thanks to @Input annotation.

The parent has a defined value name:

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

@Component({
  selector: 'app-profile',
  templateUrl: './Profile.component.html',
  styleUrls: ['./Profile.component.scss']
})
export class ProfileComponent implements OnInit {
  name = 'Pawel';

  constructor() { }

  ngOnInit() {
  }
}

Now you must declare input in your child:

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

@Component({
  selector: 'app-profile-edit',
  templateUrl: './Profile-edit.component.html',
  styleUrls: ['./Profile-edit.component.scss']
})
export class ProfileEditComponent implements OnInit {
  @Input() name: string;
  constructor() { }

  ngOnInit() {
  }
}

Then passing the instance of the profile-edit component in the parent of the name parameter looks like the following:

<app-profile-edit name="{{name}}"></app-profile-edit>

Child to parent

The @Output annotation allows us to achieve the opposite result, pass values from child to parent by using the EventEmitter.

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

EventEmitter is a generic type and we can determine what type the parameter will be. In our case – string.

To the button confirming the change of the name I pass the method that will call the event using the emit function

  changeName() {
    this.nameChanged.emit(this.name);
  }

Now, to pass the method that will be triggered when the event is emitted, all you need to do is:

<app-profile-edit (nameChange)="nameChanged($event)"></app-profile-edit>

This will result in calling the nameChanged function of the parent and passing it in the form of an argument – a new name.

Child:

<div class="form-group">
  <label>Name</label>
  <input type="text" class="form-control" [(ngModel)]="name" placeholder="name">
  <button class="btn btn-success" (click)="changeName()" style="margin-top:10px;">Change name</button>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-profile-edit',
  templateUrl: './Profile-edit.component.html',
  styleUrls: ['./Profile-edit.component.scss']
})
export class ProfileEditComponent implements OnInit {
  @Input() name: string;
  @Output() nameChanged = new EventEmitter<string>();
  constructor() { }

  ngOnInit() {
  }

  changeName() {
    this.nameChanged.emit(this.name);
  }
}

Parent:

<div class="row" style="margin: 20px;">
  <div class="col-md-3">
    <div class="card">
      <div class="card-body">
        <h5 class="card-title">{{name}}</h5>
        <p class="card-text">Some information about ..</p>
      </div>
    </div>
  </div>
  <div class="col-md-9">
    <app-profile-edit name="{{name}}" (nameChange)="nameWasChanged($event)"></app-profile-edit>
  </div>
</div>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-profile',
  templateUrl: './Profile.component.html',
  styleUrls: ['./Profile.component.scss']
})
export class ProfileComponent implements OnInit {
  name = 'Pawel';

  constructor() { }

  ngOnInit() {
  }

  nameChanged(name: string) {
    this.name = name;
  }
}

In this way we can influence the change of the name from the child to the parent.

Any to any

For communication between unrelated components we will need a injectable service which is designed to provide method or properties across to any components. For this purpose I will use the BehaviorSubject which is a type of Observable (can be subscribed to, subscribers can receive updated results and subject is a observer which mean that we can send value to it).

BehaviorSubject needs a initial value, so at first I have to create new service and create new behavior subject from rxjs’.

username = new BehaviorSubject<string>('Pawel');

So the username is now unobservable of type behavior subject so set a currentUsername property as observable.

currentUsername = this.username.asObservable();

We also need to add a method that will update the username in which we call the method next on the observable object which will notify observers about the change of state.

User service:

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

@Injectable({
  providedIn: 'root'
})
export class UserService {
  username = new BehaviorSubject<string>('Pawel');
  currentUsername = this.username.asObservable();

  constructor() { }

  changeName(username: string) {
    this.username.next(username);
  }
}

Now in nav component we need to inject userService to subscribe to the currentUsername.

import { UserService } from './../_services/user.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-nav',
  templateUrl: './nav.component.html',
  styleUrls: ['./nav.component.scss']
})
export class NavComponent implements OnInit {
  name: string;

  constructor(private userService: UserService) { }

  ngOnInit() {
    this.userService.currentUsername.subscribe(name => this.name = name);
  }
}

Then we need to call the changeName method with useservice when changing the name in the profile-edit component.

import { UserService } from './../_services/user.service';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-profile-edit',
  templateUrl: './Profile-edit.component.html',
  styleUrls: ['./Profile-edit.component.scss']
})
export class ProfileEditComponent implements OnInit {
  @Input() name: string;
  @Output() nameChanged = new EventEmitter<string>();
  constructor(private userService: UserService) { }

  ngOnInit() {
  }

  changeName() {
    this.nameChanged.emit(this.name);
    this.userService.changeName(this.name);
  }
}

Which means notifying all subscribers of a change in value.

Result:

Whole code available on github.

Leave a Reply

Your email address will not be published. Required fields are marked *