UNPKG

@ngxs/form-plugin

Version:

form plugin for @ngxs/store

329 lines (321 loc) 12.6 kB
import * as i0 from '@angular/core'; import { Injectable, inject, ChangeDetectorRef, Directive, Input, NgModule } from '@angular/core'; import { Actions, Store, ofActionDispatched, withNgxsPlugin } from '@ngxs/store'; import { getActionTypeFromInstance, setValue, getValue } from '@ngxs/store/plugins'; import { FormGroupDirective } from '@angular/forms'; import { ReplaySubject } from 'rxjs'; import { filter, takeUntil, distinctUntilChanged, debounceTime } from 'rxjs/operators'; class UpdateFormStatus { static { this.type = '[Forms] Update Form Status'; } constructor(payload) { this.payload = payload; } } class UpdateFormValue { static { this.type = '[Forms] Update Form Value'; } constructor(payload) { this.payload = payload; } } class UpdateForm { static { this.type = '[Forms] Update Form'; } constructor(payload) { this.payload = payload; } } class UpdateFormDirty { static { this.type = '[Forms] Update Form Dirty'; } constructor(payload) { this.payload = payload; } } class SetFormDirty { static { this.type = '[Forms] Set Form Dirty'; } constructor(payload) { this.payload = payload; } } class SetFormPristine { static { this.type = '[Forms] Set Form Pristine'; } constructor(payload) { this.payload = payload; } } class UpdateFormErrors { static { this.type = '[Forms] Update Form Errors'; } constructor(payload) { this.payload = payload; } } class SetFormDisabled { static { this.type = '[Forms] Set Form Disabled'; } constructor(payload) { this.payload = payload; } } class SetFormEnabled { static { this.type = '[Forms] Set Form Enabled'; } constructor(payload) { this.payload = payload; } } class ResetForm { static { this.type = '[Forms] Reset Form'; } constructor(payload) { this.payload = payload; } } class NgxsFormPlugin { handle(state, event, next) { const type = getActionTypeFromInstance(event); let nextState = state; if (type === UpdateFormValue.type || type === UpdateForm.type || type === ResetForm.type) { const { value } = event.payload; const payloadValue = Array.isArray(value) ? value.slice() : isObjectLike(value) ? { ...value } : value; const path = this.joinPathWithPropertyPath(event); nextState = setValue(nextState, path, payloadValue); } if (type === ResetForm.type) { const model = getValue(nextState, `${event.payload.path}.model`); nextState = setValue(nextState, `${event.payload.path}`, { model: model }); } if (type === UpdateFormStatus.type || type === UpdateForm.type) { nextState = setValue(nextState, `${event.payload.path}.status`, event.payload.status); } if (type === UpdateFormErrors.type || type === UpdateForm.type) { nextState = setValue(nextState, `${event.payload.path}.errors`, { ...event.payload.errors }); } if (type === UpdateFormDirty.type || type === UpdateForm.type) { nextState = setValue(nextState, `${event.payload.path}.dirty`, event.payload.dirty); } if (type === SetFormDirty.type) { nextState = setValue(nextState, `${event.payload}.dirty`, true); } if (type === SetFormPristine.type) { nextState = setValue(nextState, `${event.payload}.dirty`, false); } if (type === SetFormDisabled.type) { nextState = setValue(nextState, `${event.payload}.disabled`, true); } if (type === SetFormEnabled.type) { nextState = setValue(nextState, `${event.payload}.disabled`, false); } return next(nextState, event); } joinPathWithPropertyPath({ payload }) { let path = `${payload.path}.model`; if (payload.propertyPath) { path += `.${payload.propertyPath}`; } return path; } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } /** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPlugin }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPlugin, decorators: [{ type: Injectable }] }); function isObjectLike(target) { return target !== null && typeof target === 'object'; } class NgxsFormDirective { constructor() { this.path = null; this._debounce = 100; this._clearDestroy = false; this._updating = false; this._actions$ = inject(Actions); this._store = inject(Store); this._formGroupDirective = inject(FormGroupDirective); this._cd = inject(ChangeDetectorRef); this._destroy$ = new ReplaySubject(1); } set debounce(debounce) { this._debounce = Number(debounce); } get debounce() { return this._debounce; } set clearDestroy(val) { this._clearDestroy = val != null && `${val}` !== 'false'; } get clearDestroy() { return this._clearDestroy; } ngOnInit() { this._actions$ .pipe(ofActionDispatched(ResetForm), filter((action) => action.payload.path === this.path), takeUntil(this._destroy$)) .subscribe(({ payload: { value } }) => { this.form.reset(value); this.updateFormStateWithRawValue(true); this._cd.markForCheck(); }); this.getStateStream(`${this.path}.model`).subscribe(model => { if (this._updating || !model) { return; } this.form.patchValue(model); this._cd.markForCheck(); }); this.getStateStream(`${this.path}.dirty`).subscribe(dirty => { if (this.form.dirty === dirty || typeof dirty !== 'boolean') { return; } if (dirty) { this.form.markAsDirty(); } else { this.form.markAsPristine(); } this._cd.markForCheck(); }); // On first state change, sync form model, status and dirty with state this._store .selectOnce(state => getValue(state, this.path)) .subscribe(() => { this._store.dispatch([ new UpdateFormValue({ path: this.path, value: this.form.getRawValue() }), new UpdateFormStatus({ path: this.path, status: this.form.status }), new UpdateFormDirty({ path: this.path, dirty: this.form.dirty }) ]); }); this.getStateStream(`${this.path}.disabled`).subscribe(disabled => { if (this.form.disabled === disabled || typeof disabled !== 'boolean') { return; } if (disabled) { this.form.disable(); } else { this.form.enable(); } this._cd.markForCheck(); }); this._formGroupDirective .valueChanges.pipe(distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), this.debounceChange()) .subscribe(() => { this.updateFormStateWithRawValue(); }); this._formGroupDirective .statusChanges.pipe(distinctUntilChanged(), this.debounceChange()) .subscribe((status) => { this._store.dispatch(new UpdateFormStatus({ status, path: this.path })); }); } updateFormStateWithRawValue(withFormStatus) { if (this._updating) return; const value = this._formGroupDirective.control.getRawValue(); const actions = [ new UpdateFormValue({ path: this.path, value }), new UpdateFormDirty({ path: this.path, dirty: this._formGroupDirective.dirty }), new UpdateFormErrors({ path: this.path, errors: this._formGroupDirective.errors }) ]; if (withFormStatus) { actions.push(new UpdateFormStatus({ path: this.path, status: this._formGroupDirective.status })); } this._updating = true; this._store.dispatch(actions).subscribe({ error: () => (this._updating = false), complete: () => (this._updating = false) }); } ngOnDestroy() { this._destroy$.next(); if (this.clearDestroy) { this._store.dispatch(new UpdateForm({ path: this.path, value: null, dirty: null, status: null, errors: null })); } } debounceChange() { const skipDebounceTime = this._formGroupDirective.control.updateOn !== 'change' || this._debounce < 0; return skipDebounceTime ? (change) => change.pipe(takeUntil(this._destroy$)) : (change) => change.pipe(debounceTime(this._debounce), takeUntil(this._destroy$)); } get form() { return this._formGroupDirective.form; } getStateStream(path) { return this._store.select(state => getValue(state, path)).pipe(takeUntil(this._destroy$)); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: NgxsFormDirective, isStandalone: true, selector: "[ngxsForm]", inputs: { path: ["ngxsForm", "path"], debounce: ["ngxsFormDebounce", "debounce"], clearDestroy: ["ngxsFormClearOnDestroy", "clearDestroy"] }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormDirective, decorators: [{ type: Directive, args: [{ selector: '[ngxsForm]', standalone: true }] }], propDecorators: { path: [{ type: Input, args: ['ngxsForm'] }], debounce: [{ type: Input, args: ['ngxsFormDebounce'] }], clearDestroy: [{ type: Input, args: ['ngxsFormClearOnDestroy'] }] } }); class NgxsFormPluginModule { static forRoot() { return { ngModule: NgxsFormPluginModule, providers: [withNgxsPlugin(NgxsFormPlugin)] }; } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } /** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPluginModule, imports: [NgxsFormDirective], exports: [NgxsFormDirective] }); } /** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPluginModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: NgxsFormPluginModule, decorators: [{ type: NgModule, args: [{ imports: [NgxsFormDirective], exports: [NgxsFormDirective] }] }] }); function withNgxsFormPlugin() { return withNgxsPlugin(NgxsFormPlugin); } /** * The public api for consumers of @ngxs/form-plugin */ /** * Generated bundle index. Do not edit. */ export { NgxsFormDirective, NgxsFormPlugin, NgxsFormPluginModule, ResetForm, SetFormDirty, SetFormDisabled, SetFormEnabled, SetFormPristine, UpdateForm, UpdateFormDirty, UpdateFormErrors, UpdateFormStatus, UpdateFormValue, withNgxsFormPlugin }; //# sourceMappingURL=ngxs-form-plugin.mjs.map