You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This library depends on versions `^4.0.0` of `@angular/core`, `@angular/forms`, and `@ngrx/store`, and version `^5.0.0` of `rxjs`.
24
24
25
25
## <aname="2"></a>2 Design Principles
26
-
This library is written to be as functional and as pure as possible. Most of the heavy lifting is done in pure reducer functions while the directives are only a thin layer to connect the form states to the DOM.
26
+
This library is written to be as functional and as pure as possible. Most of the heavy lifting is done in pure reducer functions with the directives being only a thin layer to connect the form states to the DOM.
27
27
28
28
This library also tries to be as independent as possible from other libraries/modules. While there is of course a dependency on ngrx the touching points are small and it should be possible to adapt this library to any other redux library without too much effort. There is also a peer dependency on `@angular/forms` from which we re-use the `ControlValueAccessor` concept to allow easier integration with other libraries that provide custom form controls.
29
29
30
-
Conceptually this library borrows heavily from `@angular/forms`, specifically the concepts of form controls and form groups. Groups are simply a collection of named form controls. The state of a group is determined almost fully by its child controls (with the exception of errors which a group can have by itself).
30
+
Conceptually this library borrows heavily from `@angular/forms`, specifically the concepts of form controls and form groups (see the [User Guide](#3) below for a more detailed description of these concepts).
31
31
32
32
## <aname="3"></a>3 User Guide
33
33
@@ -36,18 +36,18 @@ Conceptually this library borrows heavily from `@angular/forms`, specifically th
36
36
Import the module:
37
37
38
38
```typescript
39
+
import { StoreModule } from'@ngrx/store';
39
40
import { NgrxFormsModule } from'ngrx-forms';
40
41
42
+
import { reducers } from'./reducer';
43
+
41
44
@NgModule({
42
45
declarations: [
43
46
AppComponent,
44
-
...,
45
47
],
46
48
imports: [
47
-
...,
48
49
NgrxFormsModule,
49
50
StoreModule.forRoot(reducers),
50
-
...,
51
51
],
52
52
providers: [],
53
53
bootstrap: [AppComponent]
@@ -146,29 +146,214 @@ Set the control states in your template:
146
146
147
147
### Form Controls
148
148
149
-
- explain whole state concept
150
-
- mention that state is sync'ed immediately
149
+
Form controls in ngrx-forms are represented as plain state objects. Control states have the following shape:
|`id`||The unique ID of the form control. Usually this is the name of the field in the form value prefixed by the ID of the containing group, e.g. `MY_FORM.someTextInput`.|
183
+
|`value`||The value of the form control. Controls only support values of type `string`, `number`, `boolean`, `null`, and `undefined` to keep the state string serializable.|
184
+
|`isValid`|`isInvalid`|The `isValid` flag is `true` if the control does not have any errors.|
185
+
|`errors`||The errors of the control. This property always has a value. If the control has no errors the property is set to `{}`.|
186
+
|`isEnabled`|`isDisabled`|The `isEnabled` flag indicates whether the control is enabled. When `isEnabled` is `false` the `errors` are always `{}` (i.e. the control is always valid if disabled).|
187
+
|`isDirty`|`isPristine`|The `isDirty` flag is set to `true` as soon as the value of the control changes for the first time.|
188
+
|`isTouched`|`isUntouched`|The `isTouched` flag is set to `true` based on the rules of the underlying `ControlValueAccessor` (usually on `blur` for most form elements).|
189
+
|`isSubmitted`|`isUnsubmitted`|The `isSubmitted` flag is set to `true` if the containing group is submitted.|
190
+
|`isFocused`|`isUnfocused`|The `isFocused` flag is set to `true` if the control currently has focus. Note that this feature is opt-in. To enable it you have to add ```[ngrxEnableFocusTracking]="true"``` to your form element.|
191
+
|`lastKeyDownCode`||The `lastKeyDownCode` is set to the key code of the last key that was pressed on the control. Note that this feature is opt-in. To enable it you have to add ```[ngrxEnableLastKeydownCodeTracking]="true"``` to your form element. This feature can be used for example to react to `Enter` key events. Note that this feature is likely to be changed in the near future.|
192
+
193
+
Control states are associated with a form element via the `NgrxFormControlDirective` (applied with `[ngrxFormControlState]="controlState"`). This directive is reponsible keeping the view and the state in sync. When the state is changed the update is always immediately sync'ed to the view. Currently this also always happens immediately when the view is changed (e.g. as soon as the value of an `input` changes the state is updated), but there are plans to allow specifying the event on which the sync happens (e.g. `change`, `blur` etc.).
151
194
152
195
### Form Groups
153
196
154
-
- explain exactly how each property of a group is computed
155
-
- form directive for submission status tracking (mention preventDefault())
197
+
Groups are collections of controls. Just like controls groups are represented as plain state objects. The state of a group is determined almost fully by its child controls (with the exception of `errors` which a group can have by itself). Group states have the following shape:
As you can see most properties are shared with controls via the common base interface `AbstractControlState`. The following table explains each property in the context of a group.
208
+
209
+
|Property|Negated|Description|
210
+
|-|-|-|
211
+
|`id`||The unique ID of the group.|
212
+
|`value`||The aggregated value of the group. The value is computed by aggregating the values of all children.|
213
+
|`isValid`|`isInvalid`|The `isValid` flag is `true` if the group does not have any errors itself and none of its children have any errors.|
214
+
|`errors`||The errors of the group. This property is computed by merging the errors of the control with the errors of all children where the child errors are a property of the `errors` object prefixed with an underscore (e.g. `{ groupError: true, _child: { childError: true } }`). If neither the group nor any children have errors the property is set to `{}`.|
215
+
|`isEnabled`|`isDisabled`|The `isEnabled` flag is `true` if and only if at least one child control is enabled.|
216
+
|`isDirty`|`isPristine`|The `isDirty` flag is `true` if and only if at least one child control is marked as dirty.|
217
+
|`isTouched`|`isUntouched`|The `isTouched` flag is `true` if and only if at least one child control is marked as touched.|
218
+
|`isSubmitted`|`isUnsubmitted`|The `isSubmitted` flag is set to `true` if the group is submitted. This is tracked by the `NgrxFormDirective` (which needs to be applied to a form via `[ngrxFormState]="groupState"`). Note that applying this directive to a form prevents normal form submission since that does not make much sense for ngrx forms.|
219
+
|`controls`||This property contains all child controls of the group. As you may have noticed the type of each child control is `AbstractControlState` which sometimes forces you to cast the state explicitly. It is not possible to improve this typing until [conditional mapped types](https://github.com/Microsoft/TypeScript/issues/12424) are added to TypeScript.|
220
+
221
+
Group states are usually completely independent of the DOM (with the exception of root groups that are associated with a `form` via `NgrxFormDirective`). They are updated by intercepting all actions that change their children (i.e. the group's reducer is the parent reducer of all its child reducers and forwards any actions to all children; if any children change it recomputes the state of the group). A group state can be created via `createFormGroupState`. This function takes an initial value and automatically creates all child controls recursively.
156
222
157
223
### Updating the State
158
224
159
-
- explain all helper functions
225
+
All states are internally updated by ngrx-forms through dispatching actions. While this is of course also possible for you there exist a set of utility functions that can be used to update states. This is mainly useful to change the state as a result of a different action in your reducer. Note that ngrx-forms is coded in such a way that no state references will change if nothing inside the state changes. It is therefore perfectly safe to repeatedly call any of the functions below and the state will be updated exactly once or not at all if nothing changed. Each function can be imported from `ngrx-forms`. The following table explains each function:
226
+
227
+
|Function|Description|
228
+
|-|-|
229
+
|`setValue`|This curried function takes a value and returns a function that takes a state and updates the value of the state. Note that setting the value of the group will also update all children including adding and removing children on the fly for added/removed properties. Has an uncurried overload that takes a state directly as the second parameter.|
230
+
|`validate`|This curried function takes a validation function as a parameter and returns a function that takes a state and updates the errors of the state with the result of the provided validation function applied to the state's value. Has an uncurried overload that takes a state directly as the second parameter.|
231
+
|`enable`|This function takes a state and enables it. For groups this also recursively enables all children.|
232
+
|`disable`|This function takes a state and disables it. For groups this also recursively disables all children.|
233
+
|`markAsDirty`|This function takes a state and marks it as dirty. For groups this also recursively marks all children as dirty.|
234
+
|`markAsPristine`|This function takes a state and marks it as pristine. For groups this also recursively marks all children as pristine.|
235
+
|`markAsTouched`|This function takes a state and marks it as touched. For groups this also recursively marks all children as touched.|
236
+
|`markAsUntouched`|This function takes a state and marks it as untouched. For groups this also recursively marks all children as untouched.|
237
+
|`markAsSubmitted`|This function takes a state and marks it as submitted. For groups this also recursively marks all children as submitted.|
238
+
|`markAsUnsubmitted`|This function takes a state and marks it as unsubmitted. For groups this also recursively marks all children as unsubmitted.|
239
+
|`focus`|This function takes a control state and makes it focused (which will also `.focus()` the form element).|
240
+
|`unfocus`|This function takes a control state and makes it unfocused (which will also `.blur()` the form element).|
241
+
|`setLastKeyDownCode`|This function takes a control state and sets the last keydown code.|
242
+
|`addControl`|This curried function takes a name and a value and returns a function that takes a group state and adds a child control with the given name and value to the state.|
243
+
|`removeControl`|This curried function takes a name and returns a function that takes a group state and removes a child control with the given name from the state.|
244
+
245
+
These are very basic functions that perform simple updates on states. The last two functions below contain the real magic that allows easily updating deeply nested form states.
246
+
247
+
`updateGroup`:
248
+
This curried function takes a partial object in the shape of the group's value where each key contains an update function for that child and returns a function that takes a group state, applies all the provided update functions recursively and recomputes the state of the group afterwards. As with all the functions above this function does not change the reference of the group if none of the child update functions change any children. The best example of how this can be used is simple validation:
The `updateMyFormGroup` function has a signature of `FormGroupState<MyFormValue> -> FormGroupState<MyFormValue>`. It takes a state, runs all validations, updates the errors, and returns the resulting state.
276
+
277
+
In addition, the `updateGroup` function allows specifying as many update function objects as you want and applies all of them after another. This is useful if you have dependencies between your update functions where one function's result may affect the result of another function. The following (contrived) example shows how to set the value of `someNumber` based on the `errors` of `someTextInput`.
// the `cast` (utility function exported by `ngrx-forms`) helps the type checker to recognize the
300
+
// `nested` state as a group state
301
+
});
302
+
```
303
+
304
+
`groupUpdateReducer`:
305
+
This curried function combines a `formGroupReducer` and the `updateGroup` function by taking update objects of the same shape as `updateGroup` and returns a reducer which first calls the `formGroupReducer` and afterwards applies all update functions by calling `updateGroup`. Combining all we have seen so far our final reducer would therefore look something like this:
As mentioned above ngrx-forms re-uses the `ControlValueAccessor` concept of `@angular/forms`. ngrx-forms ships its own variants of all default value accessors (most of which simply inherit the implementation from `@angular/forms`. Most libraries providing custom value accessors should also work with ngrx-forms out of the box as long as they properly export the value accessor. However, in case a library does not do this you may have to write your own value accessor. See the example app for such a custom value accessor (in this case for the `md-select` from `@angular/material` which in version `2.0.0-beta.8` does not properly export the `md-select`'s value accessor).
164
347
165
348
## <aname="4"></a>4 Open Points
166
349
350
+
* providing a simple set of common validation functions (e.g. required, min, max, pattern, etc.) and error composition
167
351
* providing option to choose when the view is sync'ed to the state (e.g. `change`, `blur` etc.)
168
-
* proper value conversion
352
+
* proper value conversion for converting values between the view and the state
169
353
* async validation (although already achievable via effects)
170
-
* providing some global configuration options
171
-
* some tests for directives
354
+
* providing some global configuration options (e.g. enabling focus tracking globally)
355
+
* add `isFocused` to groups to track whether any child is focused
0 commit comments