Angular Observable Inputs

Sometimes using an Observable for an Angular @Input is a good idea as it simplifies handling changes in your components.  In this post we'll look at using Observable @Input to do just that.

We'll use two components AdminComponent (the parent) and EmployeeComponent (the child).  The AdminComponent will pass employees to the EmployeeComponent via the EmployeeComponents @Input which will be an Observable of Employees, Observable<Employee[]>

@Input as Observable of Employees

Lets start by defining a simple Employee interface so we can properly type our @Input.

// employee.ts
export interface Employee {
  name: string;
  age: number;
}

Then we need to create our parent component, the AdminComponent.

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Employee } from './employee';

@Component({
  selector: 'app-admin',
  template: `<app-employee [employees]="employees$"></app-employee>`,
  styleUrls: ['./admin.component.scss'],
})
export class AdminComponent {
  
  employees = [
    { name: 'bob', age: 45 },
    { name: 'angie', age: 33 },
  ];

  employees$: Observable<any> = of(this.employees);
}

Next we create our child component, the EmployeeComponent.

import { Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { Employee } from './employee';

@Component({
  selector: 'app-employee',
  template: `<div *ngFor="let employee of employees$ | async">
              <p>name {{ employee.name }}</p>
              <p>age {{ employee.age }}</p>
            </div>`,
  styleUrls: ['./employee.component.scss']
})
export class EmployeeComponent {
  @Input('employees') employees$!: Observable<Employee[]>;
}

Now we have all we need to display a list of employees, note we don't need any lifecycle hooks, such as OnInit or OnChanges, making our code slightly less verbose.

We now have a base setup for our Observable Input that we can add additional operators too if we need them.  Lets add a map operator that will only return employees over the age of 40.  We take the array of employees and filter them as required.

  employees$: Observable<any> = of(this.employees).pipe(
    map((employees: Employee[]) => employees.filter(employee => employee.age > 40))
  );

Two Employee Sources

Sometimes you'll have multiple sources that need to be combined and used as an input.  Below shows two employee sources that are joined and mapped to produce a single array of employees to the EmployeeComponent.

  employeesA: Employee[] = [
    { name: 'bob', age: 45 },
    { name: 'angie', age: 33 },
  ];
  employeesB: Employee[] = [
    { name: 'fred', age: 45 },
    { name: 'sue', age: 33 },
  ];

  employeesA$: Observable<any> = of(this.employeesA);
  employeesB$: Observable<any> = of(this.employeesB);

  employees$ = forkJoin([this.employeesA$, this.employeesB$])
    .pipe(
      map(([employeesA, employeesB]) => [...employeesA, ...employeesB])
    );