@rx-signals/angular-provider
Version:
Angular provider for @rx-signals/store
342 lines (331 loc) • 17.6 kB
JavaScript
import * as i0 from '@angular/core';
import { NgModule, makeEnvironmentProviders, Pipe, Directive, Input, Output, HostListener } from '@angular/core';
import { Store, isNotNoValueType, toGetter, isOptionalLens, isValidModelValidationResult } from '@rx-signals/store';
import { BehaviorSubject, first } from 'rxjs';
// The root store is a singleton that should live as long as the whole application
// In case other lifecycles are required (e.g. restricted to the lifecycle of
// a certain module), corresponding child-stores should be derived from the
// root store
const rootStore = new Store();
class RxSignalsStoreModule {
/**
* Use withRootStore, if you want the root store (the store that shares the lifecycle of the whole application).
* If your module is a lazy-loaded feature module, it will still receive the same root-store instance, if you use withRootStore.
* This should be the standard case.
* Pass as many setup functions as you like.
*
* @param {SetupWithStore[]} setups - 0 to n optional setup functions (that will be called with the store instance)
* @returns {ModuleWithProviders<RxSignalsStoreModule>} the module providing the root-store
*/
static withRootStore(...setups) {
let doSetup = true;
return {
ngModule: RxSignalsStoreModule,
providers: [{ provide: Store, useFactory: () => {
// It's necessary to use a factory instead of useValue, because otherwise the doSetup block would not run in ngZone
if (doSetup) {
doSetup = false;
setups.forEach(setup => setup(rootStore));
}
return rootStore;
} }],
};
}
/**
* Use withChildStore, if you need a child store that is derived from the root-store.
* See store.createChildStore() for further documentation on child stores.
*
* @param {SetupWithStore[]} setups - 0 to n optional setup functions (that will be called with the child-store instance)
* @returns {ModuleWithProviders<RxSignalsStoreModule>} the module providing the child-store
*/
static withChildStore(...setups) {
let doSetup = true;
const childStore = rootStore.createChildStore();
return {
ngModule: RxSignalsStoreModule,
providers: [{ provide: Store, useFactory: () => {
if (doSetup) {
doSetup = false;
setups.forEach(setup => setup(childStore));
}
return childStore;
} }],
};
}
}
RxSignalsStoreModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxSignalsStoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
RxSignalsStoreModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: RxSignalsStoreModule });
RxSignalsStoreModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxSignalsStoreModule, providers: [{ provide: Store, useValue: rootStore }] // default to rootStore, if none of the static providers is used
});
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxSignalsStoreModule, decorators: [{
type: NgModule,
args: [{
providers: [{ provide: Store, useValue: rootStore }] // default to rootStore, if none of the static providers is used
}]
}] });
/**
* Use provideStore, in bootstrapApplication to provide the rx-signals root-store (the store that shares the lifecycle of the whole application).
* Pass as many setup functions as you like.
*
* @param {SetupWithStore[]} setups - 0 to n optional setup functions (that will be called with the store instance)
*/
const provideStore = (...setups) => {
let doSetup = true;
return makeEnvironmentProviders([
{ provide: Store, useFactory: () => {
// It's necessary to use a factory instead of useValue, because otherwise the doSetup block would not run in ngZone
if (doSetup) {
doSetup = false;
setups.forEach(setup => setup(rootStore));
}
return rootStore;
} }
]);
};
/**
* Use provideChildStore, in Route.providers to provide a rx-signals child-store to the route.
* See store.createChildStore() for further documentation on child stores.
* Pass as many setup functions as you like.
*
* @param {SetupWithStore[]} setups - 0 to n optional setup functions (that will be called with the child-store instance)
*/
const provideChildStore = (...setups) => {
let doSetup = true;
const childStore = rootStore.createChildStore();
return makeEnvironmentProviders([
{ provide: Store, useFactory: () => {
// It's necessary to use a factory instead of useValue, because otherwise the doSetup block would not run in ngZone
if (doSetup) {
doSetup = false;
setups.forEach(setup => setup(childStore));
}
return childStore;
} }
]);
};
/**
* Use this pipe to transform the rx-signals NoValueType to undefined
*/
class RxsNoValueToUndefinedPipe {
transform(value) {
return isNotNoValueType(value) ? value : undefined;
}
}
RxsNoValueToUndefinedPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxsNoValueToUndefinedPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
RxsNoValueToUndefinedPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: RxsNoValueToUndefinedPipe, isStandalone: true, name: "rxsNoValueToUndefined" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxsNoValueToUndefinedPipe, decorators: [{
type: Pipe,
args: [{
name: 'rxsNoValueToUndefined',
standalone: true,
}]
}] });
/**
* Use this pipe to get a property from a union that might contain a value with the property.
* E.g., if you have a value of type `T = number | { a: string | { b: boolean } }`,
* you can use `value | pick:'a' | pick:'b'` to get `boolean | undefined`.
* If your IDE complains about "Type argument cannot be inferred from usage", the reason is, that it infers `'a'` as type `string`
* and not as type `'a'`.
* Future versions of Angular will hopefully treat string literals in templates as literal types (just like in code), or at least allow for casting.
* As long as this is not the case, you have three options: (a) you could create a `readonly aKey: 'a' = 'a'` and use `pick:aKey`,
* or (b) you create a lens `readonly myLens = getLens<T>().k('a').k('b')` and use either `myLens | lens:value`, or `value | lens:myLens`
* or (c) you can use the anyLensKey pipe (that accepts any key) like `myLens | anyLensKey:'a' | anyLensKey:'b' | lens:value`, though the latter will return unknown | undefined,
* while the first to options will return the correct type (boolean | undefined).
*
* @deprecated use LensPipe instead
*/
class PickPipe {
transform(value, key) {
return toGetter(value).k(key).get();
}
}
PickPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: PickPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
PickPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: PickPipe, isStandalone: true, name: "pick" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: PickPipe, decorators: [{
type: Pipe,
args: [{
name: 'pick',
standalone: true,
}]
}] });
/**
* This pipe can be used to get a new `OptionalLens<T, unknown>` from an `OptionalLens<T, P>` and a key `K`.
* It is a workaround you can use instead of the lensKey pipe, as long as Angular does not treat string literals in
* templates as literal types (see `LensKeyPipe` for further information).
*/
class AnyLensKeyPipe {
transform(l, k) {
return l.k(k);
}
}
AnyLensKeyPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: AnyLensKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
AnyLensKeyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: AnyLensKeyPipe, isStandalone: true, name: "anyLensKey" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: AnyLensKeyPipe, decorators: [{
type: Pipe,
args: [{
name: 'anyLensKey',
standalone: true,
}]
}] });
/**
* This pipe can be used to get a new `OptionalLens<T, P[K]>` from an `OptionalLens<T, P>` and a key `K`.
* E.g., if you have a `value` of type `T = number | { a: string | { b: boolean } }` and you have
* a lens `myLens: OptionalLens<T, boolean> = getLens<T>().k('a).k('b)`,
* you could use `myLens | lensKey:'a' | lensKey:'b' | lens:value` to get a result of type `boolean | undefined`.
* If your IDE complains about "Type argument cannot be inferred from usage", the reason is, that it infers `'a'` as type `string`
* and not as type `'a'`.
* Future versions of Angular will hopefully treat string literals in templates as literal types (just like in code), or at least allow for casting.
* As long as this is not the case, you have two options:
* (a) you create a lens `readonly myLens = getLens<T>().k('a').k('b')` and use either `myLens | lens:value`, or `value | lens:myLens`
* or (c) you can use the anyLensKey pipe (that accepts any key) like `myLens | anyLensKey:'a' | anyLensKey:'b' | lens:value`, though the latter will return unknown | undefined,
* while the first to options will return the correct type (boolean | undefined).
*/
class LensKeyPipe {
transform(l, k) {
return l.k(k);
}
}
LensKeyPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: LensKeyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
LensKeyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: LensKeyPipe, isStandalone: true, name: "lensKey" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: LensKeyPipe, decorators: [{
type: Pipe,
args: [{
name: 'lensKey',
standalone: true,
}]
}] });
/**
* This pipe can be used to get a value of type `P | undefined` from an `OptionalLens<T, P>`.
* E.g., if you have a `value` of type `T = number | { a: string | { b: boolean } }` and you have
* a lens `myLens: OptionalLens<T, boolean> = getLens<T>().k('a).k('b)`,
* you could either use `myLens | lens:value`, or `value | lens:myLens` to get a result of type `boolean | undefined`.
* This access is fully type-safe.
*/
class LensPipe {
transform(valueOrLens1, valueOrLens2) {
return isOptionalLens(valueOrLens1)
? valueOrLens1.get(valueOrLens2)
: valueOrLens2.get(valueOrLens1);
}
}
LensPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: LensPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
LensPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "15.1.5", ngImport: i0, type: LensPipe, isStandalone: true, name: "lens" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: LensPipe, decorators: [{
type: Pipe,
args: [{
name: 'lens',
standalone: true,
}]
}] });
/**
* Use this directive to couple a validation result `VR` for a certain property with the corresponding
* isValid-status and the hasFocus- and touched-status of the corresponding control.
* The isValid-status is determined using `isValidModelValidationResult()` from rx-signals/store.
* E.g. given a model `type Model = { name: 'string'; };` you might have the following control (showing
* only the attributes/properties relevant to this example):
* ```ts
* <div>
* <label for="name">Name</label>
* <input
* id="name"
* [rxsValidation]="model.validation | pick : 'name'"
* #nameValidation="rxsValidation"
* />
* </div>
* <ng-container *ngIf="nameValidation.rxsValidationState | async as validation">
* <div *ngIf="!validation.valid && validation.touched" class="error">
* {{ validation.value }}
* </div>
* </ng-container>
* </div>
* ```
*
* Analogously to standard Angular forms, this directive will set classes `ng-touched`, `ng-untouched`, `ng-valid` and `ng-invalid`
* (it will not set classes for pristine- or dirty-status. cause these are attributes you can derive from your model behaviors)
*/
class RxsValidationDirective {
set rxsValidation(validationResult) {
this.valid = isValidModelValidationResult(validationResult);
this.value = validationResult;
this.update();
}
constructor(el, renderer) {
this.el = el;
this.renderer = renderer;
this.validationState$ = new BehaviorSubject({
value: undefined,
valid: false,
hasFocus: false,
touched: false,
});
this.hasFocus = false;
this.touched = false;
this.value = undefined;
this.valid = false;
this.rxsValidationState = this.validationState$.asObservable();
this.update();
}
onGotFocus() {
this.hasFocus = true;
this.touched = true;
this.update();
}
update() {
this.validationState$.pipe(first()).subscribe(state => {
if (this.touched && state.touched !== this.touched) {
this.renderer.removeClass(this.el.nativeElement, 'ng-untouched');
this.renderer.addClass(this.el.nativeElement, 'ng-touched');
}
else if (!state.touched) {
this.renderer.addClass(this.el.nativeElement, 'ng-untouched');
}
if (state.valid !== this.valid) {
if (this.valid) {
this.renderer.removeClass(this.el.nativeElement, 'ng-invalid');
this.renderer.addClass(this.el.nativeElement, 'ng-valid');
}
else {
this.renderer.removeClass(this.el.nativeElement, 'ng-valid');
this.renderer.addClass(this.el.nativeElement, 'ng-invalid');
}
}
if (state.touched !== this.touched ||
state.valid !== this.valid ||
state.hasFocus !== this.hasFocus ||
state.value !== this.value) {
this.validationState$.next({
touched: this.touched,
hasFocus: this.hasFocus,
valid: this.valid,
value: this.value,
});
}
});
}
}
RxsValidationDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxsValidationDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
RxsValidationDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.5", type: RxsValidationDirective, isStandalone: true, selector: "[rxsValidation]", inputs: { rxsValidation: "rxsValidation" }, outputs: { rxsValidationState: "rxsValidationState" }, host: { listeners: { "focusin": "onGotFocus()" } }, exportAs: ["rxsValidation"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.5", ngImport: i0, type: RxsValidationDirective, decorators: [{
type: Directive,
args: [{
// eslint-disable-next-line @angular-eslint/directive-selector
selector: '[rxsValidation]',
exportAs: 'rxsValidation',
standalone: true,
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { rxsValidation: [{
type: Input
}], rxsValidationState: [{
type: Output
}], onGotFocus: [{
type: HostListener,
args: ['focusin']
}] } });
/*
* Public API Surface of angular-provider
*/
/**
* Generated bundle index. Do not edit.
*/
export { AnyLensKeyPipe, LensKeyPipe, LensPipe, PickPipe, RxSignalsStoreModule, RxsNoValueToUndefinedPipe, RxsValidationDirective, provideChildStore, provideStore };
//# sourceMappingURL=rx-signals-angular-provider.mjs.map