Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 dynamically create components in ngFor

Tags:

My controller has a list of components I want to have inside a page. Those other components are created and work correctly in isolation.

In my HTML template:

<ng-container *ngFor="let component of components">
    <div {{component.name}}></div>
</ng-container>

The code should output a <div> like this:

<div my-component></div>

But it is not and I am getting this error:

DOMException: Failed to execute 'setAttribute' on 'Element'

It works well if I simply print the value inside the div tag:

<div>{{component.name}}</div>

Is it even possible to achieve what I want?

like image 983
igasparetto Avatar asked Mar 06 '17 08:03

igasparetto


1 Answers

You can create a component factory and put those components that you need as an entry component:

Component Factory View

<div #dynamicComponentContainer></div>

Component Factory Controller

import {    Component,Input,ViewContainerRef,ViewChild,ReflectiveInjector,ComponentFactoryResolver } from '@angular/core';

import { YourComponent } from './your-component.component';

@Component({
  selector: 'component-factory',
  templateUrl: './component-factory.component.html',
  styleUrls: ['./component-factory.component.css'],
  entryComponents: [ YourComponent ]
})

export class ComponentFactoryComponent {

currentComponent = null;

@ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;

// component: Class for the component you want to create
// inputs: An object with key/value pairs mapped to input name/input value
  @Input() set componentData (data: {component: any, inputs: any }) {+    if     (!data) {
  return;
}

// Give you access to the inputs inside the component
let inputProviders = Object.keys(data.inputs).map((inputName) => {return {provide: inputName, useValue: data.inputs[inputName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

// This will prepare the inject of inputs on the factory
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);

// Creates a factory
let factory = this.resolver.resolveComponentFactory(data.component);

// Here we inject the inputs on the factory
let component = factory.create(injector);

// This will add the component in the DOM container
this.dynamicComponentContainer.insert(component.hostView);

// This destroys the old component and input the new one
if (this.currentComponent) {
  this.currentComponent.destroy();+    }

this.currentComponent = component;
}

constructor(private resolver: ComponentFactoryResolver) { }

On the component you want to use you'll need to provide the inputs, we do this by the Injector:

Component You Want to Use

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

@Component({
  selector: 'your-component, [your-component]',
  templateUrl: './your-component.component.html',
  styleUrls: ['./your-component.component.css']
})
export class YourComponent {
  @Input() name: string;
  @Input() label: string;
  @Input() value: string;

  constructor(private injector: Injector) {
    this.name = this.injector.get('name');
    this.label = this.injector.get('label');
    this.value = this.injector.get('value');
  }
}

Then on your app component, or whenever you want to use it:

App Component View

<component-factory [componentData]="componentData"></component-factory>

App Component Controller

import { Component } from '@angular/core';
import {YourComponent} from  './your-component.component'

@Component({
  selector: 'app',
  templateUrl: './app.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent  {

  componentData = null;

  constructor() { }

  ngOnInit() {
    this.componentData = {
      component: YourComponent,
      inputs: {
        name: 'example',
        label: 'John Doe',
        value: 'foo'
    }
  }
 }
}
like image 162
Rafael De Almeida Avatar answered Sep 25 '22 10:09

Rafael De Almeida