简体   繁体   中英

Angular 4 how to pass data into dynamic component with NgComponentOutlet

I fount this similar question. angular 4+ assign @Input for ngComponentOutlet dynamically created component

But it has been about a month. Has anything changed?

Basically, I followed this guide and created a dynamic component: https://angular.io/docs/ts/latest/cookbook/dynamic-component-loader.html

With this approach, I can assign a value to the dynamic component: (<AdComponent>componentRef.instance).data = adItem.data;

Is it still true I can't assign a value to the dynamic component with NgComponentOutlet out of the box? ( https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html )

You can pass in a custom injector like this - https://github.com/angular/angular/issues/16373#issuecomment-306544456

This was kind of a hack so we ended up using this library-

https://www.npmjs.com/package/ng-dynamic-component

Worked like a charm!

In my case, i use ngBaseDef.inputs.data at component.

See example:


import {Component, Injectable, Input} from '@angular/core';
import {NgbActiveModal, NgbModal} from "@ng-bootstrap/ng-bootstrap";
import {ExampleComponent } from "../components/modals/moder-info/example.component";


@Component({
  selector: 'modal-template',
  template: `
    <div class="modal-header">
      <h4 class="modal-title" [innerText]="title"></h4>
      <button type="button" class="close" aria-label="Close" (click)="close()">
        <span aria-hidden="true">&times;</span>
      </button>
    </div>
    <div class="modal-body">
      <ng-content *ngComponentOutlet="childComponent;"></ng-content>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-outline-dark" (click)="close()">Close</button>
    </div>
  `
})
export class ModalTemplate {
  @Input() title;
  @Input() onClose;
  @Input() childComponent;

  constructor(public activeModal: NgbActiveModal) {}

  close() {
    this.activeModal.dismiss('close');
    this.onClose();
  }
}

export interface ModalServiceOptions {
  data: any;
  title: string
  onClose: any,
  componentName: string
}


@Injectable({
  providedIn: 'root'
})
export class ModalService {
  constructor(private mService: NgbModal) {}

  open(options: ModalServiceOptions) {
    let types = {'example': ExampleComponent };
    let modal = this.mService.open(ModalTemplate, {size: 'lg'});

    modal.componentInstance.title = options.title;
    modal.componentInstance.onClose = options.onClose;

    let component = types[options.componentName];

    component.ngBaseDef.inputs.data = options.data;
    modal.componentInstance.childComponent = types[options.componentName];
  }
}


ExampleComponent

import {Component, Input, OnInit} from '@angular/core';

@Component({
  selector: 'example-component',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.sass']
})
export class ExampleComponent implements OnInit {
  @Input() data;

  constructor() {
    let dataOfConstructor: any = this.constructor;
    let data = dataOfConstructor.ngBaseDef.inputs.data;
    console.log('data:', data); // Here is my data, i injected it above
  }

  ngOnInit() {
  }

}

Have a nice day! Hope it helps!

Basically, there are 2 ways to make the dynamic injection happen.

  1. inject values through the constructor, using a custom (or global) injector: *ngComponentOutlet="myComponent; injector: myInjector , where myInjector is the injector you want to use (if default injector is OK -- take it from your parent's context by adding public myInjector: Injector in your parent component's constructor). That wasn't an option for me as I wanted to keep my dynamic (child) component's constructor clean.

  2. make a callback that's called when the component is created, and inside that callback assign your @Input values and subscribe your @Output s manually. Now, ngComponentOutlet does not provide any callback functionality, so you may have to reinvent the wheel - create an ngComponentOutlet directive that supports callbacks. And that's exactly what this solution is all about.

I created a custom directive myComponentOutlet ( https://github.com/angular/angular/issues/15360#issuecomment-1070420494 ) - a customizable analogue for ngComponentOutlet . Hopefully, it will eventually make its way to the Angular's src.

Here's the directive:

import {
  AfterViewInit,
  ComponentFactoryResolver,
  ComponentRef,
  Directive,
  ElementRef,
  EventEmitter,
  Injector,
  Input,
  OnDestroy,
  Output,
  Type,
  ViewContainerRef
} from '@angular/core';

/*
  USAGE:

    <div myComponentOutlet
           [component]="inner.component"
           [injector]="inner.injector"
           (create)="componentCreated($event)"></div>
 */

@Directive({
  selector: '[myComponentOutlet]',
})
export class MyComponentOutletDirective<T> implements AfterViewInit, OnDestroy {

  @Input() component: Type<T>;
  @Input() injector: Injector;
  @Output() create = new EventEmitter<ComponentRef<T>>();
  @Output() destroy = new EventEmitter<ComponentRef<T>>();

  private componentRef: ComponentRef<T>;

  constructor(
    private viewContainerRef: ViewContainerRef,
    private resolver: ComponentFactoryResolver,
    private elRef: ElementRef,
    private globalInjector: Injector
  ) {
    this.injector = globalInjector;
  }

  ngAfterViewInit() {
    const injector = this.injector || this.globalInjector;
    const factory = this.resolver.resolveComponentFactory(this.component);
    this.componentRef = this.viewContainerRef.createComponent(factory, 0, injector);

    this.elRef.nativeElement.appendChild(this.componentRef.location.nativeElement);
    this.create?.emit(this.componentRef);
  }

  ngOnDestroy(): void {
    this.destroy?.emit(this.componentRef);
  }
}

And here's the usage:

<div myComponentOutlet
           [component]="inner.component"
           [injector]="inner.injector"
           (create)="componentCreated($event)"></div>

Adjust it to your own needs

It's not as robust as <div *ngComponentOutlet="myComponent; inputs:[...]; outputs:[...]"></div> , but it's something and it's quite clean and it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM