dcp3450
dcp3450

Reputation: 11187

Typescript and Angular: Type a string to a component class

a function is passing the name of my component via a string but I need it to be the actual component class:

Current: "MyComponent" Need: MyComponent

I need to "convert" it so It's passing correctly. I have an ngFor that spits out values and I'm trying to use piping to convert it:

<div id="grid">
    <gridster [options]="options">
    <gridster-item [item]="item" *ngFor="let item of dashboard;let i = index;">
      <div class="grid-header drag-handle">
        <span class="handle-icon"><i class="material-icons">open_with</i></span>
        <span class="close-icon" (click)="removePanel(item)"><i class="material-icons">close</i></span>
      </div>
      <div class="grid-content">
        <ndc-dynamic [ndcDynamicComponent]="item.viewType | dynamicComponent"></ndc-dynamic>
      </div>
    </gridster-item>
  </gridster>
</div>

Above it's item.viewType that's a strong coming from my item array. I'm passing the value to a dynamicComponent custom pipe. I have my components imported into my dynamic-component.pipe.ts. I just need to change the string to the specified viewType and return the typed value:

import { Pipe, PipeTransform } from '@angular/core';
//pulling in all the possible components
import * from '../../views';

@Pipe({
  name: 'dynamicComponent'
})
export class DynamicComponentPipe implements PipeTransform {

  transform(value: string): any {


  }

}

Upvotes: 1

Views: 2001

Answers (2)

Reactgular
Reactgular

Reputation: 54741

You'll need to manually create a mapping between string values and the components. Components are actually named functions which can be minimized to shorter variable names when compiling for production.

import ComponentA from '../views/ComponentA'; 
import ComponentB from '../views/ComponentA';

const componentMap = {
    'ComponentA': ComponentA,
    'ComponentB': ComponentB
};

@Pipe({name: 'dynamicComponent'})
export class DynamicComponentPipe implements PipeTransform {
   transform(value: string): any {
       if(componentMap[value]) {
          return componentMap[value];
       }
       throw new Error(`${componentMap} component was not found`);
   }
}

UPDATED:

The problem with using the name of a component at runtime is that it can be minimized into a smaller variable name for production. Therefore, doing something like views['MyComponent'] won't work later when MyComponent is renamed to something like a12.

An alternative approach is to use the component's selector string value to select the component. Each component in Angular has to be a unique selector string. So this is a safe value to use as a key.

You can access (at least in Angular 5) the component's metadata via the __annotations__ property. This property is an array that contains a the metadata.

So you could try something like:

import * as views from '../views';

@Pipe({name: 'dynamicComponent'})
export class DynamicComponentPipe implements PipeTransform {
   transform(value: string): any {
       const view = views.filter((component)=>component['__annotations__'][0]['selector'] === value));
       if(view) {
          return view;
       }
       throw new Error(`A component with selector "${value}" was not found`);
   }
}

Furthermore, you could drop the need for a views file by accessing the ngModule directly and iterating all components to find a matching selector. Modules will have the same __annotations__ property.

Upvotes: 1

dcp3450
dcp3450

Reputation: 11187

I did this pretty simple way:

I created a views.ts with all of my views exported at the root of my views directory. Then in directory that my pipe is being used I imported the views using:

import * as views from '../../views/views'

Then in my transform method I return views[value]; where value is the string being pulled in from my template.

Upvotes: 0

Related Questions