ChangeDetection для дочернего элемента со стратегией OnPush - PullRequest
0 голосов
/ 03 июля 2019

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

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

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

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

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

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


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


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

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

1 Ответ

0 голосов
/ 04 июля 2019

Фактическая проблема здесь заключается в том, что установка валидаторов снаружи нарушает концепцию неизменности. Входные данные - это объект, в котором изменяется только свойство внутри этого объекта. 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.log(this.formGroup);
  }

input.component.ts

constructor(private ref: ChangeDetectorRef) {

  }

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

Взгляните на это stackblitz

...