Допустим, у меня есть очень простой компонент редактора. Ничего сложного, только поле ввода, которое может также загрузить некоторые данные из внешнего источника:
@Component({
selector: 'my-editor',
template: `<input [disabled]="loading" [(ngModel)]="text">`
})
class EditorComponent {
loading: boolean = false;
text: string;
load(item: Observable<string>) {
this.loading = true;
item.subscribe(value => {
this.text = value;
this.loading = false;
});
}
}
Кроме того, вход отключен, когда он загружает некоторые данные из внешнего источника, потому что после загрузки любые данные будутбыть отвергнутым, и мы не хотим застать пользователя врасплох.
Тогда в некоторых контекстах у нас есть несколько шаблонов, из которых пользователь может выбирать. Ничего сложного тоже не выглядит, выглядит так:
@Component({
selector: 'my-template-manager',
template: `<button [disabled]="disabled">Dummy</button>`
})
class TemplateManagerComponent implements OnInit {
@Input()
disabled: boolean = false;
@Output()
load = new EventEmitter<Observable<string>>();
constructor(private cd: ChangeDetectorRef) {}
ngOnInit(): void {
this.load.emit(of('initial value').pipe(delay(1000)));
}
}
Также довольно простой компонент, он мало что дает. Когда он запускается в действие, он загружает шаблон по умолчанию из некоторого HTTP-сервиса (здесь имитируется delay
), а в противном случае, если пользователь нажимает кнопку, загружается другой шаблон (здесь не реализовано для простоты).
Теперь я хочу использовать эти два компонента вместе, и это довольно просто:
@Component({
template: `
<my-template-manager [disabled]="editor.loading" (load)="editor.load($event)"></my-template-manager>
<my-editor #editor></my-editor>
`
})
class ParentComponent {
}
Это просто клей для этих компонентов. Таким образом, у меня есть небольшие легкие и тестируемые компоненты, хорошо определенные их API, которые могут быть склеены следующим образом. Однако теперь у меня возникли проблемы: когда редактор загружает что-то, я также хочу отключить менеджер шаблонов. Но глупо: я только что создал двунаправленный поток данных между этими двумя компонентами: в методе onInit
менеджер шаблонов генерирует событие, которое возвращается к менеджеру шаблонов в виде отключенного состояния, а angular так любезенсообщите нам, что это действительно плохая практика с помощью ExpressionChangedAfterItHasBeenCheckedError
. (Для более глубокого понимания происходящего смотрите: https://stackoverflow.com/a/44691880). Также я не могу просто оставить привязку [disabled]
, потому что состояние loading
может быть вызвано из-за различных обстоятельств ...
Есть обходные пути, такие как setTimeout
или Promise.resolve().then()
или new EventEmitter(true)
. Но, например, Максим Корецкий говорит :
Я не рекомендуюиспользуя их, но скорее перепроектируйте ваше приложение
, и я склонен согласиться. По крайней мере, неудобно просто асинхронно оборачивать событие, чтобы просто «заставить его работать». Также два компонента (редактор и менеджер шаблонов)) хорошо выглядят, рассматривая их по отдельности, не так ли? А ведь цель разделения кода на несколько компонентов также заключается в том, что их можно разрабатывать, анализировать и тестировать отдельно, и, добавив такую упаковку, я бы признал, что это не так. случай. Поэтому я, вероятно, допустил большую ошибку, спроектировав взаимодействие так, как я это сделал.
Как спроектировать такие взаимодействия, как показано здесь, в соответствии сугловые принципы проектирования?