Установка выбранного элемента выбранного элемента с помощью асинхронных каналов в Angular

Специалисты по угловым! Я пытаюсь понять асинхронные каналы в Angular, но я застрял в базовом сценарии. У меня есть два элемента select в пользовательском интерфейсе, один из которых содержит сообщения, а другой - связанные с ними комментарии. Я хочу установить публикацию (последнюю) как изначально выбранную для элемента select, отображающего сообщения, и я хочу использовать выбранный элемент для фильтрации связанных комментариев во втором выборе. Это не работает в моем коде, для которого я создал упрощенную версию в Stackblitz:

https://stackblitz.com/edit/angular-p6ynuy

Может ли кто-нибудь из вас объяснить мне, что я делаю не так? Это соответствующий фрагмент кода и HTML:

ngOnInit() {
    this.postList$ = this.getPostList();

    // latestPost$ is not is use yet, but maybe it could be used to set the selected post?
    this.latestPost$ = this.postList$ 
      .pipe(
        map(posts => posts[posts.length - 1])
      );

    this.selectedPost$ = combineLatest([
      this.postList$,
      this.postSelectedAction$
    ])
      .pipe(
        map(([posts, selectedPostId]) => posts.find(post => post.id === selectedPostId))
      );

    this.commentList$ = this.selectedPost$
      .pipe(switchMap(
        post => this.getCommentList(post)
      ));
  }


<select [ngModel]="selectedPost$ | async" (change)="onSelected($event.target.value)">
  <option *ngFor="let post of postList$ | async" [ngValue]="post">
    {{post.id}} {{post.title}}
  </option>
</select>
<select>
  <option *ngFor="let comment of commentList$ | async" [ngValue]="comment">
    {{comment.id}} {{comment.postId}} {{comment.name}}
  </option>
</select>

person Frank Jusnes    schedule 27.04.2020    source источник


Ответы (1)


Angular по умолчанию сравнивает объекты по ссылке

Вы почти там. Проблема в том, что ваш select получает список option, ссылающихся на Post как часть ngFor. Теперь, чтобы узнать, какой option выбран в данный момент, Angular сравнивает каждый объект post с текущим значением selectedPost$ | async.

По умолчанию это делается с помощью оператора ===. Оператор === сравнивает примитивы по значению, а объекты по ссылке. Пример:

console.log('a' === 'a');
const obj = {'a': 'b'};
const obj2 = obj;
console.log(obj === obj2);
console.log(obj === {'a': 'b'});

Таким образом, для того, чтобы post считался тем же постом, что и selectedPost$ | async, они должны быть фактически одним и тем же объектом, а не просто объектом, который выглядит одинаково.

Фактически вы получаете несколько копий одного и того же сообщения, а не только одно сообщение

Теперь это не так: поскольку вы используете канал async, может случиться так, что во время обнаружения изменений сообщения перезагружаются из API. Когда вы проверяете вкладку сети своего браузера, вы фактически видите, что есть три запроса:

введите описание изображения здесь

Полезная нагрузка ответа на все запросы одинакова, но поскольку объекты Post возвращаются трижды, они сохраняются в памяти три раза. JavaScript не имеет возможности узнать, что они на самом деле одинаковы, и сравнение === возвращает false.

Решение: предоставьте свою compareWith функцию

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

Теперь вы можете написать свою собственную инструкцию для Angular для сравнения объектов или вашего выбора: просто добавьте compareWith вход в select:

<select [ngModel]="selectedPost$ | async" 
(change)="onSelected($event.target.value)"
[compareWith]="comparePosts"
>

Теперь Angular знает, как использовать метод comparePosts для сравнения двух сообщений. Как теперь может выглядеть этот метод? Например так:

comparePosts(p1: Post, p2: Post) {
    return p1.id === p2.id;
}

Теперь Angular знает, как правильно сравнивать два Post объекта, и ваша проблема решена.

PS: Убедитесь, что вы написали лучший comparePosts метод, чем я, например, также правильно обрабатывая значения undefined и null.

person fjc    schedule 27.04.2020
comment
Спасибо fjc, все сработало отлично, и я узнал кое-что новое! Могу я спросить, можете ли вы определить, почему второй выбор (комментарии) не обновляется, когда я выбираю сообщение в первом выборе? - person Frank Jusnes; 27.04.2020
comment
Пожалуйста. Насколько я понимаю, выбор комментария не обновляется, потому что вы нигде не связали его с каким-либо значением. Для выбора поста у вас есть набор ngModel. Для вашего комментария такого нет. - person fjc; 27.04.2020
comment
Вы правы, и я планирую добавить ngModel для получения выбранного комментария, но пока я просто хочу, чтобы список комментариев фильтровался по выбранному посту. Что я пытаюсь сделать с помощью switchMap, как вы можете видеть в строке 38. Я думаю, это не способ сделать это. Не могли бы вы предложить лучшее? - person Frank Jusnes; 27.04.2020
comment
Я сам нашёл ошибку. Я ожидал неправильного входного параметра в onSelected. Еще раз спасибо за вашу неоценимую помощь! - person Frank Jusnes; 27.04.2020
comment
Хорошо, я рада, что ты это сделал. У меня еще не было возможности проверить это. Добро пожаловать, приятно видеть хорошо написанный, ясный вопрос с действительно воспроизводимой проблемой для разнообразия. - person fjc; 27.04.2020