UNPKG

ngx-forms-typed

Version:

Angular Forms Typed provides types and helper functions fully compatible with original Angular forms

210 lines (203 loc) 8.62 kB
import * as i1 from '@angular/forms'; import { UntypedFormControl, UntypedFormArray, UntypedFormGroup, Validators } from '@angular/forms'; import * as i0 from '@angular/core'; import { Directive, Optional, Self } from '@angular/core'; import { Subscription } from 'rxjs'; /** * A helper function to create a `TypedFormControl`. It only calls the constructor of FormControl, but **strongly types** the result. * @param v the value to initialize our `TypedFormControl` with - same as in `new FormControl(v, validators, asyncValidators)` * @param validators validators - same as in new `FormControl(v, validators, asyncValidators)` * @param asyncValidators async validators - same as in `new FormControl(v, validators, asyncValidators)` * * @example * const c = typedFormControl<string>(): TypedFormControl<string>; * c.valueChanges // Observable<string> * c.patchValue('s') // expects string * c.patchValue(1) // COMPILE TIME! type error! */ function typedFormControl(v, validatorsOrOptions, asyncValidators) { return new UntypedFormControl(v, validatorsOrOptions, asyncValidators); } /** * A helper function to create a `TypedFormArray`. It only calls the constructor of FormArray, but **strongly types** the result. * @param v the value to initialize our `TypedFormArray` with - same as in `new TypedFormArray(v, validators, asyncValidators)` * @param validators validators - same as in new `TypedFormArray(v, validators, asyncValidators)` * @param asyncValidators async validators - same as in `new TypedFormArray(v, validators, asyncValidators)` * * @example * const c = typedFormArray<string>([typedFormControl('of type string')]): TypedFormArray<string[], string>; * c.valueChanges // Observable<string[]> * c.patchValue(['s']) // expects string[] * c.patchValue(1) // COMPILE TIME! type error! */ function typedFormArray(controls, validatorOrOptions, asyncValidators) { return new UntypedFormArray(controls, validatorOrOptions, asyncValidators); } function typedFormGroup(controls, validatorOrOpts, asyncValidator) { const f = new UntypedFormGroup(controls, validatorOrOpts, asyncValidator); f.keys = Object.keys(controls).reduce((acc, k) => ({ ...acc, [k]: k }), {}); return f; } /** * Does an aggregate action on a form's controls. * * @param form the form to whose controls we want to influence * * For example we want to call `markAsTouched` on each control in a form, for visualizing validation purposes. * @example * const form = new FormGroup({name: ..., email: ..., address: ..., ...}); * * forEachControlIn(form).call('markAsTouched') - will iterate over all controls and call that method */ function forEachControlIn(form) { const controls = form != null && form.controls != null ? Array.isArray(form.controls) ? form.controls : Object.getOwnPropertyNames(form.controls).map(name => form.controls[name]) : []; const composer = { /** * Enumerate which methods to call. * @param methods which methods to call - as typed string enum * * @example * * forEachControlIn(form).call('markAsPristine', 'markAsTouched', 'disable') */ call(...methods) { if (controls != null && Array.isArray(controls)) { controls.forEach(c => { methods.forEach(m => { if (c[m] && typeof c[m] === 'function') { c[m](); } }); // catch the case where we have a control that is form array/group - so for each of the children call methods if (c.controls != null) { forEachControlIn(c).call(...methods); } }); } return composer; }, markAsDirtySimultaneouslyWith(c) { if (c != null) { const markAsDirtyOriginal = c.markAsDirty.bind(c); c.markAsDirty = () => { markAsDirtyOriginal(); composer.call('markAsDirty'); }; const markAsPristineOriginal = c.markAsPristine.bind(c); c.markAsPristine = () => { markAsPristineOriginal(); composer.call('markAsPristine'); }; } return composer; }, markAsTouchedSimultaneouslyWith(c, touchIsChildInitiated) { if (c != null) { const markAsTouchedOriginal = c.markAsTouched.bind(c); c.markAsTouched = () => { markAsTouchedOriginal(); if (!touchIsChildInitiated || !touchIsChildInitiated()) { composer.call('markAsTouched'); } }; const markAsUntouchedOriginal = c.markAsUntouched.bind(c); c.markAsUntouched = () => { markAsUntouchedOriginal(); composer.call('markAsUntouched'); }; } return composer; }, /** * Get the errors in the controls from our form and append their errors to the `form` (in forEachControlIn(form) form) * @param parentControl the control that should be invalid if on of our controls is */ addValidatorsTo(parentControl) { if (parentControl != null) { parentControl.validator = Validators.compose([ parentControl.validator, () => { // could overwrite some errors - but we only need it to communicate to the "parent" form that // these controls here are valid or not const errors = controls.reduce((e, next) => ({ ...e, ...next.errors }), {}); return controls.some(c => c.errors != null) ? errors : null; } ]); } return composer; } }; return composer; } class ControlValueAccessorConnector { directive; subs = new Subscription(); touchIsChildInitiated; form; constructor(directive, form) { this.directive = directive; if (directive) { directive.valueAccessor = this; } this.form = form; } ngOnInit() { if (this.directive && this.directive.control) { forEachControlIn(this.form) .markAsTouchedSimultaneouslyWith(this.directive.control, () => this.touchIsChildInitiated) .addValidatorsTo(this.directive.control); } const values = this.form.valueChanges.subscribe(v => this.onChange(v)); const statuses = this.form.statusChanges.subscribe(s => { if (this.form.touched) { this.onTouch(); } }); this.subs.add(values); this.subs.add(statuses); } ngOnDestroy() { this.subs.unsubscribe(); } onChange = (_) => { }; onTouch = () => { }; writeValue(obj) { this.form.patchValue(obj || {}); } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouch = () => { this.touchIsChildInitiated = true; fn(); this.touchIsChildInitiated = false; }; } setDisabledState(disable) { disable ? this.form.disable() : this.form.enable(); forEachControlIn(this.form).call(disable ? 'disable' : 'enable'); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.1.3", ngImport: i0, type: ControlValueAccessorConnector, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.1.3", type: ControlValueAccessorConnector, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.1.3", ngImport: i0, type: ControlValueAccessorConnector, decorators: [{ type: Directive, args: [{}] }], ctorParameters: () => [{ type: i1.NgControl, decorators: [{ type: Optional }, { type: Self }] }, { type: undefined }] }); /* * Public API Surface of forms */ /** * Generated bundle index. Do not edit. */ export { ControlValueAccessorConnector, forEachControlIn, typedFormArray, typedFormControl, typedFormGroup }; //# sourceMappingURL=ngx-forms-typed.mjs.map