【问题标题】:NullInjectorError: Custom Angular Material CdkOverlay refNullInjectorError:自定义角度材质 CdkOverlay 参考
【发布时间】:2023-04-01 21:28:01
【问题描述】:

我在创建自定义 cdkOverlay 弹出框组件时出现问题。它通过单击过滤器图标在表格中打开。表很少有这样的图标。 这就是我的 Popover 组件服务的外观。它作为普通的角度材质对话框打开。

@Injectable()
export class PopoverService {
  constructor(
    private overlay: Overlay,
    private injector: Injector
  ) { }

  open<T = unknown, R = unknown>(component: ComponentType<T>, elementRef: ElementRef, config: PopoverConfig = {}): PopoverRef<R> {
    const popoverConfig: PopoverConfig = { ...DEFAULT_CONFIG, ...config };

    const overlayRef: OverlayRef = this.overlay.create(this.getOverlayConfig(elementRef, popoverConfig));

    const popoverRef: PopoverRef<R> = new PopoverRef<R>(overlayRef);

    const popupPortal: ComponentPortal<T> =
      new ComponentPortal<T>(component, null, this.createInjector(this.injector, popoverRef, config.data));

    overlayRef.attach(popupPortal);

    overlayRef.backdropClick().subscribe(_ => popoverRef.close());

    return popoverRef;
  }

  private createInjector(injector: Injector, popoverRef: PopoverRef, data: unknown): Injector {
    return Injector.create({
      providers: [
       { provide: PopoverRef, useValue: popoverRef },
       { provide: POPOVER_DATA, useValue: data }
      ],
      parent: injector
    });
  }

  private getOverlayConfig(elementRef: ElementRef, config: PopoverConfig): OverlayConfig {
    return new OverlayConfig({
      ...config,
      positionStrategy: this.getOverlayPositions(elementRef),
      scrollStrategy: this.overlay.scrollStrategies.reposition()
    });
  }

  private getOverlayPositions(elementRef: ElementRef): PositionStrategy {
    return this.overlay.position()
      .flexibleConnectedTo(elementRef)
      .withPositions(this.getPositions())
      .withFlexibleDimensions(false)
      .withPush(false);
  }

  private getPositions(): ConnectedPosition[] {
    return [
      {
        originX : 'center',
        originY : 'bottom',
        overlayX: 'start',
        overlayY: 'top',
      },
      {
        originX : 'center',
        originY : 'top',
        overlayX: 'start',
        overlayY: 'bottom',
      },
      {
        originX : 'center',
        originY : 'bottom',
        overlayX: 'end',
        overlayY: 'top',
      },
      {
        originX : 'center',
        originY : 'top',
        overlayX: 'end',
        overlayY: 'bottom',
      },
    ];
  }
}

我还创建了 PopoverRef 类,其中包括对覆盖的引用。

export class PopoverRef<T = unknown>{
  afterClosed$ = new Subject<OverlayCloseEvent<T>>();

  constructor(private overlayRef: OverlayRef) {
    overlayRef.backdropClick().subscribe(() => this.overlayClose('backdropClick', null));
  }

  close(data?: T): void {
    this.overlayClose('close', data);
  }

  private overlayClose(type: 'backdropClick' | 'close', data: T) {
    this.overlayRef.dispose();
    this.afterClosed$.next({
      type,
      data
    });

    this.afterClosed$.complete();

  }
}

弹出框内的组件通过this.popoverRef.close(profile) 向父级传递数据

我希望在父组件中我可以做这样的事情并接收数据this.popoverRef.afterClosed$.subscribe((res) =&gt; this.someEmitter.emit(res))

但结果我在控制台中看到错误

错误 NullInjectorError: R3InjectorError(TableModule)[PopoverRef -> PopoverRef -> PopoverRef -> PopoverRef -> PopoverRef -> PopoverRef]:
NullInjectorError:没有 PopoverRef 的提供者!

但如果 afterClosed$ 在 popover 组件中订阅,一切正常

更新 父组件

  @ViewChild(CdkOverlayOrigin) cdkOverlayOrigin: CdkOverlayOrigin;
  @Input() filterType: TableFilterType;
  @Input() filterData: TableStaticData[];

  constructor(
    private popoverRef: PopoverRef
  ) {}

  onOpenPopover(event: Event): void {
    event.stopPropagation();

    this.popoverStore.dispatch(new fromPopoverStore.OpenFilterPopover({
      filterType: this.filterType,
      elRef: this.cdkOverlayOrigin.elementRef,
      filterItemData: this.filterItemData
    }));

    this.popoverRef.afterClosed$.subsribe();
  }

【问题讨论】:

  • 好吧,您尝试在某处注入 PopoverRef,但它不包含在此代码中,因此我们无法为您提供帮助。
  • 父组件打开popover,子组件通过父组件中的this.popoverRef.close(someData)传递数据我尝试this.popoverRef.afterClosed$.subscribe()父组件在构造函数中注入PopoverRef,这个父组件有错误
  • 是否愿意贴出父组件的代码(进入实际问题)?
  • 你的父级不能在构造函数中注入 PopoverRef。
  • 添加了父组件。无论如何,它没有给我答案,为什么我不能将它注入父级,即使它不是可注入的(以 OverlayRef 类为例),对于角度注入类也是正常的,以及将数据传递给父组件的解决方案是什么

标签: angular angular-material angular-cdk


【解决方案1】:

对于每个要注入的服务/值/任何内容,您都需要有一个提供者。

  1. 将依赖注入构造函数和直接调用是有区别的。您不会在任何地方注入OverlayRef。您正在调用PopoverRef 的构造函数并将OverlayRef 作为参数传递给它。

  2. 在您在服务中手动创建的 Injector 之外没有 PopoverRef 的提供程序。该注入器仅用于在您的服务的 open 方法中创建的组件。在这些组件之外,您无法访问PopoverRef

所以,当 popoverRef 在父组件中无法访问并且您需要它时,有多种解决方案。您可以创建一个服务来存储对弹出框的引用,您可以更改它的打开方式...

我建议您应该将打开逻辑上移一个级别。如果您需要能够触发子项中的弹出框,请使用输出。

@Component({selector: 'child-component', ...})
export class ChildComponent {
  @Output() openPopover = new EventEmitter<void>;
  
  onButtonClick() { this.openPopover.emit() }
}


@Component({template: `<child-component (openPopover)="openPopover()"></child-component>`})
export class ParenComponent {
  constructor(private popoverService) {}
  openPopover() { const popoverRef = this.popoverService.open(); // now you have access }
}

【讨论】:

  • PopoverRef 在此方法中手动注入PopoverService private createInjector(injector: Injector, popoverRef: PopoverRef, data: unknown): Injector { return Injector.create({ providers: [ { provide: PopoverRef, useValue: popoverRef }, { provide: POPOVER_DATA, useValue: data } ], parent: injector });
  • 该注入器仅用于在您的服务的 open 方法中创建的组件。在这些组件之外,您无法访问 PopoverRef
猜你喜欢
  • 2022-10-05
  • 1970-01-01
  • 2019-06-14
  • 1970-01-01
  • 2020-12-03
  • 1970-01-01
  • 2020-09-20
  • 2017-12-29
  • 1970-01-01
相关资源
最近更新 更多