8000 fix(template): untrack subscription and unsubscription in push pipe · rx-angular/rx-angular@fbdbf5b · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

8000
Appearance settings

Commit fbdbf5b

Browse files
committed
fix(template): untrack subscription and unsubscription in push pipe
1 parent 944a489 commit fbdbf5b

File tree

1 file changed

+32
-19
lines changed

1 file changed

+32
-19
lines changed

libs/template/push/src/lib/push.pipe.ts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
OnDestroy,
55
Pipe,
66
PipeTransform,
7+
untracked,
78
} from '@angular/core';
89
import {
910
RxStrategyNames,
@@ -165,7 +166,7 @@ export class RxPush implements PipeTransform, OnDestroy {
165166

166167
/** @internal */
167168
ngOnDestroy(): void {
168-
this.subscription?.unsubscribe();
169+
untracked(() => this.subscription?.unsubscribe());
169170
}
170171

171172
/** @internal */
@@ -179,26 +180,38 @@ export class RxPush implements PipeTransform, OnDestroy {
179180
private handleChangeDetection(): Unsubscribable {
180181
const scope = (this.cdRef as any).context;
181182
const sub = new Subscription();
182-
const setRenderedValue = this.templateValues$.subscribe(({ value }) => {
183-
this.renderedValue = value;
184-
});
185-
const render = this.hasInitialValue(this.templateValues$)
186-
.pipe(
187-
switchMap((isSync) =>
188-
this.templateValues$.pipe(
189-
// skip ticking change detection
190-
// in case we have an initial value, we don't need to perform cd
191-
// the variable will be evaluated anyway because of the lifecycle
192-
skip(isSync ? 1 : 0),
193-
// onlyValues(),
194-
this.render(scope),
195-
tap((v) => {
196-
this._renderCallback?.next(v);
197-
})
183+
184+
// Subscription can be side-effectful, and we don't want any signal reads which happen in the
185+
// side effect of the subscription to be tracked by a component's template when that
186+
// subscription is triggered via the async pipe. So we wrap the subscription in `untracked` to
187+
// decouple from the current reactive context.
188+
//
189+
// `untracked` also prevents signal _writes_ which happen in the subscription side effect from
190+
// being treated as signal writes during the template evaluation (which throws errors).
191+
const setRenderedValue = untracked(() =>
192+
this.templateValues$.subscribe(({ value }) => {
193+
this.renderedValue = value;
194+
})
195+
);
196+
const render = untracked(() =>
197+
this.hasInitialValue(this.templateValues$)
198+
.pipe(
199+
switchMap((isSync) =>
200+
this.templateValues$.pipe(
201+
// skip ticking change detection
202+
// in case we have an initial value, we don't need to perform cd
203+
// the variable will be evaluated anyway because of the lifecycle
204+
skip(isSync ? 1 : 0),
205+
// onlyValues(),
206+
this.render(scope),
207+
tap((v) => {
208+
this._renderCallback?.next(v);
209+
})
210+
)
198211
)
199212
)
200-
)
201-
.subscribe();
213+
.subscribe()
214+
);
202215
sub.add(setRenderedValue);
203216
sub.add(render);
204217
return sub;

0 commit comments

Comments
 (0)
0