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
Leonid Bor
  • 2,064
  • 6
  • 27
  • 47
  • why you need this ? may i know – Pardeep Jain Mar 22 '17 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. – Leonid Bor Mar 22 '17 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. – KKS Mar 22 '17 at 11:13
  • Maybe there's a way to create a variable in the template then? Like ``? – Leonid Bor Mar 22 '17 at 11:17
  • What do you mean by `type`? Do you want to get a reference to the `MyComponent` class based on its name? – Nitzan Tomer Mar 22 '17 at 11:18
  • @NitzanTomer yes, that's what I mean – Leonid Bor Mar 22 '17 at 11:50

2 Answers2

11

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 { ... }
Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • 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 May 16 '17 at 12:52
  • @nmy I don't have anything that, but this code used to work. What problems do you have with this? – Nitzan Tomer May 16 '17 at 14:20
  • 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? – nmy May 17 '17 at 03:19
  • @nmy There's no correct or incorrect. If it works for you then it's fine. `Component` can be a class if you prefer to have a base class – Nitzan Tomer May 17 '17 at 03:35
  • 2
    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 May 23 '17 at 13:14
  • Do they all have the same constructor signature(s)? – Nitzan Tomer May 23 '17 at 13:37
  • 1
    @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. – Leo May 04 '18 at 20:26
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);
Vulcano
  • 415
  • 10
  • 25
  • 3
    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! – Jordan Lewallen Apr 19 '20 at 01:33