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

#angular #typescript #angular-changedetection

Вопрос:

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

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

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

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

Однако все эти решения приводили к 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;
    }
  }
 

приложение.компонент.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); 
      });
  }
 

дочерний компонент.ts

Ответ №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);
  }
}
 

Комментарии:

1. Спасибо, что сработало. У меня действительно случилась небольшая заминка. Я должен был пометить родительский компонент как общедоступный в конструкторе дочернего компонента. например, @Inject(PARENT_COMPONENT) публичный родительский компонент: родительский компонент

2. Я ничего не менял, но ошибка вернулась. Не знаю почему.. Я действительно заблудился.