5

How to instantiate angular components with new keyword?

Imagine following as a template less component:

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

@Component({
  selector: 'my-component',
})
export class MyComponent {
constructor(private myService: MyService)
}

I know that angular components are only instantiated when it finds a selector tag in the template.

But... How can I instantiate the above with new keyword? Cause my components doesn't require a template.

But it should be an angular component with @component decorator. (cause I need the feature of dependency injection)

But the problem is that when I create a new component like the following:

const comp = new MyComponent()

It also requires the service instance also to be passed to it's constructor while instantiating. Like this"

const comp = new MyComponent(new MyService())

It becomes even harder when that service we are passing in again depends on other services.

Like this...

const comp = new MyComponent(new MyService(new AnotherService(new YetAnotherService(... so on))))

So is there a workaround this?

Angular's DI is nice. Since we don't have any issues like the above. It automatically creates all the injected services. So is that possible with new keyword.

NOTE I tried using plain typescript classes. But I still don't like it. I want to use angular components itself.

Is that possible?

mx_code
  • 2,441
  • 1
  • 12
  • 37
  • Is there a way... Please let me know!!! – mx_code Feb 13 '21 at 05:19
  • Why do you want to do it?is there a particular reason ? – Madhawa Priyashantha Feb 13 '21 at 05:19
  • Yes, It's all about canvas. So I don't need any templates. But I still need the facility of dependency injection as my components depends on a lot of services. – mx_code Feb 13 '21 at 05:21
  • And So I need the ability to create components with new keyword. But the issues with this is that I will have to manually pass services along with them. – mx_code Feb 13 '21 at 05:21
  • Thats not the only issue - managing instances of those services might be difficult. Injector keeps them, so components can share same instance – Julius Dzidzevičius Feb 13 '21 at 05:26
  • So what would I do? – mx_code Feb 13 '21 at 05:27
  • Use `componentFactoryResolver` – Julius Dzidzevičius Feb 13 '21 at 05:29
  • @mex if you don't use angular's templates ,may be you don't need angular ?if you need dependency injection and services you could use something like https://github.com/typestack/typedi.this is just a suggestion. Also if you use canvas can't you just use it like this https://stackoverflow.com/questions/44426939/how-to-use-canvas-in-angular – Madhawa Priyashantha Feb 13 '21 at 05:30
  • Then How would I do the property binding ```@Input``` and ```@Output```. How can I pass params to the constructor of the component? – mx_code Feb 13 '21 at 05:31
  • @MadhawaPriyashantha... No. Not just canvas. Everything I draw inside the canvas are components (cause they are easy to manage). And I need these individual components to be made of angular components (that's why it is template less and requires new keyword) – mx_code Feb 13 '21 at 05:33
  • When you create component with `componentFactoryResolver` you get a reference to it and can do whatever needed - provide data, listen to changes etc. – Julius Dzidzevičius Feb 13 '21 at 05:41

2 Answers2

3

I strongly advise against it. Angular is not made for that, change detection might not work correctly and you have to manage the component yourself.

But anyway, if you want to create a component dynamically, you can use ComponentFactoryResolver:

import { Component, ComponentFactoryResolver, Injector, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'hello'
})
export class HelloComponent  {
  @Input() get name(): string { return this._name; }
  set name(value: string) {
    this._name = value;
    this.nameChange.emit(value);
  }
  private _name: string;

  @Output() nameChange = new EventEmitter<string>();
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent  {
  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector
  ) { }

  ngOnInit() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(HelloComponent);
    const helloRef = factory.create(this.injector);

    const hello = helloRef.instance;

    // Subscribe to output
    const subscription = hello.nameChange.subscribe(name => console.log('name changed', name));

    // Set name input
    hello.name = 'Hello!';

    // When you are done with the component, it must be destroyed
    subscription.unsubscribe();
    helloRef.destroy();
  }
}

It doesn't matter in your case since you have no template, but if you need change detection in your dynamically created component, you need to attach them to the application:

export class AppComponent  {
  constructor(
    // ...
    private applicationRef: ApplicationRef
  ) { }

  ngOnInit() {
    // create helloRef ...
    this.applicationRef.attachView(helloRef.hostView);

    // ...

    // Destroy the component
    this.applicationRef.detachView(helloRef.hostView);
    helloRef.destroy();
  }
}

  • Then How would I do the property binding ```@Input``` and ```@Output```. How can I pass params to the constructor of the component? – mx_code Feb 13 '21 at 06:05
  • I updated the answer with an example of input and output. You cannot pass params to the constructor directly, it will use Angular dependency injection, so you could provide service in AppModule and inject them into the component. – Yannick Beauchamp-H Feb 13 '21 at 06:14
0

To use DI feature, instead of blurring things by instanciating manually a "component without a template", create a service class :

@Injectable({
  providedIn: 'root' // (not sure?) put here the scope where instance of
                     // MyService will be provided
})
export class MyService {
 // your stuff
}
natwell
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 05 '22 at 03:42