【问题标题】:Angular Portal not triggering change detection for ComponentPortalAngular Portal 未触发 ComponentPortal 的更改检测
【发布时间】:2020-09-29 15:31:33
【问题描述】:

我对 Angular 比较陌生,并且有一个功能,我需要在单独的子窗口中弹出一个组件,在窗口关闭时我需要再次将其附加到父窗口

我已阅读有关角度门户的信息并决定使用它。我编写了以下服务来管理门户及其创建:

import { ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable, Injector, InjectionToken, Inject } from '@angular/core';
import { PopoutComponent } from '../popout/popout.component';
import { POPOUT_REFS } from './portal.token';
import { EventService } from './event.service';

@Injectable({
  providedIn: 'root'
})
export class PortalService {

  constructor(private injector: Injector,
    private componentFactoryResolver: ComponentFactoryResolver,
    private applicationRef: ApplicationRef,
    private eventService: EventService ) {
      this.eventService.popoutEvent$.subscribe((val) => {
        if(val && this.isPopoutWindowOpen()) {
          this.closePopoutWindow();
        }
      })
    }

  popOutComponent() {
    const popoutWindow: Window = this.createWindow('POP_OUT_COMPONENT');
    setTimeout(() => {
      this.createPortal(popoutWindow);
    }, 1000);

  }

  private createWindow(target): Window {
    const ref = window.open("index.html", "_blank", "height=400; width=400;directories=0,titlebar=0,toolbar=0,location=0,status=0,menubar=no;", true);
    setTimeout(() => {
      ref.document.body.innerHTML = "";
    }, 1000);
    return ref;
  }

  private createPortal(popoutWindow: Window) {
    if(popoutWindow) {
      const outlet = new DomPortalOutlet(popoutWindow.document.body, this.componentFactoryResolver, this.applicationRef, this.injector);
      const conatinerInstance = this.attachPopoutContainer(outlet);
      this.attachUnloadEventToPopoutWindow(popoutWindow);

      POPOUT_REFS['windowInstance'] = popoutWindow;
      POPOUT_REFS['containerInstance'] = conatinerInstance;
      POPOUT_REFS['portalOutlet'] = outlet;
    }
  }

  private attachPopoutContainer(outlet: DomPortalOutlet) {
    const containerPortal = new ComponentPortal(PopoutComponent, null, null);
    const containerRef: ComponentRef<PopoutComponent> = outlet.attach(containerPortal);
    return containerRef.instance;
  }

  private attachUnloadEventToPopoutWindow(popoutWindow: Window) {
    popoutWindow.addEventListener("beforeunload", () => {
      POPOUT_REFS['portalOutlet'].detach();
      this.eventService.publishPopoutEvent(true);
    });
  }

  isPopoutWindowOpen() {
    return POPOUT_REFS['windowInstance'] && !POPOUT_REFS['windowInstance'].closed;
  }

  closePopoutWindow() {
    Object.keys(POPOUT_REFS).forEach(popout => {
      if(POPOUT_REFS['windowInstance']) {
        POPOUT_REFS['windowInstance'].close();
      }
    });
  }

  focusPopoutWindow() {
    POPOUT_REFS['windowInstance'].focus();
  }

}

当我单击要在子窗口中呈现的组件中的分离按钮时,它会删除主窗口中的组件,因为它是基于ngIf 呈现的。该组件在新的子组件中运行良好。当我关闭子窗口并再次根据条件重新渲染组件时,即使更改了属性,也不会触发角度变化检测。我必须手动触发更改检测才能使其工作。这是我的MainComponent,我需要手动触发更改检测以反映分离组件中的更改:

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { EventService } from '../service/event.service';
import { PortalService } from '../service/portal.service';
import { ChangeDetectionStrategy } from '@angular/compiler/src/compiler_facade_interface';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
  isAttached: boolean;
  num: number;

  constructor(
    private eventService: EventService,
    private portalService: PortalService,
    private changeDetectorRef: ChangeDetectorRef
    ) {
    this.isAttached = true;
  }

  ngOnInit() {
    const popoutComponent = this.handlePopoutComponent.bind(this);
    this.eventService.numberEvent$.subscribe(val => {
      this.num = val;
    });
    this.eventService.popoutEvent$.subscribe(val => {
      if (val != null) {
        popoutComponent(val);
      }
    });
  }

  handlePopoutComponent(val: boolean) {
    this.isAttached = val;
    this.changeDetectorRef.detectChanges();
    if (this.isAttached === false && !this.portalService.isPopoutWindowOpen()) {
      this.portalService.popOutComponent();
    }
  }

  onNumberPublish() {
    this.num += 1;
    this.eventService.publishNumberEvent(this.num);
  }
}

有没有正确的方法来做到这一点?我猜窗口关闭事件不会干净地处理组件并将其重新附加到主窗口。

【问题讨论】:

    标签: angular angular-cdk


    【解决方案1】:

    对于这种情况,可能值得考虑使用轻型事件总线 ..

    export class EventBusService implements OnDestroy{
    
      private eventBus : Subject<App.Event>= new Subject();
      public eventBus$ : Observable<App.Event> = this.eventBus.asObservable();
    
      constructor() { }
    
      fire(event : App.Event){
        this.eventBus.next(event);
      }  
    
      ngOnDestroy(){
        this.eventBus.unsubscribe();
      }
    
    }
    

    您可以从主组件和弹出组件中触发事件并相应地处理它们。

    【讨论】:

    • 我的事件正在由 BehaviorSubject 处理以与组件进行通信。只是当我关闭弹出窗口时,在附加到父窗口时无法在组件上触发更改检测
    • 在 ngOnDestroy 或 closePopoutWindow 上的子组件中,您可以触发另一个可由父组件处理的事件。在我看来 ChangeDetectionStrategy.OnPush 策略是一种更好的实现方式
    • 谢谢你试试这个看看!
    猜你喜欢
    • 2021-04-09
    • 2021-09-02
    • 2023-04-09
    • 1970-01-01
    • 2022-08-08
    • 1970-01-01
    • 2020-03-30
    • 1970-01-01
    • 2016-11-21
    相关资源
    最近更新 更多