@ngxs/form-plugin
Version:
form plugin for @ngxs/store
329 lines (321 loc) • 12.6 kB
JavaScript
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