Как обновить родительский HTML на основе дочернего значения в Angular 11, не вызывая NG0100?

Я изо всех сил пытаюсь найти решение, которое работает и не выдает ошибку NG0100. https://angular.io/errors/NG0100

У меня есть логика в моем AppComponent для создания дочерних компонентов при нажатии элемента заголовка. Когда этот дочерний компонент создается, он запускает серию вызовов данных, загрузка которых занимает некоторое время. В это время я отображаю полосу загрузки. В настоящее время пользователь может снова и снова щелкать элемент заголовка в HTML-коде приложения, в результате чего выбранный дочерний компонент создается и уничтожается снова и снова. Это также приводит к тому, что несколько вызовов данных выполняются снова и снова.

В идеале я хотел бы отключить действие щелчка на родительском компоненте и изменить непрозрачность, чтобы пользователь знал, что он отключен во время загрузки. Я сделал это следующими способами:

  1. статические методы в AppComponent и статических переменных
  2. Методы @Input для дочернего компонента с объектами в качестве входных данных
  3. Методы @Output для дочернего компонента с примитивными значениями в качестве вывода

Однако все эти решения вызывали ошибку NG0100: Expression has changed after it was checked в консоли. Я не хочу реализовывать хакерское решение, которое работает, но выдает ошибки. Каков правильный путь?

  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

Примечание. Я удалил всю логику, которая вызывала ошибку. Для меня важнее чистое исполнение, чем эта функция. Раньше я изменял свойство View.shouldEnable внутри ngOnInit перед подпиской на службу данных, а также внутри подписки.

Например,

<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


person jDub9    schedule 21.05.2021    source источник


Ответы (1)


Вы можете перенаправить ссылку родителя на дочерний компонент, используя InjectionToken. Затем вы можете вызвать methods или установить properties для родителя из дочернего элемента.

Сначала вы должны создать токен инъекции, я бы поместил его в отдельный файл, например tokens.ts.

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

Затем вы должны предоставить токен для родительского компонента, как это. Затем он пересылает себя своим дочерним элементам (см. следующий блок кода).

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

Здесь мы @Inject() помещаем родителя в конструктор, чтобы у нас был доступ. Иногда не всегда есть родитель, и в этом случае вы можете использовать @Optional() @Inject().

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

  doSomething() {
    this.parentCompnent.setValue(true);
  }
}
person Get Off My Lawn    schedule 21.05.2021
comment
Спасибо, это сработало. У меня случилась легкая икота. Мне пришлось пометить parentComponent как общедоступный в моем конструкторе дочерних компонентов. например @Inject(PARENT_COMPONENT) public parentComponent: ParentComponent - person jDub9; 21.05.2021
comment
Я ничего не менял, но ошибка вернулась. Не уверен, почему.. Я действительно потерян. - person jDub9; 21.05.2021