Expand/Collapse functionality between components

Viewed 3045

I have 2 different components where I am using Angular - Expansion Panel for expandable summary view.

I am able to integrate <mat-expansion-panel> to individual components successfully.

I need help to make Component2.html as parent of Component1.html (Expand within expand- Please see below Image)enter image description here

  • Component 1 should be able to expand and collapse independently to show data

  • Component 2 should embed Component 1 within itself, so when Component 1 is expanded it can show its data and display remaining Child components

NOTE - Both the component has Sibling relation, no parent child or child - parent

Component1.html

  <div class="row">
    <div class>
      <dl class="row property_set" data-property-set-name="data">
        <mat-accordion>
          <mat-expansion-panel (opened)="doExpand = true"
                             (closed)="doExpand = false">
          <mat-expansion-panel-header>
            <mat-panel-title>
        <dt class="col-sm-4 record_property">data</dt>
      </mat-panel-title>
      <mat-panel-description>
        <dd class="col-sm-8 record_property_value data_name" id="{{genId(data.name)}}">
          <inline-misal-edit 
                        [(field)]="data.name" [elementType]="a.bName" (fieldChange)="dataModified(data)"
                        cols="30" rows="1"></inline-misal-edit>
          </dd>
      </mat-panel-description>
    </mat-expansion-panel-header>

        <dt class="col-sm-4 record_property">news</dt>
        <dd class="col-sm-8 record_property_value">{{data.news?.created | news}}</dd>
      </mat-expansion-panel>
    </mat-accordion>
      </dl>
    </div>

Component2.html


  <dl class="row property_set" data-property-set-name="misal">
    <mat-accordion>
        <mat-expansion-panel (opened)="doExpand = true"
                             (closed)="doExpand = false">
          <mat-expansion-panel-header>
            <mat-panel-title>
              misal Id
            </mat-panel-title>
            <mat-panel-description>
              <dd class="col-sm-10 record_property_value" data-property-type="select">{{misal.id || 'new'}}</dd>
            </mat-panel-description>
          </mat-expansion-panel-header>
          <dd class="col-sm-10 record_property_value misal_name">{{misal.data[0].name}}</dd>
          <dt class="col-sm-2 record_property">Country Pass</dt>
          <dt class="col-sm-2 record_property">Plugins Program</dt>
          <dd class="col-sm-10 record_property_value">
            <north-dhamma[(misal)]="misal" [editMode]="editMode" (misalChange)="recordModified.emit()"></registry-number>
          </dd>
          <dt *ngIf="misal.value === 'hovered'" class="col-sm-2 record_property">Related Plugins Program</dt>
          <dd *ngIf="misal.value === 'hovered'" class="col-sm-10 record_property_value">
            <land-hole></land-hole>
          </dd>
                </mat-expansion-panel>
      </mat-accordion>
  </dl>

.ts file

  panelOpenState = true;                 

UPDATE

The answer given below by Robbie works for parent - child component relation but not for the sibling component

4 Answers

One way to do this is using a shared service - https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

However I find the following solution much simpler, it allows to share data between 2 siblings.

app-sibling2.component.ts

import { AppSibling1Component } from '../app-sibling1/app-sibling1.component';

export class AppSibling2Component {
   @Input() data: AppSibling1Component;
   ...
}

In you parent component template:

<!-- Assigns "AppSibling1Component" instance to variable "data" -->
<app-sibling1 #data></app-sibling1>
<!-- Passes the variable "data" to AppSibling2Component instance -->
<app-sibling2 [data]="data"></app-sibling2> 

I believe this is what you are looking for. Let me know if you have any questions

You should be able to add your child component right after the close tag of mat-expansion-panel-header

<mat-accordion>
  <mat-expansion-panel>
    <mat-expansion-panel-header>
      <mat-panel-title>
        Parent Expansion
      </mat-panel-title>
    </mat-expansion-panel-header>
    <p>Content of parent</p>
    <!-- add child here -->
    <app-child></app-child>
  </mat-expansion-panel>
</mat-accordion>

https://stackblitz.com/edit/angular-5euuwx?file=src%2Fapp%2Fexpansion-overview-example.html

I am bit confused over there you want Component2.html as parent of Component1.html and you mentioned both the component has Sibling relation, no parent child or child - parent.

Keeping that in mind how about having a ExpansionWrapperComponent that wraps both Component1 and Component2 and incorporates the behavior - if Component2 is toggled Component1 must get toggled at the same time Component1 can be toggled separately too.

expansion-wrapper.component.html

<div class="expansion-wrapper">
  <div class="parent">
    <ng-content select="app-component2"></ng-content>
  </div>
  <div class="child">
    <ng-content select="app-component1"></ng-content>
  </div>
</div>

expansion-wrapper.component.ts

import { AfterContentInit, Component, ContentChild, OnInit } from '@angular/core';
import { Component1Component } from '../component1/component1.component';
import { Component2Component } from '../component2/component2.component';

@Component({
  selector: 'app-expansion-wrapper',
  templateUrl: './expansion-wrapper.component.html',
  styleUrls: ['./expansion-wrapper.component.css']
})
export class ExpansionWrapperComponent implements OnInit, AfterContentInit {

  @ContentChild(Component2Component, { static: false }) component2: Component2Component;
  
  @ContentChild(Component1Component, { static: false }) component1: Component1Component;

  constructor() { }

  ngOnInit() {
  }

  ngAfterContentInit() {
    this.component2.panelStateChanged.subscribe(res => {
      this.component1.togglePanelState(res);
    });
  }

}

Where Component1 and Component2 contains separate expansion panel. With this Component1 is a sibling of Component2 and feels like embeded into Component2.

Check the application over stackblitz

I'm confused about what are your needs.

Component 2 should embed Component 1 within itself

What you are requiring is something like this

// component2.component.ts
@Component({
  selector: "component2",
  template: `
    <component1></component1>
  `,
  styles: []
})
export class Component2 {
}

Both the component has Sibling relation, no parent child or child - parent

But here, you want this.

// app.component.ts
@Component({
  selector: "app",
  template: `
    <component2></component2>
    <component1></component1>
  `,
  styles: []
})
export class AppComponent {
}

I guess what you want is something like this, demo. 2 sibling <mat-expansion-panel> as a display unit.

2

Related