Best way to bind inputs and outputs of two child components in Angular

Viewed 921

This is a more general question, so I'm also giving general information. In Angular I'm using a parent component, which contains 2 child components. These 2 child components needs to share information with each other:

<parent-component>
   <child1-component></child1-component>
   <child2-component></child2-component>
</app-parent>

So, child1 needs information from child2 and child2 needs information from child1 (and its different information). The reason that child1 and child2 are not in one component (because that would solve the problem of course) is that it would be a very big component and I want to use child1 at another page without child2.

So I already found 2 solutions:

1: Handle all outputs of the childs in the parent and provide inputs for them as well. This means that for every output that a child has (either child1 or child2), the parent must implement a handler for it, specify a variable and bind that to an input of the other child.

2: Use a service that is injected in both childs. A child calls a method of the service, it triggers and event which will be handled by the other child directly.

Now what in your opinion is the best approach? Or is there a third and even better approach?

Thanks in advance, Herman

2 Answers

What you are really getting at here is shared state management. There are many tools out there that can help you with this. Check out Akita for example. NGRX is also popular. But to answer your question, you can create a simple state management service using straight up RxJS. Let's start with a sample service:

import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } from "rxjs";
import { State } from "./components/state.model";

/**
 * Manages simple state for app.
 */
@Injectable({ providedIn: "root" })
export class StateService {
  /**
   * State behavior contains state of the app.
   */
  private stateSub = new BehaviorSubject<State>({
    message: "Initial state",
    modifiedBy: "System"
  });

  /**
   * An observable with our shared state.
   */
  public state: Observable<State> = this.stateSub.asObservable();

  /**
   * Set app state.
   */
  public setState(message: string, modifiedBy: string) {
    this.stateSub.next({
      message: message,
      modifiedBy: modifiedBy
    });
  }
}

Next, here's a component that consumes this state:

<h1>Component 1</h1>
<!-- Capturing observable in template var to prevent multiple subscriptions. -->
<ng-container *ngIf="(appState$ | async) as appState">
  <p><strong>Message: </strong>{{ appState.message }}</p>
  <p><strong>Set by: </strong>{{ appState.modifiedBy }}</p>
</ng-container>
<button (click)="setMessage()">Modify app state</button>

... the component logic:

export class Component1 implements OnDestroy {
  appState$: Observable<State>;
  unsubscribe$ = new Subject();

  constructor(private state: StateService) {
    // Unsubscribe to avoid memory leaks.
    this.appState$ = this.state.state.pipe(takeUntil(this.unsubscribe$));
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }

  setMessage() {
    this.state.setState("Component 1 rules!", "Component 1");
  }
}

Create more components with the same logic and they can all subscribe to your state changes and modify the state themselves via the state service. It's simple, it's idiomatic, and adds zero dependencies. I've used a similar approach to this in medium/large Angular projects and generally prefer rolling my own state manager service over other approaches. Also - your first idea of a ton of input/output params QUICKLY gets unmanageable on larger projects, so I would avoid it. Generally speaking, you should try to offload as much heavy-lifting as possible to services to hopefully keep your components clean.

Finally, here's a working example on stackblitz. Give a try, modify it to contain the state you want, etc.

if the type of data that variables contain are related to UI don't crowd your code with service go simple to make those properties in parent and pass them in each child and pass them out as you see fit but if you are making some requests to server or loading data it's better to make a request for them but again you should inject it in parent because since both child components need it this a far better than injecting your service in both childs

Related