0

Is there other ways I can create a Component using a generic/string type like how Factory resolver worked by accepting a generic Type to create an component?

I tried to use ComponentFactoryResolver but it has been deprecated after v13 of Angular. See Angular site: https://angular.io/api/core/ComponentFactoryResolver

Sigcino
  • 1
  • 5
  • What I do is have an object that maps the string name as the key to the value which is the component reference. – Brandon Taylor Jul 08 '23 at 10:48
  • That is currently how I am handling it, but I want a way to save my components name on the database and just use that name directly without mapping it, maybe by converting the string to type Component and passing it to my viewContainerRef.createComponent(componentName); – Sigcino Jul 11 '23 at 07:15

2 Answers2

0

Here is a working example with DI etc, it relies on ViewComponentRef.createComponent with doesn't use the deprecated ComponentFactoryResolver :

@Component({
  selector: 'hello',
  template: '<div #container></div>',
  standalone: true,
})
export class HelloComponent implements OnInit {
  @ViewChild('container', { read: ViewContainerRef, static: true })
  container: ViewContainerRef;

  constructor(
    private injector: Injector,
    private environement: EnvironmentInjector
  ) {}

  ngOnInit() {
    // runInContext is important to allow DI with inject()
    this.environement.runInContext(() => {
      const clazz = class {
        private foo = inject(Foo); // From the local provider
        private bar = inject(Bar); // From the global provider
        public test;

        constructor() {
          this.test = 'some value';
          console.log(this.foo.rand, this.bar.rand);
        }
      };

      // Here is the trick to change the class name
      Object.defineProperty(clazz, 'name', { value: 'TheName' });

      // Define the component using Component decorator.
      const component = Component({
        selector: 'test',
        template:
          '<div>This is the dynamic template.<br> Test value: {{test}}</div>',
        styles: [':host {color: red}'],
        providers: [{ provide: Foo, useClass: Foo }],
      })(clazz);

      const componentRef = this.container.createComponent(component, {
        injector: this.injector,
      });

      timer(0, 1000).subscribe((val) => {
        componentRef.instance.test = val % 2 ? 'tic' : 'tac';
      });
    });
  }
}

@Component({
  selector: 'my-app',
  template: 'This is static <hr> <hello></hello>',
  standalone: true,
  imports: [HelloComponent],
})
export class AppComponent {
  name = 'Angular ' + VERSION.major;
}

@Injectable()
class Foo {
  rand = Math.random();
}

@Injectable({ providedIn: 'root' })
class Bar {
  rand = Math.random();
}

NB: Keep in mind that if you do this you'll pull the @angular/compiler package in your main bundle increasing the side the said bundle. @angular/compiler is stripped when you rely only on the AOT.

Stackblitz

Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
0

You can use an object to maintain a string to component reference, decorators omitted for brevity:

import { YourDynamicComponent } from 'your-module';

export class YourComponent {

  componentMap = {
    dynamicComponent: YourDynamicComponent,
  };
  
  loadComponent(componentName: string) {
    const viewContainerRef = this.yourComponentHost.viewContainerRef;
    viewContainerRef.clear();

    const component = this.componentMap[componentName];

    const componentRef = viewContainerRef.createComponent<typeof component>(component);
  }
}

The map of string/component references can be kept externally to your implementation.

Brandon Taylor
  • 33,823
  • 15
  • 104
  • 144
  • This is currently how I am doing it, I was wondering if I can do this without mapping my componentName to an object that holds my components just like how they did it in this solution: https://stackoverflow.com/questions/40115072/how-to-load-component-dynamically-using-component-name-in-angular2 . By just passing a string that I saved in my database and convert the string to a type component without mapping it. – Sigcino Jul 11 '23 at 07:12
  • Looks like the accepted answer from the link you posted has issues. I would stick with the object map. It's easy, straightforward and works. Why add needless complexity to pass a string instead of a reference via a string to the factory? Even if you create another module that has the component names, you're still having to maintain some code as you add new component classes. The map is the most pragmatic choice, IMO. – Brandon Taylor Jul 11 '23 at 12:48