ソースを参照

Improved support for asyncValidators in dynaform directive

Richard Knight 6 年 前
コミット
b97c5fea71

+ 1 - 1
src/app/_mock/testfields.v9.ts

@@ -11,7 +11,7 @@ import 'rxjs/add/operator/map';
 const testAsyncValidator = (fc: FormControl): Observable<ValidationErrors> => {
 	return Observable.of(fc).delay(5000).map(fc => {
 		console.log('Async validator got', fc.value);
-		return { is4200: fc.value === '4200' };
+		return fc.value === '4200' ? null : { is4200: false };
 	});
 };
 

+ 0 - 2
src/app/app.component.ts

@@ -79,8 +79,6 @@ export class AppComponent implements OnInit, OnChanges {
 			'SAYHELLO': this.sayHello,
 			'SAYCHEESE': this.sayCheese
 		}, this);
-		console.log('%c *** Callbacks *** ', this.hCssGreen);
-		console.dir(this.dynaform.callbacks);
 	}
 
 	ngOnChanges() {

+ 14 - 7
src/app/dynaform/directives/dynafield.directive.ts

@@ -6,7 +6,8 @@ import {
 import {
 	Form, FormControl, AbstractControl,
 	NgControl, ControlContainer, ControlValueAccessor, 
-	NG_VALIDATORS, Validator, Validators, ValidatorFn, AsyncValidatorFn
+	NG_VALIDATORS, Validator, Validators, ValidatorFn,
+	NG_ASYNC_VALIDATORS, AsyncValidatorFn
 } from '@angular/forms';
 
 import * as formFieldComponents from './../components';
@@ -54,7 +55,8 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 		private resolver: ComponentFactoryResolver,
 		private container: ViewContainerRef,
 		@SkipSelf() private cc: ControlContainer,
-		@Optional() @Self() @Inject(NG_VALIDATORS) private validators: Array<Validator | ValidatorFn>
+		@Optional() @Self() @Inject(NG_VALIDATORS) private validators: Array<Validator | ValidatorFn>,
+		@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private asyncValidators: Array<AsyncValidatorFn>
 	) {
 		super();
 	}
@@ -139,6 +141,13 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
 		}
 	}
 
+	// ---------------------------------------
+	// Override methods / getters in NgControl
+
+	viewToModelUpdate(newValue: any): void {
+		this.update.emit(newValue);
+	}
+
 	get path(): string[] {
 		return [...this.cc.path, this.name];
 	}
@@ -151,11 +160,9 @@ export class DynafieldDirective extends NgControl implements OnInit, OnDestroy {
         return this.validators !== null ? Validators.compose(this.validators.map(this.normalizeValidator)) : null;
 	}
 
-	get asyncValidator(): AsyncValidatorFn { return null; }
-
-	// Override the method in NgControl
-	viewToModelUpdate(newValue: any): void {
-		this.update.emit(newValue);
+	get asyncValidator(): AsyncValidatorFn {
+		// TODO: Test composition of asyncValidators properly
+		return this.asyncValidators !== null ? Validators.composeAsync(this.asyncValidators) : null;
 	}
 
 	normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {

+ 2 - 2
src/app/dynaform/services/dynaform.service.ts

@@ -91,7 +91,7 @@ export interface Callbacks {
 export class DynaformService {
 
 	public buildFormGroup: (meta) => FormGroup;
-	public callbacks: Callbacks = {};
+	private callbacks: Callbacks = {};
 
 	constructor(private fb: FormBuilder) {
 		this.buildFormGroup = buildFormGroupFunctionFactory(fb);
@@ -107,7 +107,7 @@ export class DynaformService {
 			// Bind the component instance to the callback, so that 'this' has the context of the component
 			Object.entries(callbacks).forEach(([key, fn]) => this.callbacks[key] = fn.bind(cref));
 		} else {
-			this.callbacks = callbacks;
+			Object.assign(this.callbacks, callbacks);
 		}
 	}
 	

+ 0 - 190
src/app/dynaform/testdata/testset.1.ts.orig

@@ -1,190 +0,0 @@
-import { Validators } from '@angular/forms';
-import { ValueTransformer } from './../interfaces';
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Native
-
-const basicTextField = {
-	type: 'Text',
-	label: 'Field One',
-	placeholder: 'Type a value here'
-};
-
-const styledTextField = {
-	type: 'Text',
-	placeholder: 'With a DOM id and CSS classes applied',
-	class: ['red', 'bgPaleBlue'],
-	id: 'yoyo'
-};
-
-const textareaField = {
-	type: 'Textarea',
-	placeholder: 'Type your long-winded comments here'
-};
-
-const passwordField = {
-	type: 'Password',
-	placeholder: 'It\'s a secret'
-};
-
-const selectField = {
-	type: 'Select',
-	options: ['', 'Apples', 'Oranges', 'Pears', 'Gorgonzola']
-};
-
-const radioField = {
-	type: 'radio',
-	options: ['Tea', 'Coffee', 'Cocoa', 'Yerba Maté'],
-	validators: [ Validators.required ],
-};
-
-const disabledTextField = {
-	type: 'Text',
-	placeholder: 'You can\'t touch this',
-	isDisabled: true
-};
-
-const radioFieldHorizontal = {
-	type: 'radio',
-	options: ['Fish', 'Fowl', 'Neither'],
-	horizontal: true
-};
-
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Custom
-
-const checkbutton = {
-	type: 'checkbutton',
-	value: '456'
-};
-
-const checkbutton2 = {
-	type: 'checkbutton',
-	value: 'Yowsa'
-};
-
-const modifiers = ['Matches', 'Starts With', 'Contains'];
-const transformerFunctions: ValueTransformer = {
-	inputFn: val => {
-		let modifier = 'Starts With';
-		if (/^%.*?%$/.test(val)) {
-			modifier = 'Contains';
-		} else if (/^[^%].*?[^%]$/.test(val)) {
-			modifier = 'Matches';
-		} else if (/^%.*/.test(val)) {
-			modifier = 'Starts With';
-		}
-		const transformedVal = val.replace(/%/g, '').trim();
-		return { modifier: modifier, value: transformedVal };
-	},
-	outputFn: (mod, val) => {
-		let transformedValue;
-		switch (mod) {
-			case 'Starts With':
-				transformedValue = `%${val}`;
-				break;
-			case 'Contains':
-				transformedValue = `%${val}%`;
-				break;
-			case 'Matches':
-			default:
-				transformedValue = val;
-				break;
-		}
-		return transformedValue;
-	}
-};
-const dropdownModifiedInput = {
-	type: 'dropdownModifiedInput',
-	value: 'lovely',
-	modifiers,
-	transform: transformerFunctions
-};
-
-const checkbuttonGroup = {
-	type: 'CheckbuttonGroup',
-	firstEnablesRest: true,
-<<<<<<< HEAD
-	meta: { iMaskTheOthers: {}, groupMemberTwo: {}, groupMemberThree: {} }
-};
-
-
-
-=======
-	meta: [{name: 'iMaskTheOthers'}, {name: 'groupMemberTwo'}, {name: 'groupMemberThree'}]
-};
-
-
->>>>>>> a3dfe85e06beb3d977f49241b2360bb3e2f4a09d
-// ---------------------------------------------------------------------------------------------------------------------
-// Kendo
-
-const timepicker = {
-	type: 'timepicker'
-};
-
-const datepicker = {
-	type: 'datepicker'
-};
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Container
-
-const basicTextField2 = {
-	type: 'Text',
-	label: 'Required Field',
-	validators: [ Validators.required, Validators.minLength(4) ],
-};
-
-<<<<<<< HEAD
-const checkbuttonGroupArray = {
-	type: 'CheckbuttonGroup',
-	firstEnablesRest: false,
-	showAllOrNone: true,
-	meta: [
-		{name: 'One', value: 111}, {name: 'Two', value: 222}, {name: 'Three', value: 333}, {name: 'Four', value: 444},
-		{name: 'Five', value: 555}, {name: 'Six', value: 666}, {name: 'Seven', value: 777}, {name: 'Eight', value: 888}
-	]
-=======
-const checkbuttonGroup2 = {
-	type: 'CheckbuttonGroup',
-	firstEnablesRest: true,
-	meta: [{name: 'One'}, {name: 'Two'}, {name: 'Three'}]
->>>>>>> a3dfe85e06beb3d977f49241b2360bb3e2f4a09d
-};
-
-const container = {
-	type: 'Container',
-	meta: {
-		basicTextField2,
-<<<<<<< HEAD
-		checkbuttonGroupArray
-=======
-		checkbuttonGroup2,
->>>>>>> a3dfe85e06beb3d977f49241b2360bb3e2f4a09d
-	}
-};
-
-// ---------------------------------------------------------------------------------------------------------------------
-
-const model = {};
-
-const meta = {
-	basicTextField,
-	styledTextField,
-	textareaField,
-	passwordField,
-	selectField,
-	radioField,
-	disabledTextField,
-	radioFieldHorizontal,
-	checkbutton,
-	dropdownModifiedInput,
-	checkbuttonGroup,
-	timepicker,
-	datepicker,
-	container
-};
-
-export { model, meta };

+ 7 - 1
src/app/dynaform/testdata/testset.2.ts

@@ -38,7 +38,13 @@ const meta = {
 			anotherNestedGroup: {
 				meta: {
 					fieldE: { type: 'radio', 'label': 'Does it work yet?', validators: Validators.required },
-					fieldF: { type: 'datepicker' }
+					fieldF: { type: 'datepicker' },
+					deeplyNestedButtonGroup: { type: 'buttonGroup', label: 'Deeply Nested Buttons',
+						meta: [
+							{ label: 'Say Hello', fnId: 'SAYHELLO' },
+							{ label: 'Say Cheese', fnId: 'SAYCHEESE' }
+						]
+					}
 				}
 			}
 		}