@bespunky/angular-zen
Version:
The Angular tools you always wished were there.
2,646 lines • 142 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, PLATFORM_ID, Directive, EventEmitter, Output, Input, NgModule, ElementRef } from '@angular/core';
import { DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
import { Subject, Subscription, BehaviorSubject, EMPTY, combineLatest, concat, forkJoin, merge, of, animationFrames, interval } from 'rxjs';
import { map, share, tap, switchMap, materialize, finalize, startWith, delay, takeWhile, filter, mapTo } from 'rxjs/operators';
/** A token used to provide the native document implementation for `DocumentRef`. By default, `CoreModule` will provide angular's DOCUMENT token. */
const DOCUMENT = new InjectionToken('DocumentToken');
/**
* Provides an injectable wrapper for the `document` object.
* Inject this in your services/components and you will be able to easily mock or spy on the native `document` object in your tests.
*
* By default, the `nativeDocument` property will point to angular's DOM adapter, thus facilitating DOM access and manipulation
* on the different platforms.
* To mock the native document, provide a value for the `DOCUMENT` token from `@bespunky/angular-zen/core`.
* You will safely mock it without trashing angular's `DOCUMENT` provider.
*
* @see document-ref.service.spec.ts for examples.
*/
class DocumentRef {
// Treating native document as `any` save users typecasting everytime and deducing if the object is of type `Document` or `object`.
/**
* Creates an instance of `DocumentRef`.
*
* @param {*} nativeDocument The native document provided by the `DOCUMENT` token of `@bespunky/angular-zen/core`. See `DocumentRef` for details.
*/
constructor(nativeDocument) {
this.nativeDocument = nativeDocument;
}
}
DocumentRef.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DocumentRef, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
DocumentRef.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DocumentRef, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DocumentRef, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }]; } });
/**
* The default provider for the `DOCUMENT` token. Uses angular's DOM adapters which will be injected according to the platform.
*/
const DocumentProvider = {
provide: DOCUMENT,
useExisting: DOCUMENT$1
};
/**
* A bundle of all providers needed for DocumentRef to work.
*/
const DocumentRefProviders = [DocumentProvider];
/**
* An injectable token that will allow us to replace the provider for the native window object when necessary (e.g. mocking the `window` object).
*/
const WINDOW = new InjectionToken('WindowToken');
/**
* Provides an injectable wrapper for the `window` object.
*
* Inject this in your services/components and you will be able to easily mock or spy on the native `window` object in your tests.
* You can replace the default `WINDOW` token provider, which allows you to mock the `window` object.
*
* @see window-ref.service.spec.ts for examples.
*/
class WindowRef {
// Treating native window as `any` save users typecasting everytime and deducing if the object is of type `Window` or `object`.
/**
* Creates an instance of WindowRef.
*
* @param {*} nativeWindow The native window provided by the `WINDOW` token of `@bespunky/angular-zen/core`. See `WindowRef` for details.
*/
constructor(nativeWindow) {
this.nativeWindow = nativeWindow;
}
}
WindowRef.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: WindowRef, deps: [{ token: WINDOW }], target: i0.ɵɵFactoryTarget.Injectable });
WindowRef.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: WindowRef, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: WindowRef, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [WINDOW]
}] }]; } });
/**
* Provides a platform dependant implementation for retrieving the `window` object.
*
* @returns `window` for browser platforms and a new object for non-browser platforms.
*/
function windowFactory(platformId) {
return isPlatformBrowser(platformId) ? window : new Object();
}
/**
* The default provider for the `WINDOW` token. Provides `window` for browser platforms and a new object for non-browser platforms.
*/
const WindowProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [PLATFORM_ID]
};
/**
* A bundle of all providers needed for WindowRef to work.
*/
const WindowRefProviders = [WindowProvider];
/**
* Facilitates working with components, directives and services which manually subscribe to observables.
* Extend this class to easily hook into ngOnDestroy and avoid memory leaks.
*
* @see [Wiki](https://bs-angular-zen.web.app/docs/zen/additional-documentation/coremodule/destroyable-(abstract).html) for full guide.
*
* @export
* @abstract
* @class Destroyable
* @implements {OnDestroy}
*/
class Destroyable {
constructor() {
/**
* Emits a value when `ngOnDestroy()` is called.
* Pipe together with `takeUntil()` to auto unsubscribe from your observables.
*
* @example
* observable.pipe(takeUntil(this.destroyed)).subscribe(...);
*
* @protected
* @type {Subject<void>}
*/
this.destroyed = new Subject();
/**
* A list of all subscriptions manually added using the `subscribe()` method.
* These are automatically unsubscribed when `ngOnDestroy()` is called.
*
* @protected
* @type {Subscription}
*/
this.subscriptions = new Subscription();
}
ngOnDestroy() {
this.destroyed.next();
this.destroyed.complete();
this.subscriptions.unsubscribe();
}
subscribe(observable, observerOrNext, error, complete) {
// Cast partial observer object
const observer = observerOrNext instanceof Function ? {
next: observerOrNext,
error,
complete
} : observerOrNext;
this.subscriptions.add(observable.subscribe(observer));
return this.subscriptions;
}
}
Destroyable.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Destroyable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
Destroyable.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: Destroyable, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: Destroyable, decorators: [{
type: Directive
}] });
/**
* The base class for all `*observeXXX` directives.
* This directive bind an observable or a collection of observables with a template, causing the values in the template to be updated whenever the observables emit.
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*
* ## ⚠️ Extending notes:
* As the base class cannot deduce the directive selector (e.g. `observeLatest`, `observeMerge`, etc.) the extending class
* is required to do 4 things:
* 1. Implement the abstract `selector` member and assign it with the directive's selector.
* 2. Implement and `@Input() public set <selector>(value: T)` which will pass its value to the `input` member.
* 3. Implement a static context TypeGuard.
*
* These will enable Angular features like template type checking and the microsyntax `as` keyword.
*
* @export
* @abstract
* @class ObserveBaseDirective
* @extends {Destroyable}
* @implements {OnInit}
* @template TInput The type of value this directive will work with. Depends on the extending class.
* @template TResolved The type of value emitted by the observable. Depends on the extending class.
* @template TContext The type of the context object the template will work with.
*/
class ObserveBaseDirective extends Destroyable {
constructor(template, viewContainer) {
super();
this.template = template;
this.viewContainer = viewContainer;
/**
* Triggered whenever the observable emits a value. `$event` will be the emitted value.
*
* @type {EventEmitter<TResolved>}
*/
this.nextCalled = new EventEmitter();
/**
* Triggered when an error occurs in the observable's pipeline. `$event` will be the error.
*
* @type {EventEmitter<unknown>}
*/
this.errorCalled = new EventEmitter();
/**
* Triggered when the observable completes. `$event` will be the void.
*
* @type {EventEmitter<void>}
*/
this.completeCalled = new EventEmitter();
/**
* ### Why BehaviorSubject<{@link TInput s} | null> and not Subject<{@link TInput}>
* `input` is set from @Input properties. For some reason, Angular passes-in the first value BEFORE
* ngOnInit, even though other @Input properties (e.g. showAfter, showFor) are passed AFTER ngOnInit.
* If subscription occurs in the constructor, `input` will emit the first observable too fast, which
* might lead to pipes breaking or misbehaving if they rely on properties to be instantiated first.
*
* This leads to subscribing in ngOnInit, to allow Angular time to initialize those.
* BUT, if `input` is a Subject, as the first value was already emitted BEFORE ngOnInit, it will not be
* captured by our subscription to `input`. Hence the BehaviorSubject - To allow capturing that first observable.
*/
this.input = new BehaviorSubject(null);
this.renderView();
}
ngOnInit() {
// See `this.input` documentation for why subscription is done in ngOnInit.
this.subscribe(this.contextFeed());
}
contextFeed() {
return this.input.pipe(
// Whenever a new value is provided into the directive use the extender's implementation to observe it and multicast it.
map(input => input ? this.observeInput(input).pipe(share()) : EMPTY),
// Replace the source observable in the context with the newly created observable.
tap(source => this.updateViewContext({ source })),
// Switch to the new observable and materialize it to watch for state changes and emit events accordingly
switchMap(source => source.pipe(materialize())),
// Whenever a materialized notification is emitted, handle it and emit the relevant event
tap(meta => this.onStateChange(meta)));
}
onStateChange(meta) {
// Call the appropriate handler according to the received notification
return meta.observe({
next: value => {
this.updateViewContext({ value });
this.nextCalled.emit(value);
},
error: error => this.errorCalled.emit(error),
complete: () => this.completeCalled.emit()
});
}
renderView() {
const context = this.createViewContext({});
this.view = this.viewContainer.createEmbeddedView(this.template, context);
}
updateViewContext(data) {
this.view.context = this.createViewContext(data);
}
createViewContext({ value, source }) {
value ?? (value = this.view?.context.$implicit || null);
source ?? (source = this.view?.context.source || EMPTY);
return { $implicit: value, [this.selector]: value, source };
}
}
ObserveBaseDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveBaseDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
ObserveBaseDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveBaseDirective, outputs: { nextCalled: "nextCalled", errorCalled: "errorCalled", completeCalled: "completeCalled" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveBaseDirective, decorators: [{
type: Directive
}], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }]; }, propDecorators: { nextCalled: [{
type: Output
}], errorCalled: [{
type: Output
}], completeCalled: [{
type: Output
}] } });
/**
* Documentation in {@link ObserveDirective.observe} to allow in-template tooltips.
*
* @export
* @class ObserveDirective
* @extends {ObserveBaseDirective<T, EmittedTypeOf<T>, ObserveContext<T>>}
* @template T The type of observable received by the directive.
*/
class ObserveDirective extends ObserveBaseDirective {
constructor() {
super(...arguments);
this.selector = 'observe';
}
/**
* Tracks an observable and updates the template with its emitted value on each emission.
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*/
set observe(value) { this.input.next(value); }
static ngTemplateContextGuard(directive, context) { return true; }
observeInput(input) {
return input;
}
}
ObserveDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveDirective, selector: "[observe]", inputs: { observe: "observe" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveDirective, decorators: [{
type: Directive,
args: [{
selector: '[observe]'
}]
}], propDecorators: { observe: [{
type: Input
}] } });
/**
* The base class for `*observe` directives combining observables using a map of observable values (i.e. { x: x$, y: y$ }).
*
* Emitted values will be available as the implicit context values but will also be spread into the context by key.
* Meaning, this would work:
* ```html
* <div *observeXXX="{ x: x$, y: y$ } as result">{{result.x}}</div>
* ```
*
* And this also:
* ```
* <div *observeXXX="{ x: x$, y: y$ }; let x = x">{{x}}</div>
* ```
*
* @export
* @abstract
* @class ObserveMapDirective
* @extends {ObserveBaseDirective<TInput, EmittedMapOf<TInput>, TContext>}
* @template TInput The type of observable map.
* @template TContext The the of context the directive will provide to the view.
*/
class ObserveMapDirective extends ObserveBaseDirective {
createViewContext(data) {
// Spread the values emitted from the observable to allow `let` microsyntax and directly accessing them
return { ...super.createViewContext(data), ...data.value };
}
}
ObserveMapDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveMapDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveMapDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveMapDirective, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveMapDirective, decorators: [{
type: Directive
}] });
/**
* Documentation in {@link ObserveLatestDirective.observeLatest} to allow in-template tooltips.
* @export
* @class ObserveLatestDirective
* @extends {ObserveMapDirective<T, ObserveLatestContext<T>>}
* @template T The type of observables map received by the directive.
*/
class ObserveLatestDirective extends ObserveMapDirective {
constructor() {
super(...arguments);
// Seems like for template typechecking to work with a generic type holding `unknown`, the generic type must be flattened
// and not represented by a new type. When I tried creating an ObservableMap = { ...key: Observable<unknown> } and use it
// as T extends ObservableMap, the type system failed to infer the inner types of the observables.
// T extends { ...key: Observable<unknown> } works fine.
this.selector = 'observeLatest';
}
/**
* Combines a map of observables using rxjs {@link https://rxjs.dev/api/index/function/combineLatest combineLatest()} and exposes the emitted values to the template.
* Values are exposed in a map which keys are the same keys as the original observable map and values are
* the emitted values corresponding to those keys.
*
* Emitted values will be available as the implicit context values but will also be spread into the context by key.
* Meaning, this would work:
* ```html
* <div *observeLatest="{ x: x$, y: y$ } as result">{{result.x}}</div>
* ```
*
* And this also:
* ```
* <div *observeLatest="{ x: x$, y: y$ }; let x = x">{{x}}</div>
* ```
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*/
set observeLatest(value) { this.input.next(value); }
static ngTemplateContextGuard(directive, context) { return true; }
observeInput(input) {
return combineLatest(input);
}
}
ObserveLatestDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveLatestDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveLatestDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveLatestDirective, selector: "[observeLatest]", inputs: { observeLatest: "observeLatest" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveLatestDirective, decorators: [{
type: Directive,
args: [{
selector: '[observeLatest]'
}]
}], propDecorators: { observeLatest: [{
type: Input
}] } });
/**
* The base class for `*observe` directives combining observables using an array of observable values (i.e. [x$, y$]).
*
* @export
* @abstract
* @class ObserveArrayDirective
* @extends {ObserveBaseDirective<TInput, TResolved, TContext>}
* @template TInput The type of observable array.
* @template TResolved The type of resolved array.
* @template TContext The the of context the directive will provide to the view.
*/
class ObserveArrayDirective extends ObserveBaseDirective {
}
ObserveArrayDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveArrayDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveArrayDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveArrayDirective, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveArrayDirective, decorators: [{
type: Directive
}] });
/**
* Documentation in {@link ObserveConcatDirective.observeConcat} to allow in-template tooltips.
*
* @export
* @class ObserveConcatDirective
* @extends {ObserveArrayDirective<T, EmittedArrayTypesOf<T>, ObserveConcatContext<T>>}
* @template T The type of observables tuple received by the directive.
*/
class ObserveConcatDirective extends ObserveArrayDirective {
constructor() {
super(...arguments);
this.selector = 'observeConcat';
}
/**
* Concats an array of observables using rxjs {@link https://rxjs.dev/api/index/function/concat concat()} and exposes the emitted values to the template.
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*/
set observeConcat(value) { this.input.next(value); }
static ngTemplateContextGuard(directive, context) { return true; }
observeInput(input) {
return concat(...input);
}
}
ObserveConcatDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveConcatDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveConcatDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveConcatDirective, selector: "[observeConcat]", inputs: { observeConcat: "observeConcat" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveConcatDirective, decorators: [{
type: Directive,
args: [{
selector: '[observeConcat]'
}]
}], propDecorators: { observeConcat: [{
type: Input
}] } });
/**
* Documentation in {@link ObserveJoinDirective.observeJoin} to allow in-template tooltips.
* @export
* @class ObserveJoinDirective
* @extends {ObserveMapDirective<T, ObserveJoinContext<T>>}
* @template T The type of observables map received by the directive.
*/
class ObserveJoinDirective extends ObserveMapDirective {
constructor() {
super(...arguments);
this.selector = 'observeJoin';
}
/**
* Joins a map of observables using rxjs {@link https://rxjs.dev/api/index/function/forkJoin forkJoin()} and exposes the emitted values to the template.
* Values are exposed in a map which keys are the same keys as the original observable map and values are
* the emitted values corresponding to those keys.
*
* Emitted values will be available as the implicit context values but will also be spread into the context by key.
* Meaning, this would work:
* ```html
* <div *observeJoin="{ x: x$, y: y$ } as result">{{result.x}}</div>
* ```
*
* And this also:
* ```
* <div *observeJoin="{ x: x$, y: y$ }; let x = x">{{x}}</div>
* ```
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*/
set observeJoin(value) { this.input.next(value); }
static ngTemplateContextGuard(directive, context) { return true; }
observeInput(input) {
return forkJoin(input);
}
}
ObserveJoinDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveJoinDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveJoinDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveJoinDirective, selector: "[observeJoin]", inputs: { observeJoin: "observeJoin" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveJoinDirective, decorators: [{
type: Directive,
args: [{
selector: '[observeJoin]'
}]
}], propDecorators: { observeJoin: [{
type: Input
}] } });
/**
* Documentation in {@link ObserveMergeDirective.observeMerge} to allow in-template tooltips.
*
* @export
* @class ObserveMergeDirective
* @extends {ObserveArrayDirective<T, EmittedArrayTypesOf<T>, ObserveMergeContext<T>>}
* @template T The type of observables tuple received by the directive.
*/
class ObserveMergeDirective extends ObserveArrayDirective {
constructor() {
super(...arguments);
this.selector = 'observeMerge';
}
/**
* Combines an array of observables using rxjs {@link https://rxjs.dev/api/index/function/merge merge()} and exposes the emitted values to the template.
*
* Any template assigned with the directive will render immediately, and its view context will be updated with the emitted value on
* each emission. The directive will be responsible for subscribing on init and unsubscribing on destroy.
*
* ## Features
*
* #### Shared observable
* The watched observable will automatically be multicasted so that any child observables created by the template will use the same
* stream.
*
* The shared observable can be accessed using the `let source = source` microsyntax.
*
* #### Observer events
* Whenever the observable changes state or emits a value, the corresponding event is emitted:
* `nextCalled` - A value has been emitted. `$event` will be the emitted value.
* `errorCalled` - An error has occured in the pipeline. `$event` will be the error.
* `completeCalled` - The observable has completed. `$event` will be void.
*
* > Because of limitations to Angular's Structural Directives, in order to bind the events the desugared syntax must be used.
* This, for example, **will trigger** the event:
* > ```html
* ><ng-template [observe]="x$" let-source="source" (nextCalled)="onNext($event)">
* > ...
* ></ng-template>
* > ```
* >
* >This **will NOT trigger** the event:
* >```html
* > <div *observe="x$; let source = source" (nextCalled)="onNext($event)">...</div>
* >```
*/
set observeMerge(value) { this.input.next(value); }
static ngTemplateContextGuard(directive, context) { return true; }
observeInput(input) {
return merge(...input);
}
}
ObserveMergeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveMergeDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
ObserveMergeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: ObserveMergeDirective, selector: "[observeMerge]", inputs: { observeMerge: "observeMerge" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveMergeDirective, decorators: [{
type: Directive,
args: [{
selector: '[observeMerge]'
}]
}], propDecorators: { observeMerge: [{
type: Input
}] } });
/**
* Provides directives to facilitate in-template subscription management to observables.
*
* @export
* @class ObserveModule
*/
class ObserveModule {
}
ObserveModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
ObserveModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: ObserveModule, declarations: [ObserveDirective,
ObserveLatestDirective,
ObserveJoinDirective,
ObserveMergeDirective,
ObserveConcatDirective], exports: [ObserveDirective,
ObserveLatestDirective,
ObserveJoinDirective,
ObserveMergeDirective,
ObserveConcatDirective] });
ObserveModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: ObserveModule, decorators: [{
type: NgModule,
args: [{
declarations: [
ObserveDirective,
ObserveLatestDirective,
ObserveJoinDirective,
ObserveMergeDirective,
ObserveConcatDirective,
],
exports: [
ObserveDirective,
ObserveLatestDirective,
ObserveJoinDirective,
ObserveMergeDirective,
ObserveConcatDirective,
]
}]
}] });
const DurationMultipliers = { ms: 1, s: 1000, m: 60000 };
function durationToMs(duration) {
if (typeof duration === 'number')
return duration;
const regex = /(?<value>\d+(.\d+)?)(?<units>\w+)/;
const { value, units } = duration.match(regex)?.groups;
return parseInt(value) * (DurationMultipliers[units] || 1);
}
function breakdownTime(showingForMs) {
const dummyDate = new Date(showingForMs);
const showingFor = {
m: dummyDate.getMinutes(),
s: dummyDate.getSeconds(),
ms: dummyDate.getMilliseconds(),
totalMinutes: showingForMs / DurationMultipliers.m,
totalSeconds: showingForMs / DurationMultipliers.s,
totalMilliseconds: showingForMs,
};
return showingFor;
}
/**
* Represents the context to be fed into a view rendered by an {@link OnObserverBaseDirective `*onObserver`} directive.
* The context is immutable.
*
* @export
* @class OnObserverContext
* @template TResolved The type of value emitted by the observable the directive is observing.
*/
class OnObserverContext {
/**
* Creates an instance of OnObserverContext.
*/
constructor(
/**
* The selector of the directive which is creating this context. This will be used to assign the emitted value to a
* property matching the selector, thus enabling the use of the microsyntax `as` keyword.
*/
selector,
/** The index of the view rendered by the directive. If the directive is in `'single'` view mode, this will always be 0. */
index,
/** The name of the observer call which triggered this context creation. */
call,
/** (Optional) The value, if any, that was emitted by the original observable. */
value,
/**
* (Optional) The time left for the view to be rendered. Only used when {@link OnObserverBaseDirective.showFor `showFor`}
* is specified in the directive.
*/
remaining,
/**
* @deprecated Use {@link OnObserverContext.remaining `remaining`} instead. This will be removed in v6.0.0.
*/
showingFor,
/**
* (Optional) The time elapsed from the moment the view was rendered. Only used when {@link OnObserverBaseDirective.showFor `showFor`}
* is specified in the directive.
*/
elapsed) {
this.index = index;
this.call = call;
this.remaining = remaining;
this.showingFor = showingFor;
this.elapsed = elapsed;
this.$implicit = this[selector] = value;
}
/**
* Creates a context object for the specified view render commitment.
*
* @static
* @template T The type of value emitted by the observable.
* @param {string} onObserverSelector The selector of the directive which is creating this context.
* @param {number} index The index of the view rendered by the directive.
* @param {ViewRenderCommitment<T>} commitment The view render commitment from which to create the context.
* @return {OnObserverContext<T>} A context object for the specified view render commitment.
*/
static fromCommitment(onObserverSelector, index, { call: { name, value } }) {
return new OnObserverContext(onObserverSelector, index, name, value);
}
}
;
/** Maps RxJS materialized notification states to their observer handler name. */
const StateNotificationMap = {
N: 'next',
E: 'error',
C: 'complete'
};
/**
* Represents an intercepted observer call made by a materialized observable.
*
* @export
* @class ObserverCall
* @template T The type of value emitted by the materialized observable.
*/
class ObserverCall {
/**
* Creates an instance of ObserverCall.
*/
/**
* Creates an instance of ObserverCall.
* @param {ObserverName} name
* @param {T} [value]
*/
constructor(
/**
* The name of the intercepted observer call.
*
* @type {ObserverName}
**/
name,
/**
* (Optional) The value, if any, emitted by the observable.
*
* @type {T}
* @template T The type of value emitted by the observable.
*/
value) {
this.name = name;
this.value = value;
}
/**
* Creates an ObserverCall representing the resolving state of an observable.
*
* @static
* @template T The type of value emitted by the observable.
* @return {ObserverCall<T>}
*/
static resolving() {
return new ObserverCall('resolving');
}
/**
* Extracts the data from a materialized observable notification and creates an `ObserverCall` representation for it.
*
* @static
* @template T The type of value emitted by the observable.
* @param {Notification<T>} notification The notification received from the materialized observable.
* @return {ObserverCall<T>} An `ObserverCall` representing the notification and its data.
*/
static fromNotification({ kind, value, error }) {
return new ObserverCall(StateNotificationMap[kind], error || value);
}
}
/**
* Represents a state describing a view to be rendered or a view already rendered. The state holds the parameterization indicating
* when a view should be rendered and destroyed, and also holds the rendered view if there is any.
*
* States are created every time an {@link ObserverCall} is emitted. They are used by {@link OnObserverBaseDirective `*onObserver`} directives
* to understand how a view should be rendered and initiate a commitment to render flow.
*
* The state is immutable.
*
* @export
* @class ViewRenderState
* @template T The type of value emitted by the observable.
*/
class ViewRenderCommitment {
/**
* Creates an instance of ViewRenderState.
*/
constructor(
/** The id of the commitment to render. Allows identifying the state within a state map. */
commitmentId,
/** The intercepted call which triggered the commitment to render. */
call,
/** The duration (in milliseconds) specified as the delay before rendering the view. */
showAfter,
/** The duration (in milliseconds) specified as the delay before destroying the view. */
showFor,
/**
* The timestamp at which the view should be rendered. This value is manually specified and not calculated automatically using
* `Date.now()` upon state creation because states might be recreated before the {@link showAfter} delay is finished.
* When a state is recreated, the time that has already passed should be considered, thus the previous value should be used.
*/
renderAt,
/** (Optional) The rendered view. Will be provided only after the recreation of the state once the delay has passed. */
view) {
this.commitmentId = commitmentId;
this.call = call;
this.showAfter = showAfter;
this.showFor = showFor;
this.renderAt = renderAt;
this.view = view;
}
/**
* The timestamp at which the view should be destroyed.
*
* @readonly
* @type {number}
*/
get destroyAt() { return this.showFor ? this.renderAt + this.showFor : undefined; }
/**
* `true` if the state represents a view that is currently rendered; otherwise `false`.
*
* @readonly
* @type {boolean}
*/
get isRendered() { return !!this.view; }
/**
* `true` if the state represents a view that should be auto-destroyed; otherwise `false`.
*
* @readonly
* @type {boolean}
*/
get autoDestroys() { return !!this.destroyAt; }
/**
* Creates a new state representing a new, fresh, commitment to render.
* Should be used in multi-view mode, or in single-view mode when there is nothing rendered.
*
* @static
* @template T The type of value emitted by the observable.
* @param {ObserverCall<T>} call The intercepted call which triggered this state.
* @param {number} showAfter The duration (in milliseconds) to wait before rendering the view.
* @param {number} showFor The duration (in milliseconds) to wait before destroying the view.
* @return {ViewRenderCommitment<T>} A new state representing fresh commitment to render.
*/
static create(call, showAfter, showFor) {
const now = Date.now();
return new ViewRenderCommitment(now.toString(), call, showAfter, showFor, now + showAfter);
}
/**
* Clones the state and replaces the call which triggered it.
* Should be used in single-view mode when the view is already rendered and a new call is intercepted to make sure the
* latest emitted value is specified.
*
* @static
* @template T The type of value emitted by the observable.
* @param {ViewRenderCommitment<T>} state The state to clone.
* @param {ObserverCall<T>} call The intercepted call which triggered this state.
* @return {ViewRenderCommitment<T>} A new state representing an updated commitment to render.
*/
static update(state, call) {
const now = Date.now();
// In single-view mode, in case the view is already rendered and a new call is intercepted, the latest emitted value should
// reset renderAt date so the user has a chance to see the latest value.
return new ViewRenderCommitment(state.commitmentId, call, state.showAfter, state.showFor, now + state.showAfter, state.view);
}
/**
* Clones the state and assigns it with a recently rendered view.
* Should be used whenever a view is rendered.
*
* @static
* @template T The type of value emitted by the observable.
* @param {ViewRenderCommitment<T>} state The state to clone.
* @param {RenderedView<T>} view The rendered view to store in the state.
* @return {ViewRenderCommitment<T>} A new state with the rendered view.
*/
static rendered(state, view) {
return new ViewRenderCommitment(state.commitmentId, state.call, state.showAfter, state.showFor, state.renderAt, view);
}
}
/**
* The default number of times the countdown will be updated in a rendered view waiting to be auto-destroyed.
* To change this, the user will have to specify a value for the {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} property.
**/
const DefaultCountdownUpdateCount = 30;
/**
* Provides functionality for `*onObserver<state>` directives that render templates according to the state of an observable.
*
* Any template assigned with the directive will render when the defined observer calls are intercepted, and destroyed when any other calls are
* intercepted. For example, if the directive intercepts `next` calls, the view will render on the first value emission, then destroy on
* `complete` or `error`.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*
* #### Multi call interception
* Create different interception combinations by specifying more than one call name in {@link OnObserverBaseDirective.renderOnCallsTo `renderOnCallsTo`}.
* This allows, for example, the combination of `'error'` and `'complete'` to create a directive named `*onObserverFinalized`.
*
* ## Extending
* As this base class doesn't know what the properties of the extending class will be, extending classes must:
* 1. Define their selector in the abstract {@link OnObserverBaseDirective.selector `selector`} property. This will allow the directive to
* assign the view context with a property which will enable the microsyntax `as` keyword.
* 2. Define the call(s) to intercept and render the view for in the abstract {@link OnObserverBaseDirective.renderOnCallsTo `renderOnCallsTo`}.
* 3. Define an `@Input() set <selector>` property which will call `this.input.next(value)`.
* 4. Define an `@Input() set <selector>ViewMode` property which will set `this.viewMode`.
* 5. Define an `@Input() set <selector>ShowAfter` property which will set `this.showAfter`.
* 6. Define an `@Input() set <selector>ShowFor` property which will set `this.showFor`.
* 7. Define an `@Input() set <selector>CountdownInterval` property which will set `this.countdownInterval`.
* 8. Define this static context type guard to allow strong typing the template:
* ```ts
* static ngTemplateContextGuard<T>(directive: ___DIRECTIVE_NAME___<T>, context: unknown): context is OnObserverContext<T>
* { return true; }
* ```
*
* @export
* @abstract
* @class OnObserverBaseDirective
* @extends {Destroyable}
* @implements {OnInit}
* @template T The type of value the observable will emit.
*/
class OnObserverBaseDirective extends Destroyable {
constructor(template, viewContainer) {
super();
this.template = template;
this.viewContainer = viewContainer;
/**
* A global commitment map holding all commitments to render for which the directive has created commitment observables.
* Ids are the timestamp of the observed calls and values are the commitments with their rendering parameters.
*
* @private
* @type {RenderCommitmentMap<T>}
*/
this.commitments = new Map();
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*
* ⚠️ Extending classes should:
* 1. Declare an `@Input()` setter named `{selector}ViewMode` (e.g. onObserverCompleteViewMode) which will set this value.
* 2. Provide the above documentation for the setter property.
*
* @default 'single'
* @protected
* @type {ViewMode}
*/
this.viewMode = 'single';
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
*
* ⚠️ Extending classes should:
* 1. Declare an `@Input()` setter named `{selector}ShowAfter` (e.g. onObserverCompleteShowAfter) which will set this value.
* 2. Provide the above documentation for the setter property.
*
* @protected
* @type {DurationAnnotation}
*/
this.showAfter = 0;
/**
* ### Why BehaviorSubject<... | null> and not Subject<...>
* `input` is set from @Input properties. For some reason, Angular passes-in the first value BEFORE
* ngOnInit, even though other @Input properties (e.g. showAfter, showFor) are passed AFTER ngOnInit.
* If subscription occurs in the constructor, `input` will emit the first observable too fast, which
* might lead to pipes breaking or misbehaving if they rely on properties to be instantiated first.
*
* This leads to subscribing in ngOnInit, to allow Angular time to initialize those.
* BUT, if `input` is a Subject, as the first value was already emitted BEFORE ngOnInit, it will not be
* captured by our subscription to `input`. Hence the BehaviorSubject - To allow capturing that first observable.
*/
this.input = new BehaviorSubject(null);
}
/**
* The first commitment in the {@link OnObserverBaseDirective.commitments global commitments map}. Used when working with a single view
* to retrieve its corresponding single commitment.
*
* @readonly
* @private
* @type {ViewRenderCommitment<T> | undefined}
*/
get mainCommitment() { return this.commitments.values().next().value; }
/**
* `true` if {@link OnObserverBaseDirective.viewMode viewMode} is `'single'`; otherwise, `false`.
*
* @readonly
* @type {boolean}
*/
get isSingleView() { return this.viewMode === 'single'; }
/**
* `true` if {@link OnObserverBaseDirective.viewMode viewMode} is `'multiple'`; otherwise, `false`.
*
* @readonly
* @type {boolean}
*/
get isMultiView() { return this.viewMode === 'multiple'; }
ngOnInit() {
// See `this.input` documentation for why subscription is done in ngOnInit.
this.subscribe(this.renderFeed());
}
/**
* Destroys any rendered view.
*
* @private
*/
destroyAll() {
this.commitments.forEach(({ view }) => view?.destroy());
}
/**
* Creates the main feed the directive will subscribe to. The feed will listen to changed to {@link OnObserverBaseDirective.input `input`},
* then switch to the newly received observable in order to start observing it.
* The newly received observable will then be materialized and calls will be aggregated as commitment objects with information about
* what to render and when. Those commitments will pass through the {@link OnObserverBaseDirective.onCommitmentsChanged onCommitmentsChanged()} method
* which will update the global commitment and create observables with commitments to render and auto destroy views according to the
* given commitments.
*
* This feed is the single reactive entrypoint, meaning any observable created by the directive will be created inside of this
* observable or its nested observables. Any time a nested observable is created it will be switched to. This allows the pipeline to
* completely startover when a new call is made or a new {@link OnObserverBaseDirective.input `input`} observable is provided, thus keeping
* a consistent stream of data to the {@link OnObserverBaseDirective.commitments global commitments map}.
*
* @private
* @return {Observable<ViewRenderCommitment<T>[]>} An observable as described above.
*/
renderFeed() {
return this.input.pipe(
// Make sure views are reset if a new observable is passed-in to the directive
tap(() => this.destroyAll()),
// Free memory once the directive is destroyed and the subscription closed
// TODO: Will this actually execute? `this.subscribe()` doesn't complete the observable but unsubscribes on component destruction
finalize(() => this.destroyAll()), switchMap(input => input ? this.observeInput(input) : EMPTY), map(call => this.shouldRender(call) ? this.aggregateCommitments(call) : this.deaggregateCommitments()), switchMap(commitments => this.onCommitmentsChanged(commitments)));
}
/**
* Materializes the observable and converts notifications to an {@link ObserverCall} object.
* The returned observable will always start with a `'resolving'` call.
*
* @private
* @param {Observable<T>} input The observable to watch.
* @return {Observable<ObserverCall<T>>} A materialized observable which describes each observable notification as an {@link ObserverCall} object.
*/
observeInput(input) {
return input.pipe(materialize(), map(ObserverCall.fromNotification), startWith(ObserverCall.resolving()));
}
/**
* Checks whether the given observer call should be rendered according to the interception config in {@link OnObserverBaseDirective.renderOnCallsTo renderOnCallsTo}.
*
* @private
* @param {ObserverCall<T>} The call to check.
* @return {boolean} `true` if the call should be rendered; otherwise `false`.
*/
shouldRender({ name }) {
const observeOn = Array.isArray(this.renderOnCallsTo) ? this.renderOnCallsTo : [this.renderOnCallsTo];
return observeOn.includes(name);
}
/**
* Creates the new commitments map when a new commitment should render.
*
* When `viewMode` is `'single'` the map will always contain a single commitment. If the commitment hasn't been rendered yet, a new commitment will be created.
* Otherwise, the existing commitment will be replaced by a clone with updated parameters (i.e. delay and countdown).
*
* When `viewMode` is `'multiple'` a new commitment will always be added to the map.
*
* @private
* @param {ObserverCall<T>} call The new call which should render.
* @return {RenderCommitmentMap<T>} The new map of commitments to render.
*/
aggregateCommitments(call) {
const commitments = this.commitments;
// In single-view mode, if there's already a commitment, we'll replace it with a new one. Otherwise, we'll create a fresh one.
const newCommitment = this.isSingleView && this.mainCommitment
? ViewRenderCommitment.update(this.mainCommitment, call)
: ViewRenderCommitment.create(call, durationToMs(this.showAfter), durationToMs(this.showFor || 0));
return new Map(commitments.set(newCommitment.commitmentId, newCommitment));
}
/**
* Creates the new commitments map when a new commitment shouldn't render.
*
* @private
* @return {RenderCommitmentMap<T>} If `showFor` is specified, meaning views should be auto destroyed after a certain duration,
* the current commitments will kept alive by returning them as a new map. This will allow recommiting to the same render parameters (i.e. delay and countdown).
* Otherwise, when views should destroy immediately, an empty map will be returned.
*/
deaggregateCommitments() {
return this.showFor ? new Map(this.commitments) : new Map();
}
/**
* Handles the changes to the current commitment of the watched observable and creates and commits to render all commitments.
*
* This will update the global commitment map. If an empty map is passed, all previous commitments will be destroyed.
*
* @private
* @param {RenderCommitmentMap<T>} commitments The current commitment map.
* @return {Observable<ViewRenderCommitment<T>[]>} An observable joining all render commitments.
*/
onCommitmentsChanged(commitments) {
// If the commitment map has been reset, destroy any previously rendered view
if (!commitments.size)
this.destroyAll();
// Update the global commitment map
this.commitments = commitments;
// Map all commitments to a commitment to render observable
const runCommitments = Array.from(commitments.keys())
.map((commitmentId, index) => this.commitToRender(commitments, commitmentId, index));
return forkJoin(runCommitments);
}
/**
* Creates an observable that initiates the render flow for an emission. Render flow is as follows:
* 1. Delay render until the time for render (i.e. {@link ViewRenderCommitment.renderAt}) has come.
* 2. Render the view.
* 3. Update the {@link OnObserverBaseDirective.commitments global commitments map} with the rendered commitment.
* 4. Initiate an auto destroy timer. See {@link OnObserverBaseDirective.autoDestroy autoDestroy()}.
* 5. Remove the destroyed commitment from the {@link OnObserverBaseDirective.commitments global commitments map}.
*
* @private
* @param {RenderCommitmentMap<T>} commitments The current commitment map holding all commitments to render.
* @param {string} commitmentId The id of the commitment to render.
* @param {number} index The index of the view to be rendered.
* @return {Observable<ViewRenderCommitment<T>>} An observable that initiates the render flow for an emission.
*/
commitToRender(commitments, commitmentId, index) {
if (!commitments.has(commitmentId))
throw new Error(`
*${this.selector} has encountered an inconsistency issue. Tried to commit to rendering commitment with ID ${commitmentId}, but no commitment object exists with that ID.
Please consider filing an issue and providing a stack trace here: https://github.com/BeSpunky/angular-zen/issues/new?assignees=BeSpunky&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=%F0%9F%90%9B+
`);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return of(commitments.get(commitmentId)).pipe(switchMap(commitment => this.delayRender(commitment)),
// Actually perform rendering (or update the view if already rendered)
switchMap(commitment => this.renderCommitment(commitment, index)),
// The commitment returned from `renderCommitment()` now contains the view, so update the global map
tap(renderedCommitment => commitments.set(commitmentId, renderedCommitment)),
// Initiate the auto-destroy mechanism (will skip if `showFor` wasn't specified)
switchMap(renderedCommitment => this.autoDestroy(renderedCommitment)),
// Commitment has completed successfully. Remove it from the global map.
tap(renderedCommitment => renderedCommitment.autoDestroys ? commitments.delete(commitmentId) : void 0));
}
/**
* Creates an observable which delays the pipeline until the time to render the view (i.e. {@link ViewRenderCommitment.renderAt}) comes.
*
* @private
* @param {ViewRenderCommitment<T>} commitment The commitment to delay.
* @return {Observable<ViewRenderCommitment<T>>} An observable which delays the pipeline until the time to render the view (i.e. {@link ViewRenderCommitment.renderAt}) comes.
*/
delayRender(commitment) {
return of(commitment).pipe(delay(new Date(commitment.renderAt)));
}
/**
* Creates a new context for the given commitment and renders (or updates) the view.
*
* @private
* @param {ViewRenderCommitment<T>} commitment The commitment for which to create the context and render the view.
* @param {number} index The index of the view. If `viewMode` is `'single'` this should always be `0` as there is only one view.
* @return {Observable<ViewRenderCommitment<T>>} An observable which renderes the commitment, then emits a new updated commitment referencing the rendered view.
*/
renderCommitment(commitment, index) {
const context = OnObserverContext.fromCommitment(this.selector, index, commitment);
const renderedCommitment = this.renderOrUpdateView(commitment, context);
return of(renderedCommitment);
}
/**
* Creates an interval observable which counts down until the time to destroy the view is reached, then destroys the view.
* While the timer is running, the rendered view's context will be updated in fixed intervals with the time left before destruction.
*
* @see {@link OnObserverBaseDirective.defineCountdownInterval defineCountdownInterval()} for more about the fixed countdown interval.
*
* If {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} is `'animationFrames'`, the rxjs `animationFrames()` function
* will be used instead of the interval.
*
* @private
* @param {ViewRenderCommitment<T>} commitment The rendered commitment for which to initiate auto destroy.
* @return {Observable<ViewRenderCommitment<T>>} A timer observable which counts down until the time to destroy the view is reached, then destroys the view, while
* updating the context with the time left for destruction. The observable will emit the commitment
*/
autoDestroy(commitment) {
const { destroyAt, view } = commitment;
if (!(destroyAt && view))
return of(commitment);
const countdownInterval = this.defineCountdownInterval();
const countdown = countdownInterval === 'animationFrames' ? animationFrames() : interval(countdownInterval);
return countdown.pipe(map(() => destroyAt - Date.now()), map(timeLeftMs => [timeLeftMs < 0 ? 0 : timeLeftMs, commitment.showFor - timeLeftMs]), tap(([timeLeftMs, elapsedTimeMs]) => this.updateViewContextCountdown(view, timeLeftMs, elapsedTimeMs)), takeWhile(([timeLeftMs]) => timeLeftMs > 0, true), filter(([timeLeftMs]) => timeLeftMs <= 0), tap(() => view.destroy()), mapTo(commitment));
}
/**
* Makes sure the specified commitment is rendered and its context is updated, then returns an updated commitment with the rendered (or updated) view.
* If the view has been previously rendered, its context will be updated. Otherwise, the view will be rendered for the first time.
*
* The new commitment will be used further down the pipeline to update the internal `commitments` map.
*
* @see {@link OnObserverBaseDirective.commitToRender `commitToRender()`}.
*
* @private
* @param {ViewRenderCommitment<T>} commitment The commitment for which the view should be rendered.countdown
* @param {OnObserverContext<T>} context The context object to feed into the view.
* @return {ViewRenderCommitment<T>} The new commitment containing the rendered (or updated) view.
*/
renderOrUpdateView(commitment, context) {
if (commitment.view) {
commitment.view.context = context;
return ViewRenderCommitment.rendered(commitment, commitment.view);
}
return ViewRenderCommitment.rendered(commitment, this.viewContainer.createEmbeddedView(this.template, context));
}
/**
* Breaks down the time left before the view is destroyed to its parts and updates the view context so that the user may present
* a countdown or any other UI component indicating when the view will be destroyed.
*
* @private
* @param {RenderedView<T>} view The view in which to update the countdown.
* @param {number} timeLeftMs The time left (in milliseconds) for the view before being destroyed.
* @param {number} timeElapsedMs The time elapsed (in milliseconds) from the moment the view was rendered.
*/
updateViewContextCountdown(view, timeLeftMs, timeElapsedMs) {
const remaining = breakdownTime(timeLeftMs);
const elapsed = breakdownTime(timeElapsedMs);
const { $implicit, call, index } = view.context;
// TODO: Remove the `showingFor` argument when launching v6.0.0
view.context = new OnObserverContext(this.selector, index, call, $implicit, remaining, remaining, elapsed);
}
/**
* Defines the interval (in milliseconds) with which countdown updates should be made to the view's context.
* If the user has defined a value through {@link OnObserverBaseDirective.countdownInterval `countdownInterval`}, that value will be used.
* If the user has defined `'animationFrames'` as the value for {@link OnObserverBaseDirective.countdownInterval `countdownInterval`}, this will return `'animationFrames'`.
* Otherwise, {@link OnObserverBaseDirective.showFor `showFor`} will be divided by a fixed number defined by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}, currently 30, meaning the user will get
* 30 countdown updates with fixed intervals between them before the view is destroyed.
*
* @private
* @return {number} The interval with which countdown updates should be made to the view's context.
*/
defineCountdownInterval() {
// If the view should persist, or it should auto-destroy but percision has been manually specified, do nothing
if (!this.showFor)
throw new Error(`
Auto-destroy countdown seems to have been initiated when 'showFor' hasn't been set. This shouldn't have happend.
Please consider filing an issue and providing a stack trace here: https://github.com/BeSpunky/angular-zen/issues/new?assignees=BeSpunky&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=%F0%9F%90%9B+
`);
if (this.countdownInterval === 'animationFrames')
return 'animationFrames';
if (this.countdownInterval)
return durationToMs(this.countdownInterval);
const showForMs = durationToMs(this.showFor);
return showForMs / DefaultCountdownUpdateCount;
}
}
OnObserverBaseDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverBaseDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
OnObserverBaseDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverBaseDirective, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverBaseDirective, decorators: [{
type: Directive
}], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }]; } });
/**
* Documentation in {@link OnObserverActiveDirective.onObserver} to allow in-template tooltips.
*
* @export
* @class OnObserverDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserver';
}
/**
* Renders the template when the specified observable makes any of the calls specified using {@link OnObserverDirective.onObserverCalls `calls`}.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*
* #### Multi call interception
* Create different interception combinations by specifying more than one call name using {@link OnObserverDirective.onObserverCalls `calls`}.
* This is how, for example, the combination of `'error'` and `'complete'` was used to create the `*onObserverFinalized` directive.
*/
set onObserver(value) { this.input.next(value); }
/**
* Defines the calls to intercept from the observable. Only intercepted calls will render the template.
*/
set onObserverCalls(calls) { this.renderOnCallsTo = calls; }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverDirective, selector: "[onObserver]", inputs: { onObserver: "onObserver", onObserverCalls: "onObserverCalls", onObserverViewMode: "onObserverViewMode", onObserverShowAfter: "onObserverShowAfter", onObserverShowFor: "onObserverShowFor", onObserverCountdownInterval: "onObserverCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserver]'
}]
}], propDecorators: { onObserver: [{
type: Input
}], onObserverCalls: [{
type: Input
}], onObserverViewMode: [{
type: Input
}], onObserverShowAfter: [{
type: Input
}], onObserverShowFor: [{
type: Input
}], onObserverCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverResolvingDirective.onObserverResolving} to allow in-template tooltips.
*
* @export
* @class OnObserverResolvingDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverResolvingDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverResolving';
this.renderOnCallsTo = 'resolving';
}
/**
* Renders the template when the specified observable is resolving its first value.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverResolving(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverResolvingViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverResolvingShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverResolvingShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverResolvingCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverResolvingDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverResolvingDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverResolvingDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverResolvingDirective, selector: "[onObserverResolving]", inputs: { onObserverResolving: "onObserverResolving", onObserverResolvingViewMode: "onObserverResolvingViewMode", onObserverResolvingShowAfter: "onObserverResolvingShowAfter", onObserverResolvingShowFor: "onObserverResolvingShowFor", onObserverResolvingCountdownInterval: "onObserverResolvingCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverResolvingDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverResolving]'
}]
}], propDecorators: { onObserverResolving: [{
type: Input
}], onObserverResolvingViewMode: [{
type: Input
}], onObserverResolvingShowAfter: [{
type: Input
}], onObserverResolvingShowFor: [{
type: Input
}], onObserverResolvingCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverNextDirective.onObserverNext} to allow in-template tooltips.
*
* @export
* @class OnObserverNextDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverNextDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverNext';
this.renderOnCallsTo = 'next';
}
/**
* Renders the template when the specified observable emits a value.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverNext(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverNextViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverNextShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverNextShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverNextCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverNextDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverNextDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverNextDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverNextDirective, selector: "[onObserverNext]", inputs: { onObserverNext: "onObserverNext", onObserverNextViewMode: "onObserverNextViewMode", onObserverNextShowAfter: "onObserverNextShowAfter", onObserverNextShowFor: "onObserverNextShowFor", onObserverNextCountdownInterval: "onObserverNextCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverNextDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverNext]'
}]
}], propDecorators: { onObserverNext: [{
type: Input
}], onObserverNextViewMode: [{
type: Input
}], onObserverNextShowAfter: [{
type: Input
}], onObserverNextShowFor: [{
type: Input
}], onObserverNextCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverErrorDirective.onObserverError} to allow in-template tooltips.
*
* @export
* @class OnObserverErrorDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverErrorDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverError';
this.renderOnCallsTo = 'error';
}
/**
* Renders the template when the specified observable errors. The error will be provided as the value in context.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverError(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverErrorViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverErrorShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverErrorShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverErrorCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverErrorDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverErrorDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverErrorDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverErrorDirective, selector: "[onObserverError]", inputs: { onObserverError: "onObserverError", onObserverErrorViewMode: "onObserverErrorViewMode", onObserverErrorShowAfter: "onObserverErrorShowAfter", onObserverErrorShowFor: "onObserverErrorShowFor", onObserverErrorCountdownInterval: "onObserverErrorCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverErrorDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverError]'
}]
}], propDecorators: { onObserverError: [{
type: Input
}], onObserverErrorViewMode: [{
type: Input
}], onObserverErrorShowAfter: [{
type: Input
}], onObserverErrorShowFor: [{
type: Input
}], onObserverErrorCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverCompleteDirective.onObserverComplete} to allow in-template tooltips.
*
* @export
* @class OnObserverCompleteDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverCompleteDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverComplete';
this.renderOnCallsTo = 'complete';
}
/**
* Renders the template when the specified observable has completed without error.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverComplete(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverCompleteViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverCompleteShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverCompleteShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverCompleteCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverCompleteDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverCompleteDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverCompleteDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverCompleteDirective, selector: "[onObserverComplete]", inputs: { onObserverComplete: "onObserverComplete", onObserverCompleteViewMode: "onObserverCompleteViewMode", onObserverCompleteShowAfter: "onObserverCompleteShowAfter", onObserverCompleteShowFor: "onObserverCompleteShowFor", onObserverCompleteCountdownInterval: "onObserverCompleteCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverCompleteDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverComplete]'
}]
}], propDecorators: { onObserverComplete: [{
type: Input
}], onObserverCompleteViewMode: [{
type: Input
}], onObserverCompleteShowAfter: [{
type: Input
}], onObserverCompleteShowFor: [{
type: Input
}], onObserverCompleteCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverActiveDirective.onObserverActive} to allow in-template tooltips.
*
* @export
* @class OnObserverActiveDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverActiveDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverActive';
this.renderOnCallsTo = ['resolving', 'next'];
}
/**
* Renders the template when the specified observable is either resolving its first value, or emits a value.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverActive(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverActiveViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverActiveShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverActiveShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverActiveCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverActiveDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverActiveDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverActiveDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverActiveDirective, selector: "[onObserverActive]", inputs: { onObserverActive: "onObserverActive", onObserverActiveViewMode: "onObserverActiveViewMode", onObserverActiveShowAfter: "onObserverActiveShowAfter", onObserverActiveShowFor: "onObserverActiveShowFor", onObserverActiveCountdownInterval: "onObserverActiveCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverActiveDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverActive]'
}]
}], propDecorators: { onObserverActive: [{
type: Input
}], onObserverActiveViewMode: [{
type: Input
}], onObserverActiveShowAfter: [{
type: Input
}], onObserverActiveShowFor: [{
type: Input
}], onObserverActiveCountdownInterval: [{
type: Input
}] } });
/**
* Documentation in {@link OnObserverFinalizedDirective.onObserverFinalized} to allow in-template tooltips.
*
* @export
* @class OnObserverFinalizedDirective
* @extends {OnObserverBaseDirective<T>}
* @template T The type of value the observable emits.
*/
class OnObserverFinalizedDirective extends OnObserverBaseDirective {
constructor() {
super(...arguments);
this.selector = 'onObserverFinalized';
this.renderOnCallsTo = ['error', 'complete'];
}
/**
* Renders the template when the specified observable is either completed or errored. In case of an error, the error will be
* provided as the value in the context.
*
* ## Features
*
* #### View Context
* Use the microsyntax `as` keyword to assign resolved values to a variable.
* Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
*
* #### Delayed rendering
* Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
*
* #### Auto destroy
* Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
*
* #### Countdown updates
* When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
* is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
* or any other UI component.
*
* Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
* property. Access it by assigning a variable using `let`, like so:
* `let remaining = remaining`
*
* #### Multi view mode
* Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
* instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
* Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
*
* #### View index
* In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
*/
set onObserverFinalized(value) { this.input.next(value); }
/**
* (Optional) The view mode the directive will operate in:
* `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
* the existing view will be updated with data from the new call.
*
* `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
*
* Default is `'single'`.
*/
set onObserverFinalizedViewMode(viewMode) { this.viewMode = viewMode; }
/**
* (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - Wait for 3 seconds, then render the view.
* - `'10s'` - Wait for 10 seconds, then render the view.
* - `'0.5m'` - Wait for 30 seconds, then render the view.
* - `'100ms'` - Wait for 100 milliseconds, then render the view.
*
* Default is `0`, meaning immediately render the view.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverFinalizedShowAfter(duration) { this.showAfter = duration; }
/**
* (Optional) The duration for which the view should be rendered. When the duration passes, the view will be auto destroyed.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - The view will be destroyed after 3 seconds.
* - `'10s'` - The view will be destroyed after 10 seconds.
* - `'0.5m'` - The view will be destroyed after 30 seconds.
* - `'100ms'` - The view will be destroyed after 100 milliseconds.
*
* During the time the view is rendered, the context will be updated with a countdown object to facilitate any UI part used to
* indicate countdown to the user. The countdown will be exposed through the {@link OnObserverContext.remaining `remaining`}
* property and the elapsed time through {@link OnObserverContext.elapsed `elapsed`} property in the view context and can both
* be accessed be declaring a `let` variable (e.g. `let remaining = remaining`).
* See {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} for changing the updates interval.
*
* When unspecified, the view will be destroyed immediately once the observer detects a call different to the intercepted ones.
*
* TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
* Read more {@link OnObserverBaseDirective About render flow}.
**/
set onObserverFinalizedShowFor(duration) { this.showFor = duration; }
;
/**
* ### Only used when passing a value to {@link OnObserverBaseDirective.showFor `showFor`}.
*
* (Optional) The interval with which countdown updates should be made to the view's context before it auto destroys.
* The lower the value, the more updates will be made to the context, but the more resources your directive will consume.
*
* You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
* Numbers can be either integers or floats.
* For example:
* - `3000` - 3 seconds between each update.
* - `'10s'` - 10 seconds between each update.
* - `'0.5m'` - 30 seconds between each update.
* - `'100ms'` - 100 milliseconds between each update.
*
* You can also specify `'animationFrames'` so the countdown gets updated each time the browser is working on animations.
*
* When unspecified, the total duration of the countdown will be divided by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}
* to get a fixed interval which will make for {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`} countdown updates.
*/
set onObserverFinalizedCountdownInterval(duration) { this.countdownInterval = duration; }
;
static ngTemplateContextGuard(directive, context) { return true; }
}
OnObserverFinalizedDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverFinalizedDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive });
OnObserverFinalizedDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverFinalizedDirective, selector: "[onObserverFinalized]", inputs: { onObserverFinalized: "onObserverFinalized", onObserverFinalizedViewMode: "onObserverFinalizedViewMode", onObserverFinalizedShowAfter: "onObserverFinalizedShowAfter", onObserverFinalizedShowFor: "onObserverFinalizedShowFor", onObserverFinalizedCountdownInterval: "onObserverFinalizedCountdownInterval" }, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverFinalizedDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[onObserverFinalized]'
}]
}], propDecorators: { onObserverFinalized: [{
type: Input
}], onObserverFinalizedViewMode: [{
type: Input
}], onObserverFinalizedShowAfter: [{
type: Input
}], onObserverFinalizedShowFor: [{
type: Input
}], onObserverFinalizedCountdownInterval: [{
type: Input
}] } });
class OnObserverModule {
}
OnObserverModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
OnObserverModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: OnObserverModule, declarations: [OnObserverDirective,
OnObserverResolvingDirective,
OnObserverNextDirective,
OnObserverErrorDirective,
OnObserverCompleteDirective,
OnObserverActiveDirective,
OnObserverFinalizedDirective], exports: [OnObserverDirective,
OnObserverResolvingDirective,
OnObserverNextDirective,
OnObserverErrorDirective,
OnObserverCompleteDirective,
OnObserverActiveDirective,
OnObserverFinalizedDirective] });
OnObserverModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverModule, decorators: [{
type: NgModule,
args: [{
declarations: [
OnObserverDirective,
OnObserverResolvingDirective,
OnObserverNextDirective,
OnObserverErrorDirective,
OnObserverCompleteDirective,
OnObserverActiveDirective,
OnObserverFinalizedDirective,
],
exports: [
OnObserverDirective,
OnObserverResolvingDirective,
OnObserverNextDirective,
OnObserverErrorDirective,
OnObserverCompleteDirective,
OnObserverActiveDirective,
OnObserverFinalizedDirective,
]
}]
}] });
class CoreModule {
}
CoreModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
CoreModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.12", ngImport: i0, type: CoreModule, imports: [ObserveModule,
OnObserverModule], exports: [ObserveModule,
OnObserverModule] });
CoreModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: CoreModule, providers: [
WindowRefProviders,
DocumentRefProviders
], imports: [ObserveModule,
OnObserverModule, ObserveModule,
OnObserverModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: CoreModule, decorators: [{
type: NgModule,
args: [{
providers: [
WindowRefProviders,
DocumentRefProviders
],
imports: [
ObserveModule,
OnObserverModule
],
exports: [
ObserveModule,
OnObserverModule
]
}]
}] });
/**
* Provides tools for dynamically interacting with the head element.
*/
class HeadService {
constructor(document) {
this.document = document;
}
/**
* Creates a <script> element, configures it and adds it to the <head> element.
*
* @param {string} type The type of script being added (e.g. 'text/javascript', 'application/javascript', etc.).
* @param {string} src The source of the script being added.
* @param {ScriptConfigurator} [config] (Optional) The configurator for the element. If an object was specified, the element's properties will be overwritten by the
* configurator's properties. If a function was specified, the function is run on the element without any other intervention.
* @returns {ElementRef<HTMLScriptElement>} A reference to the new element which has already been added to the <head> element.
*/
addScriptElement(type, src, config) {
return this.addElement('script', (script) => {
// Config the script
script.type = type;
script.src = src;
this.applyConfiguration(script, config);
});
}
/**
* Creates a <link> element, configures it and adds it to the <head> element.
*
* @param {(LinkRel | LinkRel[])} rel The relationship(s) of the link with the current document.
* @param {LinkConfigurator} [config] (Optional) The configurator for the element. If an object was specified, the element's properties will be overwritten by the
* configurator's properties. If a function was specified, the function is run on the element without any other intervention.
* @returns {ElementRef<HTMLLinkElement>} A reference to the new element which has already been added to the <head> element.
*/
addLinkElement(rel, config) {
return this.addElement('link', link => {
link.rel = Array.isArray(rel) ? rel.join(' ') : rel;
this.applyConfiguration(link, config);
});
}
/**
* Removes the first <link> element matching the specified params.
*
* @param {(LinkRel | LinkRel[])} rel The rel attribute value to look for.
* @param {ElementConfig<HTMLLinkElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns {(HTMLLinkElement | null)} The removed element, or null if none found.
*/
removeLinkElement(rel, lookup) {
return this.removeElement('link', this.buildLinkLookup(rel, lookup));
}
/**
* Removes all <link> elements matching the specified params.
*
* @param {(LinkRel | LinkRel[])} rel The rel attribute value to look for.
* @param {ElementConfig<HTMLLinkElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns {NodeListOf<HTMLLinkElement>} The list of removed elements.
*/
removeLinkElements(rel, lookup) {
return this.removeElements('link', this.buildLinkLookup(rel, lookup));
}
buildLinkLookup(rel, lookup) {
// If rel is an array, join to a space-separated string
const fullRel = Array.isArray(rel) ? rel.join(' ') : rel;
// Combine with the lookup object and return
return Object.assign(lookup, { rel: fullRel });
}
/**
* Creates an element of the given name, configures it and adds it to the <head> element.
*
* @template TElement The type of element being created.
* @param {string} name The name of the tag to create.
* @param {ElementConfigurator<TElement>} [config] (Optional) The configurator for the element. If an object was specified, the element's properties will be overwritten by the
* configurator's properties. If a function was specified, the function is run on the element without any other intervention.
* @returns {ElementRef<TElement>} A reference to the new element which has already been added to the <head> element.
*/
addElement(name, config) {
// Get DOM elements
const document = this.document.nativeDocument;
const head = document.head;
// Create the element tag
const element = document.createElement(name);
// Apply configuration on the element
this.applyConfiguration(element, config);
// Add the element tag to the <head> element
head.appendChild(element);
return new ElementRef(element);
}
/**
* Applies a configurator on an element.
*
* @private
* @template TElement The type of html element being configured.
* @param {TElement} element The element to configure.
* @param {ElementConfigurator<TElement>} [config] (Optional) The configurator for the element. If an object was specified, the element's properties will be overwritten by the
* configurator's properties. If a function was specified, the function is run on the element without any other intervention.
*/
applyConfiguration(element, config) {
config instanceof Function ? config(element) : Object.assign(element, config);
}
/**
* Finds the first element matching in name and attributes to the specified params and removes it from the <head> element.
*
* @template TElement The type of element being searched for.
* @param {string} name The name of the tag to look for.
* @param {ElementConfig<TElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns The removed element, or null if none found.
*/
removeElement(name, lookup) {
const element = this.findElements(name, lookup)[0];
element?.remove();
return element || null;
}
/**
* Finds all elements matching in name and attributes to the specified params and removes them from the <head> element.
*
* @template TElement The type of element being searched for.
* @param {string} name The name of the tag to look for.
* @param {ElementConfig<TElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns The list of removed elements.
*/
removeElements(name, lookup) {
const elements = this.findElements(name, lookup);
elements.forEach(element => element.remove());
return elements;
}
/**
* Finds all elements inside of <head> which match in name and attributes to the specified params.
*
* @template TElement The type of element being searched for.
* @param {string} name The name of the tag to look for.
* @param {ElementConfig<TElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns A node list of all matching elements inside of <head>.
*/
findElements(name, lookup) {
// Get DOM elements
const document = this.document.nativeDocument;
const head = document.head;
const attributes = Object.keys(lookup).map(key => {
const attribute = key;
const value = lookup[attribute];
// If a wildcard was specified for the attribute...
return value === '**' ?
// ... Query only by attribute name
`[${String(attribute)}]` :
// Otherwise, match the exact value
`[${String(attribute)}="${value}"]`;
}).join('');
return head.querySelectorAll(`${name}${attributes}`);
}
/**
* Checks whether an element with the given tag name and attributes exists in <head>.
*
* @template TElement The type of element being searched for.
* @param {string} name The name of the tag to look for.
* @param {ElementConfig<TElement>} lookup A map of attribute names and values to match with the element. All must match for the element to be detected.
* To match all elements containing a specific attribute regardless of the attribute's value, use the `'**'` value.
* @returns {boolean} `true` if <head> contains a matching element; otherwise `false.
*/
contains(name, lookup) {
return !!this.findElements(name, lookup).length;
}
}
HeadService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: HeadService, deps: [{ token: DocumentRef }], target: i0.ɵɵFactoryTarget.Injectable });
HeadService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: HeadService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: HeadService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return [{ type: DocumentRef }]; } });
/**
* Generated bundle index. Do not edit.
*/
export { CoreModule, DOCUMENT, Destroyable, DocumentProvider, DocumentRef, DocumentRefProviders, HeadService, ObserveConcatDirective, ObserveDirective, ObserveJoinDirective, ObserveLatestDirective, ObserveMergeDirective, ObserveModule, OnObserverActiveDirective, OnObserverCompleteDirective, OnObserverDirective, OnObserverErrorDirective, OnObserverFinalizedDirective, OnObserverModule, OnObserverNextDirective, OnObserverResolvingDirective, WINDOW, WindowProvider, WindowRef, WindowRefProviders, windowFactory };
//# sourceMappingURL=bespunky-angular-zen-core.mjs.map