【问题标题】:RxJs - What is the proper pattern/way to create an Observable array of Observables?RxJs - 创建 Observable 的 Observable 数组的正确模式/方法是什么?
【发布时间】:2020-09-10 11:16:37
【问题描述】:

我对 RxJs 相当陌生,并试图让我的头脑了解正确的模式是简单地创建 Observable 的 Observable 数组。

我想检索用户帖子列表。 Posts 本身应该是 Observables,我想将它们全部保存在一个 Observable 数组中,这样当数组更改时,应该通知调用代码并更新订阅帖子“列表”的任何内容。这很简单,但我也希望每个帖子都是可观察的,所以如果我从中检索特定的帖子[i],我也应该能够订阅这些单独的对象。 这样做的正确方法是什么?

我正在使用 Angular 9,我有:

public getPosts(): Observable<Array<Post>> {
    return new Promise((resolve, reject) = {
        let posts: Observable<Array<Post>> = new Observable<Array<Post>>();
        this.get<Array<Post>>('posts').subscribe(r => {
            posts = from(r);
            return resolve(posts);
        });
    });
}

这给了我一个Observable&lt;Array&lt;Post&gt;&gt;,但我应该如何创建一个Observable&lt;Array&lt;Observable&lt;Post&gt;&gt;&gt;? 这是反模式吗?

【问题讨论】:

  • 真的不清楚你打算如何处理这个...... http 客户端在那个时间点对posts 发出单个请求。无需您再次请求,它不会收到更改通知。
  • 我不确定你为什么要这样做。一般来说,这将是一种反模式。就可读性和可维护性而言,创建嵌套的 observable 绝不是一个好主意。 rxjs 中有很多内置的操作符只是为了扁平化嵌套的 observables 以防止它成为问题。
  • 谢谢@Xinan,这很有意义......我只是想确定如何在每次需要时检索单个 Observable,而无需随时重新创建新的 Observable客户端请求一个(它应该返回现有的 Observable,如果它存在),所以我想我只需要在创建现有的 Observable 对象时缓存或存储它们,然后返回这些对象,或者创建一个新的一个……

标签: javascript rxjs observable publish-subscribe


【解决方案1】:

这一切都是为了方便,如果您的服务器为您提供帖子中更改的差异数据,那么继续创建Observable&lt;Observable&lt;Post&gt;[]&gt;


但是,在您的帖子中,存在多个问题。您不能将ObservablesPromises 混合使用。 getPosts 方法将只返回您从 API 获得的第一篇文章。


这是要求的解决方案,但我不确定,这是否是您真正想要的...

public getPosts(): Observable<Array<Observable<Post>>> {
  return this.get('posts').pipe(
    switchMap(posts => combineLatest(
      posts.map(post => this.get('post', post.id))
    )),
  );
}

【讨论】:

  • 谢谢!我认为您的解决方案是有道理的,但据我了解,它会在请求所有帖子时对每个帖子发出请求(使用 this.get('post, post.id) 将它们全部转换为 Observables) ......这不应该是可以避免的,因为我已经有了完整的帖子数组吗?有没有办法将它们都变成 Observables 而不必从服务器重新请求它们?或者这不是使用 RxJs 做事的首选方式吗?
【解决方案2】:

目前尚不清楚您要在这里完成什么,但您可能想要更像这样的东西:

@Injectable({providedIn:'root'})
export class PostService {
  // private replay subject will cache one value
  private postSource = new ReplaySubject<Post[]>(1)
  // public list of posts observable
  posts$ = this.postSource.asObservable();
  // function to select item by id out of list
  post$ = (id) => this.posts$.pipe(map(posts => posts.find(p => p.id === id)))

  getPosts() {
    // function to get remote posts
    return this.get<Post[]>('posts');
  }

  loadPosts() {
    // function to load posts and set the subject value
    this.getPosts().subscribe(posts => this.postSource.next(posts));
  }
}

您必须定义 get 函数并在每次要更新列表时调用 loadPosts

【讨论】:

  • 谢谢,我认为这个解决方案最有意义,看起来最干净,我会在这里尝试一下。为了澄清,正如我试图解释的那样,我真的只想要一个帖子数组。数组本身应该是可观察的(因此,如果添加/删除/更新任何帖子,它应该向订阅者发出一个新值,以及所有当前/更新的帖子)。那么这个 Observable Array 中的每个 Post 本身也应该是 Observables,所以如果某个客户端只使用特定的单个 Post,然后它的内容发生了变化,则 Post 对象应该为它自己和更新的 Array 发出更新。
  • 如果你可以利用操作符从 Observable&lt;Array&lt;Post&gt;&gt; 类型中获取单个帖子的可观察值,那么制作 Observable&lt;Array&lt;Observable&lt;Post&gt;&gt;&gt; 之类的类型会过于复杂而没有明显的好处,而且事情会更清晰跨度>
  • 这也是我的想法,但是,我只是不确定从 Observable> 返回 Observable 而不在每次 getPostById 时都创建一个新的 Observable 的首选模式() 被任何客户端调用。所有客户端都应该得到相同的 Observable,对吧?为此,我认为我只需要在客户端请求它们时将单个帖子缓存/存储为 Observables(在 PostStore 中)......否则获取(从现有数组或服务器)并创建一个如果它尚不存在,则为新的 Observable。这是通常的做法吗?
  • 为什么你反对每次都创建一个新的 observable?涉及存储可观察对象的可观察对象的模式非常少见
  • 你错了。只要它们具有相同的源(如我的答案和@Jonathan Stellwag 的答案),通过某个运算符从该源派生的每个可观察对象都会在触发源时触发......在我的答案中,在@ 上调用next 987654327@ 主题将触发通过post$ 函数构建的每个可观察对象,因为该函数构建的所有内容都来自同一来源
【解决方案3】:

给定信息:

!如果这些陈述有任何错误,请告诉我,我会更新答案!

  1. 获取函数,该函数返回一个可观察对象,其中包含一个填充了帖子的数组
  2. get observable 总是在帖子发生变化时发出
  3. observable (Array>) 中的值是不可观察的,不会随时间变化
this.get<Array<Post>>('posts')

可能的功能

  1. () =&gt; getPostById$
// This function returns you an observable with the post related to your id.
// If the id is not found the observable will not emit
// If the id is found the observable will only emit if the interface values have been changed
function getPostById$(id: string): Observable<Post> {
  // Returns you either the post or undefined if not found
  const findId = (id: string) => (posts: Array<Post>): Post | undefined =>
     posts.find(post => post.id === id);

  // Allows you only to emit, if id has been found
  const existingPost = (post: Post | undefined): boolean => post != null;

  // Allows you only to emit if your id has been changed
  const postComparator = (prevPost: Post, currPost: Post): boolean =>
    prevPost.value === currPost.value && prevPost.name === currPost.name;

  return this.get('posts').pipe(
    map(findId(id)),
    filter(existingPost),
    distinctUntilChanged(postComparator)
  );
}

  1. () =&gt; getPosts$
function getPosts$(): Observable<Array<Post>> {
  return this.get('posts');
}
  1. () =&gt; getStatePosts$
// This function allows to manage your own state
// 1. posts$: overwrites all posts
// 2. clear$: empties your posts$ observable
// 3. add$: adds one observable to the end of your posts
function statePosts$(posts$: Observable<Array<Posts>>, clear$: Observable<void>, add$: Observable<Post>): Observable<Array<Post>> {
  const updatePosts = (newPosts: Array<Posts>) => (oldPosts: Array<Posts>) => newPosts;

  const clearPosts = () => (oldPosts: Array<Posts>) => [];

  const addPost = (post: Post) => (oldPosts: Array<Posts>) => [...oldPosts, post];

  return merge(
    // You can add as much update functions as you need/want (eg: deleteId, addPostAtStart, sortPosts, ...)
    posts$.pipe(map(updatePosts)),
    clear$.pipe(map(clearPosts)),
    add$.pipe(map(addPost))
  ).pipe(
    // The fn in you scan is the (oldPosts: Array<Posts>) function from one of your three update functions (updatePosts, clearPosts and addPosts).
    // Whenever one of those three observables emits it first calls the left side of the function inside the map (post: Post) and returns a new function
    // When this function reaches the scan it gets the oldPosts and is able to update it
    scan((oldPosts, fn) => fn(oldPosts), [])
  )
}

// Usage
private posts$: Observable<Array<Post>> = this.get('posts');
private clear$: Subject<void> = new Subject();
private add$: Subject<Post> = new Subject();

public statePosts$ = getStatePosts(posts$, clear$, add$);

提示: 首先尝试从 return 语句中读取函数。然后检查映射/过滤或其他操作中发生了什么。希望我没有让你太困惑。如果您有任何问题,请随时提问。

【讨论】:

  • 感谢您的详细信息!有道理:) ...我知道如何检索 Observable>,但想知道是否有适当的方法让帖子本身成为数组中的 Observables,但也许这不是考虑它的方式...而应该只是一个可观察的帖子数组(如您所示),然后使用单独的 getPostById() 函数将常规帖子转换为可观察的...我只是不确定是否有多个客户端调用getPostById() 分别返回,将返回完全独立的 Observables...然后更改一个不会将事件发送给其他人...?
  • 我同意这里的可观察状态管理的一般思想,但我认为如果您遵循更多带有单个动作调度主题的商店类型模型,它会更清洁、更通用/可重用,即发出状态修改函数和有效载荷(如果需要)。
  • 谢谢。我需要阅读有关此模式的更多信息...我确实有一个商店,并且该商店应该基本上只提供两件事:1)当前帖子数组(Observable)和2)检索特定帖子的接口,其中应该只是从这个数组中拉出它,并将其作为一个可观察的返回。但是可观察的返回应该是任何其他调用都会得到的相同的可观察(即它不应该使我调用 getPostById(id) 的所有东西都成为一个新的可观察) ......所以试图把我的头包裹起来。也许它在您的解决方案中,但我需要尝试一下。
猜你喜欢
  • 2020-07-26
  • 1970-01-01
  • 2017-11-30
  • 1970-01-01
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多