ng2 - dynamically creating a component based on a template

8.7k Views Asked by At

I've been looking at the Angular 2 APIs for ComponentResolver and DynamicComponentResolver for creating dynamic components, but I have something different in mind than those APIs offer.

Is there any way in NG2 to create a component based on a string of its class name?

For example, Im building a configurable charts dashboard. Each user's layout is stored in the database, stating they want 2x line charts here, 3x bar charts there, etc.

When I load this data as json it looks something like:

user.charts = [
     { type: 'LineChartComponent', position: ... }
     { type: 'BarChartComponent', position: ... }
];

Where type is the component's class name that I want to reflectively create.

So far I have the following:

 this.chartMap = {
    'LineChartComponent': LineChartComponent
 };

let config = this.configuration;
let chartComponentType = this.chartMap[config.type];
let factory = this.componentFactory.resolveComponentFactory(chartComponentType);
let component = factory.create(this.injector);
component.instance.configuration = config;
this.chartContainer.insert(component.hostView);

But the whole idea is to eliminate the need for chartMap. How can I reflectively create this class based on a string without having a reference to the type?

2

There are 2 best solutions below

2
On BEST ANSWER

Update2:

As @estus mentioned in comments version with className won't work with minification. To do it working with minification you can

1) add some static key on each of your entryComponents like:

export LineChartComponent {
  static key = "LineChartComponent";
}

and then use this key as unique.

const factoryClass = <Type<any>>factories.find((x: any) => x.key === compClassKey);

2) create a dictionary like

export const entryComponentsMap = {
  'comp1': Component1,
  'comp2': Component2,
  'comp3': Component3
};

and then

const factory = this.resolver.resolveComponentFactory(entryComponentsMap.comp1);

Update1:

Here's version from component`s class name

const factories = Array.from(this.resolver['_factories'].keys());
const factoryClass = <Type<any>>factories.find((x: any) => x.name === compClassName);
const factory = this.resolver.resolveComponentFactory(factoryClass);

Old version

You can get factory by component selector but you have to use private property.

It might look something like:

const factories = Array.from(this.resolver['_factories'].values());
const factory = factories.find((x: any) => x.selector === selector);

Plunker Example

0
On

It's also possible to iterate through import:

import * as possibleComponents from './someComponentLocation'
...
let inputComponent;
for(var key in possibleComponents ){
      if(key == componentStringName){
          inputComponent = possibleComponents[key];
          break;
      }
 }

Update: or just :-)

let inputComponent = possibleComponents[componentStringName]

then you can create instance of component for example:

if (inputComponent) {
    let inputs = {model: model};
    let inputProviders = Object.keys(inputs).map((inputName) => { return { provide: inputName, useValue: inputs[inputName] }; });
    let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
    let injector: ReflectiveInjector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.dynamicInsert.parentInjector);
    let factory = this.resolver.resolveComponentFactory(inputComponent as any);
    let component = factory.create(injector);
    this.dynamicInsert.insert(component.hostView);
}

note that component has to be in @NgModule entryComponents