Home > front end >  Angular - Child to parent communication when child is content projected
Angular - Child to parent communication when child is content projected

Time:06-28

I am in the middle of a project building a Header component in Angular which has 2 levels of navigation. The basic component structure is as follows:

<b-header>
  <b-header-nav>
    <b-header-nav-item>
      <button b-header-link>Primary 1<button>
    </b-header-nav-item>
    <b-header-nav-item>
      <button b-header-link>Primary 2<button>
    </b-header-nav-item>
    <b-header-nav-item>
      <button b-header-link>Primary 3<button>
    </b-header-nav-item>
  </b-header-nav>
</b-header>

When the user clicks on one of the b-header-link buttons I would like to toggle CSS active state classes on both the button itself and its parent - b-header-nav-item. I have researched child to parent communication in Angular online but most examples show the use of Output and Event Emitters which I'd ideally not like to use as this work is part of a component library where the inner workings of a component should not really be exposed to the end user. Also as shown the child in this case is added via content projection ng-content in b-header-nav-item. I've created a Stackblitz to demo a more featured example of the code (although still simplified compared to the real one).

https://stackblitz.com/edit/angular-ivy-g8vdml?file=src/app/header/header-link/header-link.component.ts

Any help would be much appreciated,

Thanks

CodePudding user response:

If you don´t like the solution with output and event emitter. I would recommend to use service with behavior subject or viewChild.

Amazing website wich describe each solution Sharing data between angular components

CodePudding user response:

Since you're using Ng-Content, you can handle all the logic in one component. I therefore suggest you try using an array containing all of your buttons data (is active, title, href), then using ngfor to render it.

1 Define NavButton interface :

export interface NavButton {
       isActive: boolean;
       name: string;
       href: string;
       subElems: NavButton[];
   }
 

2 Create buttons list and function to toggle the btn's 'active' parameter

navItems: NavButton[] = [{isActive: false, name: 'btn1', href: '', subElems: []}, {isActive: false, name: 'btn2', href: '', subElems: [{isActive: false, name: 'subBtn 2.1', href: '', subElems: []}]}, {isActive: false, name: 'btn3', href: '', subElems: []}, ];
  
  changeState = (navBtn: NavButton) => {
    navBtn.isActive = !navBtn.isActive;
  }
 

3 Render nav buttons and subButtons like so :

<b-header>
  <b-header-nav>
    navItemMap
    <b-header-nav-item *ngFor="let currBtn of navItems; let i = index">
      <button b-header-link (click)="changeState(currBtn)">
        {{currBtn.name}} (number {{ i }})
      </button>

      <b-header-subnav *ngIf="currBtn.subElems.length > 0">
      <b-header-subnav-item *ngFor="let subNavBtn of currBtn.subElems; let i2 = index;">
        <a [href]="subNavBtn.href" (click)="changeState(subNavBtn)">{{subNavBtn.name}} (number {{ i2 }})</a>
      </b-header-subnav-item>
    </b-header-subnav>
    </b-header-nav-item>
  </b-header-nav>
</b-header>

4 Everything is set up, just add style binding to HTML, for example if you want the "b-header-nav-item" to get a specific CSS class as well as the "b-header-link" once toggled :

 <b-header-nav-item *ngFor="....." [class.active]="currBtn.isActive">
          <button b-header-link (click)="...." [class.active]="currBtn.isActive"></button>
       
[...]

        </b-header-nav-item>

Note: Here, I've hidden the non-style related logic for visibility

Cheers

  • Related