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
JavaScript
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