UNPKG

ngrx-forms

Version:

Proper integration of forms in Angular 4 applications using ngrx

1,322 lines (1,279 loc) 148 kB
import * as i0 from '@angular/core'; import { InjectionToken, forwardRef, Directive, HostListener, Input, PLATFORM_ID, Optional, Inject, Host, Self, HostBinding, EventEmitter, Output, NgModule } from '@angular/core'; import { isPlatformBrowser, DOCUMENT } from '@angular/common'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i1 from '@ngrx/store'; import { ActionsSubject } from '@ngrx/store'; // NOTE: the explicit type declaration for the `TYPE` properties is required // for the output declarations to properly use the literal string type instead // of just `string` class SetValueAction { constructor(controlId, value) { this.controlId = controlId; this.value = value; this.type = SetValueAction.TYPE; } } SetValueAction.TYPE = 'ngrx/forms/SET_VALUE'; class SetErrorsAction { constructor(controlId, errors) { this.controlId = controlId; this.errors = errors; this.type = SetErrorsAction.TYPE; } } SetErrorsAction.TYPE = 'ngrx/forms/SET_ERRORS'; class SetAsyncErrorAction { constructor(controlId, name, value) { this.controlId = controlId; this.name = name; this.value = value; this.type = SetAsyncErrorAction.TYPE; } } SetAsyncErrorAction.TYPE = 'ngrx/forms/SET_ASYNC_ERROR'; class ClearAsyncErrorAction { constructor(controlId, name) { this.controlId = controlId; this.name = name; this.type = ClearAsyncErrorAction.TYPE; } } ClearAsyncErrorAction.TYPE = 'ngrx/forms/CLEAR_ASYNC_ERROR'; class StartAsyncValidationAction { constructor(controlId, name) { this.controlId = controlId; this.name = name; this.type = StartAsyncValidationAction.TYPE; } } StartAsyncValidationAction.TYPE = 'ngrx/forms/START_ASYNC_VALIDATION'; class MarkAsDirtyAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsDirtyAction.TYPE; } } MarkAsDirtyAction.TYPE = 'ngrx/forms/MARK_AS_DIRTY'; class MarkAsPristineAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsPristineAction.TYPE; } } MarkAsPristineAction.TYPE = 'ngrx/forms/MARK_AS_PRISTINE'; class EnableAction { constructor(controlId) { this.controlId = controlId; this.type = EnableAction.TYPE; } } EnableAction.TYPE = 'ngrx/forms/ENABLE'; class DisableAction { constructor(controlId) { this.controlId = controlId; this.type = DisableAction.TYPE; } } DisableAction.TYPE = 'ngrx/forms/DISABLE'; class MarkAsTouchedAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsTouchedAction.TYPE; } } MarkAsTouchedAction.TYPE = 'ngrx/forms/MARK_AS_TOUCHED'; class MarkAsUntouchedAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsUntouchedAction.TYPE; } } MarkAsUntouchedAction.TYPE = 'ngrx/forms/MARK_AS_UNTOUCHED'; class FocusAction { constructor(controlId) { this.controlId = controlId; this.type = FocusAction.TYPE; } } FocusAction.TYPE = 'ngrx/forms/FOCUS'; class UnfocusAction { constructor(controlId) { this.controlId = controlId; this.type = UnfocusAction.TYPE; } } UnfocusAction.TYPE = 'ngrx/forms/UNFOCUS'; class MarkAsSubmittedAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsSubmittedAction.TYPE; } } MarkAsSubmittedAction.TYPE = 'ngrx/forms/MARK_AS_SUBMITTED'; class MarkAsUnsubmittedAction { constructor(controlId) { this.controlId = controlId; this.type = MarkAsUnsubmittedAction.TYPE; } } MarkAsUnsubmittedAction.TYPE = 'ngrx/forms/MARK_AS_UNSUBMITTED'; class AddArrayControlAction { constructor(controlId, value, index) { this.controlId = controlId; this.value = value; this.index = index; this.type = AddArrayControlAction.TYPE; } } AddArrayControlAction.TYPE = 'ngrx/forms/ADD_ARRAY_CONTROL'; class AddGroupControlAction { constructor(controlId, name, value) { this.controlId = controlId; this.name = name; this.value = value; this.type = AddGroupControlAction.TYPE; } } AddGroupControlAction.TYPE = 'ngrx/forms/ADD_GROUP_CONTROL'; class RemoveArrayControlAction { constructor(controlId, index) { this.controlId = controlId; this.index = index; this.type = RemoveArrayControlAction.TYPE; } } RemoveArrayControlAction.TYPE = 'ngrx/forms/REMOVE_ARRAY_CONTROL'; class SwapArrayControlAction { constructor(controlId, fromIndex, toIndex) { this.controlId = controlId; this.fromIndex = fromIndex; this.toIndex = toIndex; this.type = SwapArrayControlAction.TYPE; } } SwapArrayControlAction.TYPE = 'ngrx/forms/SWAP_ARRAY_CONTROL'; class MoveArrayControlAction { constructor(controlId, fromIndex, toIndex) { this.controlId = controlId; this.fromIndex = fromIndex; this.toIndex = toIndex; this.type = MoveArrayControlAction.TYPE; } } MoveArrayControlAction.TYPE = 'ngrx/forms/MOVE_ARRAY_CONTROL'; class RemoveGroupControlAction { constructor(controlId, name) { this.controlId = controlId; this.name = name; this.type = RemoveGroupControlAction.TYPE; } } RemoveGroupControlAction.TYPE = 'ngrx/forms/REMOVE_CONTROL'; class SetUserDefinedPropertyAction { constructor(controlId, name, value) { this.controlId = controlId; this.name = name; this.value = value; this.type = SetUserDefinedPropertyAction.TYPE; } } SetUserDefinedPropertyAction.TYPE = 'ngrx/forms/SET_USER_DEFINED_PROPERTY'; class ResetAction { constructor(controlId) { this.controlId = controlId; this.type = ResetAction.TYPE; } } ResetAction.TYPE = 'ngrx/forms/RESET'; function isNgrxFormsAction(action) { return !!action.type && action.type.startsWith('ngrx/forms/'); } const ALL_NGRX_FORMS_ACTION_TYPES = [ SetValueAction.TYPE, SetErrorsAction.TYPE, SetAsyncErrorAction.TYPE, ClearAsyncErrorAction.TYPE, StartAsyncValidationAction.TYPE, MarkAsDirtyAction.TYPE, MarkAsPristineAction.TYPE, EnableAction.TYPE, DisableAction.TYPE, MarkAsTouchedAction.TYPE, MarkAsUntouchedAction.TYPE, FocusAction.TYPE, UnfocusAction.TYPE, MarkAsSubmittedAction.TYPE, MarkAsUnsubmittedAction.TYPE, AddGroupControlAction.TYPE, RemoveGroupControlAction.TYPE, AddArrayControlAction.TYPE, RemoveArrayControlAction.TYPE, SetUserDefinedPropertyAction.TYPE, ResetAction.TYPE, SwapArrayControlAction.TYPE, MoveArrayControlAction.TYPE, ]; function isBoxed(value) { return !!value && value.__boxed === ''; } function box(value) { return { __boxed: '', value, }; } function unbox(value) { if (['string', 'boolean', 'number', 'undefined'].indexOf(typeof value) >= 0 || value === null || value === undefined) { return value; } if (isBoxed(value)) { return value.value; } if (Array.isArray(value)) { return value.map(unbox); } return Object.keys(value).reduce((a, k) => Object.assign(a, { [k]: unbox(value[k]) }), {}); } function isEmpty(obj) { return Object.keys(obj).length === 0; } const defaultOptions = { treatUndefinedAndMissingKeyAsSame: false, }; function deepEquals(_1, _2, options = {}) { const { treatUndefinedAndMissingKeyAsSame } = Object.assign({}, defaultOptions, options); const leftChain = []; const rightChain = []; function compare2Objects(x, y) { let p; // remember that NaN === NaN returns false // and isNaN(undefined) returns true if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') { return true; } // Compare primitives and functions. // Check if both arguments link to the same object. // Especially useful on the step where we compare prototypes if (x === y) { return true; } // Works in case when functions are created in constructor. // Comparing dates is a common scenario. Another built-ins? // We can even handle functions passed across iframes if ((typeof x === 'function' && typeof y === 'function') || (x instanceof Date && y instanceof Date) || (x instanceof RegExp && y instanceof RegExp) || (x instanceof String && y instanceof String) || (x instanceof Number && y instanceof Number)) { return x.toString() === y.toString(); } // At last checking prototypes as good as we can if (!(x instanceof Object && y instanceof Object)) { return false; } if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) { return false; } if (x.constructor !== y.constructor) { return false; } // Check for infinitive linking loops if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) { return false; } // Quick checking of one object being a subset of another. for (p in y) { if (treatUndefinedAndMissingKeyAsSame && y.hasOwnProperty(p) && !x.hasOwnProperty(p) && y[p] === undefined) { continue; } if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { return false; } else if (typeof y[p] !== typeof x[p]) { return false; } } // tslint:disable:forin for (p in x) { if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) { if (!treatUndefinedAndMissingKeyAsSame || !x.hasOwnProperty(p) || y.hasOwnProperty(p) || x[p] !== undefined) { return false; } } switch (typeof (x[p])) { case 'object': case 'function': leftChain.push(x); rightChain.push(y); if (!compare2Objects(x[p], y[p])) { return false; } leftChain.pop(); rightChain.pop(); break; default: if (x[p] !== y[p]) { return false; } break; } } return true; } if (arguments.length <= 1) { throw new Error('Need two or more arguments to compare'); } return compare2Objects(_1, _2); } /** * This function determines if a value is a form state. */ function isFormState(state) { return !!state && state.hasOwnProperty('id') && state.hasOwnProperty('value') && state.hasOwnProperty('errors'); } /** * This function determines if a value is an array state. */ function isArrayState(state) { return isFormState(state) && state.hasOwnProperty('controls') && Array.isArray(state.controls); } /** * This function determines if a value is a group state. */ function isGroupState(state) { return isFormState(state) && state.hasOwnProperty('controls') && !Array.isArray(state.controls) && typeof state.controls !== 'function'; } function createChildState(id, childValue) { if (isBoxed(childValue)) { return createFormControlState(id, childValue); } if (childValue !== null && Array.isArray(childValue)) { return createFormArrayState(id, childValue); } if (childValue !== null && typeof childValue === 'object') { return createFormGroupState(id, childValue); } return createFormControlState(id, childValue); } function verifyFormControlValueIsValid(value) { if (value === null || ['string', 'number', 'boolean', 'undefined'].indexOf(typeof value) >= 0) { return value; } if (!isBoxed(value)) { const errorMsg = 'Form control states only support undefined, null, string, number, and boolean values as well as boxed values'; throw new Error(`${errorMsg}; got ${JSON.stringify(value)} of type ${typeof value}`); // `; } if (value.value === null || ['string', 'number', 'boolean', 'undefined'].indexOf(typeof value.value) >= 0) { return value; } const serialized = JSON.stringify(value); const deserialized = JSON.parse(serialized); if (deepEquals(value, deserialized, { treatUndefinedAndMissingKeyAsSame: true })) { return value; } throw new Error(`A form control value must be serializable (i.e. value === JSON.parse(JSON.stringify(value))), got: ${JSON.stringify(value)}`); } /** * This function creates a form control state with an ID and a value. */ function createFormControlState(id, value) { return { id, value: verifyFormControlValueIsValid(value), errors: {}, pendingValidations: [], isValidationPending: false, isValid: true, isInvalid: false, isEnabled: true, isDisabled: false, isDirty: false, isPristine: true, isTouched: false, isUntouched: true, isSubmitted: false, isUnsubmitted: true, isFocused: false, isUnfocused: true, userDefinedProperties: {}, }; } function getFormGroupValue(controls, originalValue) { let hasChanged = Object.keys(originalValue).length !== Object.keys(controls).length; const newValue = Object.keys(controls).reduce((res, key) => { const control = controls[key]; hasChanged = hasChanged || originalValue[key] !== control.value; res[key] = control.value; return res; }, {}); return hasChanged ? newValue : originalValue; } function getFormGroupErrors(controls, originalErrors) { let hasChanged = false; const groupErrors = Object.keys(originalErrors) .filter(key => !key.startsWith('_')) .reduce((res, key) => Object.assign(res, { [key]: originalErrors[key] }), {}); const newErrors = Object.keys(controls).reduce((res, key) => { const control = controls[key]; const controlErrors = control.errors; if (!isEmpty(controlErrors)) { hasChanged = hasChanged || originalErrors[`_${key}`] !== controlErrors; Object.assign(res, { [`_${key}`]: control.errors }); } else { hasChanged = hasChanged || originalErrors.hasOwnProperty(`_${key}`); } return res; }, groupErrors); hasChanged = hasChanged || Object.keys(originalErrors).length !== Object.keys(newErrors).length; return hasChanged ? newErrors : originalErrors; } function computeGroupState(id, controls, value, errors, pendingValidations, userDefinedProperties, flags) { value = getFormGroupValue(controls, value); errors = getFormGroupErrors(controls, errors); const isValid = isEmpty(errors); const isDirty = flags.wasOrShouldBeDirty || Object.keys(controls).some(key => controls[key].isDirty); const isEnabled = flags.wasOrShouldBeEnabled || Object.keys(controls).some(key => controls[key].isEnabled); const isTouched = flags.wasOrShouldBeTouched || Object.keys(controls).some(key => controls[key].isTouched); const isSubmitted = flags.wasOrShouldBeSubmitted || Object.keys(controls).some(key => controls[key].isSubmitted); const isValidationPending = pendingValidations.length > 0 || Object.keys(controls).some(key => controls[key].isValidationPending); return { id, value, errors, pendingValidations, isValidationPending, isValid, isInvalid: !isValid, isEnabled, isDisabled: !isEnabled, isDirty, isPristine: !isDirty, isTouched, isUntouched: !isTouched, isSubmitted, isUnsubmitted: !isSubmitted, userDefinedProperties, controls, }; } /** * This function creates a form group state with an ID and a value. * From the value the shape of the group state is inferred, i.e. * object properties are inferred as form groups, array properties * are inferred as form arrays, and primitive properties are inferred * as form controls. */ function createFormGroupState(id, initialValue) { const controls = Object.keys(initialValue) .map((key) => [key, createChildState(`${id}.${key}`, initialValue[key])]) .reduce((res, [controlId, state]) => Object.assign(res, { [controlId]: state }), {}); return computeGroupState(id, controls, initialValue, {}, [], {}, { wasOrShouldBeEnabled: true }); } function getFormArrayValue(controls, originalValue) { let hasChanged = Object.keys(originalValue).length !== Object.keys(controls).length; const newValue = controls.map((state, i) => { hasChanged = hasChanged || originalValue[i] !== state.value; return state.value; }); return hasChanged ? newValue : originalValue; } function getFormArrayErrors(controls, originalErrors) { let hasChanged = false; const groupErrors = Object.keys(originalErrors) .filter(key => !key.startsWith('_')) .reduce((res, key) => Object.assign(res, { [key]: originalErrors[key] }), {}); const newErrors = controls.reduce((res, state, i) => { const controlErrors = state.errors; if (!isEmpty(controlErrors)) { hasChanged = hasChanged || originalErrors[`_${i}`] !== controlErrors; Object.assign(res, { [`_${i}`]: controlErrors }); } else { hasChanged = hasChanged || originalErrors.hasOwnProperty(`_${i}`); } return res; }, groupErrors); hasChanged = hasChanged || Object.keys(originalErrors).length !== Object.keys(newErrors).length; return hasChanged ? newErrors : originalErrors; } function computeArrayState(id, inferredControls, value, errors, pendingValidations, userDefinedProperties, flags) { const controls = inferredControls; value = getFormArrayValue(controls, value); errors = getFormArrayErrors(controls, errors); const isValid = isEmpty(errors); const isDirty = flags.wasOrShouldBeDirty || controls.some(state => state.isDirty); const isEnabled = flags.wasOrShouldBeEnabled || controls.some(state => state.isEnabled); const isTouched = flags.wasOrShouldBeTouched || controls.some(state => state.isTouched); const isSubmitted = flags.wasOrShouldBeSubmitted || controls.some(state => state.isSubmitted); const isValidationPending = pendingValidations.length > 0 || controls.some(state => state.isValidationPending); return { id, value, errors, pendingValidations, isValidationPending, isValid, isInvalid: !isValid, isEnabled, isDisabled: !isEnabled, isDirty, isPristine: !isDirty, isTouched, isUntouched: !isTouched, isSubmitted, isUnsubmitted: !isSubmitted, userDefinedProperties, controls: inferredControls, }; } /** * This function creates a form array state with an ID and a value. * From the value the shape of the array state is inferred, i.e. * object values are inferred as form groups, array values * are inferred as form arrays, and primitive values are inferred * as form controls. */ function createFormArrayState(id, initialValue) { const controls = initialValue .map((value, i) => createChildState(`${id}.${i}`, value)); return computeArrayState(id, controls, initialValue, {}, [], {}, { wasOrShouldBeEnabled: true }); } function clearAsyncErrorReducer$2(state, action) { if (action.type !== ClearAsyncErrorAction.TYPE) { return state; } const name = `$${action.name}`; let errors = state.errors; if (errors.hasOwnProperty(name)) { errors = Object.assign({}, state.errors); delete errors[name]; } const pendingValidations = state.pendingValidations.filter(v => v !== action.name); const isValid = isEmpty(errors); if (errors === state.errors && isValid === state.isValid && pendingValidations.length === state.pendingValidations.length) { return state; } return Object.assign(Object.assign({}, state), { isValid, isInvalid: !isValid, errors, pendingValidations, isValidationPending: pendingValidations.length > 0 }); } function disableReducer$2(state, action) { if (action.type !== DisableAction.TYPE) { return state; } if (state.isDisabled) { return state; } return Object.assign(Object.assign({}, state), { isEnabled: false, isDisabled: true, isValid: true, isInvalid: false, errors: {}, pendingValidations: [], isValidationPending: false }); } function enableReducer$2(state, action) { if (action.type !== EnableAction.TYPE) { return state; } if (state.isEnabled) { return state; } return Object.assign(Object.assign({}, state), { isEnabled: true, isDisabled: false }); } function focusReducer(state, action) { if (action.type !== FocusAction.TYPE) { return state; } if (state.isFocused) { return state; } return Object.assign(Object.assign({}, state), { isFocused: true, isUnfocused: false }); } function markAsDirtyReducer$2(state, action) { if (action.type !== MarkAsDirtyAction.TYPE) { return state; } if (state.isDirty) { return state; } return Object.assign(Object.assign({}, state), { isDirty: true, isPristine: false }); } function markAsPristineReducer$2(state, action) { if (action.type !== MarkAsPristineAction.TYPE) { return state; } if (state.isPristine) { return state; } return Object.assign(Object.assign({}, state), { isDirty: false, isPristine: true }); } function markAsSubmittedReducer$2(state, action) { if (action.type !== MarkAsSubmittedAction.TYPE) { return state; } if (state.isSubmitted) { return state; } return Object.assign(Object.assign({}, state), { isSubmitted: true, isUnsubmitted: false }); } function markAsTouchedReducer$2(state, action) { if (action.type !== MarkAsTouchedAction.TYPE) { return state; } if (state.isTouched) { return state; } return Object.assign(Object.assign({}, state), { isTouched: true, isUntouched: false }); } function markAsUnsubmittedReducer$2(state, action) { if (action.type !== MarkAsUnsubmittedAction.TYPE) { return state; } if (state.isUnsubmitted) { return state; } return Object.assign(Object.assign({}, state), { isSubmitted: false, isUnsubmitted: true }); } function markAsUntouchedReducer$2(state, action) { if (action.type !== MarkAsUntouchedAction.TYPE) { return state; } if (state.isUntouched) { return state; } return Object.assign(Object.assign({}, state), { isTouched: false, isUntouched: true }); } function resetReducer$2(state, action) { if (action.type !== ResetAction.TYPE) { return state; } if (state.isPristine && state.isUntouched && state.isUnsubmitted) { return state; } return Object.assign(Object.assign({}, state), { isDirty: false, isPristine: true, isTouched: false, isUntouched: true, isSubmitted: false, isUnsubmitted: true }); } function setAsyncErrorReducer$2(state, action) { if (action.type !== SetAsyncErrorAction.TYPE) { return state; } if (state.isDisabled) { return state; } const name = `$${action.name}`; let value = action.value; if (deepEquals(state.errors[name], action.value)) { value = state.errors[name]; } const errors = Object.assign(Object.assign({}, state.errors), { [name]: value }); const pendingValidations = state.pendingValidations.filter(v => v !== action.name); return Object.assign(Object.assign({}, state), { isValid: false, isInvalid: true, errors, pendingValidations, isValidationPending: pendingValidations.length > 0 }); } function setErrorsReducer$2(state, action) { if (action.type !== SetErrorsAction.TYPE) { return state; } if (state.isDisabled) { return state; } if (state.errors === action.errors) { return state; } if (deepEquals(state.errors, action.errors)) { return state; } if (!action.errors || typeof action.errors !== 'object' || Array.isArray(action.errors)) { throw new Error(`Control errors must be an object; got ${action.errors}`); // `; } if (Object.keys(action.errors).some(key => key.startsWith('$'))) { throw new Error(`Control errors must not use $ as a prefix; got ${JSON.stringify(action.errors)}`); // `; } const asyncErrors = Object.keys(state.errors) .filter(key => key.startsWith('$')) .reduce((res, key) => Object.assign(res, { [key]: state.errors[key] }), {}); const newErrors = isEmpty(asyncErrors) ? action.errors : Object.assign(asyncErrors, action.errors); const isValid = isEmpty(newErrors); return Object.assign(Object.assign({}, state), { isValid, isInvalid: !isValid, errors: newErrors }); } function setUserDefinedPropertyReducer$2(state, action) { if (action.type !== SetUserDefinedPropertyAction.TYPE) { return state; } if (state.userDefinedProperties[action.name] === action.value) { return state; } return Object.assign(Object.assign({}, state), { userDefinedProperties: Object.assign(Object.assign({}, state.userDefinedProperties), { [action.name]: action.value }) }); } function setValueReducer$2(state, action) { if (action.type !== SetValueAction.TYPE) { return state; } if (state.value === action.value) { return state; } return Object.assign(Object.assign({}, state), { value: verifyFormControlValueIsValid(action.value) }); } function startAsyncValidationReducer$2(state, action) { if (action.type !== StartAsyncValidationAction.TYPE) { return state; } if (state.pendingValidations.indexOf(action.name) >= 0) { return state; } return Object.assign(Object.assign({}, state), { pendingValidations: [...state.pendingValidations, action.name], isValidationPending: true }); } function unfocusReducer(state, action) { if (action.type !== UnfocusAction.TYPE) { return state; } if (state.isUnfocused) { return state; } return Object.assign(Object.assign({}, state), { isFocused: false, isUnfocused: true }); } function formControlReducerInternal(state, action) { if (isGroupState(state) || isArrayState(state)) { throw new Error('The state must be a control state'); } if (action.controlId !== state.id) { return state; } state = setValueReducer$2(state, action); state = setErrorsReducer$2(state, action); state = startAsyncValidationReducer$2(state, action); state = setAsyncErrorReducer$2(state, action); state = clearAsyncErrorReducer$2(state, action); state = enableReducer$2(state, action); state = disableReducer$2(state, action); state = focusReducer(state, action); state = unfocusReducer(state, action); state = markAsDirtyReducer$2(state, action); state = markAsPristineReducer$2(state, action); state = markAsTouchedReducer$2(state, action); state = markAsUntouchedReducer$2(state, action); state = markAsSubmittedReducer$2(state, action); state = markAsUnsubmittedReducer$2(state, action); state = setUserDefinedPropertyReducer$2(state, action); state = resetReducer$2(state, action); return state; } /** * This reducer function updates a form control state with actions. */ function formControlReducer(state, action) { if (!state) { throw new Error('The control state must be defined!'); } return formControlReducerInternal(state, action); } function dispatchActionPerChild$1(controls, actionCreator) { let hasChanged = false; const newControls = controls .map(state => { const newState = formStateReducer(state, actionCreator(state.id)); hasChanged = hasChanged || state !== newState; return newState; }); return hasChanged ? newControls : controls; } function callChildReducers$1(controls, action) { let hasChanged = false; const newControls = controls .map(state => { const newState = formStateReducer(state, action); hasChanged = hasChanged || state !== newState; return newState; }); return hasChanged ? newControls : controls; } function childReducer$1(state, action) { const controls = callChildReducers$1(state.controls, action); if (state.controls === controls) { return state; } return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function updateIdRecursiveForGroup(state, newId) { const controls = Object.keys(state.controls) .reduce((agg, key) => Object.assign(agg, { [key]: updateIdRecursive(state.controls[key], `${newId}.${key}`), }), {}); return Object.assign(Object.assign({}, state), { id: newId, controls }); } function updateIdRecursiveForArray(state, newId) { const controls = state.controls.map((c, i) => updateIdRecursive(c, `${newId}.${i}`)); return Object.assign(Object.assign({}, state), { id: newId, controls }); } function updateIdRecursive(state, newId) { if (state.id === newId) { return state; } if (isGroupState(state)) { return updateIdRecursiveForGroup(state, newId); } if (isArrayState(state)) { return updateIdRecursiveForArray(state, newId); } return Object.assign(Object.assign({}, state), { id: newId }); } function addControlReducer$1(state, action) { if (action.type !== AddArrayControlAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const index = action.index === undefined ? state.controls.length : action.index; if (index > state.controls.length || index < 0) { throw new Error(`Index ${index} is out of bounds for array '${state.id}' with length ${state.controls.length}!`); } let controls = [...state.controls]; controls.splice(index, 0, createChildState(`${state.id}.${index}`, action.value)); controls = controls.map((c, i) => updateIdRecursive(c, `${state.id}.${i}`)); return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: true, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function clearAsyncErrorReducer$1(state, action) { if (action.type !== ClearAsyncErrorAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const name = `$${action.name}`; let errors = state.errors; if (state.errors.hasOwnProperty(name)) { errors = Object.assign({}, state.errors); delete errors[name]; } const pendingValidations = state.pendingValidations.filter(v => v !== action.name); if (errors === state.errors && pendingValidations.length === state.pendingValidations.length) { return state; } return computeArrayState(state.id, state.controls, state.value, errors, pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function disableReducer$1(state, action) { if (action.type !== DisableAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isDisabled) { return state; } return computeArrayState(state.id, dispatchActionPerChild$1(state.controls, controlId => new DisableAction(controlId)), state.value, {}, [], state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: false, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function enableReducer$1(state, action) { if (action.type !== EnableAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const controls = dispatchActionPerChild$1(state.controls, controlId => new EnableAction(controlId)); if (controls === state.controls && state.isEnabled) { return state; } return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: true, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function markAsDirtyReducer$1(state, action) { if (action.type !== MarkAsDirtyAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const controls = dispatchActionPerChild$1(state.controls, controlId => new MarkAsDirtyAction(controlId)); if (controls === state.controls && state.isDirty) { return state; } return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: true, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function markAsPristineReducer$1(state, action) { if (action.type !== MarkAsPristineAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isPristine) { return state; } return computeArrayState(state.id, dispatchActionPerChild$1(state.controls, controlId => new MarkAsPristineAction(controlId)), state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: false, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function markAsSubmittedReducer$1(state, action) { if (action.type !== MarkAsSubmittedAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const controls = dispatchActionPerChild$1(state.controls, controlId => new MarkAsSubmittedAction(controlId)); if (controls === state.controls) { return state; } return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: true, }); } function markAsTouchedReducer$1(state, action) { if (action.type !== MarkAsTouchedAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const controls = dispatchActionPerChild$1(state.controls, controlId => new MarkAsTouchedAction(controlId)); if (controls === state.controls) { return state; } return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: true, wasOrShouldBeSubmitted: state.isSubmitted, }); } function markAsUnsubmittedReducer$1(state, action) { if (action.type !== MarkAsUnsubmittedAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isUnsubmitted) { return state; } return computeArrayState(state.id, dispatchActionPerChild$1(state.controls, controlId => new MarkAsUnsubmittedAction(controlId)), state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: false, }); } function markAsUntouchedReducer$1(state, action) { if (action.type !== MarkAsUntouchedAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isUntouched) { return state; } return computeArrayState(state.id, dispatchActionPerChild$1(state.controls, controlId => new MarkAsUntouchedAction(controlId)), state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: false, wasOrShouldBeSubmitted: state.isSubmitted, }); } function move(array, fromIndex, toIndex) { const item = array[fromIndex]; const length = array.length; if (fromIndex > toIndex) { return [ ...array.slice(0, toIndex), item, ...array.slice(toIndex, fromIndex), ...array.slice(fromIndex + 1, length), ]; } else { const targetIndex = toIndex + 1; return [ ...array.slice(0, fromIndex), ...array.slice(fromIndex + 1, targetIndex), item, ...array.slice(targetIndex, length), ]; } } function moveControlReducer(state, action) { if (action.type !== MoveArrayControlAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const fromIndex = action.fromIndex; const toIndex = action.toIndex; if (fromIndex === toIndex) { return state; } if (fromIndex < 0 || toIndex < 0) { throw new Error(`fromIndex ${fromIndex} or toIndex ${fromIndex} was negative`); } if (fromIndex >= state.controls.length || toIndex >= state.controls.length) { throw new Error(`fromIndex ${fromIndex} or toIndex ${toIndex} is out of bounds with the length of the controls ${state.controls.length}`); } let controls = move(state.controls, fromIndex, toIndex); controls = controls.map((c, i) => updateIdRecursive(c, `${state.id}.${i}`)); return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: true, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function removeControlReducer$1(state, action) { if (action.type !== RemoveArrayControlAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (action.index >= state.controls.length || action.index < 0) { throw new Error(`Index ${action.index} is out of bounds for array '${state.id}' with length ${state.controls.length}!`); } const index = action.index; const controls = state.controls.filter((_, i) => i !== index).map((c, i) => updateIdRecursive(c, `${state.id}.${i}`)); return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: true, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function resetReducer$1(state, action) { if (action.type !== ResetAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isPristine && state.isUntouched && state.isUnsubmitted) { return state; } return computeArrayState(state.id, dispatchActionPerChild$1(state.controls, controlId => new ResetAction(controlId)), state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: false, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: false, wasOrShouldBeSubmitted: false, }); } function setAsyncErrorReducer$1(state, action) { if (action.type !== SetAsyncErrorAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isDisabled) { return state; } const name = `$${action.name}`; let value = action.value; if (deepEquals(state.errors[name], action.value)) { value = state.errors[name]; } const errors = Object.assign(Object.assign({}, state.errors), { [name]: value }); const pendingValidations = state.pendingValidations.filter(v => v !== action.name); return computeArrayState(state.id, state.controls, state.value, errors, pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function setErrorsReducer$1(state, action) { if (action.type !== SetErrorsAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.isDisabled) { return state; } if (state.errors === action.errors) { return state; } if (deepEquals(state.errors, action.errors)) { return state; } if (!action.errors || typeof action.errors !== 'object' || Array.isArray(action.errors)) { throw new Error(`Control errors must be an object; got ${action.errors}`); } if (Object.keys(action.errors).some(key => key.startsWith('_'))) { throw new Error(`Control errors must not use underscore as a prefix; got ${JSON.stringify(action.errors)}`); } if (Object.keys(action.errors).some(key => key.startsWith('$'))) { throw new Error(`Control errors must not use $ as a prefix; got ${JSON.stringify(action.errors)}`); } const childAndAsyncErrors = Object.keys(state.errors) .filter(key => key.startsWith('_') || key.startsWith('$')) .reduce((res, key) => Object.assign(res, { [key]: state.errors[key] }), {}); const newErrors = Object.assign(childAndAsyncErrors, action.errors); return computeArrayState(state.id, state.controls, state.value, newErrors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function setUserDefinedPropertyReducer$1(state, action) { if (action.type !== SetUserDefinedPropertyAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.userDefinedProperties[action.name] === action.value) { return state; } return Object.assign(Object.assign({}, state), { userDefinedProperties: Object.assign(Object.assign({}, state.userDefinedProperties), { [action.name]: action.value }) }); } function setValueReducer$1(state, action) { if (action.type !== SetValueAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.value === action.value) { return state; } if (action.value instanceof Date) { throw new Error('Date values are not supported. Please used serialized strings instead.'); } const value = action.value; const controls = value .map((v, i) => { if (!state.controls[i]) { return createChildState(`${state.id}.${i}`, v); } return formStateReducer(state.controls[i], new SetValueAction(state.controls[i].id, v)); }); return computeArrayState(state.id, controls, value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function startAsyncValidationReducer$1(state, action) { if (action.type !== StartAsyncValidationAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } if (state.pendingValidations.indexOf(action.name) >= 0) { return state; } const pendingValidations = [...state.pendingValidations, action.name]; return computeArrayState(state.id, state.controls, state.value, state.errors, pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: state.isDirty, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function swapArrayValues(a, i, j) { const n = [...a]; [n[i], n[j]] = [n[j], n[i]]; return n; } function swapControlReducer(state, action) { if (action.type !== SwapArrayControlAction.TYPE) { return state; } if (action.controlId !== state.id) { return childReducer$1(state, action); } const fromIndex = action.fromIndex; const toIndex = action.toIndex; if (fromIndex === toIndex) { return state; } if (fromIndex < 0 || toIndex < 0) { throw new Error(`fromIndex ${fromIndex} or toIndex ${fromIndex} was negative`); } if (fromIndex >= state.controls.length || toIndex >= state.controls.length) { throw new Error(`fromIndex ${fromIndex} or toIndex ${toIndex} is out of bounds with the length of the controls ${state.controls.length}`); } let controls = swapArrayValues(state.controls, fromIndex, toIndex); controls = controls.map((c, i) => (i >= fromIndex || i >= toIndex) ? updateIdRecursive(c, `${state.id}.${i}`) : c); return computeArrayState(state.id, controls, state.value, state.errors, state.pendingValidations, state.userDefinedProperties, { wasOrShouldBeDirty: true, wasOrShouldBeEnabled: state.isEnabled, wasOrShouldBeTouched: state.isTouched, wasOrShouldBeSubmitted: state.isSubmitted, }); } function formArrayReducerInternal(state, action) { if (!isArrayState(state)) { throw new Error('The state must be an array state'); } if (!isNgrxFormsAction(action)) { return state; } if (!action.controlId.startsWith(state.id)) { return state; } switch (action.type) { case FocusAction.TYPE: case UnfocusAction.TYPE: case AddGroupControlAction.TYPE: case RemoveGroupControlAction.TYPE: return childReducer$1(state, action); default: break; } state = setValueReducer$1(state, action); state = setErrorsReducer$1(state, action); state = startAsyncValidationReducer$1(state, action); state = setAsyncErrorReducer$1(state, action); state = clearAsyncErrorReducer$1(state, action); state = enableReducer$1(state, action); state = disableReducer$1(state, action); state = markAsDirtyReducer$1(state, action); state = markAsPristineReducer$1(state, action); state = markAsTouchedReducer$1(state, action); state = markAsUntouchedReducer$1(state, action); state = markAsSubmittedReducer$1(state, action); state = markAsUnsubmittedReducer$1(state, action); state = setUserDefinedPropertyReducer$1(state, action); state = resetReducer$1(state, action); state = addControlReducer$1(state, action); state = removeControlReducer$1(state, action); state = swapControlReducer(state, action); state = moveControlReducer(state, action); return state; } /