У меня есть InputComponent, который работает как оболочка для нескольких нативных входных данных и добавляет некоторую логику.
Этот компонент имеет вход control, который принимает FormControl и привязывает его к полю ввода через [formControl].
Чтобы улучшить производительность, я установил changeDetectionStrategy на OnPush.
Теперь я заметил, что когда я использую formGroup.get(...).setValidators(...), InputComponent не обновляет свое состояние. Как я могу это сделать? Я не вижу никаких зацепок в цикле смены валидаторов.
В качестве обходного пути я добавил общедоступный API для ручного вызова detectChanges().
Есть ли Observable, который я могу прослушать и вызвать detectChanges(), когда есть изменения в правилах проверки и не превысить размер стека вызовов.
Это можно перефразировать как Как я могу вызвать обнаружение изменений всего дерева, когда используется стратегия обнаружения OnPush
Маленький пример здесь вы видите один флажок. Нажмите на кнопку и убедитесь, что разницы нет. Однако, если вы нажмете на флажок, он покажет звездочку.
Что также интересно, так это то, что когда requiredTrue назначается элементу управления, а элемент управления не имеет true в качестве значения, форма все еще действительна. Я не могу понять, почему.
Я предполагаю, что detectChanges может вызвать некоторую переоценку формы и, таким образом, вызвать переполнение стека.
Можно ли увидеть минимальный рабочий пример, например. как стекблиц?





Реальная проблема здесь заключается в том, что установка валидаторов снаружи нарушает концепцию неизменности. Вход, который у вас есть, представляет собой объект, в котором изменяется только свойство внутри этого объекта. 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 и почему это на самом деле работает, потому что для запуска изменения требуется асинхронная задача. обнаружение после этой "метки"
Angular вызывает сам метод detectChanges. С помощью markForCheck вы только сообщаете angular, что есть изменение, которое необходимо учитывать во время следующего цикла changeDetection. Если вы используете ChangeDetection.OnPush и изменяете свойство компонента внутри подписки, вам всегда нужно вызывать markForCheck, иначе это изменение не будет проецируемым шаблоном.
Почему
detectChangesвызывает переполнение стека?