How to instantiate angular components with new keyword?

Viewed 663

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?

1 Answers

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();
  }
}

Related