Sharing data across components in Angular

Chris Engelsma
4 min readApr 28, 2021

At some point during an application’s development, you’ll eventually come across some data in a component that you will need to share with another component. In Angular, we can accomplish this multiple ways.

Scenario 1: Passing data from a parent component to its child

Assume we have two components: ParentComponent and ChildComponent, and we want the name variable to pass from parent to child. We can accomplish this by using either attribute bindings or by getting an instance of the component and updating the instance directly. Let’s start with attribute bindings.

We first define our custom binding attribute in the ChildComponent using the @Input() property:

import { Component, Input } from '@angular/core';@Component({ 
selector: 'app-child',
template: `name = ${name}`
})
export class ChildComponent {
@Input() name: string = '';}

We can now bind to this attribute directly in the html template in ParentComponent where we call the ChildComponent:

import { Component, Input } from '@angular/core';@Component({ 
selector: 'app-parent',
template: `<app-child [name]="name"></app-child>`
})
export class ParentComponent {
name: string = 'Jim Bob';}

The line <app-child [name]="name"></app-child> is the magic sauce. In Angular, the direction of the binding is determined by the type of bracket style enveloping the attribute.

  • [] means the binding is one-directional, input-only.
  • () means the binding is one-directional, output-only.
  • [()] means the binding is two-way. This requires a bit more setup so I’ll skip this approach for now.

Let’s now pass data to the child component by getting an instance of the component in the parent.

We now leverage the @ViewChild property in conjunction with the ngAfterViewInit function the parent component:

import { Component, Input, AfterViewInit, ViewChild } from '@angular/core';@Component({ 
selector: 'app-parent',
template: `<app-child #childComponent></app-child>`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('childComponent') childComponent!: ChildComponent; name: string = 'Jim Bob'; ngAfterViewInit(): void {
this.childComponent.name = this.name;
}
}

A few things to note:

  1. In order to convert a DOM element to a variable, we add the hash attribute #childComponent to the template in ParentComponent. This gets passed into @ViewChild() .
  2. We use ngAfterViewInit as opposed to ngOnInit or the constructor to assign the variable because we need the child component to be instantiated, otherwise childComponent will be undefined.
  3. This example sets the value once — at the beginning — but we can set it whenever we need to because we have our instance cached.

Scenario 2: Passing data from a child component to its parent

Passing data from child to parent requires using the @Output() property on the ChildComponent.

import { Component, Output } from '@angular/core';@Component({ 
selector: 'app-child',
template: `name = ${name}`
})
export class ChildComponent {
@Output() nameChange = new EventEmitter<string>(''); updateName(name: string): void {
this.nameChange.emit(name);
}
}

The @Output() property type is an EventEmitter. We invoke this EventEmitter by calling the emit function and passing in the thing we want to send — in this case, it’s the name variable.

The parent component then needs to bind to this event and handle the results appropriately:

import { Component, Input, AfterViewInit, ViewChild } from '@angular/core';@Component({ 
selector: 'app-parent',
template: `<app-child (nameChange)="handleNameChange($event)"></app-child>`
})
export class ParentComponent implements AfterViewInit {
name: string = ''; handleNameChange(name): void { this.name = name; }}

The parent component ingests the name that was emitted from the child component and caches it.

Note the parentheses around (nameChange) in the parent template. This is because we’re binding one-directionally to changes being emitted from that component.

Scenario 3: Passing data between arbitrary components

We can pass data between components regardless of their relationship to one another using a service. Angular’s dependency-injection architecture makes this approach the preferred method for passing information across larger applications. By creating and storing data in services, we have our components subscribe to changes in the service, so whenever the value in service changes, it will update in all components that subscribe to it as well.

We first create a service:

import { Injectable } from '@angular/core';@Injectable({
providedIn: 'root'
})
export class DataService {
private _name: BehaviorSubject<string>; name$: Observable<string>; constructor() {
this._name = new BehaviorSubject('') as BehaviorSubject<string>;
this.name$ = this._name.asObservable();
}
set name(name: string): void {
this._name.next(name);
}
}

Our service contains:

  • A BehaviorSubject which will store the value and emit an event on change.
  • An observable of that BehaviorSubject. This is what components will bind to.
  • A setter that takes in a variable and updates the BehaviorSubject using next() .

In our components, we inject the data service by adding it to the constructors. We then must subscribe to the observable in the data service.

import { Component, OnInit } from '@angular/core';@Component({ 
selector: 'app-just-another-component',
template: `Name: ${name}`
})
export class JustAnotherComponent implements OnInit {
name: string = ''; constructor(private _dataService: DataService) { } ngOnInit(): void {
this._dataService.name$.subscribe(name => this.name = name);
}
}

We subscribe to the observable in the ngOnInit function, and our callback simply caches the value returned. Obviously, the callback can do whatever you need it to do, I’m just caching it here.

Sharing data between components can sound daunting, but using these techniques will help you build more robust apps.

Happy coding!

--

--

Chris Engelsma

Geophysicist, software engineer, and web developer. Hopefully if I throw enough spaghetti at the wall something sticks.