PortalHost не обнаруживается, когда он упакованв угловых - PullRequest
0 голосов
/ 30 октября 2019

Итак, у меня есть некоторый внешний компонент, который принимает

<external-component>
    <ng-content>

    </ng-content>
</external-component>

В моем хост-компоненте я использую этот внешний компонент

<host-component>
    <external-component>
        <div id="portal-host">
            <!-- NOT DETECTED -->
        </div>
    </external-component>
    <div id="portal-host">
        <!-- DETECTED -->
    </div>
</host-component>

Теперь, когда я пытаюсь привязать шаблон к этомухост не обнаружен.

<portal-component>
    <ng-template cdk-portal>
        Coming from Definition
    </ng-template>
</portal-component>
public ngAfterViewInit() {
    // Create a portalHost from a DOM element
    let el: any = document.querySelector('#page-actions-container'); <--- returns null
    this.portalHost = new DomPortalHost(
      el,
      this.componentFactoryResolver,
      this.appRef,
      this.injector
    );
    this.portalHost.attach(this.portal); // not working
}

1 Ответ

0 голосов
/ 01 ноября 2019

Я мог бы воспроизвести эту проблему, когда хост-элемент находится в маршрутизируемых компонентах (внешний компонент). Чтобы убедиться, что хост-элемент загружен, вы можете создать сервис, который хранит и предоставляет хосту 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
}
...