UNPKG

@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.

706 lines (695 loc) 30 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, Injectable, ElementRef, DestroyRef, input, booleanAttribute, signal, computed, Directive, ViewContainerRef } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { RxStrategyProvider } from '@rx-angular/cdk/render-strategies'; import { ReplaySubject, NEVER, BehaviorSubject, combineLatest } from 'rxjs'; import { distinctUntilChanged, switchMap, map, tap, finalize } from 'rxjs/operators'; const VIRTUAL_VIEW_CONFIG_TOKEN = new InjectionToken('VIRTUAL_VIEW_CONFIG_TOKEN', { providedIn: 'root', factory: () => VIRTUAL_VIEW_CONFIG_DEFAULT, }); const VIRTUAL_VIEW_CONFIG_DEFAULT = { keepLastKnownSize: false, useContentVisibility: false, useContainment: true, placeholderStrategy: 'low', contentStrategy: 'normal', startWithPlaceholderAsap: false, cacheEnabled: true, cache: { contentCacheSize: 20, placeholderCacheSize: 20, }, }; /** * Provides a configuration object for the `VirtualView` service. * * Can be used to customize the behavior of the `VirtualView` service. * * Default configuration: * - contentCacheSize: 20 * - placeholderCacheSize: 20 * * Example usage: * * ```ts * import { provideVirtualViewConfig } from '@rx-angular/template/virtual-view'; * * const appConfig: ApplicationConfig = { * providers: [ * provideVirtualViewConfig({ * contentCacheSize: 50, * placeholderCacheSize: 50, * }), * ], * }; * ``` * * @developerPreview * * @param config - The configuration object. * @returns An object that can be provided to the `VirtualView` service. */ function provideVirtualViewConfig(config) { return { provide: VIRTUAL_VIEW_CONFIG_TOKEN, useValue: { ...VIRTUAL_VIEW_CONFIG_DEFAULT, ...config, cache: { ...VIRTUAL_VIEW_CONFIG_DEFAULT.cache, ...(config?.cache ?? {}) }, }, }; } /** * @internal */ class _RxVirtualViewObserver { } /** * @internal */ class _RxVirtualView { } /** * A service that caches templates and placeholders to optimize view rendering. * It makes sure that all cached resources are cleared when the service is destroyed. * * @developerPreview */ class VirtualViewCache { #config = inject(VIRTUAL_VIEW_CONFIG_TOKEN); // Maximum number of content that can be stored in the cache. #contentCacheSize = this.#config.cache.contentCacheSize; // Cache for storing content views, identified by a unique key, which is the directive instance. #contentCache = new Map(); // Maximum number of placeholders that can be stored in the cache. #placeholderCacheSize = this.#config.cache.placeholderCacheSize; // Cache for storing placeholder views, identified by a unique key. #placeholderCache = new Map(); /** * Stores a placeholder view in the cache. When the cache reaches its limit, * the oldest entry is removed. * * @param key - The key used to identify the placeholder in the cache. * @param view - The ViewRef of the placeholder to cache. */ storePlaceholder(key, view) { if (this.#placeholderCacheSize <= 0) { view.destroy(); return; } if (this.#placeholderCache.size >= this.#placeholderCacheSize) { this.#removeOldestEntry(this.#placeholderCache); } this.#placeholderCache.set(key, view); } /** * Retrieves a cached placeholder view using the specified key. * * @param key - The key of the placeholder to retrieve. * @returns The ViewRef of the cached placeholder, or undefined if not found. */ getPlaceholder(key) { const view = this.#placeholderCache.get(key); this.#placeholderCache.delete(key); return view; } /** * Stores a content view in the cache. When the cache reaches its limit, * the oldest entry is removed. * * @param key - The key used to identify the content in the cache. * @param view - The ViewRef of the content to cache. */ storeContent(key, view) { if (this.#contentCacheSize <= 0) { view.destroy(); return; } if (this.#contentCache.size >= this.#contentCacheSize) { this.#removeOldestEntry(this.#contentCache); } this.#contentCache.set(key, view); } /** * Retrieves a cached content view using the specified key. * * @param key - The key of the content to retrieve. * @returns The ViewRef of the cached content, or undefined if not found. */ getContent(key) { const view = this.#contentCache.get(key); this.#contentCache.delete(key); return view; } /** * Clears both content and placeholder caches for a given key. * * @param key - The key of the content and placeholder to remove. */ clear(key) { this.#contentCache.get(key)?.destroy(); this.#contentCache.delete(key); this.#placeholderCache.get(key)?.destroy(); this.#placeholderCache.delete(key); } /** * Clears all cached resources when the service is destroyed. */ ngOnDestroy() { this.#contentCache.forEach((view) => view.destroy()); this.#placeholderCache.forEach((view) => view.destroy()); this.#contentCache.clear(); this.#placeholderCache.clear(); } #removeOldestEntry(cache) { const oldestValue = cache.entries().next().value; if (oldestValue !== undefined) { const [key, view] = oldestValue; view?.destroy(); cache.delete(key); } } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: VirtualViewCache, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: VirtualViewCache }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: VirtualViewCache, decorators: [{ type: Injectable }] }); /** * The RxVirtualView directive is a directive that allows you to create virtual views. * * It can be used on an element/component to create a virtual view. * * It works by using 3 directives: * - `rxVirtualViewContent`: The content to render when the virtual view is visible. * - `rxVirtualViewPlaceholder`: The placeholder to render when the virtual view is not visible. * - `rxVirtualViewObserver`: The directive that observes the virtual view and emits a boolean value indicating whether the virtual view is visible. * * The `rxVirtualViewObserver` directive is mandatory for the `rxVirtualView` directive to work. * And it needs to be a sibling of the `rxVirtualView` directive. * * @example * ```html * <div rxVirtualViewObserver> * <div rxVirtualView> * <div *rxVirtualViewContent>Virtual View 1</div> * <div *rxVirtualViewPlaceholder>Loading...</div> * </div> * </div> * ``` * * @developerPreview */ class RxVirtualView { #observer; #elementRef; #strategyProvider; #viewCache; #destroyRef; #config; #content; #placeholder; #placeholderVisible; #contentIsShown; #visible$; constructor() { this.#observer = inject(_RxVirtualViewObserver, { optional: true }); this.#elementRef = inject(ElementRef); this.#strategyProvider = inject(RxStrategyProvider); this.#viewCache = inject(VirtualViewCache, { optional: true }); this.#destroyRef = inject(DestroyRef); this.#config = inject(VIRTUAL_VIEW_CONFIG_TOKEN); this.#content = null; this.#placeholder = null; /** * Useful when we want to cache the templates and placeholders to optimize view rendering. * * Enabled by default. */ this.cacheEnabled = input(this.#config.cacheEnabled, { transform: booleanAttribute, }); /** * Whether to start with the placeholder asap or not. * * If `true`, the placeholder will be rendered immediately, without waiting for the content to be visible. * This is useful when you want to render the placeholder immediately, but you don't want to wait for the content to be visible. * * This is to counter concurrent rendering, and to avoid flickering. */ this.startWithPlaceholderAsap = input(this.#config.startWithPlaceholderAsap, { transform: booleanAttribute, }); /** * This will keep the last known size of the host element while the content is visible. */ this.keepLastKnownSize = input(this.#config.keepLastKnownSize, { transform: booleanAttribute, }); /** * Whether to use content visibility or not. * * It will add the `content-visibility` CSS class to the host element, together with * `contain-intrinsic-width` and `contain-intrinsic-height` CSS properties. */ this.useContentVisibility = input(this.#config.useContentVisibility, { transform: booleanAttribute, }); /** * Whether to use containment or not. * * It will add `contain` css property with: * - `size layout paint`: if `useContentVisibility` is `true` && placeholder is visible * - `content`: if `useContentVisibility` is `false` || content is visible */ this.useContainment = input(this.#config.useContainment, { transform: booleanAttribute, }); /** * The strategy to use for rendering the placeholder. */ this.placeholderStrategy = input(this.#config.placeholderStrategy); /** * The strategy to use for rendering the content. */ this.contentStrategy = input(this.#config.contentStrategy); /** * A function extracting width & height from a ResizeObserverEntry */ this.extractSize = input(defaultExtractSize); /** * ResizeObserverOptions */ this.resizeObserverOptions = input(); this.#placeholderVisible = signal(false); this.#contentIsShown = false; this.#visible$ = new ReplaySubject(1); this.size = signal({ width: 0, height: 0 }); this.width = computed(() => this.size().width ? `${this.size().width}px` : 'auto'); this.height = computed(() => this.size().height ? `${this.size().height}px` : 'auto'); this.containment = computed(() => { if (!this.useContainment()) { return null; } return this.useContentVisibility() && this.#placeholderVisible() ? 'size layout paint' : 'content'; }); this.intrinsicWidth = computed(() => { if (!this.useContentVisibility()) { return null; } return this.width() === 'auto' ? 'auto' : `auto ${this.width()}`; }); this.intrinsicHeight = computed(() => { if (!this.useContentVisibility()) { return null; } return this.height() === 'auto' ? 'auto' : `auto ${this.height()}`; }); this.minHeight = computed(() => { return this.keepLastKnownSize() && this.#placeholderVisible() ? this.height() : null; }); this.minWidth = computed(() => { return this.keepLastKnownSize() && this.#placeholderVisible() ? this.width() : null; }); if (!this.#observer) { throw new Error('RxVirtualView expects you to provide a RxVirtualViewObserver'); } } ngAfterContentInit() { if (!this.#content) { throw new Error('RxVirtualView expects you to provide a RxVirtualViewContent'); } if (this.startWithPlaceholderAsap()) { this.renderPlaceholder(); } this.#observer ?.observeElementVisibility(this.#elementRef.nativeElement) .pipe(takeUntilDestroyed(this.#destroyRef)) .subscribe((visible) => this.#visible$.next(visible)); this.#visible$ .pipe(distinctUntilChanged(), switchMap((visible) => { if (visible) { return this.#contentIsShown ? NEVER : this.showContent$().pipe(switchMap((view) => { const resize$ = this.#observer.observeElementSize(this.#elementRef.nativeElement, this.resizeObserverOptions()); view.detectChanges(); return resize$; }), map(this.extractSize()), tap(({ width, height }) => this.size.set({ width, height }))); } return this.#placeholderVisible() ? NEVER : this.showPlaceholder$(); }), finalize(() => { this.#viewCache.clear(this); }), takeUntilDestroyed(this.#destroyRef)) .subscribe(); } ngOnDestroy() { this.#content = null; this.#placeholder = null; } registerContent(content) { this.#content = content; } registerPlaceholder(placeholder) { this.#placeholder = placeholder; } /** * Shows the content using the configured rendering strategy (by default: normal). * @private */ showContent$() { return this.#strategyProvider.schedule(() => { this.#contentIsShown = true; this.#placeholderVisible.set(false); const placeHolder = this.#content.viewContainerRef.detach(); if (this.cacheEnabled() && placeHolder) { this.#viewCache.storePlaceholder(this, placeHolder); } else if (!this.cacheEnabled() && placeHolder) { placeHolder.destroy(); } const tmpl = this.#viewCache.getContent(this) ?? this.#content.templateRef.createEmbeddedView({}); this.#content.viewContainerRef.insert(tmpl); placeHolder?.detectChanges(); return tmpl; }, { scope: this, strategy: this.contentStrategy() }); } /** * Shows the placeholder using the configured rendering strategy (by default: low). * @private */ showPlaceholder$() { return this.#strategyProvider.schedule(() => this.renderPlaceholder(), { scope: this, strategy: this.placeholderStrategy(), }); } /** * Renders a placeholder within the view container, and hides the content. * * If we already have a content and cache enabled, we store the content in * the cache, so we can reuse it later. * * When we want to render the placeholder, we try to get it from the cache, * and if it is not available, we create a new one. * * Then insert the placeholder into the view container and trigger a CD. */ renderPlaceholder() { this.#placeholderVisible.set(true); this.#contentIsShown = false; const content = this.#content.viewContainerRef.detach(); if (content) { if (this.cacheEnabled()) { this.#viewCache.storeContent(this, content); } else { content.destroy(); } content?.detectChanges(); } if (this.#placeholder) { const placeholderRef = this.#viewCache.getPlaceholder(this) ?? this.#placeholder.templateRef.createEmbeddedView({}); this.#content.viewContainerRef.insert(placeholderRef); placeholderRef.detectChanges(); } } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualView, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.0", type: RxVirtualView, isStandalone: true, selector: "[rxVirtualView]", inputs: { cacheEnabled: { classPropertyName: "cacheEnabled", publicName: "cacheEnabled", isSignal: true, isRequired: false, transformFunction: null }, startWithPlaceholderAsap: { classPropertyName: "startWithPlaceholderAsap", publicName: "startWithPlaceholderAsap", isSignal: true, isRequired: false, transformFunction: null }, keepLastKnownSize: { classPropertyName: "keepLastKnownSize", publicName: "keepLastKnownSize", isSignal: true, isRequired: false, transformFunction: null }, useContentVisibility: { classPropertyName: "useContentVisibility", publicName: "useContentVisibility", isSignal: true, isRequired: false, transformFunction: null }, useContainment: { classPropertyName: "useContainment", publicName: "useContainment", isSignal: true, isRequired: false, transformFunction: null }, placeholderStrategy: { classPropertyName: "placeholderStrategy", publicName: "placeholderStrategy", isSignal: true, isRequired: false, transformFunction: null }, contentStrategy: { classPropertyName: "contentStrategy", publicName: "contentStrategy", isSignal: true, isRequired: false, transformFunction: null }, extractSize: { classPropertyName: "extractSize", publicName: "extractSize", isSignal: true, isRequired: false, transformFunction: null }, resizeObserverOptions: { classPropertyName: "resizeObserverOptions", publicName: "resizeObserverOptions", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--rx-vw-h": "height()", "style.--rx-vw-w": "width()", "style.min-height": "minHeight()", "style.min-width": "minWidth()", "style.contain": "containment()", "style.contain-intrinsic-width": "intrinsicWidth()", "style.contain-intrinsic-height": "intrinsicHeight()", "style.content-visibility": "useContentVisibility() ? \"auto\" : null" } }, providers: [{ provide: _RxVirtualView, useExisting: RxVirtualView }], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualView, decorators: [{ type: Directive, args: [{ selector: '[rxVirtualView]', host: { '[style.--rx-vw-h]': 'height()', '[style.--rx-vw-w]': 'width()', '[style.min-height]': 'minHeight()', '[style.min-width]': 'minWidth()', '[style.contain]': 'containment()', '[style.contain-intrinsic-width]': 'intrinsicWidth()', '[style.contain-intrinsic-height]': 'intrinsicHeight()', '[style.content-visibility]': 'useContentVisibility() ? "auto" : null', }, providers: [{ provide: _RxVirtualView, useExisting: RxVirtualView }], }] }], ctorParameters: () => [] }); const defaultExtractSize = (entry) => ({ width: entry.borderBoxSize[0].inlineSize, height: entry.borderBoxSize[0].blockSize, }); /** * The RxVirtualViewTemplate directive is a directive that allows you to create a content template for the virtual view. * * It can be used on an element/component to create a content template for the virtual view. * * It needs to be a sibling of the `rxVirtualView` directive. * * @example * ```html * <div rxVirtualViewObserver> * <div rxVirtualView> * <div *rxVirtualViewContent>Virtual View 1</div> * <div *rxVirtualViewPlaceholder>Loading...</div> * </div> * </div> * ``` * * @developerPreview */ class RxVirtualViewContent { #virtualView; constructor(templateRef) { this.templateRef = templateRef; this.#virtualView = inject(RxVirtualView); this.viewContainerRef = inject(ViewContainerRef); this.#virtualView.registerContent(this); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewContent, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: RxVirtualViewContent, isStandalone: true, selector: "[rxVirtualViewContent]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewContent, decorators: [{ type: Directive, args: [{ selector: '[rxVirtualViewContent]', standalone: true }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); /** * A service that observes the resize of the elements. * * @developerPreview */ class RxaResizeObserver { #destroyRef = inject(DestroyRef); #resizeObserver = new ResizeObserver((entries) => { entries.forEach((entry) => { if (this.#elements.has(entry.target)) this.#elements.get(entry.target).next(entry); }); }); /** @internal */ #elements = new Map(); constructor() { this.#destroyRef.onDestroy(() => { this.#elements.clear(); this.#resizeObserver.disconnect(); }); } observeElement(element, options) { const resizeEvent$ = new ReplaySubject(1); this.#elements.set(element, resizeEvent$); this.#resizeObserver.observe(element, options); return resizeEvent$.pipe(distinctUntilChanged(), finalize(() => { this.#resizeObserver.unobserve(element); this.#elements.delete(element); })); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxaResizeObserver, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxaResizeObserver }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxaResizeObserver, decorators: [{ type: Injectable }], ctorParameters: () => [] }); /** * The RxVirtualViewObserver directive observes the virtual view and emits a boolean value indicating whether the virtual view is visible. * This is the container for the RxVirtualView directives. * * This is a mandatory directive for the RxVirtualView directives to work. * * @example * ```html * <div rxVirtualViewObserver> * <div rxVirtualView> * <div *rxVirtualViewContent>Virtual View 1</div> * <div *rxVirtualViewPlaceholder>Loading...</div> * </div> * </div> * ``` * * @developerPreview */ class RxVirtualViewObserver extends _RxVirtualViewObserver { constructor() { super(...arguments); this.#elementRef = inject(ElementRef); this.#observer = null; this.#resizeObserver = inject(RxaResizeObserver, { self: true }); /** * The root element to observe. * * If not provided, the root element is the element that the directive is attached to. */ this.root = input(); /** * The root margin to observe. * * This is useful when you want to observe the virtual view in a specific area of the root element. */ this.rootMargin = input(''); /** * The threshold to observe. * * If you want to observe the virtual view when it is partially visible, you can set the threshold to a number between 0 and 1. * * For example, if you set the threshold to 0.5, the virtual view will be observed when it is half visible. */ this.threshold = input(0); this.#rootElement = computed(() => { const root = this.root(); if (root) { if (root instanceof ElementRef) { return root.nativeElement; } return root; } else if (root === null) { return null; } return this.#elementRef.nativeElement; }); this.#elements = new Map(); this.#forcedHidden$ = new BehaviorSubject(false); } #elementRef; #observer; #resizeObserver; #rootElement; #elements; #forcedHidden$; ngOnInit() { this.#observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (this.#elements.has(entry.target)) this.#elements.get(entry.target)?.next(entry.isIntersecting); }); }, { root: this.#rootElement(), rootMargin: this.rootMargin(), threshold: this.threshold(), }); } ngOnDestroy() { this.#elements.clear(); this.#observer?.disconnect(); this.#observer = null; } /** * Hide all the virtual views. * * This is useful when you want to hide all the virtual views when the user cannot see them. * * For example, when the user opens a modal, you can hide all the virtual views to improve performance. * * **IMPORTANT:** * * Don't forget to call `showAllVisible()` when you want to show the virtual views again. */ hideAll() { this.#forcedHidden$.next(true); } /** * Show all the virtual views that are currently visible. * * This needs to be called if `hideAll()` was called before. */ showAllVisible() { this.#forcedHidden$.next(false); } observeElementVisibility(virtualView) { const isVisible$ = new ReplaySubject(1); // Store the view and the visibility state in the map. // This allows us to retrieve the visibility state later. this.#elements.set(virtualView, isVisible$); // Start observing the virtual view immediately. this.#observer?.observe(virtualView); return combineLatest([isVisible$, this.#forcedHidden$]).pipe(map(([isVisible, forcedHidden]) => (forcedHidden ? false : isVisible)), distinctUntilChanged(), finalize(() => { this.#observer?.unobserve(virtualView); this.#elements.delete(virtualView); })); } observeElementSize(element, options) { return this.#resizeObserver.observeElement(element, options); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewObserver, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.0", type: RxVirtualViewObserver, isStandalone: true, selector: "[rxVirtualViewObserver]", inputs: { root: { classPropertyName: "root", publicName: "root", isSignal: true, isRequired: false, transformFunction: null }, rootMargin: { classPropertyName: "rootMargin", publicName: "rootMargin", isSignal: true, isRequired: false, transformFunction: null }, threshold: { classPropertyName: "threshold", publicName: "threshold", isSignal: true, isRequired: false, transformFunction: null } }, providers: [ VirtualViewCache, RxaResizeObserver, { provide: _RxVirtualViewObserver, useExisting: RxVirtualViewObserver }, ], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewObserver, decorators: [{ type: Directive, args: [{ selector: '[rxVirtualViewObserver]', standalone: true, providers: [ VirtualViewCache, RxaResizeObserver, { provide: _RxVirtualViewObserver, useExisting: RxVirtualViewObserver }, ], }] }] }); /** * The RxVirtualViewPlaceholder directive is a directive that allows you to create a placeholder for the virtual view. * * It can be used on an element/component to create a placeholder for the virtual view. * * It needs to be a sibling of the `rxVirtualView` directive. * * @example * ```html * <div rxVirtualViewObserver> * <div rxVirtualView> * <div *rxVirtualViewContent>Virtual View 1</div> * <div *rxVirtualViewPlaceholder>Loading...</div> * </div> * </div> * ``` * * @developerPreview */ class RxVirtualViewPlaceholder { #virtualView = inject(_RxVirtualView); constructor(templateRef) { this.templateRef = templateRef; this.#virtualView.registerPlaceholder(this); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewPlaceholder, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: RxVirtualViewPlaceholder, isStandalone: true, selector: "[rxVirtualViewPlaceholder]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxVirtualViewPlaceholder, decorators: [{ type: Directive, args: [{ selector: '[rxVirtualViewPlaceholder]', standalone: true }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); /** * Generated bundle index. Do not edit. */ export { RxVirtualView, RxVirtualViewContent, RxVirtualViewObserver, RxVirtualViewPlaceholder, provideVirtualViewConfig }; //# sourceMappingURL=template-virtual-view.mjs.map