13

Is it possible to get type of component (Type<T>) from string value? Smth like:

let typeStr: string = 'MyComponent';
let type: any = resolveType(typeStr); // actual type
6
  • why you need this ? may i know Commented Mar 22, 2017 at 11:06
  • 4
    I made a directive which creates components dynamically, based on provided type. At the moment I have to declare a variable in the parent component and assign it's value to desired type, then bind this value to my directive in html layout. I want to get rid of this and just pass a string to directive. Commented Mar 22, 2017 at 11:09
  • 1
    I'd say to have a service in a module that contains an array of registered cmoponents - type and selector. Use this service to find a type by selector and render it dynamically in your view. You will not be able to get a type based on a string as shown in example above. You also use angular's internal list of registered components but creating your own service will give you flexibility. Commented Mar 22, 2017 at 11:13
  • Maybe there's a way to create a variable in the template then? Like <button createComponent='let type = MyComponent'>Create</button>? Commented Mar 22, 2017 at 11:17
  • What do you mean by type? Do you want to get a reference to the MyComponent class based on its name? Commented Mar 22, 2017 at 11:18

2 Answers 2

12

You can't do that without maintaining a "registry" for your classes.

interface Component { }

type ComponentClass = { new (): Component };

const REGISTRY = new Map<string, ComponentClass>();

function getTypeFor(name: string): ComponentClass {
    return REGISTRY.get(name);
}

As for how to add entries to this REGISTRY, you have a few options, here are two:

(1) Manually add it after every class definition:

class ComponentA implements Component { ... }
REGISTRY.set("ComponentA", ComponentA);

Or make a function for it:

function register(cls: ComponentClass): void {
    REGISTRY.set(cls.name, cls);
}

class ComponentA implements Component { ... }
register(ComponentA);

(2) Use a decorator:
Just use the above register function as a decorator:

@register
class ComponentA implements Component { ... }
Sign up to request clarification or add additional context in comments.

7 Comments

Do you have a working example for this? I'm unable to understand how to separate the above first code segment into different files in an actual project.
@nmy I don't have anything that, but this code used to work. What problems do you have with this?
I got this work by exporting ComponentClass type and getTypeFor. I used the decorator approach for register by exporting that too. Is that correct? Should Component be an interface, can't we use a class for that?
When I implement this in my project I had to face another problem. My components have constructors and they use different input parameters. Then compiler says I cannot use the register decorator. Because the component is not compatible with ComponentClass. Do you have any ideas how this can be handled?
@nmy Try passing the interface type as the parameter to the register function. Like this: function register(ci: Component): void And then do something like this inside register function: let cls = ci as ComponentClass; I hope it helps.
|
6

Just in case anyone stumbles across this question like me. It is possible without maintaining a registry.

Full credit goes to yurzui for his solution.

Just a copy from there:

import { Type } from '@angular/core';

@Input() comp: string;
...
const factories = Array.from(this.resolver['_factories'].keys());
const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.comp);
const factory = this.resolver.resolveComponentFactory(factoryClass);
const compRef = this.vcRef.createComponent(factory);

1 Comment

FYI this no longer works in Angular 9 as they removed the _factories from CFR. That said it was probably best to stay away from private props anyways!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.