UNPKG

@rx-signals/angular-provider

Version:
342 lines (331 loc) 17.6 kB
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