@ngxs/form-plugin
Version:
form plugin for @ngxs/store
343 lines (335 loc) • 12.7 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, ChangeDetectorRef, DestroyRef, Input, Directive, NgModule } from '@angular/core';
import { Actions, Store, ofActionDispatched, withNgxsPlugin } from '@ngxs/store';
import { getActionTypeFromInstance, setValue, getValue } from '@ngxs/store/plugins';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormGroupDirective } from '@angular/forms';
import { distinctUntilChanged, debounceTime } from 'rxjs';
class UpdateFormStatus {
payload;
static type = '[Forms] Update Form Status';
constructor(payload) {
this.payload = payload;
}
}
class UpdateFormValue {
payload;
static type = '[Forms] Update Form Value';
constructor(payload) {
this.payload = payload;
}
}
class UpdateForm {
payload;
static type = '[Forms] Update Form';
constructor(payload) {
this.payload = payload;
}
}
class UpdateFormDirty {
payload;
static type = '[Forms] Update Form Dirty';
constructor(payload) {
this.payload = payload;
}
}
class SetFormDirty {
payload;
static type = '[Forms] Set Form Dirty';
constructor(payload) {
this.payload = payload;
}
}
class SetFormPristine {
payload;
static type = '[Forms] Set Form Pristine';
constructor(payload) {
this.payload = payload;
}
}
class UpdateFormErrors {
payload;
static type = '[Forms] Update Form Errors';
constructor(payload) {
this.payload = payload;
}
}
class SetFormDisabled {
payload;
static type = '[Forms] Set Form Disabled';
constructor(payload) {
this.payload = payload;
}
}
class SetFormEnabled {
payload;
static type = '[Forms] Set Form Enabled';
constructor(payload) {
this.payload = payload;
}
}
class ResetForm {
payload;
static 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 ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPlugin, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPlugin });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPlugin, decorators: [{
type: Injectable
}] });
function isObjectLike(target) {
return target !== null && typeof target === 'object';
}
class NgxsFormDirective {
path = null;
set debounce(debounce) {
this._debounce = Number(debounce);
}
get debounce() {
return this._debounce;
}
_debounce = 100;
set clearDestroy(val) {
this._clearDestroy = val != null && `${val}` !== 'false';
}
get clearDestroy() {
return this._clearDestroy;
}
_clearDestroy = false;
_updating = false;
_actions$ = inject(Actions);
_store = inject(Store);
_formGroupDirective = inject(FormGroupDirective);
_cd = inject(ChangeDetectorRef);
_destroyRef = inject(DestroyRef);
constructor() {
this._destroyRef.onDestroy(() => {
if (this.clearDestroy) {
this._store.dispatch(new UpdateForm({
path: this.path,
value: null,
dirty: null,
status: null,
errors: null
}));
}
});
}
ngOnInit() {
const resetForm$ = this._actions$.pipe(ofActionDispatched(ResetForm), takeUntilDestroyed(this._destroyRef));
resetForm$.subscribe((action) => {
if (action.payload.path !== this.path) {
return;
}
this.form.reset(action.payload.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))
.pipe(takeUntilDestroyed(this._destroyRef))
.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)
});
}
debounceChange() {
const skipDebounceTime = this._formGroupDirective.control.updateOn !== 'change' || this._debounce < 0;
return skipDebounceTime
? (change) => change.pipe(takeUntilDestroyed(this._destroyRef))
: (change) => change.pipe(debounceTime(this._debounce), takeUntilDestroyed(this._destroyRef));
}
get form() {
return this._formGroupDirective.form;
}
getStateStream(path) {
return this._store
.select(state => getValue(state, path))
.pipe(takeUntilDestroyed(this._destroyRef));
}
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
/** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.2", 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: "20.3.2", ngImport: i0, type: NgxsFormDirective, decorators: [{
type: Directive,
args: [{ selector: '[ngxsForm]', standalone: true }]
}], ctorParameters: () => [], 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 ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPluginModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
/** @nocollapse */ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPluginModule, imports: [NgxsFormDirective], exports: [NgxsFormDirective] });
/** @nocollapse */ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: NgxsFormPluginModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", 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