Как я могу избежать двунаправленного потока данных между угловыми компонентами, когда они взаимодействуют друг с другом? - PullRequest
2 голосов
/ 02 ноября 2019

Допустим, у меня есть очень простой компонент редактора. Ничего сложного, только поле ввода, которое может также загрузить некоторые данные из внешнего источника:

@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). Но, например, Максим Корецкий говорит :

Я не рекомендуюиспользуя их, но скорее перепроектируйте ваше приложение

, и я склонен согласиться. По крайней мере, неудобно просто асинхронно оборачивать событие, чтобы просто «заставить его работать». Также два компонента (редактор и менеджер шаблонов)) хорошо выглядят, рассматривая их по отдельности, не так ли? А ведь цель разделения кода на несколько компонентов также заключается в том, что их можно разрабатывать, анализировать и тестировать отдельно, и, добавив такую ​​упаковку, я бы признал, что это не так. случай. Поэтому я, вероятно, допустил большую ошибку, спроектировав взаимодействие так, как я это сделал.

Как спроектировать такие взаимодействия, как показано здесь, в соответствии сугловые принципы проектирования?

1 Ответ

0 голосов
/ 02 ноября 2019

На начальном примере вы можете избежать ExpressionChangedAfterItHasBeenCheckedError с помощью этого шага:

  • используйте changeDetection: ChangeDetectionStrategy.OnPush в своем компоненте.
  • Определите loading как loading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
  • используйте loading$ | async в своем HTML.
  • замените this.loading = true; на this.loading$.next(true);

Почему? OnPush позволяет не перезагружать компонент без реальных изменений @Input, @Output или наблюдаемых в вашем html с помощью async. И мы изменяем loading на наблюдаемый, чтобы он мог визуализировать компонент.

(я пробовал на примере @Andrew Allen и он работает)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...