ChangeDetection для дочернего элемента со стратегией OnPush

У меня есть InputComponent, который работает как оболочка для нескольких нативных входных данных и добавляет некоторую логику.

Этот компонент имеет вход control, который принимает FormControl и привязывает его к полю ввода через [formControl].

Чтобы улучшить производительность, я установил changeDetectionStrategy на OnPush.

Теперь я заметил, что когда я использую formGroup.get(...).setValidators(...), InputComponent не обновляет свое состояние. Как я могу это сделать? Я не вижу никаких зацепок в цикле смены валидаторов.

В качестве обходного пути я добавил общедоступный API для ручного вызова detectChanges().

Есть ли Observable, который я могу прослушать и вызвать detectChanges(), когда есть изменения в правилах проверки и не превысить размер стека вызовов.


Это можно перефразировать как Как я могу вызвать обнаружение изменений всего дерева, когда используется стратегия обнаружения OnPush


Маленький пример здесь вы видите один флажок. Нажмите на кнопку и убедитесь, что разницы нет. Однако, если вы нажмете на флажок, он покажет звездочку.

Что также интересно, так это то, что когда requiredTrue назначается элементу управления, а элемент управления не имеет true в качестве значения, форма все еще действительна. Я не могу понять, почему.

Почему detectChanges вызывает переполнение стека?

Roberto Zvjerković 03.07.2019 10:40

Я предполагаю, что detectChanges может вызвать некоторую переоценку формы и, таким образом, вызвать переполнение стека.

Sergey 03.07.2019 10:42

Можно ли увидеть минимальный рабочий пример, например. как стекблиц?

Erbsenkoenig 03.07.2019 10:55
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
2
3
1 644
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Реальная проблема здесь заключается в том, что установка валидаторов снаружи нарушает концепцию неизменности. Вход, который у вас есть, представляет собой объект, в котором изменяется только свойство внутри этого объекта. Angular не регистрирует это как изменение.

Интересно, что это не просто проблема с ChangeDetction.OnPush. Используя обнаружение изменений по умолчанию, angular обновляет представление, потому что все компоненты проверяются, но если вы зарегистрируетесь в хуке жизненного цикла onChanges, вы заметите, что фактическое изменение не зарегистрировано внутри хука жизненного цикла ни в одной из конфигураций обнаружения изменений. Так что будьте осторожны, кажется, что он работает только с обнаружением изменений по умолчанию, но не работает полностью. Поэтому будьте осторожны, чтобы помнить об изменчивости объекта независимо от используемого обнаружения изменений.


Сказав это: в вашем случае, следуя SoC, я бы фактически переместил настройку валидатора внутрь компонента ввода, в зависимости от логического свойства ввода, вместо того, чтобы устанавливать валидатор извне. Рядом с SoC это также будет иметь то преимущество, что в случае, если есть другие валидаторы, которые могут понадобиться вашему входному компоненту, у вас действительно есть все они в одном месте, и вы можете сбросить их, как только вы захотите удалить только необходимый валидатор, поскольку, насколько я знаю, нет возможности удалить конкретный валидатор (пока).

Ваш input.component.ts будет выглядеть примерно так (имейте в виду, что это всего лишь псевдокод):

input.component.ts

export class InputComponent implements OnChanges {
  @Input() required: boolean;
  @Input() control: FormControl;

  ngOnChanges(simpleChanges: SimpleChanges){
    if (simpleChanges.required) {
      if (simpleChanges.required.nextValue) {
        this.control.addValidator(...)
      } else {
        this.control.clearValidators()
      }
    }
  }

}

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

app.component.ts

constructor(private fb: FormBuilder, private ref: ChangeDetectorRef) {}

  toggleValidator() {
    if (this.requiredTrue) {
      this.requiredTrue = false;
      this.formGroup.get('agreed').clearValidators();
      // this is important
      this.formGroup.get('agreed').updateValueAndValidity();
    } else {
      this.requiredTrue = true;
      this.formGroup.get('agreed').setValidators(Validators.requiredTrue)
      // this is important
      this.formGroup.get('agreed').updateValueAndValidity();
    }

    console.info(this.formGroup);
  }

input.component.ts

constructor(private ref: ChangeDetectorRef) {

  }

  ngOnInit() {
    this.control.valueChanges.subscribe(() => {
        this.ref.markForCheck();
      });
  }

Взгляните на этот стекблиц

Идея вокруг valueChanges классная (я думал поиграться с изменением состояния), однако я не понимаю, почему вы используете markForCheck вместо detectChanges и почему это на самом деле работает, потому что для запуска изменения требуется асинхронная задача. обнаружение после этой "метки"

Sergey 04.07.2019 16:00

Angular вызывает сам метод detectChanges. С помощью markForCheck вы только сообщаете angular, что есть изменение, которое необходимо учитывать во время следующего цикла changeDetection. Если вы используете ChangeDetection.OnPush и изменяете свойство компонента внутри подписки, вам всегда нужно вызывать markForCheck, иначе это изменение не будет проецируемым шаблоном.

Erbsenkoenig 04.07.2019 16:05

Другие вопросы по теме