rx-form-mapper
Version:
Proper decorator-based transformation / serialization / deserialization of plain javascript classes to angular reactive forms
404 lines (380 loc) • 13.7 kB
JavaScript
import 'reflect-metadata';
import { negate, isNil, castArray, isFunction } from 'lodash';
import { InjectFlags, Injectable, Injector, NgModule, Optional, SkipSelf } from '@angular/core';
import { FormArray as FormArray$1, FormControl as FormControl$1, FormGroup as FormGroup$1 } from '@angular/forms';
const isNotNil = negate(isNil);
function coerceArray(value) {
return castArray(value).filter(isNotNil);
}
function isType(value) {
return isFunction(value);
}
class ValidableMetadata {
constructor() {
this._validators = [];
this._asyncValidators = [];
}
setValidators(opts) {
this._validators = coerceArray(opts === null || opts === void 0 ? void 0 : opts.validators);
this._asyncValidators = coerceArray(opts === null || opts === void 0 ? void 0 : opts.asyncValidators);
this._updateOn = opts === null || opts === void 0 ? void 0 : opts.updateOn;
}
get validators() {
return this._validators;
}
get asyncValidators() {
return this._asyncValidators;
}
get updateOn() {
return this._updateOn;
}
}
class CustomControlMetadata extends ValidableMetadata {
constructor(mapper) {
super();
this.mapper = mapper;
}
accept(visitor) {
return visitor.visitCustomControlMetadata(this);
}
}
class FormArrayMetadata extends ValidableMetadata {
constructor(itemForm) {
super();
this.itemForm = itemForm;
}
accept(visitor) {
return visitor.visitFormArrayMetadata(this);
}
}
class FormControlMetadata extends ValidableMetadata {
accept(visitor) {
return visitor.visitFormControlMetadata(this);
}
}
class FormGroupMetadata {
constructor(form) {
this.form = form;
}
accept(visitor) {
return visitor.visitFormGroupMetadata(this);
}
}
class FormMetadata extends ValidableMetadata {
constructor(type) {
super();
this.type = type;
this._controls = {};
}
accept(visitor) {
return visitor.visitFormMetadata(this);
}
get controls() {
return this._controls;
}
setFormControl(name, opts) {
const control = new FormControlMetadata();
control.setValidators(opts);
this.controls[name] = control;
}
setFormGroup(name, form) {
const control = new FormGroupMetadata(form);
this.controls[name] = control;
}
setCustomControl(name, mapper, opts) {
const control = new CustomControlMetadata(mapper);
control.setValidators(opts);
this.controls[name] = control;
}
setFormArray(name, itemform, opts) {
const control = new FormArrayMetadata(itemform);
control.setValidators(opts);
this.controls[name] = control;
}
}
class ModelBinder {
constructor() {
this.metadataKey = 'rx-form-mapper-metadata';
}
getMetadata(target) {
if (!Reflect.hasMetadata(this.metadataKey, target)) {
Reflect.defineMetadata(this.metadataKey, new FormMetadata(target), target);
}
return Reflect.getMetadata(this.metadataKey, target);
}
bindForm(target, opts) {
const formMetadata = this.getMetadata(target);
formMetadata.setValidators(opts);
}
bindCustomControl(target, propertyName, opts) {
this.getMetadata(target.constructor).setCustomControl(propertyName, opts.mapper, opts);
}
bindFormControl(target, propertyName, opts) {
this.getMetadata(target.constructor).setFormControl(propertyName, opts);
}
bindFormGroup(target, propertyName, type) {
const propertyType = type !== null && type !== void 0 ? type : Reflect.getMetadata(MetadataDesignTypes.TYPE, target, propertyName);
const propertyFormMetadata = this.getMetadata(propertyType);
this.getMetadata(target.constructor).setFormGroup(propertyName, propertyFormMetadata);
}
bindFormArray(target, propertyName, opts) {
const itemFormMetadata = this.getMetadata(opts.type);
this.getMetadata(target.constructor).setFormArray(propertyName, itemFormMetadata, opts);
}
}
ModelBinder.instance = new ModelBinder();
var MetadataDesignTypes;
(function (MetadataDesignTypes) {
MetadataDesignTypes["TYPE"] = "design:type";
MetadataDesignTypes["RETURN_TYPE"] = "design:returntype";
MetadataDesignTypes["PARAM_TYPE"] = "design:paramtypes";
})(MetadataDesignTypes || (MetadataDesignTypes = {}));
function CustomControl(optsOrMapper) {
return (target, propertyName) => {
if (isNil(optsOrMapper)) {
throw new Error(`unexpected CustomControl configuration: ${optsOrMapper}`);
}
if (typeof (optsOrMapper) !== 'object') {
optsOrMapper = { mapper: optsOrMapper };
}
ModelBinder.instance.bindCustomControl(target, propertyName, optsOrMapper);
};
}
function FormArray(optsOrType) {
return (target, propertyName) => {
if (isNil(optsOrType)) {
throw new Error(`unexpected FormArray configuration: ${optsOrType}`);
}
if (isType(optsOrType)) {
optsOrType = { type: optsOrType };
}
ModelBinder.instance.bindFormArray(target, propertyName, optsOrType);
};
}
function FormControl(opts) {
return (target, propertyName) => {
ModelBinder.instance.bindFormControl(target, propertyName, opts);
};
}
function FormGroup(type) {
return (target, propertyName) => {
ModelBinder.instance.bindFormGroup(target, propertyName, type);
};
}
function Form(opts) {
return (target) => {
ModelBinder.instance.bindForm(target, opts);
};
}
class CustomMapperResolver {
constructor(injector) {
this.injector = injector;
}
resolve(type) {
var _a;
return (_a = this.injector.get(type, null, InjectFlags.Optional)) !== null && _a !== void 0 ? _a : new type();
}
}
CustomMapperResolver.decorators = [
{ type: Injectable }
];
CustomMapperResolver.ctorParameters = () => [
{ type: Injector }
];
class FormReader {
constructor(control, customMapperResolver) {
this.control = control;
this.customMapperResolver = customMapperResolver;
}
visitCustomControlMetadata(customControlMetadata) {
return this.customMapperResolver.resolve(customControlMetadata.mapper).readForm(this.control);
}
visitFormArrayMetadata(formArrayMetadata) {
if (!this.control) {
return void (0);
}
this.checkControlType(this.control, FormArray$1);
return this.control.controls.map(control => {
const subFormReader = this.copyPrototype(control);
return formArrayMetadata.itemForm.accept(subFormReader);
});
}
visitFormControlMetadata(formControlMetadata) {
if (!this.control) {
return void (0);
}
this.checkControlType(this.control, FormControl$1);
return this.control.value;
}
visitFormGroupMetadata(formGroupMetadata) {
return this.visitFormMetadata(formGroupMetadata.form);
}
visitFormMetadata(formMetadata) {
if (!this.control) {
return void (0);
}
this.checkControlType(this.control, FormGroup$1);
const value = new formMetadata.type();
const formGroup = this.control;
for (const [key, controlMetadata] of Object.entries(formMetadata.controls)) {
const formField = formGroup.controls[key];
const formFieldReader = this.copyPrototype(formField);
value[key] = controlMetadata.accept(formFieldReader);
}
return value;
}
copyPrototype(control) {
return new FormReader(control, this.customMapperResolver);
}
checkControlType(control, type) {
if (control instanceof type) {
return;
}
throw new Error(`control is not ${type.name} instance`);
}
}
class FormWriter {
constructor(value, customMapperResolver, validatorResolver) {
this.value = value;
this.customMapperResolver = customMapperResolver;
this.validatorResolver = validatorResolver;
}
visitCustomControlMetadata(customControlMetadata) {
const mapper = this.customMapperResolver.resolve(customControlMetadata.mapper);
return mapper.writeForm(this.value, this.buildAbstractControlOptions(customControlMetadata));
}
visitFormArrayMetadata(formArrayMetadata) {
var _a;
const values = (_a = this.value) !== null && _a !== void 0 ? _a : [];
const controls = [];
for (const item of values) {
const subWriter = this.copyPrototype(item);
const control = formArrayMetadata.itemForm.accept(subWriter);
controls.push(control);
}
return new FormArray$1(controls, this.buildAbstractControlOptions(formArrayMetadata));
}
visitFormControlMetadata(formControlMetadata) {
return new FormControl$1(this.value, this.buildAbstractControlOptions(formControlMetadata));
}
visitFormGroupMetadata(formGroupMetadata) {
return this.visitFormMetadata(formGroupMetadata.form);
}
visitFormMetadata(formMetadata) {
var _a;
const controls = {};
for (const [key, controlMetadata] of Object.entries(formMetadata.controls)) {
const fieldValue = (_a = this.value) === null || _a === void 0 ? void 0 : _a[key];
const subWriter = this.copyPrototype(fieldValue);
controls[key] = controlMetadata.accept(subWriter);
}
return new FormGroup$1(controls, this.buildAbstractControlOptions(formMetadata));
}
buildAbstractControlOptions(metadata) {
return {
validators: metadata.validators.map(v => this.validatorResolver.resolve(v)),
asyncValidators: metadata.asyncValidators.map(v => this.validatorResolver.resolve(v)),
updateOn: metadata.updateOn
};
}
copyPrototype(value) {
return new FormWriter(value, this.customMapperResolver, this.validatorResolver);
}
}
class ValidatorResolver {
constructor(injector) {
this.injector = injector;
}
resolve(validator) {
if (this.isValidatorFn(validator)) {
return validator;
}
let validatorInstance = null;
if (this.isValidatorInstance(validator)) {
validatorInstance = validator;
}
else {
validatorInstance = this.injector.get(validator, null, InjectFlags.Optional);
validatorInstance !== null && validatorInstance !== void 0 ? validatorInstance : (validatorInstance = new validator());
}
return (c) => validatorInstance.validate(c);
}
isValidatorFn(value) {
return isFunction(value) && !value.prototype.validate;
}
isValidatorInstance(value) {
return 'validate' in value;
}
}
ValidatorResolver.decorators = [
{ type: Injectable }
];
ValidatorResolver.ctorParameters = () => [
{ type: Injector }
];
class RxFormMapper {
constructor(customMapperResolver, validatorResolver) {
this.customMapperResolver = customMapperResolver;
this.validatorResolver = validatorResolver;
}
fromType(type) {
if (isNil(type)) {
throw new Error('type cannot be inferred implicitly');
}
const formWriter = new FormWriter(void (0), this.customMapperResolver, this.validatorResolver);
return ModelBinder.instance.getMetadata(type).accept(formWriter);
}
writeForm(value, type) {
if (isNil(value) && isNil(type)) {
throw new Error('type cannot be inferred implicitly');
}
const valueType = type !== null && type !== void 0 ? type : Object.getPrototypeOf(value).constructor;
const formWriter = new FormWriter(value, this.customMapperResolver, this.validatorResolver);
return ModelBinder.instance.getMetadata(valueType).accept(formWriter);
}
readForm(form, type) {
if (isNil(type)) {
throw new Error('type cannot be inferred implicitly');
}
const formReader = new FormReader(form, this.customMapperResolver);
return ModelBinder.instance.getMetadata(type).accept(formReader);
}
}
RxFormMapper.decorators = [
{ type: Injectable }
];
RxFormMapper.ctorParameters = () => [
{ type: CustomMapperResolver },
{ type: ValidatorResolver }
];
class RxFormMapperModule {
constructor(parentModule) {
if (parentModule) {
throw new Error('RxFormMapperModule is already loaded. Import it in the AppModule only');
}
}
static forRoot() {
return {
ngModule: RxFormMapperModule,
providers: [
RxFormMapper,
CustomMapperResolver,
ValidatorResolver
]
};
}
}
RxFormMapperModule.decorators = [
{ type: NgModule }
];
RxFormMapperModule.ctorParameters = () => [
{ type: RxFormMapperModule, decorators: [{ type: Optional }, { type: SkipSelf }] }
];
/*
* Public API Surface of rx-form-mapper
*/
/**
* Generated bundle index. Do not edit.
*/
export { CustomControl, CustomControlMetadata, CustomMapperResolver, Form, FormArray, FormArrayMetadata, FormControl, FormControlMetadata, FormGroup, FormGroupMetadata, FormMetadata, FormReader, FormWriter, MetadataDesignTypes, ModelBinder, RxFormMapper, RxFormMapperModule, ValidableMetadata, ValidatorResolver, coerceArray, isType, RxFormMapper as ɵa, CustomMapperResolver as ɵb, ValidatorResolver as ɵc };
//# sourceMappingURL=rx-form-mapper.js.map