@rx-angular/template
Version:
**Fully** Reactive Component Template Rendering in Angular. @rx-angular/template aims to be a reflection of Angular's built in renderings just reactive.
1 lines • 12.6 kB
Source Map (JSON)
{"version":3,"file":"template-push.mjs","sources":["../../../../libs/template/push/src/lib/push.pipe.ts","../../../../libs/template/push/src/template-push.ts"],"sourcesContent":["import {\n ChangeDetectorRef,\n inject,\n NgZone,\n OnDestroy,\n Pipe,\n PipeTransform,\n untracked,\n} from '@angular/core';\nimport {\n createTemplateNotifier,\n RxNotification,\n RxNotificationKind,\n} from '@rx-angular/cdk/notifications';\nimport {\n RxStrategyNames,\n RxStrategyProvider,\n strategyHandling,\n} from '@rx-angular/cdk/render-strategies';\nimport {\n MonoTypeOperatorFunction,\n NextObserver,\n Observable,\n ObservableInput,\n OperatorFunction,\n Subscription,\n Unsubscribable,\n} from 'rxjs';\nimport {\n filter,\n shareReplay,\n skip,\n switchMap,\n tap,\n withLatestFrom,\n} from 'rxjs/operators';\n\n/**\n * @Pipe RxPush\n *\n * @description\n *\n * The push pipe serves as a drop-in replacement for angulars built-in async pipe.\n * Just like the *rxLet Directive, it leverages a\n * [RenderStrategy](https://rx-angular.io/docs/cdk/render-strategies)\n * under the hood which takes care of optimizing the ChangeDetection of your component. The rendering behavior can be\n * configured per RxPush instance using either a strategy name or provide a\n * `RxComponentInput` config.\n *\n * Usage in the template\n *\n * ```html\n * <hero-search [term]=\"searchTerm$ | push\"> </hero-search>\n * <hero-list-component [heroes]=\"heroes$ | push\"> </hero-list-component>\n * ```\n *\n * Using different strategies\n *\n * ```html\n * <hero-search [term]=\"searchTerm$ | push: 'immediate'\"> </hero-search>\n * <hero-list-component [heroes]=\"heroes$ | push: 'normal'\"> </hero-list-component>\n * ```\n *\n * Provide a config object\n *\n * ```html\n * <hero-search [term]=\"searchTerm$ | push: { strategy: 'immediate' }\"> </hero-search>\n * <hero-list-component [heroes]=\"heroes$ | push: { strategy: 'normal' }\"> </hero-list-component>\n * ```\n *\n * Other Features:\n *\n * - lazy rendering (see\n * [LetDirective](https://github.com/rx-angular/rx-angular/tree/main/libs/template/docs/api/let-directive.md))\n * - Take observables or promises, retrieve their values and render the value to the template\n * - a unified/structured way of handling null, undefined or error\n * - distinct same values in a row skip not needed re-renderings\n *\n * @usageNotes\n *\n * ```html\n * {{observable$ | push}}\n * <ng-container *ngIf=\"observable$ | push as o\">{{o}}</ng-container>\n * <component [value]=\"observable$ | push\"></component>\n * ```\n *\n * @publicApi\n */\n@Pipe({ name: 'push', pure: false, standalone: true })\nexport class RxPush implements PipeTransform, OnDestroy {\n /** @internal */\n private strategyProvider = inject(RxStrategyProvider);\n /** @internal */\n private ngZone = inject(NgZone);\n /**\n * @internal\n * This is typed as `any` because the type cannot be inferred\n * without a class-level generic argument, which was removed to\n * fix https://github.com/rx-angular/rx-angular/pull/684\n */\n private renderedValue: any | null | undefined;\n /** @internal */\n private subscription: Unsubscribable;\n /** @internal */\n private readonly templateObserver = createTemplateNotifier<any>();\n private readonly templateValues$ = this.templateObserver.values$.pipe(\n onlyValues(),\n shareReplay({ bufferSize: 1, refCount: true })\n );\n /** @internal */\n private readonly strategyHandler = strategyHandling(\n this.strategyProvider.primaryStrategy,\n this.strategyProvider.strategies\n );\n /** @internal */\n private patchZone: false | NgZone;\n /** @internal */\n private _renderCallback: NextObserver<any>;\n\n constructor(private cdRef: ChangeDetectorRef) {}\n\n transform<U>(\n potentialObservable: null,\n config?: RxStrategyNames | Observable<RxStrategyNames>,\n renderCallback?: NextObserver<U>\n ): null;\n transform<U>(\n potentialObservable: undefined,\n config?: RxStrategyNames | Observable<RxStrategyNames>,\n renderCallback?: NextObserver<U>\n ): undefined;\n transform<U>(\n potentialObservable: ObservableInput<U> | U,\n config?: RxStrategyNames | Observable<RxStrategyNames>,\n renderCallback?: NextObserver<U>\n ): U;\n transform<U>(\n potentialObservable: ObservableInput<U>,\n config?: PushInput<U>\n ): U;\n transform<U>(\n potentialObservable: ObservableInput<U> | U | null | undefined,\n config:\n | PushInput<U>\n | RxStrategyNames\n | Observable<RxStrategyNames>\n | undefined,\n renderCallback?: NextObserver<U>\n ): U | null | undefined {\n this._renderCallback = renderCallback;\n if (config) {\n if (isRxComponentInput(config)) {\n this.strategyHandler.next(config.strategy as string);\n this._renderCallback = config.renderCallback;\n // set fallback if patchZone is not set\n this.setPatchZone(config.patchZone);\n } else {\n this.strategyHandler.next(config as string);\n }\n }\n this.templateObserver.next(potentialObservable);\n if (!this.subscription) {\n this.subscription = this.handleChangeDetection();\n }\n return this.renderedValue as U;\n }\n\n /** @internal */\n ngOnDestroy(): void {\n untracked(() => this.subscription?.unsubscribe());\n }\n\n /** @internal */\n private setPatchZone(patch?: boolean): void {\n const doPatch =\n patch == null ? this.strategyProvider.config.patchZone : patch;\n this.patchZone = doPatch ? this.ngZone : false;\n }\n\n /** @internal */\n private handleChangeDetection(): Unsubscribable {\n const scope = (this.cdRef as any).context;\n const sub = new Subscription();\n\n // Subscription can be side-effectful, and we don't want any signal reads which happen in the\n // side effect of the subscription to be tracked by a component's template when that\n // subscription is triggered via the async pipe. So we wrap the subscription in `untracked` to\n // decouple from the current reactive context.\n //\n // `untracked` also prevents signal _writes_ which happen in the subscription side effect from\n // being treated as signal writes during the template evaluation (which throws errors).\n const setRenderedValue = untracked(() =>\n this.templateValues$.subscribe(({ value }) => {\n this.renderedValue = value;\n })\n );\n const render = untracked(() =>\n this.hasInitialValue(this.templateValues$)\n .pipe(\n switchMap((isSync) =>\n this.templateValues$.pipe(\n // skip ticking change detection\n // in case we have an initial value, we don't need to perform cd\n // the variable will be evaluated anyway because of the lifecycle\n skip(isSync ? 1 : 0),\n // onlyValues(),\n this.render(scope),\n tap((v) => {\n this._renderCallback?.next(v);\n })\n )\n )\n )\n .subscribe()\n );\n sub.add(setRenderedValue);\n sub.add(render);\n return sub;\n }\n\n /** @internal */\n private render<T>(scope: object): OperatorFunction<RxNotification<T>, T> {\n return (o$) =>\n o$.pipe(\n withLatestFrom(this.strategyHandler.strategy$),\n switchMap(([notification, strategy]) =>\n this.strategyProvider.schedule(\n () => {\n strategy.work(this.cdRef, scope);\n return notification.value;\n },\n {\n scope,\n strategy: strategy.name,\n patchZone: this.patchZone,\n }\n )\n )\n );\n }\n\n /** @internal */\n private hasInitialValue(value$: Observable<unknown>): Observable<boolean> {\n return new Observable<boolean>((subscriber) => {\n let hasInitialValue = false;\n const inner = value$.subscribe(() => {\n hasInitialValue = true;\n });\n inner.unsubscribe();\n subscriber.next(hasInitialValue);\n subscriber.complete();\n });\n }\n}\n\ninterface PushInput<T> {\n strategy?: RxStrategyNames | Observable<RxStrategyNames>;\n renderCallback?: NextObserver<T>;\n patchZone?: boolean;\n}\n\n// https://eslint.org/docs/rules/no-prototype-builtins\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction onlyValues<T>(): MonoTypeOperatorFunction<RxNotification<T>> {\n return (o$) =>\n o$.pipe(\n filter(\n (n) =>\n n.kind === RxNotificationKind.Suspense ||\n n.kind === RxNotificationKind.Next\n )\n );\n}\n\nfunction isRxComponentInput<U>(value: any): value is PushInput<U> {\n return (\n value != null &&\n (hasOwnProperty.call(value, 'strategy') ||\n hasOwnProperty.call(value, 'renderCallback') ||\n hasOwnProperty.call(value, 'patchZone'))\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AAqCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDG;MAEU,MAAM,CAAA;AA8BjB,IAAA,WAAA,CAAoB,KAAwB,EAAA;QAAxB,IAAK,CAAA,KAAA,GAAL,KAAK;;AA5BjB,QAAA,IAAA,CAAA,gBAAgB,GAAG,MAAM,CAAC,kBAAkB,CAAC;;AAE7C,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;;QAWd,IAAgB,CAAA,gBAAA,GAAG,sBAAsB,EAAO;QAChD,IAAe,CAAA,eAAA,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CACnE,UAAU,EAAE,EACZ,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAC/C;;AAEgB,QAAA,IAAA,CAAA,eAAe,GAAG,gBAAgB,CACjD,IAAI,CAAC,gBAAgB,CAAC,eAAe,EACrC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CACjC;;AA2BD,IAAA,SAAS,CACP,mBAA8D,EAC9D,MAIa,EACb,cAAgC,EAAA;AAEhC,QAAA,IAAI,CAAC,eAAe,GAAG,cAAc;QACrC,IAAI,MAAM,EAAE;AACV,YAAA,IAAI,kBAAkB,CAAC,MAAM,CAAC,EAAE;gBAC9B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,QAAkB,CAAC;AACpD,gBAAA,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,cAAc;;AAE5C,gBAAA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC;;iBAC9B;AACL,gBAAA,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAgB,CAAC;;;AAG/C,QAAA,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,mBAAmB,CAAC;AAC/C,QAAA,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;AACtB,YAAA,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,qBAAqB,EAAE;;QAElD,OAAO,IAAI,CAAC,aAAkB;;;IAIhC,WAAW,GAAA;QACT,SAAS,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC;;;AAI3C,IAAA,YAAY,CAAC,KAAe,EAAA;AAClC,QAAA,MAAM,OAAO,GACX,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,SAAS,GAAG,KAAK;AAChE,QAAA,IAAI,CAAC,SAAS,GAAG,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK;;;IAIxC,qBAAqB,GAAA;AAC3B,QAAA,MAAM,KAAK,GAAI,IAAI,CAAC,KAAa,CAAC,OAAO;AACzC,QAAA,MAAM,GAAG,GAAG,IAAI,YAAY,EAAE;;;;;;;;AAS9B,QAAA,MAAM,gBAAgB,GAAG,SAAS,CAAC,MACjC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,KAAI;AAC3C,YAAA,IAAI,CAAC,aAAa,GAAG,KAAK;SAC3B,CAAC,CACH;AACD,QAAA,MAAM,MAAM,GAAG,SAAS,CAAC,MACvB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe;AACtC,aAAA,IAAI,CACH,SAAS,CAAC,CAAC,MAAM,KACf,IAAI,CAAC,eAAe,CAAC,IAAI;;;;QAIvB,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;;QAEpB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAClB,GAAG,CAAC,CAAC,CAAC,KAAI;AACR,YAAA,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC,CAAC;SAC9B,CAAC,CACH,CACF;aAEF,SAAS,EAAE,CACf;AACD,QAAA,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC;AACzB,QAAA,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;AACf,QAAA,OAAO,GAAG;;;AAIJ,IAAA,MAAM,CAAI,KAAa,EAAA;AAC7B,QAAA,OAAO,CAAC,EAAE,KACR,EAAE,CAAC,IAAI,CACL,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAC9C,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,KACjC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAC5B,MAAK;YACH,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC;YAChC,OAAO,YAAY,CAAC,KAAK;AAC3B,SAAC,EACD;YACE,KAAK;YACL,QAAQ,EAAE,QAAQ,CAAC,IAAI;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CACF,CACF,CACF;;;AAIG,IAAA,eAAe,CAAC,MAA2B,EAAA;AACjD,QAAA,OAAO,IAAI,UAAU,CAAU,CAAC,UAAU,KAAI;YAC5C,IAAI,eAAe,GAAG,KAAK;AAC3B,YAAA,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,MAAK;gBAClC,eAAe,GAAG,IAAI;AACxB,aAAC,CAAC;YACF,KAAK,CAAC,WAAW,EAAE;AACnB,YAAA,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC;YAChC,UAAU,CAAC,QAAQ,EAAE;AACvB,SAAC,CAAC;;iIAlKO,MAAM,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,IAAA,EAAA,CAAA,CAAA;+HAAN,MAAM,EAAA,YAAA,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,KAAA,EAAA,CAAA,CAAA;;2FAAN,MAAM,EAAA,UAAA,EAAA,CAAA;kBADlB,IAAI;mBAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;;AA6KrD;AACA,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc;AAEtD,SAAS,UAAU,GAAA;AACjB,IAAA,OAAO,CAAC,EAAE,KACR,EAAE,CAAC,IAAI,CACL,MAAM,CACJ,CAAC,CAAC,KACA,CAAC,CAAC,IAAI,KAAgC,UAAA;AACtC,QAAA,CAAC,CAAC,IAAI,KAA4B,MAAA,+BACrC,CACF;AACL;AAEA,SAAS,kBAAkB,CAAI,KAAU,EAAA;IACvC,QACE,KAAK,IAAI,IAAI;AACb,SAAC,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC;AACrC,YAAA,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,gBAAgB,CAAC;YAC5C,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;AAE9C;;AC1RA;;AAEG;;;;"}