Reputation: 922
I am attempting to use Angular Material components inside of a web component. Everything is working except for one edge case with the cdk-overlays. So far I have had success using an override of _createContainer() to append the overlay container to a div inside of the shadowroot so that the styles will apply. However if I hide the parent element with an *ngIf and then toggle it back the overlay container is present in the DOM but it and its contained elements are not visible.
Is there something I'm missing where I need to manually clear or toggle something to get that behavior to work correctly?
Code is based off of this answer: Add MatDialog popup to Angular Root and not to body
@Injectable({ providedIn: "root" })
export class AppOverlayContainer extends OverlayContainer implements OnDestroy {
constructor(@Inject(DOCUMENT) _document: any) {
super(_document);
}
ngOnDestroy() {
super.ngOnDestroy();
}
getRootElement(): Element {
return this._document
.querySelector("web-component-tag")
.shadowRoot.querySelector("#angular-app-root");
}
createContainer(): void {
this._createContainer();
}
protected _createContainer(): void {
super._createContainer();
this._appendToRootComponent();
}
private _appendToRootComponent(): void {
if (!this._containerElement) {
return;
}
const rootElement = this.getRootElement();
const parent = rootElement || this._document.body;
parent.appendChild(this._containerElement);
}
}
HTML
<ng-container *ngIf="toggleWidget$ | async">
<app-conversations
id="angular-app-root"
[toggleWidget$]="toggleWidget$"
[contextTitle]="contexttitle"
[contexts]="contexts"
[preloadConversations]="subscribeonload"
[widgetIdentifier]="uniqueId"
></app-conversations>
</ng-container>
<ng-container
*ngIf="(toggleWidget$ | async) === false && !overridelaunchbutton"
>
<button
mat-fab
class="message-button"
color="primary"
(click)="toggleWidget$.next(true)"
>
<mat-icon svgIcon="message"></mat-icon>
<div class="unread-pip" *ngIf="hasUnreadMessages">
{{ unreadMessageCount }}
</div>
</button>
</ng-container>
<ng-container *ngIf="overridelaunchbutton">
<button
mat-icon-button
[ngClass]="launchbuttonclass"
[color]="launchbuttoncolor"
(click)="toggleWidget$.next(true)"
>
<mat-icon svgIcon="{{ launchbuttonicon }}"></mat-icon>
<div class="unread-pip" *ngIf="hasUnreadMessages">
{{ unreadMessageCount }}
</div>
</button>
</ng-container>
Upvotes: 4
Views: 3202
Reputation: 11
The method worked for me, there was one problem though:
Application used module federation, web component was opened on separate route. When visiting the route for the first time, all was good, but when changing to other route and then revisiting web component, it stopped working.
The problem was that div
with class .cdk-overlay-container
was created during the first visit only. Next visit resulted in missing div. There was no container some element could be attached to.
As a fix:
inject(AppOverlayContainer)
into AppComponent
,createContainer
method in ngAfterViewInit
hook.To make sure that AppOverlayContainer
and OverlayContainer
share the same instance, do the following in providers section of an AppModule
AppOverlayContainer, { provide: OverlayContainer, useExisting: AppOverlayContainer }
Upvotes: 0
Reputation: 5857
Reason for this behavior is because of the base type you are inheriting. Even when the whole web component is destroyed, it will still have the cached element you created in _createContainer
.
There are 2 fixes for it
OverlayContainer
getContainerElement
while is inside the based class like below
override getContainerElement(): HTMLElement {
if (!this._containerElement || !this._containerElement.isConnected) {
this._createContainer();
}
return this._containerElement;
}
Upvotes: 0
Reputation: 922
I never found root to this beyond the weird behavior occurs if the overlay container is wrapped by an *ngIf. When the *ngIf is shown the first time, toggled to not be visible and then displayed again it causes an issue with the content being rendered inside the container. Moving it outside of the *ngIf fixed the issue in my case.
Upvotes: 1