Skip to content

Commit

Permalink
feat(dynamic-form-builder): Add support use ValidatorOptions
Browse files Browse the repository at this point in the history
Example:
```
this.form = this.fb.group(ExpUser, undefined, {
      customValidatorOptions: {
        groups: ['user']
      }
    });
```
  • Loading branch information
EndyKaufman committed Mar 9, 2018
1 parent c684fcd commit 57ac15a
Show file tree
Hide file tree
Showing 12 changed files with 986 additions and 471 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<div class="experimental-page" fxLayout.gt-md="row" fxLayout.lt-md="column" fxLayoutAlign="space-evenly stretch">
<ngx-docs-example title="Experimental: user panel" [html]="source.html" [ts]="source.ts" [launch]="source.launch" fxFlex.gt-sm="50"
fxFlex.lt-sm="100">
<div class="body">
<exp-user-panel></exp-user-panel>
</div>
</ngx-docs-example>
<div fxFlex.gt-sm="50" fxFlex.lt-sm="100">
<ngx-docs-example title="Experimental: user panel" [html]="source.html" [ts]="source.ts" [launch]="source.launch">
<div class="body">
<exp-user-panel></exp-user-panel>
</div>
</ngx-docs-example>
<ngx-docs-example title="Experimental: login panel" [html]="loginSource.html" [ts]="loginSource.ts" [launch]="loginSource.launch">
<div class="body">
<exp-login-panel></exp-login-panel>
</div>
</ngx-docs-example>
</div>
<source-tabs title="Other files" [files]="otherFiles" fxFlex.gt-sm="50" fxFlex.lt-sm="100"></source-tabs>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export class ExperimentalPageComponent {
launch: { location: 'https://stackblitz.com/edit/ngx-dynamic-form-builder-experimental', tooltip: `Edit in http://stackblitz.com` }
};

loginSource = {
html: require('!!raw-loader?lang=html!./../../panels/exp-login-panel/exp-login-panel.component.html'),
ts: require('!!raw-loader?lang=typescript!./../../panels/exp-login-panel/exp-login-panel.component.ts'),
launch: {
location: 'https://stackblitz.com/edit/ngx-dynamic-form-builder-experimental-login',
tooltip: `Edit in http://stackblitz.com`
}
};

otherFiles: { name: string, language: string, content: string }[] = [
{
name: 'exp-user.ts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { RouterModule } from '@angular/router';
import { ExperimentalPageRoutes } from './experimental-page.routes';
import { SourceTabsModule } from '../../others/source-tabs/source-tabs.module';
import { ExpUserPanelModule } from '../../panels/exp-user-panel/exp-user-panel.module';
import { ExpLoginPanelModule } from '../../panels/exp-login-panel/exp-login-panel.module';

@NgModule({
imports: [
SharedModule.forRoot(),
FlexLayoutModule,
DocsExampleModule.forRoot(),
ExpUserPanelModule.forRoot(),
ExpLoginPanelModule.forRoot(),
RouterModule.forChild(ExperimentalPageRoutes),
SourceTabsModule.forRoot()
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<form [formGroup]="form" novalidate>
<h3>Group form</h3>
<mat-form-field class="full-width">
<input matInput formControlName="username" placeholder="Username">
<mat-error *ngIf="(form?.customValidateErrors | async)?.username?.length">
{{(form?.customValidateErrors | async).username[0]}}
</mat-error>
</mat-form-field>
<mat-form-field class="full-width">
<input matInput type="password" formControlName="password" placeholder="Password">
<mat-error *ngIf="(form?.customValidateErrors | async)?.password?.length">
{{(form.customValidateErrors | async).password[0]}}
</mat-error>
</mat-form-field>
<div class="full-width">
<button mat-raised-button (click)="onLoginClick()" [disabled]="!form.valid" cdkFocusInitial>Login</button>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DynamicFormGroup, DynamicFormBuilder } from 'ngx-dynamic-form-builder';
import { Company } from './../../shared/models/company';
import { Department } from '../../shared/models/department';
import { ExpUser } from '../../shared/models/exp-user';
import { Input, Component, OnInit } from '@angular/core';
import { Validators } from '@angular/forms';

@Component({
selector: 'exp-login-panel',
templateUrl: './exp-login-panel.component.html'
})
export class ExpLoginPanelComponent implements OnInit {

@Input()
form: DynamicFormGroup<ExpUser>;

fb = new DynamicFormBuilder();
savedItem: ExpUser;

constructor() {
this.form = this.fb.group(ExpUser, undefined, {
customValidatorOptions: {
groups: ['guest']
}
});
}
ngOnInit() {
this.savedItem = undefined;
this.form.object = new ExpUser();
this.form.validateAllFormFields();
}
onLoginClick(): void {
if (this.form.valid) {
this.savedItem = this.form.object;
} else {
this.form.validateAllFormFields();
}
}
}
31 changes: 31 additions & 0 deletions apps/demo/src/app/panels/exp-login-panel/exp-login-panel.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { ModuleWithProviders } from '@angular/core';
import { ExpLoginPanelComponent } from '../../panels/exp-login-panel/exp-login-panel.component';
import { MatInputModule, MatCheckboxModule } from '@angular/material';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { SharedModule } from '../../shared/shared.module';

@NgModule({
imports: [
SharedModule.forRoot(),
MatButtonModule,
MatInputModule,
MatCheckboxModule,
FormsModule,
ReactiveFormsModule,
FlexLayoutModule
],
entryComponents: [ExpLoginPanelComponent],
exports: [ExpLoginPanelComponent],
declarations: [ExpLoginPanelComponent]
})
export class ExpLoginPanelModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ExpLoginPanelModule,
providers: []
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ <h3>Group form</h3>
{{(form?.customValidateErrors | async).username[0]}}
</mat-error>
</mat-form-field>
<mat-form-field class="full-width">
<input matInput type="password" formControlName="password" placeholder="Password">
<mat-error *ngIf="(form?.customValidateErrors | async)?.password?.length">
{{(form.customValidateErrors | async).password[0]}}
</mat-error>
</mat-form-field>
<mat-form-field class="full-width">
<input matInput type="email" formControlName="email" placeholder="Email">
<mat-error *ngIf="(form?.customValidateErrors | async)?.email?.length">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ export class ExpUserPanelComponent {
savedItem: ExpUser;

constructor() {
this.form = this.fb.group(ExpUser);
this.form = this.fb.group(ExpUser, undefined, {
customValidatorOptions: {
groups: ['user']
}
});
}
onLoadClick(): void {
this.savedItem = undefined;
Expand Down
23 changes: 18 additions & 5 deletions apps/demo/src/app/shared/models/exp-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,29 @@ import { ExpDepartment } from './exp-department';
export class ExpUser {

id: number;
@IsNotEmpty()
@IsNotEmpty({
groups: ['user', 'guest']
})
username: string;
@IsNotEmpty({
groups: ['guest']
})
password: string;
@IsEmail()
@IsNotEmpty()
@IsEmail(undefined, {
groups: ['user']
})
@IsNotEmpty({
groups: ['user']
})
email: string;
isSuperuser: boolean;
isStaff: boolean;
@ValidateNested()
@IsOptional()
@ValidateNested({
groups: ['user']
})
@IsOptional({
groups: ['user']
})
department: ExpDepartment;
dateOfBirth: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FormBuilder } from '@angular/forms';
import { DynamicFormGroup } from './dynamic-form-group';
import 'reflect-metadata';
import { ClassType } from 'class-transformer/ClassTransformer';
import { ValidatorOptions } from 'class-validator';

export class DynamicFormBuilder extends FormBuilder {
group<TModel>(
Expand All @@ -11,8 +12,12 @@ export class DynamicFormBuilder extends FormBuilder {
},
extra?: {
[key: string]: any;
customValidatorOptions?: ValidatorOptions;
} | null
): DynamicFormGroup<TModel> {
if (extra !== undefined && extra.customValidatorOptions === undefined) {
extra.customValidatorOptions = { validationError: { target: false } };
}
let newControlsConfig;
if (controlsConfig !== undefined) {
newControlsConfig = controlsConfig;
Expand Down Expand Up @@ -45,7 +50,13 @@ export class DynamicFormBuilder extends FormBuilder {
const formGroup = super.group(
DynamicFormGroup.getClassValidators<TModel>(
factoryModel,
newControlsConfig
newControlsConfig,
'',
(extra &&
extra.customValidatorOptions &&
extra.customValidatorOptions.groups) ?
extra.customValidatorOptions.groups :
undefined
)
, extra
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { FormGroup } from '@angular/forms';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ValidationMetadata } from 'class-validator/metadata/ValidationMetadata';
import { MetadataStorage, Validator, getFromContainer, ValidationTypes, validateSync, ValidationError } from 'class-validator';
import {
MetadataStorage, Validator, getFromContainer, ValidationTypes,
validateSync, ValidationError, ValidatorOptions
} from 'class-validator';
import { FormControl } from '@angular/forms';
import { classToClass, plainToClass } from 'class-transformer';
import 'reflect-metadata';
Expand All @@ -23,10 +26,16 @@ export class DynamicFormGroup<TModel> extends FormGroup {
factoryModel: ClassType<TModel>,
fields: {
[key: string]: any
}
},
targetSchema?: string,
groups?: string[]
) {
const validationMetadatas: ValidationMetadata[] =
getFromContainer(MetadataStorage).getTargetValidationMetadatas(factoryModel, '');
getFromContainer(MetadataStorage).getTargetValidationMetadatas(
factoryModel,
targetSchema ? targetSchema : '',
groups ? groups : undefined
);
const formGroupFields = {};
const validator = new Validator();
Object.keys(fields).filter(key => key.indexOf('__') !== 0).forEach(key => {
Expand Down Expand Up @@ -225,11 +234,11 @@ export class DynamicFormGroup<TModel> extends FormGroup {
plainToClass<TClassModel, Object>(cls: ClassType<TClassModel>, plain: Object) {
return plainToClass(cls, plain, { ignoreDecorators: true });
}
validate(otherErrors?: ValidationError[]) {
validate(otherErrors?: ValidationError[], validatorOptions?: ValidatorOptions) {
if (otherErrors === undefined) {
otherErrors = [];
}
const errors = validateSync(this.object, { validationError: { target: false } });
const errors = validateSync(this.object, validatorOptions);
this.customValidateErrors.next(
this.transformValidationErrors(
[...errors, ...otherErrors]
Expand Down
Loading

0 comments on commit 57ac15a

Please sign in to comment.