0

I recently updated my code from Angular 13 to version 14.2.x and saw that ComponentFactoryResolver is deprecated. Decided to update the few instances of ComponentFactoryResolver in my library, but started running into the following error:

ERROR Error: Uncaught (in promise): NullInjectorError: R3InjectorError(ModalDemoModule)[ModalService -> DomService -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef]: 
  NullInjectorError: No provider for ViewContainerRef!
NullInjectorError: R3InjectorError(ModalDemoModule)[ModalService -> DomService -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef -> ViewContainerRef]: 
  NullInjectorError: No provider for ViewContainerRef!

Basically, I have a dom.service.ts that creates a consistent way to append new components to the DOM. Then I have a modal.service.ts and tooltip.service.ts that handles modals and tooltips, which uses dom.service.ts.

@Injectable()
export class DomService {
  private renderer: Renderer2;

  constructor(
    @Inject(DOCUMENT) private document: Document,
    private appRef: ApplicationRef,
    private viewContainerRef: ViewContainerRef,
    private injector: Injector,
    private rendererFactory: RendererFactory2,
  ) {
    this.renderer = this.rendererFactory.createRenderer(null, null);
  }

@Injectable({
  providedIn: 'root'
})
export class ModalService {
  constructor(
    private domService: DomService,
  ) {}
@Injectable({ providedIn: 'root' })
export class TooltipService {
  constructor(
    private domService: DomService,
  ) {}

This was working when I utilized the ComponentFactoryResolver. I have seen other solutions, but I would like to keep the code as similar to what it currently is, if possible.

Any suggestions? Is this just a stupid pattern that I should abandon? Thanks for any help!

I have tried a portion of the solution outlined here by injecting the ViewContainerRef into modal.service.ts and then providing it to dom.service.ts. I I also tried to make dom.service.ts a singleton and inject it into my demo application's top-level module, but no luck. Not really sure where else to go without potentially doing a major refactor, which I would like to avoid if possible since things were working fine before.

Ken White
  • 123,280
  • 14
  • 225
  • 444
bmangaman
  • 1
  • 1
  • Any solution for this issue? I am struggling with the same thing. – Nabeel Hassan Dec 08 '22 at 13:29
  • Unfortunately no, I have not found a solution yet (though I have not tried again in about a month). I ended up replacing all of the instances of `ComponentFactoryResolver` where I could, and left the problem areas alone for now. – bmangaman Dec 09 '22 at 19:37

1 Answers1

0

I have found a solution that I think can work well for your use case, as I have a very similar situation (I have a dom.service which I use as the service to append DOM elements from other services, such as a notification and a modal service).

Digging around StackOverflow I found this issue: https://github.com/angular/angular/issues/45263, in which AndrewKushnir comments that they are working in a replacement for this behavior, and looking at the completed PR (https://github.com/angular/angular/pull/46685) I saw that a new function was available from @angular/core: createComponent.

Now, my dom.service looks something like this (ommiting some types because they are very application specific):

import { ComponentType } from '@angular/cdk/portal';
import {
  ApplicationRef,
  EnvironmentInjector,
  Injectable,
  createComponent
} from '@angular/core';

@Injectable()
export class DomService {
  constructor(private appRef: ApplicationRef, private environmentInjector: EnvironmentInjector) {}

  public appendComponentTo(childComponentType: ComponentType<any>, target?: Element) {
    const childComponentRef = createComponent(childComponentType, {
      environmentInjector: this.environmentInjector
    });

    this.appRef.attachView(childComponentRef.hostView);

    // Some more work to append DOM element to the body, possibly as a child node to the argument target
    // ...

    return childComponentRef;
  }

This way you can work around the need to use a ViewContainerRef.