1

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 {


  }

}
dcp3450
  • 10,959
  • 23
  • 58
  • 110

2 Answers2

1

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.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
  • I'm pulling in my list of possible components by placing them in a `ts` file using `export` for each then importing them into my `pipe` file with `import * as views from '../../views/views';` This returns an an array with key value pairs already. Couldn't I just keep using that? – dcp3450 May 23 '18 at 19:01
  • FYI, I'm not saying you're wrong. Just asking if using `views` from the import above is acceptable since it's already an array and `views[value]` returns the same thing as what you did above. – dcp3450 May 23 '18 at 19:03
  • I think the keys in `views` will be the minimized version in production. Just run `console.log(views)` from a production build to see if I'm right. – Reactgular May 23 '18 at 19:07
  • I see the concern. Listing out each component in the pipe file can be a bit heavy when others start working in this system. The `views.ts` file in the `views` directory is used to provide a single point of export. How would you suggest addressing that? – dcp3450 May 23 '18 at 19:14
  • @dcp3450 how do you assign the string values to `item.viewType`? Do you have control over what those values can be? – Reactgular May 23 '18 at 19:41
  • @dcp3450 I updated my answer. I ran a quick test on my own angular project, and it seems to work for me. – Reactgular May 23 '18 at 19:54
  • I referenced this answer for my answer: https://stackoverflow.com/questions/46937746/how-to-retrieve-a-components-metadata-in-angular – Reactgular May 23 '18 at 19:56
0

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.

dcp3450
  • 10,959
  • 23
  • 58
  • 110