4

I'm looking for a substitute for deprecated ComponentFactoryResolver and ComponentFactory classes in Angular 13. The official documentation as well as another question here on SO suggest using ViewContainerRef, but my application instantiates Angular components in a service, outside of the DOM. There is no ViewContainerRef available.

The instantiated components are not to be shown to the user. They are used to generate in-memory SVGs that are then rasterized and used for further purposes.

This is what I've used until now in my service:

let factoryHistogram = this.resolver.resolveComponentFactory(HistogramComponent);
let newNode = document.createElement('div');
this.histogramComponent = factoryHistogram.create(this.injector, [], newNode);
this.app.attachView(this.histogramComponent.hostView);

How can I achieve the same in Angular 13?

4 Answers 4

2

You can provide a service to the component then in the components constructor assign the viewRef to a variable within the service.

Stackblitz example

@Component({
  providers: [AppService],
})
export class ChildComponent {
  constructor(
    private readonly appService: AppService,
    viewRef: ViewContainerRef
  ) {
    this.appService.viewRef = viewRef;
  }

  ngOnInit() {
    this.appService.createComponent();
  }
}

Then in your service, you just create the component like this:

@Injectable()
export class AppService {
  // Assigned by the component
  viewRef!: ViewContainerRef;

  createComponent() {
    this.viewRef.createComponent(HistogramComponent);
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Hmm, but as I described, I don't want this component to appear anywhere. It is supposed to be disconnected from the DOM completely.
You will always have a Angular Component somewhere such as your AppComponent. You need to get the view reference from a component or a directive no matter what, there is no other way to get it.
Well, the old code doesn't need that component, obviously. If I suddenly need to provide this to my service through such a hack, I see that as a step back.
You could take a look at this revision and use a provider, it might help: stackoverflow.com/revisions/71364435/1
2

The fact that it is no longer possible to create a component without also inserting it into DOM has been reported as a possible regression over there:

https://github.com/angular/angular/issues/45263

Hopefully the core team will be able to figure out a way to keep supporting this use case.

Comments

2

This is basically the same question as: Attach Angular component to document body but I'm going to copy my answer from there in case people stumble upon this first.

The way I finally fixed this problem was to get the ApplicationRef within my service. From there you can take the first component, which is the root component (which is a ComponentRef). Once you have this ComponentRef, you can access its injector and get/inject the ViewContainerRef for that component. Having the ViewContainerRef then allows you to call createComponent on it, like so:

export class MyService
    private static overlayRef: OverlayComponent

    constructor(
        private applicationRef: ApplicationRef
    ) {
        if (MyService.overlayRef === undefined) {
            // Get the root view container ref of the application by injecting it into the root component
            const rootViewContainerRef = this.applicationRef.components[0].injector.get(ViewContainerRef);
            // Insert the overlay component into the root view container
            const componentRef = rootViewContainerRef.createComponent(OverlayComponent);
            // Get the instance of the overlay component
            MyService.overlayRef = componentRef.instance;
        }
    }

The reason you need the static property is that if you're using standalone components and not using this service through a module the service will destroy itself and recreate itself constantly, leading you to have multiple OverlayComponents.

Also, this code can only insert your OverlayComponent adjacent to your current app root (as a sibling). If your app root isn't in the HTML body, it can't do anything about that (but I'm guessing, since it's an overlay, you don't really care if it's in the body or not since you just want it on top of other page elements (which you'll be able to do using this method).

Comments

0

I have found a replacement that does not depend on providing a ViewContainerRef to the service: NullInjectorError: No provider for ViewContainerRef! in Angular Service

Disclaimer: my code creates the element to eventually insert it into de DOM. I don't think it makes it an invalid solution to this particular problem, because I create the element outside the DOM (in a service I provide in an internal component library), but just in case I will mention it.

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.