用Observables构成视图模型

时间:2019-11-20 04:44:53

标签: angular rxjs

下面的代码轻松地说明了我要使用2种模型进行的操作。假设我们有一个Book模型和一个Author模型。首先,我们使用返回类型Books的服务来获取Observable<Book>的列表。然后,我们使用*ngFor遍历每本书,但我们也想在本书旁边显示Author,因此在我们的迭代中,我们有一些类似以下的HTML:

<span>{{ getAuthorName(book) | async }}</span>

getAuthorName有自己的请求,在AuthorService上发出,要求ID提取作者。问题在于,当我们在此处返回Observable<string>时,浏览器完全崩溃,因为更改检测运行不正常,原因是每个摘要周期都返回了一个“新”可观察值,这导致更改检测再次无休止地运行,对吧?

下面的代码可以轻松地放入StackBlitz中并进行复制(我没有链接到它,因为它炸毁了您的浏览器CPU)

<ul>
  <li *ngFor="let item of getData() | async">
    {{ getOtherData(item.id) | async }}
  </li>
</ul>
import { Component } from '@angular/core';
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';

  getData() {
    return of([{
      id: 1
    },{
      id: 2
    },{
      id: 3
    }]);
  }

  getOtherData(id: number) {
    const data = of([{
      id: 1,
      name: 'Matt'
    },{
      id: 2,
      name: 'Steve'
    },{
      id: 3,
      name: 'Alice'
    }]);

    return data.pipe(
      map(x => x.find(d => d.id == id))
    )
  }
}

很明显,可以在此处完成一些优化,例如Authors的缓存等,但是总的来说,在RXJS的世界中应该如何处理这种情况?我知道有用于处理诸如Akita和ngrx之类的状态的整个框架,但是如果我不想处理所有这些而只想用原始rxjs来构建我的应用程序该怎么办?该问题的预期解决方案是什么?您是否使用其他rxjs运算符阻止这种情况的发生? (由于每次每次都会返回一个新的Obserable,所以我找不到帮助它的东西)您是否只需要使用视图模型而不是方法?这样,它们始终只是可观察对象的单一参考吗? (例如,BookAuthorViewModel具有一个$author属性,该属性在我们将视图迭代到单个可观察对象之前被设置吗?

我还没有找到一个“感觉不错”的解决方案,我觉得我被迫使用这些状态管理框架只是为了做这样的简单事情。

3 个答案:

答案 0 :(得分:2)

您不应在视图中调用方法。如果您有一个返回可观察值的方法,那么每次更改保留检查值时,都会创建一个新的可观察值。

data$ = of([{
  id: 1
},{
  id: 2
},{
  id: 3
}]);

otherData$ = of([{
  id: 1,
  name: 'Matt'
},{
  id: 2,
  name: 'Steve'
},{
  id: 3,
  name: 'Alice'
}]);

然后绑定到可观察的

<li *ngFor="let item of data$ | async">
  {{ otherData$ | async | find: item.id }}
</li>

并创建查找管道

@Pipe({
  name: 'find'
})
export class FindPipe implements PipeTransform {
  transform(options, id): string {
    return options.find(option => option.id === id);
  }
}

StackBlitz https://stackblitz.com/edit/angular-6vgosc

答案 1 :(得分:2)

出于性能原因,您不应在html模板中使用函数。我建议先合并数据,然后再在模板中使用它。看起来可能像这样:

this.books$ = this.myService.getBooks();
this.authors$ = this.myService.getAuthors();

this.bookAuthorPairs$ = combineLatest([this.books$, this.authors$]).pipe(
  map(([books, authors]) => books.map(
    (book) => {
      return { book, author: authors.find(author => book.authorId === author.id) };
    }
  )),
);

答案 2 :(得分:1)

另一种选择是合并服务中的流。

对于每本书,您都需要作者的名字。在我的示例中,对于每个ToDo,它都会获取用户名。

  todos$ = this.http
    .get<ToDo[]>(this.todoUrl)
    .pipe(catchError(err => throwError("Error occurred")));

  todosWithUser$ = this.todos$.pipe(
    mergeMap(todos =>
      forkJoin(
        todos.map(todo =>
          this.http.get<User>(`${this.userUrl}/${todo.userId}`).pipe(
            map(user =>({
                  ...todo,
                  usersName: user.name
                } as ToDo)
            )
          )
        )
      )
    )
  );

https://stackblitz.com/edit/angular-users-todos-deborahk

todos$流会获取所有待办事项(或您的情况下的书籍)。

它使用mergeMap映射并合并列表中的每个ToDo。这是自动订阅可检索用户的“内部” Observable所必需的。

forkJoin允许我们处理ToDo的数组并为每个数组找到用户。然后将它们全部重新组合成一个数组。

最后一个mapusersName属性添加到每个Todo。