如何根据 Angular 11 中的 child 值更新 parent HTML 而不会导致 NG0100?

How to update parent HTML based on child value in Angular 11 without causing NG0100?

我正在努力寻找一种有效且不会引发 NG0100 错误的解决方案。 https://angular.io/errors/NG0100

当单击 header 元素时,我的 AppComponent 中有创建 child 组件的逻辑。当 child 组件被创建时,它会启动一系列需要一些时间来加载的数据调用。在那段时间里,我显示了一个加载栏。目前,用户可以一次又一次地点击 App HTML 中的 header 元素,这会导致选择的 child 组件被一次又一次地创建和销毁。这也具有多次数据调用的效果。

理想情况下,我想禁用 parent 组件上的点击操作并更改不透明度,以便用户知道它在加载时被禁用。我确实通过以下方式实现了这一点:

  1. AppComponent 中的静态方法和静态变量
  2. @child 组件上的输入法以 Objects 作为输入
  3. @child 组件上的输出方法,原始值作为输出

但是,所有这些解决方案都在控制台中引发了 NG0100: Expression has changed after it was checked 错误。我不想实施一个有效但会抛出错误的 hacky 解决方案。什么是正确的方法?

  certificationView: View = {
    shouldEnable: true,
    shouldRender: false
  }

  toggleViewShouldRender(view: View): void {
    if (view.shouldEnable) {
      view.shouldRender = !view.shouldRender;
    }
  }

app.component.ts

<div>
  <mat-card
    [ngClass]="{'disabled': !certificationView.shouldEnable}"
    (click)="toggleViewShouldRender(certificationView)"
  >
    Certifications
  </mat-card>
  <app-certifications *ngIf="certificationView.shouldRender"></app-certifications>
</div>

app.component.html

  ngOnInit(): void {
    this.facade.retrieve()
      .pipe(
        takeUntil(this.destroyed$),
        filter(state => !!state?.data)
      )
      .subscribe(state => {
        this.state = state;
      });
  }

child.component.ts

注意:我删除了导致错误的所有逻辑。干净利落的执行对我来说比这个功能更重要。我之前在订阅数据服务之前以及订阅内部修改了 ngOnInit 中的 View.shouldEnable 属性。

例如,

<div>
  <mat-card
    [ngClass]="{'disabled': !certificationView.shouldEnable}"
    (click)="toggleViewShouldRender(certificationView)"
  >
    Certifications
  </mat-card>
  <app-certifications 
    *ngIf="certificationView.shouldRender"
    [(shouldEnable)]="certificationView.shouldEnable"
  >
  </app-certifications>
</div>

app.component.html


  @Input shouldEnable: boolean;
  @Output shouldEnableChange = new EventEmitter<boolean>();

  ngOnInit(): void {
    // disable header in parent
    this.shouldEnableChange.emit(false); 

    // load data
    this.facade.retrieve()
      .pipe(
        takeUntil(this.destroyed$),
        filter(state => !!state?.data)
      )
      .subscribe(state => {
        this.state = state;
        
        // enable header in parent
        this.shouldEnableChange.emit(true); 
      });
  }

child.component.ts

您可以使用 InjectionToken 将 parent 的引用转发给 child 组件。然后你可以在 child.

的 parent 上触发 methods 或设置 properties

首先你会创建一个注入令牌,我会把它放在它自己的文件中,比如 tokens.ts

export const PARENT_COMPONENT = new InjectionToken<ParentComponent>('MyParentInjectionToken')

接下来,您将像这样在 parent 组件上提供令牌。然后它将自己转发给它的 children(见下一个代码块)。

@Comonent({
  providers: [{
    provide: PARENT_COMPONENT, 
    useExisting: forwardRef(() => ParentComponent)
  }]
})
export class ParentComponent {
  valueToSet = false;
  setValue(val: boolean) {
    this.valueToSet = val
  }
}

这是我们 @Inject() 将 parent 放入构造函数的地方,因此我们可以访问。有时并不总是 parent 在这种情况下你可以使用 @Optional() @Inject().

@Component({
  template: '<button (click)="doSomething()">Click Me</button>'
})
export class ChildComponent {
  constructor(
    @Inject(PARENT_COMPONENT) parentComponent: ParentComponent
  ){}

  doSomething() {
    this.parentCompnent.setValue(true);
  }
}