4

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)

3
  • 2
    Have you at least tried it out before asking? And if so, what was the result or the error? Commented Apr 2, 2017 at 18:33
  • 1
    Yes. I was not able to Commented Apr 2, 2017 at 18:44
  • A work around is using a service in your component. There is a proposal of create event. The issue is here github.com/angular/angular/issues/15360 and here github.com/angular/angular/pull/15362. Commented Jul 26, 2017 at 11:34

3 Answers 3

2

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!

Sign up to request clarification or add additional context in comments.

Comments

0

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!

Comments

0

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 @Outputs 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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.