Skip to content

Commit

Permalink
feat: add factoryFormBuilder for customizations, add classValidatorOp…
Browse files Browse the repository at this point in the history
…tions and classTransformOptions to DynamicFormBuilderOptions for set base options for all groups
  • Loading branch information
EndyKaufman committed Sep 15, 2020
1 parent b3e75d4 commit 5b2dd39
Show file tree
Hide file tree
Showing 17 changed files with 90 additions and 32 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ company.ts

```typescript
import { Validate, IsNotEmptym } from 'class-validator-multi-lang';
import { plainToClassFromExist } from 'class-transformer';
import { TextLengthMore15 } from '../utils/custom-validators';
import { marker } from '@ngneat/transloco-keys-manager/marker';

Expand All @@ -36,7 +35,13 @@ export class Company {
name: string = undefined;

constructor(data?: any) {
plainToClassFromExist(this, data);
Object.keys(data || {}).map((key) => (this[key] = data ? data[key] : undefined));
}

toJSON() {
return {
...this,
};
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export class ExpLoginPanelComponent implements OnInit {
this.form = this.fb.group(ExpUser, {
classValidatorOptions: {
groups: ['guest'],
always: true,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export class ExpRegistrationPanelComponent implements OnInit {
this.form = this.fb.group(ExpUser, {
classValidatorOptions: {
groups: ['new'],
always: true,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export class ExpUserPanelComponent {
this.form = this.fb.group(ExpUser, {
classValidatorOptions: {
groups: ['user'],
always: true,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export class ProjectPanelStep1Component implements OnDestroy {
return this.fb.group(Project, {
classValidatorOptions: {
groups: [ProjectPanelStepsEnum.Step1],
always: true,
},
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export class ProjectPanelStep2Component implements OnDestroy {
return this.fb.group(Project, {
classValidatorOptions: {
groups: [ProjectPanelStepsEnum.Step2],
always: true,
},
validator: this.classLevelValidator,
});
Expand Down
9 changes: 7 additions & 2 deletions apps/demo/src/app/shared/models/company.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { marker } from '@ngneat/transloco-keys-manager/marker';
import { plainToClassFromExist } from 'class-transformer';
import { IsNotEmpty, IsOptional, Max, MaxLength, Min, Validate } from 'class-validator-multi-lang';
import { TextLengthMore15 } from '../utils/custom-validators';
export class Company {
Expand Down Expand Up @@ -32,6 +31,12 @@ export class Company {
}

constructor(data?: any) {
plainToClassFromExist(this, data);
Object.keys(data || {}).map((key) => (this[key] = data ? data[key] : undefined));
}

toJSON() {
return {
...this,
};
}
}
13 changes: 9 additions & 4 deletions apps/demo/src/app/shared/models/department.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { marker } from '@ngneat/transloco-keys-manager/marker';
import { plainToClassFromExist, Type } from 'class-transformer';
import { IsNotEmpty, IsOptional, ValidateNested } from 'class-validator-multi-lang';
import { serializeModel } from '../utils/custom-transforms';
import { Company } from './company';

export class Department {
Expand All @@ -18,14 +16,21 @@ export class Department {

@ValidateNested()
@IsOptional()
@Type(serializeModel(Company))
company: Company;

toString() {
return this.name;
}

constructor(data?: any) {
plainToClassFromExist(this, data);
Object.keys(data || {}).map((key) => (this[key] = data ? data[key] : undefined));
this.company = new Company(this.company);
}

toJSON() {
return {
...this,
company: this.company instanceof Company ? this.company.toJSON() : this.company,
};
}
}
2 changes: 1 addition & 1 deletion apps/demo/src/app/shared/models/exp-department.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export class ExpDepartment {
}

toJSON() {
return { ...this, company: this.company ? this.company.toJSON() : this.company };
return { ...this, company: this.company instanceof ExpCompany ? this.company.toJSON() : this.company };
}
}
2 changes: 1 addition & 1 deletion apps/demo/src/app/shared/models/exp-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class ExpUser {
toJSON() {
return {
...this,
department: this.department ? this.department.toJSON() : this.department,
department: this.department instanceof ExpDepartment ? this.department.toJSON() : this.department,
dateOfBirth: this.dateOfBirth ? this.dateOfBirth.toISOString() : undefined,
};
}
Expand Down
1 change: 0 additions & 1 deletion apps/demo/src/app/shared/models/task.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { plainToClassFromExist } from 'class-transformer';
import { IsNotEmpty } from 'class-validator-multi-lang';

export class Task {
Expand Down
16 changes: 11 additions & 5 deletions apps/demo/src/app/shared/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { marker } from '@ngneat/transloco-keys-manager/marker';
import { plainToClassFromExist, Transform, Type } from 'class-transformer';
import { Transform, Type, Expose } from 'class-transformer';
import { IsEmail, IsNotEmpty, IsOptional, Matches, ValidateNested } from 'class-validator-multi-lang';
import { serializeModel, transformDateToString, transformStringToDate } from '../utils/custom-transforms';
import { Department } from './department';
Expand All @@ -17,20 +17,30 @@ export class User {
abc: marker('Only abc field'),
};

@Expose()
id: number;

@IsNotEmpty()
@Expose()
username: string;

@Expose()
password: string;

// flag "g" in RegExp work incorrect, please read issue: https://github.com/typestack/class-validator/issues/484
@Matches(RegExp('^abc$', 'i'), { message: marker(`it should match the cool 'abc' string`) })
@Expose()
abc: string;

@IsEmail()
@IsNotEmpty()
@Expose()
email: string;

@Expose()
isSuperuser: boolean;

@Expose()
isStaff: boolean;

@ValidateNested()
Expand All @@ -45,8 +55,4 @@ export class User {
toString() {
return this.username;
}

constructor(data?: any) {
plainToClassFromExist(this, data);
}
}
9 changes: 7 additions & 2 deletions libs/ngx-dynamic-form-builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ company.ts

```typescript
import { Validate, IsNotEmptym } from 'class-validator-multi-lang';
import { plainToClassFromExist } from 'class-transformer';
import { TextLengthMore15 } from '../utils/custom-validators';
import { marker } from '@ngneat/transloco-keys-manager/marker';

Expand All @@ -34,7 +33,13 @@ export class Company {
name: string = undefined;

constructor(data?: any) {
plainToClassFromExist(this, data);
Object.keys(data || {}).map((key) => (this[key] = data ? data[key] : undefined));
}

toJSON() {
return {
...this,
};
}
}
```
Expand Down
2 changes: 2 additions & 0 deletions libs/ngx-dynamic-form-builder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ export * from './lib/utils/dynamic-form-control';
export * from './lib/utils/dynamic-form-group';
export * from './lib/utils/dynamic-form-group.util';
export * from './lib/utils/get-global.util';
export * from './lib/utils/get-or-set-empty-object';
export * from './lib/utils/has-to-json';
export * from './lib/validators/forever-invalid.validator';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AbstractControlOptions, AsyncValidatorFn, ValidatorFn } from '@angular/forms';
import { ClassTransformOptions } from 'class-transformer';
import { ValidatorOptions } from 'class-validator-multi-lang';

export interface DynamicFormGroupConfig {
Expand All @@ -8,9 +9,10 @@ export interface DynamicFormGroupConfig {
asyncValidators?: AsyncValidatorFn[] | undefined;
updateOn?: any | undefined;
classValidatorOptions?: ValidatorOptions | undefined;
classTransformOptions?: ClassTransformOptions | undefined;
}
export function isDynamicFormGroupConfig(options: AbstractControlOptions | DynamicFormGroupConfig) {
return options && !!options['classValidatorOptions'];
return options && (!!options['classValidatorOptions'] || !!options['classTransformOptions']);
}
export function isLegacyOrOpts(options: AbstractControlOptions | DynamicFormGroupConfig) {
return options && (!!options['validator'] || !!options['asyncValidator']);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { AbstractControlOptions, AsyncValidatorFn, FormBuilder, ValidatorFn } from '@angular/forms';
import { ClassTransformOptions } from 'class-transformer';
import { ClassType } from 'class-transformer/ClassTransformer';
import { ValidatorOptions } from 'class-validator-multi-lang';
import 'reflect-metadata';
import {
DynamicFormGroupConfig,
Expand All @@ -11,13 +13,21 @@ import { FormModel } from '../models/form-model';
import { DynamicFormGroup, getClassValidators } from './dynamic-form-group';
const cloneDeep = require('lodash.clonedeep');

export const DEFAULT_CLASS_TRANSFORM_OPTIONS: ClassTransformOptions = {
strategy: 'excludeAll',
};
export const DEFAULT_CLASS_VALIDATOR_OPTIONS: ValidatorOptions = { validationError: { target: false }, always: true };

export interface DynamicFormBuilderOptions {
factoryFormBuilder?: () => FormBuilder;
factoryDynamicFormGroup?: <TModel, TDynamicFormGroup extends DynamicFormGroup<TModel>>(
factoryModel: ClassType<TModel>,
fields?: FormModel<TModel>,
validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null,
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
) => TDynamicFormGroup;
classValidatorOptions?: ValidatorOptions;
classTransformOptions?: ClassTransformOptions;
}

export class DynamicFormBuilder extends FormBuilder {
Expand All @@ -27,6 +37,7 @@ export class DynamicFormBuilder extends FormBuilder {
constructor(private options?: DynamicFormBuilderOptions) {
super();
}

// ******************
// Public API

Expand All @@ -49,7 +60,7 @@ export class DynamicFormBuilder extends FormBuilder {
) {
return this.group(factoryModel, undefined, controlsConfig);
}
const extra: DynamicFormGroupConfig = options as DynamicFormGroupConfig;
let extra: DynamicFormGroupConfig = cloneDeep(options) as DynamicFormGroupConfig;

let validators: ValidatorFn[] | null = null;
let asyncValidators: AsyncValidatorFn[] | null = null;
Expand All @@ -73,10 +84,24 @@ export class DynamicFormBuilder extends FormBuilder {
asyncValidators.push(extra.asyncValidator);
}
}
// Set default classValidatorOptions
if (!isDynamicFormGroupConfig(extra)) {
extra.classValidatorOptions = { validationError: { target: false } };
if (isDynamicFormGroupConfig(extra)) {
if (!this.options?.classValidatorOptions && !extra.classValidatorOptions) {
extra.classValidatorOptions = this.options?.classValidatorOptions;
}
if (!this.options?.classTransformOptions && !extra.classTransformOptions) {
extra.classTransformOptions = this.options?.classTransformOptions;
}
}
} else {
extra = {};
}

// Set default classValidatorOptions
if (!extra.classValidatorOptions) {
extra.classValidatorOptions = DEFAULT_CLASS_VALIDATOR_OPTIONS;
}
if (!extra.classTransformOptions) {
extra.classTransformOptions = DEFAULT_CLASS_TRANSFORM_OPTIONS;
}

let newControlsConfig: FormModel<TModel> | undefined;
Expand All @@ -93,7 +118,8 @@ export class DynamicFormBuilder extends FormBuilder {
if (canCreateGroup() && newControlsConfig) {
// recursively create a dynamic group for the nested object
newControlsConfig[key] = this.group(newControlsConfig[key].constructor, undefined, {
...(extra.classValidatorOptions ? { classValidatorOptions: extra.classValidatorOptions } : {}),
classValidatorOptions: extra.classValidatorOptions,
classTransformOptions: extra.classTransformOptions,
asyncValidators,
updateOn,
validators,
Expand All @@ -105,7 +131,8 @@ export class DynamicFormBuilder extends FormBuilder {
newControlsConfig[key] = super.array(
newControlsConfig[key].map((newControlsConfigItem) =>
this.group(newControlsConfigItem.constructor, undefined, {
...(extra.classValidatorOptions ? { classValidatorOptions: extra.classValidatorOptions } : {}),
classValidatorOptions: extra.classValidatorOptions,
classTransformOptions: extra.classTransformOptions,
asyncValidators,
updateOn,
validators,
Expand Down Expand Up @@ -189,6 +216,10 @@ export class DynamicFormBuilder extends FormBuilder {
return dynamicFormGroup;
}

private factoryFormBuilder() {
return new FormBuilder();
}

public factoryDynamicFormGroup<TModel>(
factoryModel: ClassType<TModel>,
fields?: FormModel<TModel>,
Expand All @@ -199,6 +230,9 @@ export class DynamicFormBuilder extends FormBuilder {
? this.options.factoryDynamicFormGroup(factoryModel, fields, validatorOrOpts, asyncValidator)
: new DynamicFormGroup<TModel>(factoryModel, fields, validatorOrOpts, asyncValidator);
formGroup.dynamicFormBuilder = this;
formGroup.originalFormBuilder = this.options?.factoryFormBuilder
? this.options?.factoryFormBuilder()
: this.factoryFormBuilder();
return formGroup;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ export class DynamicFormGroup<TModel> extends FormGroup {
public objectChange = new Subject();
public valueChangesSubscription: Subscription;
public dynamicFormBuilder: DynamicFormBuilder;
public originalFormBuilder: FormBuilder;

private _object: TModel;
private _externalErrors: ShortValidationErrors;
private _validatorOptions: ValidatorOptions;
private _classTransformOptions: ClassTransformOptions;
private _fb = new FormBuilder();
private _validateSubscription: Subscription | undefined;

constructor(
Expand Down Expand Up @@ -600,7 +600,7 @@ export class DynamicFormGroup<TModel> extends FormGroup {

dynamicFormGroup.setParent(formArray);

formGroup = this._fb.group(
formGroup = this.originalFormBuilder.group(
getClassValidators<TModel>(firstFormGroup.factoryModel, firstFormGroup.formFields)
);

Expand Down

0 comments on commit 5b2dd39

Please sign in to comment.