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