angularportalangular-cdk

Pass data back from cdkPortalOutlet ComponentPortal to parent component


I have tried to use this method cdkPortalOutlet with ComponentPortal, and I used Injector.create to pass data from parent to portal component it works fine.

I want to pass data back from portal component to parent component

html

<div>
   <ng-template [cdkPortalOutlet]="defaultComponent"></ng-template>
</div>

ts

defaultComponent!: Portal<any>;

ngViewInit(): void {
  this._loadDefaultCompnent(data);
}

private _loadDefaultCompnent(data: any) {
  const _data = this._getPortalComponentData();
  this.defaultComponent = this._getProtalComponent(_data);
}

private _getPortalComponentDatat(data: any) {
  return new ComponentPortal(portalComponent, null, this._portalInjector(data));
}


private _getPortaComponentData() {
  return {
    data: this.list
  };
}

private _portalInjector(data: any) {
  return Injector.create({
    providers: [{ provide: PORTAL_DATA, useValue: data }]
  });
}

How to pass data back from portal component to parent component?


Solution

  • We can use the event bus pattern, since it's dynamically rendered, it will be difficult to send code from child to parent.

    Although this code does not match with the question code, please check this example and try to apply the same concept on your code.

    Create a shared Service that is imported in both child and parent, also the service need to be present in the providers array of parent, or you can make the service globally available using providedIn: 'root'

    import { Injectable } from '@angular/core';
    import { Observable, Subject } from 'rxjs';
    
    @Injectable()
    export class AppService {
      subject: Subject<any> = new Subject<any>();
      subjectObservable$: Observable<any>;
      name: string;
      constructor() {
        this.subjectObservable$ = this.subject.asObservable();
      }
    
      emit(eventName: string, data = {}) {
        this.subject.next({ eventName, data });
      }
    
      getName(): string {
        return this.name;
      }
    
      setName(name: string): void {
        this.name = name;
        console.log(this.name);
      }
    }
    

    Child component emits an event, to send to parent:

    import { Component, OnInit } from '@angular/core';
    
    import { AppService } from '../../app.service';
    import { EVENTS } from '../../constants/constants';
    @Component({
      selector: 'header',
      templateUrl: './header.component.html',
      styleUrls: ['./header.component.css'],
    })
    export class HeaderComponent implements OnInit {
      constructor(private appService: AppService) {}
    
      ngOnInit() {}
    
      changeName(): void {
        this.appService.emit(EVENTS.CHANGE_NAME, 'New Name');
      }
    }
    

    Parent component receives the data and can call a method, or set some data!

    import { Component, OnInit } from '@angular/core';
    import { Subscription } from 'rxjs';
    
    import { AppService } from '../../app.service';
    import { EVENTS } from '../../constants/constants';
    
    @Component({
      selector: 'home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css'],
    })
    export class HomeComponent implements OnInit {
      subscription: Subscription = new Subscription();
      name: string;
      constructor(private appService: AppService) {}
    
      ngOnInit() {
        this.subscription.add(
          this.appService.subjectObservable$.subscribe((event: any) => {
            if (event.eventName === EVENTS.CHANGE_NAME) {
              this.name = event.data;
            }
          })
        );
      }
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }
    }
    

    Stackblitz Demo