Angular: *NGIF使用与异步管的con缩示例失败

发布于 2025-02-06 08:42:45 字数 1809 浏览 2 评论 0原文

如果我有一个可观察的随着时间的推移释放值:

values$ = from([1, 2, 3, 'done'])
  .pipe(
    concatMap((x) => of(x).pipe(delay(1000)))
  );

并且我有一个函数返回对该可观察的

getOutputs(): Observable<'done' | number> {
    return this.values$;
  }

并通过函数订阅observable在模板中使用*NGIFasync

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

预期行为:浏览器显示'Hello 1!','Hello 2!','Hello 2!','Hello 3!','您好!',大约一秒钟的间隔。

相反,如果我将最新值存储在caping ubyubject中,并通过该cravive umagiSubjectngoninit中循环所有值:

outputs$ = new BehaviorSubject<number | 'done' | null>(null);
ngOnInit(): void {
    this.subscriptions.add(
      from<[number, number, number, 'done']>([
        1,
        2,
        3,
        'done',
      ]).subscribe((val) => this.outputs$.next(val))
    );
  }

当然,该行为是不同:这些值全部都发送到cistionUbject输出$ .VALUE非常快地变为“完成”。因此,以后出现的任何事情都只会“完成”。也期望。

'Hello hello!

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$;
  }

如果我更改getOutputs()以使用this.outputs $而不是 >较早使用的是:

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$
    .pipe(
      concatMap((x) => of(x).pipe(delay(1000)))
    );
  }

“完成”一遍又一遍地发送,一秒钟(可以通过tap(Console.log))看到,但是模板什么也没显示。这是出乎意料的:我认为HTML会显示“你好!”。

为什么会发生这种情况?

参见这个stackblitz

If I have an Observable releasing values over time:

values$ = from([1, 2, 3, 'done'])
  .pipe(
    concatMap((x) => of(x).pipe(delay(1000)))
  );

And I have a function returning access to that Observable:

getOutputs(): Observable<'done' | number> {
    return this.values$;
  }

And subscribe to the Observable through a function in the template using *ngIf and async:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

The behavior is expected: the browser shows 'Hello 1!', 'Hello 2!', 'Hello 3!', 'Hello done!', with an interval for each of about a second.

If, instead, I store the latest value in a BehaviorSubject and cycle all of the values through that BehaviorSubject in ngOnInit:

outputs$ = new BehaviorSubject<number | 'done' | null>(null);
ngOnInit(): void {
    this.subscriptions.add(
      from<[number, number, number, 'done']>([
        1,
        2,
        3,
        'done',
      ]).subscribe((val) => this.outputs$.next(val))
    );
  }

The behavior is of course different: the values are all sent to the BehaviorSubject, and outputs$.value becomes 'done' very quickly. So anything coming along later and subscribing would only get 'done'. Also expected.

If I change getOutputs() to use this.outputs$ instead, I just get 'Hello done!':

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$;
  }

But if I add the same concatMap used earlier, like this:

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$
    .pipe(
      concatMap((x) => of(x).pipe(delay(1000)))
    );
  }

'done' gets sent over and over, once a second (which can be seen through tap(console.log)), but the template shows nothing. This is unexpected: I would think that the HTML would show 'Hello done!'.

Why is this happening?

See this Stackblitz.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

好倦 2025-02-13 08:42:45

Tl; dr

这是由角度处理变化检测方式引起的。

Angular将定期检查您的视图是否与模型中的数据有关,实际上每秒都连续调用您的getOuputs()方法!


简而言之,Angular的更改检测

考虑您的app.component.html模板:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

在这里,Angular将定期重新评估getOutputs()|异步,“几次”,直到您的应用程序处于稳定状态。

但是,对于每次评估,您都将返回新的,唯一的 可观察到的,因为您 create getOutputs方法中:

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
     concatMap(x => of(x).pipe(delay(1000))),
  ); // it's not `this.outputs

Tl; dr

这是由角度处理变化检测方式引起的。

Angular将定期检查您的视图是否与模型中的数据有关,实际上每秒都连续调用您的 getOuputs()方法!


简而言之,Angular的更改检测

考虑您的 app.component.html 模板:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

在这里,Angular将定期重新评估 getOutputs()|异步,“几次”,直到您的应用程序处于稳定状态。

但是,对于每次评估,您都将返回新的,唯一的 可观察到的,因为您 create getOutputs 方法中:

, it's a **new Observable** }

的在您

 export class AppComponent implements OnInit, OnDestroy {
   private subscriptions = new Subscription();
   private outputs$ = new BehaviorSubject</* ... */>(null);
+  private actualOutputs$ = this.outputs$.pipe(
+    concatMap(x => of(x).pipe(delay(1000))),
+  );

   public getOutputs(): Observable</* ... */> {
+    return this.actualOutputs$;
-    return this.outputs$.pipe(
-       concatMap(x => of(x).pipe(delay(1000))),
-    );
   }
 }

可观察 ,对此感到抱歉...)

...那么您的申请将完全按照您的期望!


还有更多的探索

,但是为什么要删除延迟,但每次都会产生不同的可观察到的呢?

让我们考虑getOutputs下面的替代实现:

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
    concatMap(x => of(x)/* .pipe(delay(1000)) */), // no more delay here
  ); // that's a **new** Observable every time.
}

这是因为我(故意

TL;DR

This is caused by how Angular handles change detection.

Angular will regularly check that your view is up-to-date with the data in your model, and is in fact continuously calling your getOuputs() method, once every second!


Angular's Change Detection in a nutshell

Consider your app.component.html template:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

Here, Angular will regularly re-evaluate getOutputs() | async, a "couple of times", until your application is in a stable state.

However, for each evaluation, you are returning a new, unique Observable, because you create that new, unique Observable in your getOutputs method:

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
     concatMap(x => of(x).pipe(delay(1000))),
  ); // it's not `this.outputs

TL;DR

This is caused by how Angular handles change detection.

Angular will regularly check that your view is up-to-date with the data in your model, and is in fact continuously calling your getOuputs() method, once every second!


Angular's Change Detection in a nutshell

Consider your app.component.html template:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

Here, Angular will regularly re-evaluate getOutputs() | async, a "couple of times", until your application is in a stable state.

However, for each evaluation, you are returning a new, unique Observable, because you create that new, unique Observable in your getOutputs method:

, it's a **new Observable** }

Therefore, if you where to create another member like that:

 export class AppComponent implements OnInit, OnDestroy {
   private subscriptions = new Subscription();
   private outputs$ = new BehaviorSubject</* ... */>(null);
+  private actualOutputs$ = this.outputs$.pipe(
+    concatMap(x => of(x).pipe(delay(1000))),
+  );

   public getOutputs(): Observable</* ... */> {
+    return this.actualOutputs$;
-    return this.outputs$.pipe(
-       concatMap(x => of(x).pipe(delay(1000))),
-    );
   }
 }

(welp, StackOverflow doesn't support diff syntax highlighting, sorry about that...)

... then your application would behave exactly as you expect!


Some more exploration

But then why does removing the delay, yet yielding a different Observable every time also works?

Let's consider the alternative implementation for getOutputs below:

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
    concatMap(x => of(x)/* .pipe(delay(1000)) */), // no more delay here
  ); // that's a **new** Observable every time.
}

It's because I (purposely ????) overlooked something earlier:
Angular isn't simply re-evaluating getOutputs() until your component is stable, but actually getOutputs() | async, which translates roughly as async(getOutput()) in a pure TypeScript world.

What is happening here is also somewhat simple:
Contrary to the previous case, where the AsyncPipe, when having to wait for a delayed Observble, won't be definitely stable; that AsyncPipe yields a value immediately and yields the same ('done') every time; Angular considers your component "stable" and proceeds with updating the view.

If you debug the code attentively (or drop a console.log at the top of getOutputs()'s body), you can notice that getOutputs() is then still called 4 times in rapid succession, one for each evaluation of async(getOutputs()).

Switching to a more conservative changeDetection strategy for your component as follows:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush, // different strategy here
})
export class AppComponent implements OnInit, OnDestroy {

... would allow getOutputs() to only be executed once in that case!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文