Я мог бы воспроизвести эту проблему, когда хост-элемент находится в маршрутизируемых компонентах (внешний компонент). Чтобы убедиться, что хост-элемент загружен, вы можете создать сервис, который хранит и предоставляет хосту ElementRef
- только если он действительно там (ReplaySubject
очень хорошо подходит здесь).
@Injectable({ providedIn: 'root' })
export class PortalService implements OnDestroy {
private _hostElement$ = new ReplaySubject<ElementRef<any>>(1);
get hostElement$(): Observable<ElementRef<any>> {
return this._hostElement$.asObservable();
}
setHost(hostElement: ElementRef) {
this._hostElement$.next(hostElement);
}
ngOnDestroy(): void {
this._hostElement$.complete();
}
}
Затем,в компоненте, содержащем элемент хоста (заполнитель / слот), вы просто предоставляете ElementRef для PortalService. Получение элемента с @ViewChild
гарантирует, что мы получаем ссылку в AfterViewInit
, в отличие от document.querySelector
(который возвращает ноль даже в AfterViewInit).
@Component({
selector: 'external-component',
template: `<div #extendedToolbar></div>`,
})
export class ExternalComponent implements AfterViewInit {
@ViewChild('extendedToolbar', { static: false }) extendedToolbar: ElementRef;
ngAfterViewInit(): void {
this.portalService.setHost(this.extendedToolbar);
}
// constructor left out for brevity
}
Наконец, в компоненте portal мы получаем ElementRef от PortalService и создаем экземпляр DomPortalOutlet
, который затем используем для присоединения / отсоединения.
@Component({
selector: 'portal-component',
template: `<ng-template cdk-portal> <ng-content></ng-content> </ng-template>`,
})
export class PortalComponent implements AfterViewInit, OnDestroy {
@ViewChild(CdkPortal, { static: false }) portal: CdkPortal;
// holds the DomPortalOutlet instance
private host$ = this.portalService.hostElement$.pipe(
take(1),
map(hostElement => {
return new DomPortalOutlet(
hostElement.nativeElement,
this.componentFactoryResolver,
this.applicationRef,
this.injector);
}),
publishReplay(1),
refCount(),
);
ngAfterViewInit(): void {
this.host$.subscribe(host => host.attach(this.portal));
}
ngOnDestroy(): void {
this.host$.subscribe(host => host.detach());
}
// constructor left out for brevity
}