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