简体   繁体   English

Rxjs 扫描不触发 combineLatest

[英]Rxjs scan does not trigger combineLatest

I am battling with combineLatest and scan rxjs operators (with Angular 8).我正在与 combineLatest 作战并扫描 rxjs 运算符(使用 Angular 8)。 Basically, I am displaying a list that can be filtered.基本上,我正在显示一个可以过滤的列表。

So I have a first Observable list$ = this.api.getAll()所以我有一个第一个 Observable list$ = this.api.getAll()
and a second one filteredList$ = combineLatest(this.list$, this.form.valueChanges())第二个filteredList$ = combineLatest(this.list$, this.form.valueChanges())
and, in my HTML, I'm displaying filteredList$ content using filteredList$ | async并且,在我的 HTML 中,我使用filteredList$ | async显示filteredList$内容。 filteredList$ | async . filteredList$ | async This works fine (meaning, any change in the form update my filteredList$ )这很好用(意思是,表单中的任何更改都会更新我的filteredList$

However, the end user is able to update one element of the list;但是,最终用户可以更新列表中的一个元素; when he does, I update my list$ Observable this way:当他这样做时,我会以这种方式更新我的list$ Observable:

this.api.update(listElement)
    .subscribe((updatedElmt) => {
         this.list$ = this.list$.pipe(
            concatMap(value => of(...value)),
            scan((acc, listElmt) => listElmt._id === updatedElmt._id ? [...acc, updatedElmt] : [...acc, listElmt], []),
        );
    });

This also works well.这也很有效。
However, the combineLatest is not triggered and therefore, my UI is never updated until I refresh my page.但是,不会触发combineLatest ,因此,在刷新页面之前,我的 UI 永远不会更新。 I assume it is because the list$ pointer has not change, so theere's no reason for the combineLatest to be triggered;我认为这是因为list$指针没有改变,所以没有理由触发 combineLatest; but how can I force it?但我怎么能强迫它呢?

BTW, using changeDetector to detectChanges or markForCheck does not work.顺便说一句,使用 changeDetector 来检测Changes 或 markForCheck 不起作用。

You can force both with a conditional and concatenate them;您可以使用条件强制并连接它们;

concat(list$, filteredList$).subscribe(function() {/*do something */});

What exactly is it you want to achieve?你到底想达到什么目的? If it is changing the updated Element in your list then you should consider something like this:如果它正在更改列表中的更新元素,那么您应该考虑这样的事情:

  1. Define your List as Subject:将您的列表定义为主题:

     this.list$ = new Subject<ListType[]>();
  2. Get the current list and next the list$获取当前列表和下一个列表$

     this.api.getAll().subscribe(this.list$.next); // dont forget to unsubscribe or youll get a memory leak
  3. Observe for updates:观察更新:

     updateList$ = this.api.update(listElement).pipe( withLatestFrom(this.currentList$), map(([updatedElement, currentList]) => { return currentList.map(listElement => listElement._id === udaptedElement._id? updatedElement: listElement), }) ).subscribe(this.list$.next) // dont forget to unsubscribe (takeUntil()) should work here
  4. Combine your list$ with valueChanges将您的 list$ 与 valueChanges 结合起来

    combineLatest([this.list$, this.form.valueChanges()])).pipe(....)

this.list$ =... sets a new value to the property, but combineLatest ist subscribing to the old value of this.list$. this.list$ =... 为属性设置了一个新值,但是 combineLatest 订阅了 this.list$ 的旧值。 Which will result in a memory leak.这将导致 memory 泄漏。

this.list$ = someObservableFunc$(); // gives first Observable<any>
const observeList$ = combineLatest([this.list$, otherObservable$]); // gives combination of first Observable<any> and otherObservable$
this.list$ = anotherObservableFunc$(); // gives second Observable<any> but observeList$ keeps subscription to first Observable<any>

A Similar Problem类似的问题

Consider the following code:考虑以下代码:

let a = 1;
const b = 2;
const c = a + b;
a = 10;
console.log(c);

I'm sure you've seen this sort of thing before.我敢肯定你以前见过这种事情。 Of course, this prints 3 ( 1 + 2 ) and not 12 ( 10 + 2 ).当然,这会打印 3 ( 1 + 2 ) 而不是 12 ( 10 + 2 )。 So the question "how do I force c to be 12 here?"所以问题“我如何在这里强制 c 为 12?” is a tough question to answer.是一个很难回答的问题。

The answer might be to remove c entirely.答案可能是完全删除 c。

let a = 1;
const b = 2;
a = 10;
console.log(a + b);

But if you want to be able to reference c later, this doesn't really work.但是,如果您希望以后能够引用 c,这实际上是行不通的。 Maybe you could thunk c (make it a parameterless function) and invoke it to get a lazy value.也许你可以 thunk c (使其成为无参数函数)并调用它以获得惰性值。

let a = 1;
const b = 2;
const c = () => a + b;
a = 10;
console.log(c());

These sorts of problems have as many solutions as there are stars and they all depends on what you're trying to accomplish.这类问题的解决方案与星星一样多,它们都取决于您要完成的工作。 There's no silver bullet.没有灵丹妙药。


A Possible Solution一个可能的解决方案

You're using RxJS, so I think it fits to lean on some of that machinery in order to accomplish your goal.您正在使用 RxJS,所以我认为它适合依靠其中一些机器来实现您的目标。

Instead of having list$ be an api observable, why not subscribe your list$ to that observable somehow instead.与其让list$成为 api 可观察对象,不如让您的list$以某种方式订阅该可观察对象。 Of course, to do that, you'd need list$ to be both an observable (stream of value) and an observer.当然,要做到这一点,您需要list$既是可观察的(价值流)又是观察者。

Fortunately, RxJS comes with a few constructs that do this already.幸运的是,RxJS 带有一些已经做到这一点的结构。 You can read up a bit on Subjects, as I'll use a ReplaySubject here (but perhaps a Subject or BehaviourSubject is better suited to the job. Depends on your needs).您可以阅读一些关于主题的内容,因为我将在这里使用 ReplaySubject(但也许主题或 BehaviourSubject 更适合这项工作。取决于您的需要)。

// list$ is an observable **and** and observer
const list$ = new ReplaySubject();

// Tell list to observe the api
this.api.getAll().subscribe(this.list$);

// Keep this as-is
filteredList$ = combineLatest(this.list$, this.form.valueChanges())

// To update
combineLatest(this.list$, this.api.update(listElement)).pipe(
  take(1),
  map(([list, updatedElmt]) => list.map(listElmt => 
    listElmt._id === updatedElmt._id ? updatedElmt : listElmt
  ))
).subscribe(this.list$);

This isn't the cleanest possible solution since you need to re-create your update logic each time an update comes in, but it's still relatively close to what you had before.这不是最简洁的解决方案,因为您需要在每次更新时重新创建更新逻辑,但它仍然相对接近您之前的内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM