Attach Angular component to document body
Asked Answered
C

1

5

I would like to render an Angular component (an overlay) as a direct child of the documents body element. Today this can be done as follows:

constructor(
  private applicationRef: ApplicationRef,
  private componentFactoryResolver: ComponentFactoryResolver,
  @Inject(DOCUMENT) private document: Document,
  private injector: Injector
) {}

public renderOverlayInBody(): void {
  const overlayRef = this.componentFactoryResolver
    .resolveComponentFactory(OverlayComponent)
    .create(this.injector);
  this.applicationRef.attachView(overlayRef.hostView);
  const overlayRoot = (overlayRef.hostView as EmbeddedViewRef<unknown>)
    .rootNodes[0] as HTMLElement;

  // This component can be appended to any place in the DOM,
  // in particular directly to the document body.
  this.document.body.appendChild(overlayRoot);
}

Demo in Stackblitz

Unfortunately, ComponentFactoryResolver has been deprecated in Angular 13 and may be removed in Angular 16. The suggested replacement is ViewContainerRef.createComponent:

constructor(private viewContainerRef: ViewContainerRef) {}

public ngOnInit(): void {
  // This component can only be appended to the current one,
  // in particular not directly to the document body.
  this.viewContainerRef.createComponent(OverlayComponent);
}

While this is much simpler to read, it doesn't allow to render components as direct children of the documents body element. Is there any way to do that, which doesn't rely on currently deprecated code?

Crawler answered 15/9, 2022 at 16:27 Comment(6)
You can access the overlayRoot pretty much exactly like you're accessing it in the old code. The only difference is that you'll have to remove the rendered component before adding it to body: this.viewContainerRef.detach(), like this.Snook
Wow, this is cool! Related question: Is it possible to get a viewContainerRef in a service to extract this logic?Crawler
Unfortunately, nope: #40637340Snook
Although, apparently there's something new in Angular 14.1 that could help you: netbasal.com/…Snook
Hi. Why aren't you using the angular cdk overlay? Docs - Demo - codeZeitgeist
@Zeitgeist To reduce API boundary. This would be the only reason why my lib requires the Angular CDK and I'd rather not force consumers to use it.Crawler
E
6

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. 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 modal component into the root view container
            const componentRef = rootViewContainerRef.createComponent(OverlayComponent);
            // Get the instance of the modal 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).

Emphasis answered 7/9, 2023 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.