fix(forms): remove deprecated forms APIs (#10624)
BREAKING CHANGE: The deprecated forms APIs in @angular/common have been removed. Please update to the new forms API in @angular/forms. See angular.io for more information.
This commit is contained in:
parent
3466232f8b
commit
7606c96c80
|
@ -12,7 +12,6 @@ import {COMMON_PIPES} from './src/pipes';
|
|||
|
||||
export * from './src/pipes';
|
||||
export * from './src/directives';
|
||||
export * from './src/forms-deprecated';
|
||||
export * from './src/common_directives';
|
||||
export * from './src/location';
|
||||
export {NgLocalization} from './src/localization';
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
|
||||
* consists of
|
||||
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
|
||||
* to read information
|
||||
* from the form DOM elements.
|
||||
*
|
||||
* Forms providers are not included in default providers; you must import these providers
|
||||
* explicitly.
|
||||
*/
|
||||
import {NgModule, Type} from '@angular/core';
|
||||
|
||||
import {FORM_DIRECTIVES} from './forms-deprecated/directives';
|
||||
import {RadioControlRegistry} from './forms-deprecated/directives/radio_control_value_accessor';
|
||||
import {FormBuilder} from './forms-deprecated/form_builder';
|
||||
|
||||
export {FORM_DIRECTIVES, RadioButtonState} from './forms-deprecated/directives';
|
||||
export {AbstractControlDirective} from './forms-deprecated/directives/abstract_control_directive';
|
||||
export {CheckboxControlValueAccessor} from './forms-deprecated/directives/checkbox_value_accessor';
|
||||
export {ControlContainer} from './forms-deprecated/directives/control_container';
|
||||
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms-deprecated/directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './forms-deprecated/directives/default_value_accessor';
|
||||
export {Form} from './forms-deprecated/directives/form_interface';
|
||||
export {NgControl} from './forms-deprecated/directives/ng_control';
|
||||
export {NgControlGroup} from './forms-deprecated/directives/ng_control_group';
|
||||
export {NgControlName} from './forms-deprecated/directives/ng_control_name';
|
||||
export {NgControlStatus} from './forms-deprecated/directives/ng_control_status';
|
||||
export {NgForm} from './forms-deprecated/directives/ng_form';
|
||||
export {NgFormControl} from './forms-deprecated/directives/ng_form_control';
|
||||
export {NgFormModel} from './forms-deprecated/directives/ng_form_model';
|
||||
export {NgModel} from './forms-deprecated/directives/ng_model';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './forms-deprecated/directives/select_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator} from './forms-deprecated/directives/validators';
|
||||
export {FormBuilder} from './forms-deprecated/form_builder';
|
||||
export {AbstractControl, Control, ControlArray, ControlGroup} from './forms-deprecated/model';
|
||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './forms-deprecated/validators';
|
||||
|
||||
|
||||
/**
|
||||
* Shorthand set of providers used for building Angular forms.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* bootstrap(MyApp, [FORM_PROVIDERS]);
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_PROVIDERS: Type<any>[] = [FormBuilder, RadioControlRegistry];
|
||||
|
||||
|
||||
/**
|
||||
* The ng module for the deprecated forms API.
|
||||
* @deprecated
|
||||
*/
|
||||
@NgModule({
|
||||
providers: [
|
||||
FORM_PROVIDERS,
|
||||
],
|
||||
declarations: FORM_DIRECTIVES,
|
||||
exports: FORM_DIRECTIVES
|
||||
})
|
||||
export class DeprecatedFormsModule {
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
import {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
import {NgControlGroup} from './directives/ng_control_group';
|
||||
import {NgControlName} from './directives/ng_control_name';
|
||||
import {NgControlStatus} from './directives/ng_control_status';
|
||||
import {NgForm} from './directives/ng_form';
|
||||
import {NgFormControl} from './directives/ng_form_control';
|
||||
import {NgFormModel} from './directives/ng_form_model';
|
||||
import {NgModel} from './directives/ng_model';
|
||||
import {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
import {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
export {DefaultValueAccessor} from './directives/default_value_accessor';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {NgControlGroup} from './directives/ng_control_group';
|
||||
export {NgControlName} from './directives/ng_control_name';
|
||||
export {NgControlStatus} from './directives/ng_control_status';
|
||||
export {NgForm} from './directives/ng_form';
|
||||
export {NgFormControl} from './directives/ng_form_control';
|
||||
export {NgFormModel} from './directives/ng_form_model';
|
||||
export {NgModel} from './directives/ng_model';
|
||||
export {NumberValueAccessor} from './directives/number_value_accessor';
|
||||
export {RadioButtonState, RadioControlValueAccessor} from './directives/radio_control_value_accessor';
|
||||
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of all the form directives used as part of a `@Component` annotation.
|
||||
*
|
||||
* This is a shorthand for importing them each individually.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* class MyApp {}
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export const FORM_DIRECTIVES: Type<any>[] = [
|
||||
NgControlName,
|
||||
NgControlGroup,
|
||||
|
||||
NgFormControl,
|
||||
NgModel,
|
||||
NgFormModel,
|
||||
NgForm,
|
||||
|
||||
NgSelectOption,
|
||||
NgSelectMultipleOption,
|
||||
DefaultValueAccessor,
|
||||
NumberValueAccessor,
|
||||
CheckboxControlValueAccessor,
|
||||
SelectControlValueAccessor,
|
||||
SelectMultipleControlValueAccessor,
|
||||
RadioControlValueAccessor,
|
||||
NgControlStatus,
|
||||
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator,
|
||||
];
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseException} from '@angular/core';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new BaseException('unimplemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for control directives.
|
||||
*
|
||||
* Only used internally in the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControlDirective {
|
||||
get control(): AbstractControl { return unimplemented(); }
|
||||
|
||||
get value(): any { return isPresent(this.control) ? this.control.value : null; }
|
||||
|
||||
get valid(): boolean { return isPresent(this.control) ? this.control.valid : null; }
|
||||
|
||||
get errors(): {[key: string]: any} {
|
||||
return isPresent(this.control) ? this.control.errors : null;
|
||||
}
|
||||
|
||||
get pristine(): boolean { return isPresent(this.control) ? this.control.pristine : null; }
|
||||
|
||||
get dirty(): boolean { return isPresent(this.control) ? this.control.dirty : null; }
|
||||
|
||||
get touched(): boolean { return isPresent(this.control) ? this.control.touched : null; }
|
||||
|
||||
get untouched(): boolean { return isPresent(this.control) ? this.control.untouched : null; }
|
||||
|
||||
get path(): string[] { return null; }
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const CHECKBOX_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CheckboxControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a checkbox input element.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="checkbox" ngControl="rememberLogin">
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=checkbox][ngControl],input[type=checkbox][ngFormControl],input[type=checkbox][ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()'},
|
||||
providers: [CHECKBOX_VALUE_ACCESSOR]
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', value);
|
||||
}
|
||||
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {Form} from './form_interface';
|
||||
|
||||
|
||||
/**
|
||||
* A directive that contains multiple {@link NgControl}s.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlContainer extends AbstractControlDirective {
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Get the form to which this container belongs.
|
||||
*/
|
||||
get formDirective(): Form { return null; }
|
||||
|
||||
/**
|
||||
* Get the path to this container.
|
||||
*/
|
||||
get path(): string[] { return null; }
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
|
||||
/**
|
||||
* A bridge between a control and a native element.
|
||||
*
|
||||
* A `ControlValueAccessor` abstracts the operations of writing a new value to a
|
||||
* DOM element representing an input control.
|
||||
*
|
||||
* Please see {@link DefaultValueAccessor} for more information.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ControlValueAccessor {
|
||||
/**
|
||||
* Write a new value to the element.
|
||||
*/
|
||||
writeValue(obj: any): void;
|
||||
|
||||
/**
|
||||
* Set the function to be called when the control receives a change event.
|
||||
*/
|
||||
registerOnChange(fn: any): void;
|
||||
|
||||
/**
|
||||
* Set the function to be called when the control receives a touch event.
|
||||
*/
|
||||
registerOnTouched(fn: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to provide a {@link ControlValueAccessor} for form controls.
|
||||
*
|
||||
* See {@link DefaultValueAccessor} for how to implement one.
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALUE_ACCESSOR: OpaqueToken = new OpaqueToken('NgValueAccessor');
|
|
@ -1,54 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {isBlank} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const DEFAULT_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DefaultValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The default accessor for writing a value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="text" ngControl="searchQuery">
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input:not([type=checkbox])[ngControl],textarea[ngControl],input:not([type=checkbox])[ngFormControl],textarea[ngFormControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
|
||||
// TODO: vsavkin replace the above selector with the one below it once
|
||||
// https://github.com/angular/angular/issues/3011 is implemented
|
||||
// selector: '[ngControl],[ngModel],[ngFormControl]',
|
||||
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
providers: [DEFAULT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
var normalizedValue = isBlank(value) ? '' : value;
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, ControlGroup} from '../model';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
|
||||
|
||||
/**
|
||||
* An interface that {@link NgFormModel} and {@link NgForm} implement.
|
||||
*
|
||||
* Only used by the forms module.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Form {
|
||||
/**
|
||||
* Add a control to this form.
|
||||
*/
|
||||
addControl(dir: NgControl): void;
|
||||
|
||||
/**
|
||||
* Remove a control from this form.
|
||||
*/
|
||||
removeControl(dir: NgControl): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link Control} associated with a particular {@link NgControl}.
|
||||
*/
|
||||
getControl(dir: NgControl): Control;
|
||||
|
||||
/**
|
||||
* Add a group of controls to this form.
|
||||
*/
|
||||
addControlGroup(dir: NgControlGroup): void;
|
||||
|
||||
/**
|
||||
* Remove a group of controls from this form.
|
||||
*/
|
||||
removeControlGroup(dir: NgControlGroup): void;
|
||||
|
||||
/**
|
||||
* Look up the {@link ControlGroup} associated with a particular {@link NgControlGroup}.
|
||||
*/
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup;
|
||||
|
||||
/**
|
||||
* Update the model for a particular control with a new value.
|
||||
*/
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseException} from '@angular/core';
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new BaseException('unimplemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class that all control directive extend.
|
||||
* It binds a {@link Control} object to a DOM element.
|
||||
*
|
||||
* Used internally by Angular forms.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Host, Inject, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {composeAsyncValidators, composeValidators, controlPath} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const controlGroupProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgControlGroup)
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and binds a control group to a DOM element.
|
||||
*
|
||||
* This directive can only be used as a child of {@link NgForm} or {@link NgFormModel}.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/7EJ11uGeaggViYM6T5nq?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular Control & ControlGroup Example</h2>
|
||||
* <form #f="ngForm">
|
||||
* <div ngControlGroup="name" #cgName="ngForm">
|
||||
* <h3>Enter your name:</h3>
|
||||
* <p>First: <input ngControl="first" required></p>
|
||||
* <p>Middle: <input ngControl="middle"></p>
|
||||
* <p>Last: <input ngControl="last" required></p>
|
||||
* </div>
|
||||
* <h3>Name value:</h3>
|
||||
* <pre>{{valueOf(cgName)}}</pre>
|
||||
* <p>Name is {{cgName?.control?.valid ? "valid" : "invalid"}}</p>
|
||||
* <h3>What's your favorite food?</h3>
|
||||
* <p><input ngControl="food"></p>
|
||||
* <h3>Form value</h3>
|
||||
* <pre>{{valueOf(f)}}</pre>
|
||||
* </form>
|
||||
* </div>
|
||||
* `
|
||||
* })
|
||||
* export class App {
|
||||
* valueOf(cg: NgControlGroup): string {
|
||||
* if (cg.control == null) {
|
||||
* return null;
|
||||
* }
|
||||
* return JSON.stringify(cg.control.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This example declares a control group for a user's name. The value and validation state of
|
||||
* this group can be accessed separately from the overall form.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControlGroup]',
|
||||
providers: [controlGroupProvider],
|
||||
inputs: ['name: ngControlGroup'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgControlGroup extends ControlContainer implements OnInit,
|
||||
OnDestroy {
|
||||
/** @internal */
|
||||
_parent: ControlContainer;
|
||||
|
||||
constructor(
|
||||
@Host() @SkipSelf() parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
ngOnInit(): void { this.formDirective.addControlGroup(this); }
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeControlGroup(this); }
|
||||
|
||||
/**
|
||||
* Get the {@link ControlGroup} backing this binding.
|
||||
*/
|
||||
get control(): ControlGroup { return this.formDirective.getControlGroup(this); }
|
||||
|
||||
/**
|
||||
* Get the path to this control group.
|
||||
*/
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
/**
|
||||
* Get the {@link Form} to which this group belongs.
|
||||
*/
|
||||
get formDirective(): Form { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Host, Inject, OnChanges, OnDestroy, Optional, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {Control} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export const controlNameBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgControlName)
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and binds a control with a specified name to a DOM element.
|
||||
*
|
||||
* This directive can only be used as a child of {@link NgForm} or {@link NgFormModel}.
|
||||
|
||||
* ### Example
|
||||
*
|
||||
* In this example, we create the login and password controls.
|
||||
* We can work with each control separately: check its validity, get its value, listen to its
|
||||
* changes.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form #f="ngForm" (submit)='onLogIn(f.value)'>
|
||||
* Login <input type='text' ngControl='login' #l="ngForm">
|
||||
* <div *ngIf="!l.valid">Login is invalid</div>
|
||||
*
|
||||
* Password <input type='password' ngControl='password'>
|
||||
* <button type='submit'>Log in!</button>
|
||||
* </form>
|
||||
* `})
|
||||
* class LoginComp {
|
||||
* onLogIn(value): void {
|
||||
* // value === {login: 'some login', password: 'some password'}
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* We can also use ngModel to bind a domain model to the form.
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form (submit)='onLogIn()'>
|
||||
* Login <input type='text' ngControl='login' [(ngModel)]="credentials.login">
|
||||
* Password <input type='password' ngControl='password'
|
||||
* [(ngModel)]="credentials.password">
|
||||
* <button type='submit'>Log in!</button>
|
||||
* </form>
|
||||
* `})
|
||||
* class LoginComp {
|
||||
* credentials: {login:string, password:string};
|
||||
*
|
||||
* onLogIn(): void {
|
||||
* // this.credentials.login === "some login"
|
||||
* // this.credentials.password === "some password"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControl]',
|
||||
providers: [controlNameBinding],
|
||||
inputs: ['name: ngControl', 'model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgControlName extends NgControl implements OnChanges,
|
||||
OnDestroy {
|
||||
/** @internal */
|
||||
update = new EventEmitter();
|
||||
model: any;
|
||||
viewModel: any;
|
||||
private _added = false;
|
||||
|
||||
constructor(@Host() @SkipSelf() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
this.formDirective.addControl(this);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Self} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../../facade/lang';
|
||||
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
|
||||
/**
|
||||
* Directive automatically applied to Angular forms that sets CSS classes
|
||||
* based on control status (valid/invalid/dirty/etc).
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngControl],[ngModel],[ngFormControl]',
|
||||
host: {
|
||||
'[class.ng-untouched]': 'ngClassUntouched',
|
||||
'[class.ng-touched]': 'ngClassTouched',
|
||||
'[class.ng-pristine]': 'ngClassPristine',
|
||||
'[class.ng-dirty]': 'ngClassDirty',
|
||||
'[class.ng-valid]': 'ngClassValid',
|
||||
'[class.ng-invalid]': 'ngClassInvalid'
|
||||
}
|
||||
})
|
||||
export class NgControlStatus {
|
||||
private _cd: NgControl;
|
||||
|
||||
constructor(@Self() cd: NgControl) { this._cd = cd; }
|
||||
|
||||
get ngClassUntouched(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.untouched : false;
|
||||
}
|
||||
get ngClassTouched(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.touched : false;
|
||||
}
|
||||
get ngClassPristine(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.pristine : false;
|
||||
}
|
||||
get ngClassDirty(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.dirty : false;
|
||||
}
|
||||
get ngClassValid(): boolean {
|
||||
return isPresent(this._cd.control) ? this._cd.control.valid : false;
|
||||
}
|
||||
get ngClassInvalid(): boolean {
|
||||
return isPresent(this._cd.control) ? !this._cd.control.valid : false;
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, Optional, Self, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
import {AbstractControl, Control, ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
|
||||
|
||||
export const formDirectiveProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgForm)
|
||||
};
|
||||
|
||||
let _formWarningDisplayed: boolean = false;
|
||||
|
||||
const resolvedPromise = Promise.resolve(null);
|
||||
|
||||
/**
|
||||
* If `NgForm` is bound in a component, `<form>` elements in that component will be
|
||||
* upgraded to use the Angular form system.
|
||||
*
|
||||
* ### Typical Use
|
||||
*
|
||||
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link Component} annotation
|
||||
* to use `NgForm` and its associated controls.
|
||||
*
|
||||
* ### Structure
|
||||
*
|
||||
* An Angular form is a collection of `Control`s in some hierarchy.
|
||||
* `Control`s can be at the top level or can be organized in `ControlGroup`s
|
||||
* or `ControlArray`s. This hierarchy is reflected in the form's `value`, a
|
||||
* JSON object that mirrors the form structure.
|
||||
*
|
||||
* ### Submission
|
||||
*
|
||||
* The `ngSubmit` event signals when the user triggers a form submission.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/ltdgYj4P0iY64AR71EpL?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <p>Submit the form to see the data object Angular builds</p>
|
||||
* <h2>NgForm demo</h2>
|
||||
* <form #f="ngForm" (ngSubmit)="onSubmit(f.value)">
|
||||
* <h3>Control group: credentials</h3>
|
||||
* <div ngControlGroup="credentials">
|
||||
* <p>Login: <input type="text" ngControl="login"></p>
|
||||
* <p>Password: <input type="password" ngControl="password"></p>
|
||||
* </div>
|
||||
* <h3>Control group: person</h3>
|
||||
* <div ngControlGroup="person">
|
||||
* <p>First name: <input type="text" ngControl="firstName"></p>
|
||||
* <p>Last name: <input type="text" ngControl="lastName"></p>
|
||||
* </div>
|
||||
* <button type="submit">Submit Form</button>
|
||||
* <p>Form data submitted:</p>
|
||||
* </form>
|
||||
* <pre>{{data}}</pre>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* constructor() {}
|
||||
*
|
||||
* data: string;
|
||||
*
|
||||
* onSubmit(data) {
|
||||
* this.data = JSON.stringify(data, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {
|
||||
'(submit)': 'onSubmit()',
|
||||
},
|
||||
outputs: ['ngSubmit'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgForm extends ControlContainer implements Form {
|
||||
private _submitted: boolean = false;
|
||||
|
||||
form: ControlGroup;
|
||||
ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this._displayWarning();
|
||||
this.form = new ControlGroup(
|
||||
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
|
||||
}
|
||||
|
||||
private _displayWarning() {
|
||||
// TODO(kara): Update this when the new forms module becomes the default
|
||||
if (!_formWarningDisplayed) {
|
||||
_formWarningDisplayed = true;
|
||||
console.warn(`
|
||||
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
|
||||
will eventually be removed in favor of the new forms module. For more information, see:
|
||||
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): ControlGroup { return this.form; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
|
||||
|
||||
addControl(dir: NgControl): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var ctrl = new Control();
|
||||
setUpControl(ctrl, dir);
|
||||
container.registerControl(dir.name, ctrl);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
}
|
||||
|
||||
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addControlGroup(dir: NgControlGroup): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var group = new ControlGroup({});
|
||||
setUpControlGroup(group, dir);
|
||||
container.registerControl(dir.name, group);
|
||||
group.updateValueAndValidity({emitEvent: false});
|
||||
});
|
||||
}
|
||||
|
||||
removeControlGroup(dir: NgControlGroup): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup {
|
||||
return <ControlGroup>this.form.find(dir.path);
|
||||
}
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
resolvedPromise.then(() => {
|
||||
var ctrl = <Control>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_findContainer(path: string[]): ControlGroup {
|
||||
path.pop();
|
||||
return ListWrapper.isEmpty(path) ? this.form : <ControlGroup>this.form.find(path);
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {StringMapWrapper} from '../../facade/collection';
|
||||
import {Control} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgFormControl)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds an existing {@link Control} to a DOM element.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/jcQlZ2tTh22BZZ2ucNAT?p=preview))
|
||||
*
|
||||
* In this example, we bind the control to an input element. When the value of the input element
|
||||
* changes, the value of the control will reflect that change. Likewise, if the value of the
|
||||
* control changes, the input element reflects that change.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>NgFormControl Example</h2>
|
||||
* <form>
|
||||
* <p>Element with existing control: <input type="text"
|
||||
* [ngFormControl]="loginControl"></p>
|
||||
* <p>Value of existing control: {{loginControl.value}}</p>
|
||||
* </form>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginControl: Control = new Control('');
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ### ngModel
|
||||
*
|
||||
* We can also use `ngModel` to bind a domain model to the form.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/yHMLuHO7DNgT8XvtjTDH?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: "<input type='text' [ngFormControl]='loginControl' [(ngModel)]='login'>"
|
||||
* })
|
||||
* class LoginComp {
|
||||
* loginControl: Control = new Control('');
|
||||
* login:string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngFormControl]',
|
||||
providers: [formControlBinding],
|
||||
inputs: ['form: ngFormControl', 'model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgFormControl extends NgControl implements OnChanges {
|
||||
form: Control;
|
||||
update = new EventEmitter();
|
||||
model: any;
|
||||
viewModel: any;
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this._isControlChanged(changes)) {
|
||||
setUpControl(this.form, this);
|
||||
this.form.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.form.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
get control(): Control { return this.form; }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
|
||||
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
||||
return StringMapWrapper.contains(changes, 'form');
|
||||
}
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseException, Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
|
||||
import {isBlank} from '../../facade/lang';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
|
||||
|
||||
export const formDirectiveProvider: any = {
|
||||
provide: ControlContainer,
|
||||
useExisting: forwardRef(() => NgFormModel)
|
||||
};
|
||||
|
||||
let _formModelWarningDisplayed: boolean = false;
|
||||
|
||||
/**
|
||||
* Binds an existing control group to a DOM element.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/jqrVirudY8anJxTMUjTP?p=preview))
|
||||
*
|
||||
* In this example, we bind the control group to the form element, and we bind the login and
|
||||
* password controls to the login and password elements.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>NgFormModel Example</h2>
|
||||
* <form [ngFormModel]="loginForm">
|
||||
* <p>Login: <input type="text" ngControl="login"></p>
|
||||
* <p>Password: <input type="password" ngControl="password"></p>
|
||||
* </form>
|
||||
* <p>Value:</p>
|
||||
* <pre>{{value}}</pre>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginForm: ControlGroup;
|
||||
*
|
||||
* constructor() {
|
||||
* this.loginForm = new ControlGroup({
|
||||
* login: new Control(""),
|
||||
* password: new Control("")
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* get value(): string {
|
||||
* return JSON.stringify(this.loginForm.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* We can also use ngModel to bind a domain model to the form.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "login-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `
|
||||
* <form [ngFormModel]='loginForm'>
|
||||
* Login <input type='text' ngControl='login' [(ngModel)]='credentials.login'>
|
||||
* Password <input type='password' ngControl='password'
|
||||
* [(ngModel)]='credentials.password'>
|
||||
* <button (click)="onLogin()">Login</button>
|
||||
* </form>`
|
||||
* })
|
||||
* class LoginComp {
|
||||
* credentials: {login: string, password: string};
|
||||
* loginForm: ControlGroup;
|
||||
*
|
||||
* constructor() {
|
||||
* this.loginForm = new ControlGroup({
|
||||
* login: new Control(""),
|
||||
* password: new Control("")
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* onLogin(): void {
|
||||
* // this.credentials.login === 'some login'
|
||||
* // this.credentials.password === 'some password'
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
|
||||
@Directive({
|
||||
selector: '[ngFormModel]',
|
||||
providers: [formDirectiveProvider],
|
||||
inputs: ['form: ngFormModel'],
|
||||
host: {'(submit)': 'onSubmit()'},
|
||||
outputs: ['ngSubmit'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgFormModel extends ControlContainer implements Form,
|
||||
OnChanges {
|
||||
private _submitted: boolean = false;
|
||||
|
||||
form: ControlGroup = null;
|
||||
directives: NgControl[] = [];
|
||||
ngSubmit = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
|
||||
super();
|
||||
this._displayWarning();
|
||||
}
|
||||
|
||||
private _displayWarning() {
|
||||
// TODO(kara): Update this when the new forms module becomes the default
|
||||
if (!_formModelWarningDisplayed) {
|
||||
_formModelWarningDisplayed = true;
|
||||
console.warn(`
|
||||
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
|
||||
will eventually be removed in favor of the new forms module. For more information, see:
|
||||
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this._checkFormPresent();
|
||||
if (StringMapWrapper.contains(changes, 'form')) {
|
||||
var sync = composeValidators(this._validators);
|
||||
this.form.validator = Validators.compose([this.form.validator, sync]);
|
||||
|
||||
var async = composeAsyncValidators(this._asyncValidators);
|
||||
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
|
||||
|
||||
this.form.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
this._updateDomValue();
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
get formDirective(): Form { return this; }
|
||||
|
||||
get control(): ControlGroup { return this.form; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
addControl(dir: NgControl): void {
|
||||
var ctrl: any = this.form.find(dir.path);
|
||||
setUpControl(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
this.directives.push(dir);
|
||||
}
|
||||
|
||||
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: NgControl): void { ListWrapper.remove(this.directives, dir); }
|
||||
|
||||
addControlGroup(dir: NgControlGroup) {
|
||||
var ctrl: any = this.form.find(dir.path);
|
||||
setUpControlGroup(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
|
||||
removeControlGroup(dir: NgControlGroup) {}
|
||||
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup {
|
||||
return <ControlGroup>this.form.find(dir.path);
|
||||
}
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
var ctrl = <Control>this.form.find(dir.path);
|
||||
ctrl.updateValue(value);
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateDomValue() {
|
||||
this.directives.forEach(dir => {
|
||||
var ctrl: any = this.form.find(dir.path);
|
||||
dir.valueAccessor.writeValue(ctrl.value);
|
||||
});
|
||||
}
|
||||
|
||||
private _checkFormPresent() {
|
||||
if (isBlank(this.form)) {
|
||||
throw new BaseException(
|
||||
`ngFormModel expects a form. Please pass one in. Example: <form [ngFormModel]="myCoolForm">`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {EventEmitter} from '../../facade/async';
|
||||
import {Control} from '../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
useExisting: forwardRef(() => NgModel)
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds a domain model to a form control.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* `ngModel` binds an existing domain model to a form control. For a
|
||||
* two-way binding, use `[(ngModel)]` to ensure the model updates in
|
||||
* both directions.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: "search-comp",
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* template: `<input type='text' [(ngModel)]="searchQuery">`
|
||||
* })
|
||||
* class SearchComp {
|
||||
* searchQuery: string;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
|
||||
providers: [formControlBinding],
|
||||
inputs: ['model: ngModel'],
|
||||
outputs: ['update: ngModelChange'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class NgModel extends NgControl implements OnChanges {
|
||||
/** @internal */
|
||||
_control = new Control();
|
||||
/** @internal */
|
||||
_added = false;
|
||||
update = new EventEmitter();
|
||||
model: any;
|
||||
viewModel: any;
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
setUpControl(this._control, this);
|
||||
this._control.updateValueAndValidity({emitEvent: false});
|
||||
this._added = true;
|
||||
}
|
||||
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this._control.updateValue(this.model);
|
||||
this.viewModel = this.model;
|
||||
}
|
||||
}
|
||||
|
||||
get control(): Control { return this._control; }
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControl} from '../model';
|
||||
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <ValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <AsyncValidatorFn>validator;
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper, isBlank} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const NUMBER_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => NumberValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The accessor for writing a number value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* <input type="number" [(ngModel)]="age">
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=number][ngControl],input[type=number][ngFormControl],input[type=number][ngModel]',
|
||||
host: {
|
||||
'(change)': 'onChange($event.target.value)',
|
||||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()'
|
||||
},
|
||||
providers: [NUMBER_VALUE_ACCESSOR]
|
||||
})
|
||||
export class NumberValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: number): void {
|
||||
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
|
||||
const normalizedValue = isBlank(value) ? '' : value;
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: number) => void): void {
|
||||
this.onChange = (value) => { fn(value == '' ? null : NumberWrapper.parseFloat(value)); };
|
||||
}
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
|
||||
export const RADIO_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => RadioControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal class used by Angular to uncheck radio buttons with the matching name.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RadioControlRegistry {
|
||||
private _accessors: any[] = [];
|
||||
|
||||
add(control: NgControl, accessor: RadioControlValueAccessor) {
|
||||
this._accessors.push([control, accessor]);
|
||||
}
|
||||
|
||||
remove(accessor: RadioControlValueAccessor) {
|
||||
var indexToRemove = -1;
|
||||
for (var i = 0; i < this._accessors.length; ++i) {
|
||||
if (this._accessors[i][1] === accessor) {
|
||||
indexToRemove = i;
|
||||
}
|
||||
}
|
||||
ListWrapper.removeAt(this._accessors, indexToRemove);
|
||||
}
|
||||
|
||||
select(accessor: RadioControlValueAccessor) {
|
||||
this._accessors.forEach((c) => {
|
||||
if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
|
||||
c[1].fireUncheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _isSameGroup(
|
||||
controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) {
|
||||
return controlPair[0].control.root === accessor._control.control.root &&
|
||||
controlPair[1].name === accessor.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value provided by the forms API for radio buttons.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class RadioButtonState {
|
||||
constructor(public checked: boolean, public value: string) {}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The accessor for writing a radio control value and listening to changes that is used by the
|
||||
* {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives.
|
||||
*
|
||||
* ### Example
|
||||
* ```
|
||||
* @Component({
|
||||
* template: `
|
||||
* <input type="radio" name="food" [(ngModel)]="foodChicken">
|
||||
* <input type="radio" name="food" [(ngModel)]="foodFish">
|
||||
* `
|
||||
* })
|
||||
* class FoodCmp {
|
||||
* foodChicken = new RadioButtonState(true, "chicken");
|
||||
* foodFish = new RadioButtonState(false, "fish");
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'input[type=radio][ngControl],input[type=radio][ngFormControl],input[type=radio][ngModel]',
|
||||
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
|
||||
providers: [RADIO_VALUE_ACCESSOR]
|
||||
})
|
||||
export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
OnDestroy, OnInit {
|
||||
/** @internal */
|
||||
_state: RadioButtonState;
|
||||
/** @internal */
|
||||
_control: NgControl;
|
||||
@Input() name: string;
|
||||
/** @internal */
|
||||
_fn: Function;
|
||||
onChange = () => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(
|
||||
private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
private _registry: RadioControlRegistry, private _injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._control = this._injector.get(NgControl);
|
||||
this._registry.add(this._control, this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._registry.remove(this); }
|
||||
|
||||
writeValue(value: any): void {
|
||||
this._state = value;
|
||||
if (isPresent(value) && value.checked) {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: (_: any) => {}): void {
|
||||
this._fn = fn;
|
||||
this.onChange = () => {
|
||||
fn(new RadioButtonState(true, this._state.value));
|
||||
this._registry.select(this);
|
||||
};
|
||||
}
|
||||
|
||||
fireUncheck(): void { this._fn(new RadioButtonState(false, this._state.value)); }
|
||||
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, looseIdentical} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*
|
||||
* Note: We have to listen to the 'change' event because 'input' events aren't fired
|
||||
* for selects in Firefox and IE:
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
|
||||
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector:
|
||||
'select:not([multiple])[ngControl],select:not([multiple])[ngFormControl],select:not([multiple])[ngModel]',
|
||||
host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
value: any;
|
||||
/** @internal */
|
||||
_optionMap: Map<string, any> = new Map<string, any>();
|
||||
/** @internal */
|
||||
_idCounter: number = 0;
|
||||
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
var valueString = _buildValueString(this._getOptionId(value), value);
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', valueString);
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (valueString: string) => {
|
||||
this.value = valueString;
|
||||
fn(this._getOptionValue(valueString));
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
/** @internal */
|
||||
_registerOption(): string { return (this._idCounter++).toString(); }
|
||||
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (let id of MapWrapper.keys(this._optionMap)) {
|
||||
if (looseIdentical(this._optionMap.get(id), value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
let value = this._optionMap.get(_extractId(valueString));
|
||||
return isPresent(value) ? value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <select ngControl="city">
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectOption implements OnDestroy {
|
||||
id: string;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectControlValueAccessor) {
|
||||
if (isPresent(this._select)) this.id = this._select._registerOption();
|
||||
}
|
||||
|
||||
@Input('ngValue')
|
||||
set ngValue(value: any) {
|
||||
if (this._select == null) return;
|
||||
this._select._optionMap.set(this.id, value);
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
@Input('value')
|
||||
set value(value: any) {
|
||||
this._setElementValue(value);
|
||||
if (isPresent(this._select)) this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setElementValue(value: string): void {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (isPresent(this._select)) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,185 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
|
||||
multi: true
|
||||
};
|
||||
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (isString(value)) value = `'${value}'`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
return valueString.split(':')[0];
|
||||
}
|
||||
|
||||
/** Mock interface for HTML Options */
|
||||
interface HTMLOption {
|
||||
value: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
/** Mock interface for HTMLCollection */
|
||||
abstract class HTMLCollection {
|
||||
length: number;
|
||||
abstract item(_: number): HTMLOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* The accessor for writing a value and listening to changes on a select element.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'select[multiple][ngControl],select[multiple][ngFormControl],select[multiple][ngModel]',
|
||||
host: {'(change)': 'onChange($event.target)', '(blur)': 'onTouched()'},
|
||||
providers: [SELECT_MULTIPLE_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectMultipleControlValueAccessor implements ControlValueAccessor {
|
||||
value: any;
|
||||
/** @internal */
|
||||
_optionMap: Map<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
|
||||
/** @internal */
|
||||
_idCounter: number = 0;
|
||||
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor() {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
if (value == null) return;
|
||||
let values: Array<any> = <Array<any>>value;
|
||||
// convert values to ids
|
||||
let ids = values.map((v) => this._getOptionId(v));
|
||||
this._optionMap.forEach((opt, o) => { opt._setSelected(ids.indexOf(o.toString()) > -1); });
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => any): void {
|
||||
this.onChange = (_: any) => {
|
||||
let selected: Array<any> = [];
|
||||
if (_.hasOwnProperty('selectedOptions')) {
|
||||
let options: HTMLCollection = _.selectedOptions;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: any = options.item(i);
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
// Degrade on IE
|
||||
else {
|
||||
let options: HTMLCollection = <HTMLCollection>_.options;
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
let opt: HTMLOption = options.item(i);
|
||||
if (opt.selected) {
|
||||
let val: any = this._getOptionValue(opt.value);
|
||||
selected.push(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn(selected);
|
||||
};
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
/** @internal */
|
||||
_registerOption(value: NgSelectMultipleOption): string {
|
||||
let id: string = (this._idCounter++).toString();
|
||||
this._optionMap.set(id, value);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionId(value: any): string {
|
||||
for (let id of MapWrapper.keys(this._optionMap)) {
|
||||
if (looseIdentical(this._optionMap.get(id)._value, value)) return id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getOptionValue(valueString: string): any {
|
||||
let opt = this._optionMap.get(_extractId(valueString));
|
||||
return isPresent(opt) ? opt._value : valueString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <select multiple ngControl="city">
|
||||
* <option *ngFor="let c of cities" [value]="c"></option>
|
||||
* </select>
|
||||
* ```
|
||||
*/
|
||||
@Directive({selector: 'option'})
|
||||
export class NgSelectMultipleOption implements OnDestroy {
|
||||
id: string;
|
||||
/** @internal */
|
||||
_value: any;
|
||||
|
||||
constructor(
|
||||
private _element: ElementRef, private _renderer: Renderer,
|
||||
@Optional() @Host() private _select: SelectMultipleControlValueAccessor) {
|
||||
if (isPresent(this._select)) {
|
||||
this.id = this._select._registerOption(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Input('ngValue')
|
||||
set ngValue(value: any) {
|
||||
if (this._select == null) return;
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
|
||||
@Input('value')
|
||||
set value(value: any) {
|
||||
if (isPresent(this._select)) {
|
||||
this._value = value;
|
||||
this._setElementValue(_buildValueString(this.id, value));
|
||||
this._select.writeValue(this._select.value);
|
||||
} else {
|
||||
this._setElementValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setElementValue(value: string): void {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setSelected(selected: boolean) {
|
||||
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (isPresent(this._select)) {
|
||||
this._select._optionMap.delete(this.id);
|
||||
this._select.writeValue(this._select.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];
|
|
@ -1,129 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {BaseException} from '@angular/core';
|
||||
|
||||
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
|
||||
import {hasConstructor, isBlank, isPresent, looseIdentical} from '../../facade/lang';
|
||||
import {Control, ControlGroup} from '../model';
|
||||
import {Validators} from '../validators';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {ControlContainer} from './control_container';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {DefaultValueAccessor} from './default_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {NgControlGroup} from './ng_control_group';
|
||||
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
|
||||
import {NumberValueAccessor} from './number_value_accessor';
|
||||
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
var p = ListWrapper.clone(parent.path);
|
||||
p.push(name);
|
||||
return p;
|
||||
}
|
||||
|
||||
export function setUpControl(control: Control, dir: NgControl): void {
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
|
||||
if (isBlank(dir.valueAccessor)) _throwError(dir, 'No value accessor for form control with');
|
||||
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
dir.valueAccessor.writeValue(control.value);
|
||||
|
||||
// view -> model
|
||||
dir.valueAccessor.registerOnChange((newValue: any) => {
|
||||
dir.viewToModelUpdate(newValue);
|
||||
control.updateValue(newValue, {emitModelToViewChange: false});
|
||||
control.markAsDirty();
|
||||
});
|
||||
|
||||
// model -> view
|
||||
control.registerOnChange((newValue: any) => dir.valueAccessor.writeValue(newValue));
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
}
|
||||
|
||||
export function setUpControlGroup(control: ControlGroup, dir: NgControlGroup) {
|
||||
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
|
||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||
}
|
||||
|
||||
function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||
let messageEnd: string;
|
||||
if (dir.path.length > 1) {
|
||||
messageEnd = `path: '${dir.path.join(' -> ')}'`;
|
||||
} else if (dir.path[0]) {
|
||||
messageEnd = `name: '${dir.path}'`;
|
||||
} else {
|
||||
messageEnd = 'unspecified name';
|
||||
}
|
||||
throw new BaseException(`${message} ${messageEnd}`);
|
||||
}
|
||||
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
|
||||
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function composeAsyncValidators(validators: /* Array<Validator|Function> */ any[]):
|
||||
AsyncValidatorFn {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
||||
null;
|
||||
}
|
||||
|
||||
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
||||
if (!StringMapWrapper.contains(changes, 'model')) return false;
|
||||
var change = changes['model'];
|
||||
|
||||
if (change.isFirstChange()) return true;
|
||||
return !looseIdentical(viewModel, change.currentValue);
|
||||
}
|
||||
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(
|
||||
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
if (isBlank(valueAccessors)) return null;
|
||||
|
||||
var defaultAccessor: ControlValueAccessor;
|
||||
var builtinAccessor: ControlValueAccessor;
|
||||
var customAccessor: ControlValueAccessor;
|
||||
valueAccessors.forEach((v: ControlValueAccessor) => {
|
||||
if (hasConstructor(v, DefaultValueAccessor)) {
|
||||
defaultAccessor = v;
|
||||
|
||||
} else if (
|
||||
hasConstructor(v, CheckboxControlValueAccessor) || hasConstructor(v, NumberValueAccessor) ||
|
||||
hasConstructor(v, SelectControlValueAccessor) ||
|
||||
hasConstructor(v, SelectMultipleControlValueAccessor) ||
|
||||
hasConstructor(v, RadioControlValueAccessor)) {
|
||||
if (isPresent(builtinAccessor))
|
||||
_throwError(dir, 'More than one built-in value accessor matches form control with');
|
||||
builtinAccessor = v;
|
||||
|
||||
} else {
|
||||
if (isPresent(customAccessor))
|
||||
_throwError(dir, 'More than one custom value accessor matches form control with');
|
||||
customAccessor = v;
|
||||
}
|
||||
});
|
||||
|
||||
if (isPresent(customAccessor)) return customAccessor;
|
||||
if (isPresent(builtinAccessor)) return builtinAccessor;
|
||||
if (isPresent(defaultAccessor)) return defaultAccessor;
|
||||
|
||||
_throwError(dir, 'No valid value accessor for form control with');
|
||||
return null;
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, Directive, forwardRef} from '@angular/core';
|
||||
|
||||
import {NumberWrapper} from '../../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
import {NG_VALIDATORS, Validators} from '../validators';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface that can be implemented by classes that can act as validators.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```typescript
|
||||
* @Directive({
|
||||
* selector: '[custom-validator]',
|
||||
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
|
||||
* })
|
||||
* class CustomValidatorDirective implements Validator {
|
||||
* validate(c: Control): {[key: string]: any} {
|
||||
* return {"custom": true};
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
|
||||
|
||||
export const REQUIRED = Validators.required;
|
||||
|
||||
export const REQUIRED_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useValue: REQUIRED,
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A Directive that adds the `required` validator to any controls marked with the
|
||||
* `required` attribute, via the {@link NG_VALIDATORS} binding.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <input ngControl="fullName" required>
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[required][ngControl],[required][ngFormControl],[required][ngModel]',
|
||||
providers: [REQUIRED_VALIDATOR]
|
||||
})
|
||||
export class RequiredValidator {
|
||||
}
|
||||
|
||||
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
|
||||
export interface AsyncValidatorFn {
|
||||
(c: AbstractControl): any /*Promise<{[key: string]: any}>|Observable<{[key: string]: any}>*/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provivder which adds {@link MinLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='min'}
|
||||
*/
|
||||
export const MIN_LENGTH_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MinLengthValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A directive which installs the {@link MinLengthValidator} for any `ngControl`,
|
||||
* `ngFormControl`, or control with `ngModel` that also has a `minlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[minlength][ngControl],[minlength][ngFormControl],[minlength][ngModel]',
|
||||
providers: [MIN_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MinLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('minlength') minLength: string) {
|
||||
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider which adds {@link MaxLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* {@example common/forms/ts/validators/validators.ts region='max'}
|
||||
*/
|
||||
export const MAX_LENGTH_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => MaxLengthValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
/**
|
||||
* A directive which installs the {@link MaxLengthValidator} for any `ngControl, `ngFormControl`,
|
||||
* or control with `ngModel` that also has a `maxlength` attribute.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[maxlength][ngControl],[maxlength][ngFormControl],[maxlength][ngModel]',
|
||||
providers: [MAX_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MaxLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('maxlength') maxLength: string) {
|
||||
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
|
||||
export const PATTERN_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => PatternValidator),
|
||||
multi: true
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Directive that adds the `pattern` validator to any controls marked with the
|
||||
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
|
||||
* as the regex to validate Control value against. Follows pattern attribute
|
||||
* semantics; i.e. regex must match entire Control value.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]',
|
||||
providers: [PATTERN_VALIDATOR]
|
||||
})
|
||||
export class PatternValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute('pattern') pattern: string) {
|
||||
this._validator = Validators.pattern(pattern);
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isArray, isPresent} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {AbstractControl, Control, ControlArray, ControlGroup} from './model';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a form object from a user-specified configuration.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/ENgZo8EuIECZNensZCVr?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* viewProviders: [FORM_BINDINGS]
|
||||
* template: `
|
||||
* <form [ngFormModel]="loginForm">
|
||||
* <p>Login <input ngControl="login"></p>
|
||||
* <div ngControlGroup="passwordRetry">
|
||||
* <p>Password <input type="password" ngControl="password"></p>
|
||||
* <p>Confirm password <input type="password" ngControl="passwordConfirmation"></p>
|
||||
* </div>
|
||||
* </form>
|
||||
* <h3>Form value:</h3>
|
||||
* <pre>{{value}}</pre>
|
||||
* `,
|
||||
* directives: [FORM_DIRECTIVES]
|
||||
* })
|
||||
* export class App {
|
||||
* loginForm: ControlGroup;
|
||||
*
|
||||
* constructor(builder: FormBuilder) {
|
||||
* this.loginForm = builder.group({
|
||||
* login: ["", Validators.required],
|
||||
* passwordRetry: builder.group({
|
||||
* password: ["", Validators.required],
|
||||
* passwordConfirmation: ["", Validators.required, asyncValidator]
|
||||
* })
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* get value(): string {
|
||||
* return JSON.stringify(this.loginForm.value, null, 2);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class FormBuilder {
|
||||
/**
|
||||
* Construct a new {@link ControlGroup} with the given map of configuration.
|
||||
* Valid keys for the `extra` parameter map are `optionals` and `validator`.
|
||||
*
|
||||
* See the {@link ControlGroup} constructor for more details.
|
||||
*/
|
||||
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): ControlGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'optionals') : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'asyncValidator') : null;
|
||||
return new ControlGroup(controls, optionals, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
|
||||
*/
|
||||
control(value: Object, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null):
|
||||
Control {
|
||||
return new Control(value, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an array of {@link Control}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator` and `asyncValidator`.
|
||||
*/
|
||||
array(
|
||||
controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): ControlArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
return new ControlArray(controls, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceControls(controlsConfig: {[k: string]: any}): {[key: string]: AbstractControl} {
|
||||
var controls: {[key: string]: AbstractControl} = {};
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
|
||||
controls[controlName] = this._createControl(controlConfig);
|
||||
});
|
||||
return controls;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_createControl(controlConfig: any): AbstractControl {
|
||||
if (controlConfig instanceof Control || controlConfig instanceof ControlGroup ||
|
||||
controlConfig instanceof ControlArray) {
|
||||
return controlConfig;
|
||||
|
||||
} else if (isArray(controlConfig)) {
|
||||
var value = controlConfig[0];
|
||||
var validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
return this.control(value, validator, asyncValidator);
|
||||
|
||||
} else {
|
||||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,538 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {PromiseObservable} from 'rxjs/observable/PromiseObservable';
|
||||
|
||||
import {EventEmitter, Observable} from '../facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent, isPromise, normalizeBool} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
|
||||
*/
|
||||
export const VALID = 'VALID';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is invalid, i.e. that an error exists in the input value.
|
||||
*/
|
||||
export const INVALID = 'INVALID';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is pending, i.e. that async validation is occurring and
|
||||
* errors are not yet available for the input value.
|
||||
*/
|
||||
export const PENDING = 'PENDING';
|
||||
|
||||
export function isControl(control: Object): boolean {
|
||||
return control instanceof AbstractControl;
|
||||
}
|
||||
|
||||
function _find(control: AbstractControl, path: Array<string|number>| string) {
|
||||
if (isBlank(path)) return null;
|
||||
|
||||
if (!(path instanceof Array)) {
|
||||
path = (<string>path).split('/');
|
||||
}
|
||||
if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
|
||||
|
||||
return (<Array<string|number>>path).reduce((v, name) => {
|
||||
if (v instanceof ControlGroup) {
|
||||
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
||||
} else if (v instanceof ControlArray) {
|
||||
var index = <number>name;
|
||||
return isPresent(v.at(index)) ? v.at(index) : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, control);
|
||||
}
|
||||
|
||||
function toObservable(r: any): Observable<any> {
|
||||
return isPromise(r) ? PromiseObservable.create(r) : r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class AbstractControl {
|
||||
/** @internal */
|
||||
_value: any;
|
||||
|
||||
private _valueChanges: EventEmitter<any>;
|
||||
private _statusChanges: EventEmitter<any>;
|
||||
private _status: string;
|
||||
private _errors: {[key: string]: any};
|
||||
private _pristine: boolean = true;
|
||||
private _touched: boolean = false;
|
||||
private _parent: ControlGroup|ControlArray;
|
||||
private _asyncValidationSubscription: any;
|
||||
|
||||
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
|
||||
|
||||
get value(): any { return this._value; }
|
||||
|
||||
get status(): string { return this._status; }
|
||||
|
||||
get valid(): boolean { return this._status === VALID; }
|
||||
|
||||
/**
|
||||
* Returns the errors of this control.
|
||||
*/
|
||||
get errors(): {[key: string]: any} { return this._errors; }
|
||||
|
||||
get pristine(): boolean { return this._pristine; }
|
||||
|
||||
get dirty(): boolean { return !this.pristine; }
|
||||
|
||||
get touched(): boolean { return this._touched; }
|
||||
|
||||
get untouched(): boolean { return !this._touched; }
|
||||
|
||||
get valueChanges(): Observable<any> { return this._valueChanges; }
|
||||
|
||||
get statusChanges(): Observable<any> { return this._statusChanges; }
|
||||
|
||||
get pending(): boolean { return this._status == PENDING; }
|
||||
|
||||
markAsTouched(): void { this._touched = true; }
|
||||
|
||||
markAsDirty({onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
this._pristine = false;
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.markAsDirty({onlySelf: onlySelf});
|
||||
}
|
||||
}
|
||||
|
||||
markAsPending({onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
this._status = PENDING;
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.markAsPending({onlySelf: onlySelf});
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent: ControlGroup|ControlArray): void { this._parent = parent; }
|
||||
|
||||
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
|
||||
void {
|
||||
onlySelf = normalizeBool(onlySelf);
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._updateValue();
|
||||
|
||||
this._errors = this._runValidator();
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (this._status == VALID || this._status == PENDING) {
|
||||
this._runAsyncValidator(emitEvent);
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
this._valueChanges.emit(this._value);
|
||||
this._statusChanges.emit(this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
|
||||
}
|
||||
}
|
||||
|
||||
private _runValidator(): {[key: string]: any} {
|
||||
return isPresent(this.validator) ? this.validator(this) : null;
|
||||
}
|
||||
|
||||
private _runAsyncValidator(emitEvent: boolean): void {
|
||||
if (isPresent(this.asyncValidator)) {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
var obs = toObservable(this.asyncValidator(this));
|
||||
this._asyncValidationSubscription = obs.subscribe(
|
||||
{next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent})});
|
||||
}
|
||||
}
|
||||
|
||||
private _cancelExistingSubscription(): void {
|
||||
if (isPresent(this._asyncValidationSubscription)) {
|
||||
this._asyncValidationSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets errors on a control.
|
||||
*
|
||||
* This is used when validations are run not automatically, but manually by the user.
|
||||
*
|
||||
* Calling `setErrors` will also update the validity of the parent control.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* ```
|
||||
* var login = new Control("someLogin");
|
||||
* login.setErrors({
|
||||
* "notUnique": true
|
||||
* });
|
||||
*
|
||||
* expect(login.valid).toEqual(false);
|
||||
* expect(login.errors).toEqual({"notUnique": true});
|
||||
*
|
||||
* login.updateValue("someOtherLogin");
|
||||
*
|
||||
* expect(login.valid).toEqual(true);
|
||||
* ```
|
||||
*/
|
||||
setErrors(errors: {[key: string]: any}, {emitEvent}: {emitEvent?: boolean} = {}): void {
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._errors = errors;
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (emitEvent) {
|
||||
this._statusChanges.emit(this._status);
|
||||
}
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
this._parent._updateControlsErrors();
|
||||
}
|
||||
}
|
||||
|
||||
find(path: Array<string|number>|string): AbstractControl { return _find(this, path); }
|
||||
|
||||
getError(errorCode: string, path: string[] = null): any {
|
||||
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
|
||||
if (isPresent(control) && isPresent(control._errors)) {
|
||||
return StringMapWrapper.get(control._errors, errorCode);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hasError(errorCode: string, path: string[] = null): boolean {
|
||||
return isPresent(this.getError(errorCode, path));
|
||||
}
|
||||
|
||||
get root(): AbstractControl {
|
||||
let x: AbstractControl = this;
|
||||
|
||||
while (isPresent(x._parent)) {
|
||||
x = x._parent;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateControlsErrors(): void {
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
this._parent._updateControlsErrors();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_initObservables() {
|
||||
this._valueChanges = new EventEmitter();
|
||||
this._statusChanges = new EventEmitter();
|
||||
}
|
||||
|
||||
|
||||
private _calculateStatus(): string {
|
||||
if (isPresent(this._errors)) return INVALID;
|
||||
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
|
||||
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
|
||||
return VALID;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
abstract _updateValue(): void;
|
||||
|
||||
/** @internal */
|
||||
abstract _anyControlsHaveStatus(status: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a part of a form that cannot be divided into other controls. `Control`s have values and
|
||||
* validation state, which is determined by an optional validation function.
|
||||
*
|
||||
* `Control` is one of the three fundamental building blocks used to define forms in Angular, along
|
||||
* with {@link ControlGroup} and {@link ControlArray}.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* By default, a `Control` is created for every `<input>` or other form component.
|
||||
* With {@link NgFormControl} or {@link NgFormModel} an existing {@link Control} can be
|
||||
* bound to a DOM element instead. This `Control` can be configured with a custom
|
||||
* validation function.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class Control extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(
|
||||
value: any = null, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
this._initObservables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the control to `value`.
|
||||
*
|
||||
* If `onlySelf` is `true`, this change will only affect the validation of this `Control`
|
||||
* and not its parent component. If `emitEvent` is `true`, this change will cause a
|
||||
* `valueChanges` event on the `Control` to be emitted. Both of these options default to
|
||||
* `false`.
|
||||
*
|
||||
* If `emitModelToViewChange` is `true`, the view will be notified about the new value
|
||||
* via an `onChange` event. This is the default behavior if `emitModelToViewChange` is not
|
||||
* specified.
|
||||
*/
|
||||
updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange}: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean
|
||||
} = {}): void {
|
||||
emitModelToViewChange = isPresent(emitModelToViewChange) ? emitModelToViewChange : true;
|
||||
this._value = value;
|
||||
if (isPresent(this._onChange) && emitModelToViewChange) this._onChange(this._value);
|
||||
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_updateValue() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_anyControlsHaveStatus(status: string): boolean { return false; }
|
||||
|
||||
/**
|
||||
* Register a listener for change events.
|
||||
*/
|
||||
registerOnChange(fn: Function): void { this._onChange = fn; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a part of a form, of fixed length, that can contain other controls.
|
||||
*
|
||||
* A `ControlGroup` aggregates the values of each {@link Control} in the group.
|
||||
* The status of a `ControlGroup` depends on the status of its children.
|
||||
* If one of the controls in a group is invalid, the entire group is invalid.
|
||||
* Similarly, if a control changes its value, the entire group changes as well.
|
||||
*
|
||||
* `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other
|
||||
* controls, but is of variable length.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(
|
||||
public controls: {[key: string]: AbstractControl}, optionals: {[key: string]: boolean} = null,
|
||||
validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a control with the group's list of controls.
|
||||
*/
|
||||
registerControl(name: string, control: AbstractControl): void {
|
||||
this.controls[name] = control;
|
||||
control.setParent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl): void {
|
||||
this.registerControl(name, control);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a control from this group.
|
||||
*/
|
||||
removeControl(name: string): void {
|
||||
StringMapWrapper.delete(this.controls, name);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as non-optional.
|
||||
*/
|
||||
include(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, true);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as optional.
|
||||
*/
|
||||
exclude(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, false);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is a control with the given name in the group.
|
||||
*/
|
||||
contains(controlName: string): boolean {
|
||||
var c = StringMapWrapper.contains(this.controls, controlName);
|
||||
return c && this._included(controlName);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls() {
|
||||
StringMapWrapper.forEach(
|
||||
this.controls, (control: AbstractControl, name: string) => { control.setParent(this); });
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateValue() { this._value = this._reduceValue(); }
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
var res = false;
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
res = res || (this.contains(name) && control.status == status);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceValue() {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceChildren(initValue: any, fn: Function) {
|
||||
var res = initValue;
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
if (this._included(name)) {
|
||||
res = fn(res, control, name);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_included(controlName: string): boolean {
|
||||
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
|
||||
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a part of a form, of variable length, that can contain other controls.
|
||||
*
|
||||
* A `ControlArray` aggregates the values of each {@link Control} in the group.
|
||||
* The status of a `ControlArray` depends on the status of its children.
|
||||
* If one of the controls in a group is invalid, the entire array is invalid.
|
||||
* Similarly, if a control changes its value, the entire array changes as well.
|
||||
*
|
||||
* `ControlArray` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain
|
||||
* other controls, but is of fixed length.
|
||||
*
|
||||
* ## Adding or removing controls
|
||||
*
|
||||
* To change the controls in the array, use the `push`, `insert`, or `removeAt` methods
|
||||
* in `ControlArray` itself. These methods ensure the controls are properly tracked in the
|
||||
* form's hierarchy. Do not modify the array of `AbstractControl`s used to instantiate
|
||||
* the `ControlArray` directly, as that will result in strange and unexpected behavior such
|
||||
* as broken change detection.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class ControlArray extends AbstractControl {
|
||||
constructor(
|
||||
public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link AbstractControl} at the given `index` in the array.
|
||||
*/
|
||||
at(index: number): AbstractControl { return this.controls[index]; }
|
||||
|
||||
/**
|
||||
* Insert a new {@link AbstractControl} at the end of the array.
|
||||
*/
|
||||
push(control: AbstractControl): void {
|
||||
this.controls.push(control);
|
||||
control.setParent(this);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new {@link AbstractControl} at the given `index` in the array.
|
||||
*/
|
||||
insert(index: number, control: AbstractControl): void {
|
||||
ListWrapper.insert(this.controls, index, control);
|
||||
control.setParent(this);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the control at the given `index` in the array.
|
||||
*/
|
||||
removeAt(index: number): void {
|
||||
ListWrapper.removeAt(this.controls, index);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the control array.
|
||||
*/
|
||||
get length(): number { return this.controls.length; }
|
||||
|
||||
/** @internal */
|
||||
_updateValue(): void { this._value = this.controls.map((control) => control.value); }
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
return this.controls.some(c => c.status == status);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls(): void {
|
||||
this.controls.forEach((control) => { control.setParent(this); });
|
||||
}
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
import {toPromise} from 'rxjs/operator/toPromise';
|
||||
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent, isPromise, isString} from '../facade/lang';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {AbstractControl} from './model';
|
||||
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link Control}s in a form.
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
|
||||
|
||||
/**
|
||||
* Providers for asynchronous validators to be used for {@link Control}s
|
||||
* in a form.
|
||||
*
|
||||
* Provide this using `multi: true` to add validators.
|
||||
*
|
||||
* See {@link NG_VALIDATORS} for more details.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const NG_ASYNC_VALIDATORS: OpaqueToken = new OpaqueToken('NgAsyncValidators');
|
||||
|
||||
/**
|
||||
* Provides a set of validators used by form controls.
|
||||
*
|
||||
* A validator is a function that processes a {@link Control} or collection of
|
||||
* controls and returns a map of errors. A null map means that validation has passed.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
* var loginControl = new Control("", Validators.required)
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
|
||||
{'required': true} :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
{'minlength': {'requiredLength': minLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': v.length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires a control to match a regex to its value.
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
return regex.test(v) ? null :
|
||||
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': v}};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: AbstractControl): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
* of the individual error maps.
|
||||
*/
|
||||
static compose(validators: ValidatorFn[]): ValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: AbstractControl) {
|
||||
return _mergeErrors(_executeValidators(control, presentValidators));
|
||||
};
|
||||
}
|
||||
|
||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: AbstractControl) {
|
||||
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
|
||||
return Promise.all(promises).then(_mergeErrors);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function _convertToPromise(obj: any): Promise<any> {
|
||||
return isPromise(obj) ? obj : toPromise.call(obj);
|
||||
}
|
||||
|
||||
function _executeValidators(control: AbstractControl, validators: ValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _executeAsyncValidators(control: AbstractControl, validators: AsyncValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
|
||||
var res: {[key: string]: any} =
|
||||
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
|
@ -1,489 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CheckboxControlValueAccessor, Control, ControlGroup, ControlValueAccessor, DefaultValueAccessor, NgControl, NgControlGroup, NgControlName, NgForm, NgFormControl, NgFormModel, NgModel, SelectControlValueAccessor, Validator, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {composeValidators, selectValueAccessor} from '@angular/common/src/forms-deprecated/directives/shared';
|
||||
import {SimpleChange} from '@angular/core/src/change_detection';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {SpyNgControl, SpyValueAccessor} from '../spies';
|
||||
|
||||
class DummyControlValueAccessor implements ControlValueAccessor {
|
||||
writtenValue: any /** TODO #9100 */;
|
||||
|
||||
registerOnChange(fn: any /** TODO #9100 */) {}
|
||||
registerOnTouched(fn: any /** TODO #9100 */) {}
|
||||
|
||||
writeValue(obj: any): void { this.writtenValue = obj; }
|
||||
}
|
||||
|
||||
class CustomValidatorDirective implements Validator {
|
||||
validate(c: Control): {[key: string]: any} { return {'custom': true}; }
|
||||
}
|
||||
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
return new Promise((resolve) => {
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
if (timeout == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
setTimeout(() => { resolve(res); }, timeout);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('Form Directives', () => {
|
||||
var defaultAccessor: DefaultValueAccessor;
|
||||
|
||||
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
|
||||
|
||||
describe('shared', () => {
|
||||
describe('selectValueAccessor', () => {
|
||||
var dir: NgControl;
|
||||
|
||||
beforeEach(() => { dir = <any>new SpyNgControl(); });
|
||||
|
||||
it('should throw when given an empty array',
|
||||
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
|
||||
|
||||
it('should return the default value accessor when no other provided',
|
||||
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
|
||||
|
||||
it('should return checkbox accessor when provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, checkboxAccessor
|
||||
])).toEqual(checkboxAccessor);
|
||||
});
|
||||
|
||||
it('should return select accessor when provided', () => {
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, [
|
||||
defaultAccessor, selectAccessor
|
||||
])).toEqual(selectAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one build-in accessor is provided', () => {
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
var selectAccessor = new SelectControlValueAccessor(null, null);
|
||||
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
||||
});
|
||||
|
||||
it('should return custom accessor when provided', () => {
|
||||
var customAccessor = new SpyValueAccessor();
|
||||
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
|
||||
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
|
||||
.toEqual(customAccessor);
|
||||
});
|
||||
|
||||
it('should throw when more than one custom accessor is provided', () => {
|
||||
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
|
||||
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeValidators', () => {
|
||||
it('should compose functions', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
|
||||
var v = composeValidators([dummy1, dummy2]);
|
||||
expect(v(new Control(''))).toEqual({'dummy1': true, 'dummy2': true});
|
||||
});
|
||||
|
||||
it('should compose validator directives', () => {
|
||||
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
|
||||
expect(v(new Control(''))).toEqual({'dummy1': true, 'custom': true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgFormModel', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgFormModel([], []);
|
||||
formModel = new ControlGroup({
|
||||
'login': new Control(),
|
||||
'passwords':
|
||||
new ControlGroup({'password': new Control(), 'passwordConfirm': new Control()})
|
||||
});
|
||||
form.form = formModel;
|
||||
|
||||
loginControlDir = new NgControlName(
|
||||
form, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl', () => {
|
||||
it('should throw when no control found', () => {
|
||||
var dir = new NgControlName(form, null, null, [defaultAccessor]);
|
||||
dir.name = 'invalidName';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(`Cannot find control with name: 'invalidName'`));
|
||||
});
|
||||
|
||||
it('should throw when no value accessor', () => {
|
||||
var dir = new NgControlName(form, null, null, null);
|
||||
dir.name = 'login';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(`No value accessor for form control with name: 'login'`));
|
||||
});
|
||||
|
||||
it('should throw when no value accessor with path', () => {
|
||||
const group = new NgControlGroup(form, null, null);
|
||||
const dir = new NgControlName(group, null, null, null);
|
||||
group.name = 'passwords';
|
||||
dir.name = 'password';
|
||||
|
||||
expect(() => form.addControl(dir))
|
||||
.toThrowError(new RegExp(
|
||||
`No value accessor for form control with path: 'passwords -> password'`));
|
||||
});
|
||||
|
||||
it('should set up validators', fakeAsync(() => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('required', ['login'])).toBe(true);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(false);
|
||||
|
||||
(<Control>formModel.find(['login'])).updateValue('invalid value');
|
||||
|
||||
// sync validator passes, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('required', ['login'])).toBe(false);
|
||||
expect(formModel.hasError('async', ['login'])).toBe(true);
|
||||
}));
|
||||
|
||||
it('should write value to the DOM', () => {
|
||||
(<Control>formModel.find(['login'])).updateValue('initValue');
|
||||
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('initValue');
|
||||
});
|
||||
|
||||
it('should add the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
expect(form.directives).toEqual([loginControlDir]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addControlGroup', () => {
|
||||
var matchingPasswordsValidator = (g: any /** TODO #9100 */) => {
|
||||
if (g.controls['password'].value != g.controls['passwordConfirm'].value) {
|
||||
return {'differentPasswords': true};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
var group = new NgControlGroup(
|
||||
form, [matchingPasswordsValidator], [asyncValidator('expected')]);
|
||||
group.name = 'passwords';
|
||||
form.addControlGroup(group);
|
||||
|
||||
(<Control>formModel.find(['passwords', 'password'])).updateValue('somePassword');
|
||||
(<Control>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('someOtherPassword');
|
||||
|
||||
// sync validators are set
|
||||
expect(formModel.hasError('differentPasswords', ['passwords'])).toEqual(true);
|
||||
|
||||
(<Control>formModel.find([
|
||||
'passwords', 'passwordConfirm'
|
||||
])).updateValue('somePassword');
|
||||
|
||||
// sync validators pass, running async validators
|
||||
expect(formModel.pending).toBe(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.hasError('async', ['passwords'])).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('removeControl', () => {
|
||||
it('should remove the directive to the list of directives included in the form', () => {
|
||||
form.addControl(loginControlDir);
|
||||
form.removeControl(loginControlDir);
|
||||
expect(form.directives).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should update dom values of all the directives', () => {
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
(<Control>formModel.find(['login'])).updateValue('new value');
|
||||
|
||||
form.ngOnChanges({});
|
||||
|
||||
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('new value');
|
||||
});
|
||||
|
||||
it('should set up a sync validator', () => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new NgFormModel([formValidator], []);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
expect(formModel.errors).toEqual({'custom': true});
|
||||
});
|
||||
|
||||
it('should set up an async validator', fakeAsync(() => {
|
||||
var f = new NgFormModel([], [asyncValidator('expected')]);
|
||||
f.form = formModel;
|
||||
f.ngOnChanges({'form': new SimpleChange(null, null)});
|
||||
|
||||
tick();
|
||||
|
||||
expect(formModel.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgForm', () => {
|
||||
var form: any /** TODO #9100 */;
|
||||
var formModel: ControlGroup;
|
||||
var loginControlDir: any /** TODO #9100 */;
|
||||
var personControlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
form = new NgForm([], []);
|
||||
formModel = form.form;
|
||||
|
||||
personControlGroupDir = new NgControlGroup(form, [], []);
|
||||
personControlGroupDir.name = 'person';
|
||||
|
||||
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
|
||||
loginControlDir.name = 'login';
|
||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(form.control).toBe(formModel);
|
||||
expect(form.value).toBe(formModel.value);
|
||||
expect(form.valid).toBe(formModel.valid);
|
||||
expect(form.errors).toBe(formModel.errors);
|
||||
expect(form.pristine).toBe(formModel.pristine);
|
||||
expect(form.dirty).toBe(formModel.dirty);
|
||||
expect(form.touched).toBe(formModel.touched);
|
||||
expect(form.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
|
||||
describe('addControl & addControlGroup', () => {
|
||||
it('should create a control with the given name', fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person', 'login'])).not.toBeNull;
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
describe('removeControl & removeControlGroup', () => {
|
||||
it('should remove control', fakeAsync(() => {
|
||||
form.addControlGroup(personControlGroupDir);
|
||||
form.addControl(loginControlDir);
|
||||
|
||||
form.removeControlGroup(personControlGroupDir);
|
||||
form.removeControl(loginControlDir);
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(formModel.find(['person'])).toBeNull();
|
||||
expect(formModel.find(['person', 'login'])).toBeNull();
|
||||
}));
|
||||
|
||||
// should update the form's value and validity
|
||||
});
|
||||
|
||||
it('should set up sync validator', fakeAsync(() => {
|
||||
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
|
||||
var f = new NgForm([formValidator], []);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'custom': true});
|
||||
}));
|
||||
|
||||
it('should set up async validator', fakeAsync(() => {
|
||||
var f = new NgForm([], [asyncValidator('expected')]);
|
||||
|
||||
tick();
|
||||
|
||||
expect(f.form.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('NgControlGroup', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlGroupDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new ControlGroup({'login': new Control(null)});
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({'group': formModel});
|
||||
controlGroupDir = new NgControlGroup(parent, [], []);
|
||||
controlGroupDir.name = 'group';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlGroupDir.control).toBe(formModel);
|
||||
expect(controlGroupDir.value).toBe(formModel.value);
|
||||
expect(controlGroupDir.valid).toBe(formModel.valid);
|
||||
expect(controlGroupDir.errors).toBe(formModel.errors);
|
||||
expect(controlGroupDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlGroupDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlGroupDir.touched).toBe(formModel.touched);
|
||||
expect(controlGroupDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgFormControl', () => {
|
||||
var controlDir: any /** TODO #9100 */;
|
||||
var control: any /** TODO #9100 */;
|
||||
var checkProperties = function(control: any /** TODO #9100 */) {
|
||||
expect(controlDir.control).toBe(control);
|
||||
expect(controlDir.value).toBe(control.value);
|
||||
expect(controlDir.valid).toBe(control.valid);
|
||||
expect(controlDir.errors).toBe(control.errors);
|
||||
expect(controlDir.pristine).toBe(control.pristine);
|
||||
expect(controlDir.dirty).toBe(control.dirty);
|
||||
expect(controlDir.touched).toBe(control.touched);
|
||||
expect(controlDir.untouched).toBe(control.untouched);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
|
||||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new Control(null);
|
||||
controlDir.form = control;
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => { checkProperties(control); });
|
||||
|
||||
it('should reexport new control properties', () => {
|
||||
var newControl = new Control(null);
|
||||
controlDir.form = newControl;
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(control, newControl)});
|
||||
|
||||
checkProperties(newControl);
|
||||
});
|
||||
|
||||
it('should set up validator', () => {
|
||||
expect(control.valid).toBe(true);
|
||||
|
||||
// this will add the required validator and recalculate the validity
|
||||
controlDir.ngOnChanges({'form': new SimpleChange(null, control)});
|
||||
|
||||
expect(control.valid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NgModel', () => {
|
||||
var ngModel: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
ngModel =
|
||||
new NgModel([Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
var control = ngModel.control;
|
||||
expect(ngModel.control).toBe(control);
|
||||
expect(ngModel.value).toBe(control.value);
|
||||
expect(ngModel.valid).toBe(control.valid);
|
||||
expect(ngModel.errors).toBe(control.errors);
|
||||
expect(ngModel.pristine).toBe(control.pristine);
|
||||
expect(ngModel.dirty).toBe(control.dirty);
|
||||
expect(ngModel.touched).toBe(control.touched);
|
||||
expect(ngModel.untouched).toBe(control.untouched);
|
||||
});
|
||||
|
||||
it('should throw when no value accessor with unnamed control', () => {
|
||||
const unnamedDir = new NgModel(null, null, null);
|
||||
|
||||
expect(() => unnamedDir.ngOnChanges({}))
|
||||
.toThrowError(new RegExp(`No value accessor for form control with unspecified name`));
|
||||
});
|
||||
|
||||
|
||||
it('should set up validator', fakeAsync(() => {
|
||||
// this will add the required validator and recalculate the validity
|
||||
ngModel.ngOnChanges({});
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'required': true});
|
||||
|
||||
ngModel.control.updateValue('someValue');
|
||||
tick();
|
||||
|
||||
expect(ngModel.control.errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('NgControlName', () => {
|
||||
var formModel: any /** TODO #9100 */;
|
||||
var controlNameDir: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
formModel = new Control('name');
|
||||
|
||||
var parent = new NgFormModel([], []);
|
||||
parent.form = new ControlGroup({'name': formModel});
|
||||
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
|
||||
controlNameDir.name = 'name';
|
||||
});
|
||||
|
||||
it('should reexport control properties', () => {
|
||||
expect(controlNameDir.control).toBe(formModel);
|
||||
expect(controlNameDir.value).toBe(formModel.value);
|
||||
expect(controlNameDir.valid).toBe(formModel.valid);
|
||||
expect(controlNameDir.errors).toBe(formModel.errors);
|
||||
expect(controlNameDir.pristine).toBe(formModel.pristine);
|
||||
expect(controlNameDir.dirty).toBe(formModel.dirty);
|
||||
expect(controlNameDir.touched).toBe(formModel.touched);
|
||||
expect(controlNameDir.untouched).toBe(formModel.untouched);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, FormBuilder} from '@angular/common/src/forms-deprecated';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
function syncValidator(_: any): any { return null; }
|
||||
function asyncValidator(_: any) { return Promise.resolve(null); }
|
||||
|
||||
describe('Form Builder', () => {
|
||||
var b: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { b = new FormBuilder(); });
|
||||
|
||||
it('should create controls from a value', () => {
|
||||
var g = b.group({'login': 'some value'});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
});
|
||||
|
||||
it('should create controls from an array', () => {
|
||||
var g = b.group(
|
||||
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['password'].value).toEqual('some value');
|
||||
expect(g.controls['password'].validator).toEqual(syncValidator);
|
||||
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
|
||||
});
|
||||
|
||||
it('should use controls', () => {
|
||||
var g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
|
||||
|
||||
expect(g.controls['login'].value).toEqual('some value');
|
||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create groups with optional controls', () => {
|
||||
var g = b.group({'login': 'some value'}, {'optionals': {'login': false}});
|
||||
|
||||
expect(g.contains('login')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should create groups with a custom validator', () => {
|
||||
var g = b.group(
|
||||
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
|
||||
|
||||
expect(g.validator).toBe(syncValidator);
|
||||
expect(g.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
|
||||
it('should create control arrays', () => {
|
||||
var c = b.control('three');
|
||||
var a = b.array(
|
||||
['one', ['two', syncValidator], c, b.array(['four'])], syncValidator, asyncValidator);
|
||||
|
||||
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
|
||||
expect(a.validator).toBe(syncValidator);
|
||||
expect(a.asyncValidator).toBe(asyncValidator);
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,856 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {EventEmitter} from '../../src/facade/async';
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, timeouts = {}) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
return new Promise((resolve) => {
|
||||
var t = isPresent((timeouts as any /** TODO #9100 */)[c.value]) ?
|
||||
(timeouts as any /** TODO #9100 */)[c.value] :
|
||||
0;
|
||||
var res = c.value != expected ? {'async': true} : null;
|
||||
|
||||
if (t == 0) {
|
||||
resolve(res);
|
||||
} else {
|
||||
setTimeout(() => { resolve(res); }, t);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function asyncValidatorReturningObservable(c: any /** TODO #9100 */) {
|
||||
var e = new EventEmitter();
|
||||
Promise.resolve(null).then(() => { e.emit({'async': true}); });
|
||||
return e;
|
||||
}
|
||||
|
||||
describe('Form Model', () => {
|
||||
describe('Control', () => {
|
||||
it('should default the value to null', () => {
|
||||
var c = new Control();
|
||||
expect(c.value).toBe(null);
|
||||
});
|
||||
|
||||
describe('validator', () => {
|
||||
it('should run validator with the initial value', () => {
|
||||
var c = new Control('value', Validators.required);
|
||||
expect(c.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('should rerun the validator when the value changes', () => {
|
||||
var c = new Control('value', Validators.required);
|
||||
c.updateValue(null);
|
||||
expect(c.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return errors', () => {
|
||||
var c = new Control(null, Validators.required);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run validator with the initial value', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should support validators returning observables', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidatorReturningObservable);
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should rerun the validator when the value changes', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should run the async validator only when the sync validator passes', fakeAsync(() => {
|
||||
var c = new Control('', Validators.required, asyncValidator('expected'));
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
|
||||
c.updateValue('some value');
|
||||
tick();
|
||||
|
||||
expect(c.errors).toEqual({'async': true});
|
||||
}));
|
||||
|
||||
it('should mark the control as pending while running the async validation',
|
||||
fakeAsync(() => {
|
||||
var c = new Control('', null, asyncValidator('expected'));
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
|
||||
tick();
|
||||
|
||||
expect(c.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should only use the latest async validation run', fakeAsync(() => {
|
||||
var c =
|
||||
new Control('', null, asyncValidator('expected', {'long': 200, 'expected': 100}));
|
||||
|
||||
c.updateValue('long');
|
||||
c.updateValue('expected');
|
||||
|
||||
tick(300);
|
||||
|
||||
expect(c.valid).toEqual(true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
it('should be false after creating a control', () => {
|
||||
var c = new Control('value');
|
||||
expect(c.dirty).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
var c = new Control('value');
|
||||
c.markAsDirty();
|
||||
expect(c.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateValue', () => {
|
||||
var g: any /** TODO #9100 */, c: any /** TODO #9100 */;
|
||||
beforeEach(() => {
|
||||
c = new Control('oldValue');
|
||||
g = new ControlGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should update the value of the control', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(c.value).toEqual('newValue');
|
||||
});
|
||||
|
||||
it('should invoke ngOnChanges if it is present', () => {
|
||||
var ngOnChanges: any /** TODO #9100 */;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => ngOnChanges = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(ngOnChanges).toEqual(['invoked', 'newValue']);
|
||||
});
|
||||
|
||||
it('should not invoke on change when explicitly specified', () => {
|
||||
var onChange: any /** TODO #9100 */ = null;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => onChange = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue', {emitModelToViewChange: false});
|
||||
|
||||
expect(onChange).toBeNull();
|
||||
});
|
||||
|
||||
it('should update the parent', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(g.value).toEqual({'one': 'newValue'});
|
||||
});
|
||||
|
||||
it('should not update the parent when explicitly specified', () => {
|
||||
c.updateValue('newValue', {onlySelf: true});
|
||||
expect(g.value).toEqual({'one': 'oldValue'});
|
||||
});
|
||||
|
||||
it('should fire an event', fakeAsync(() => {
|
||||
|
||||
c.valueChanges.subscribe(
|
||||
{next: (value: any) => { expect(value).toEqual('newValue'); }});
|
||||
|
||||
c.updateValue('newValue');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should not fire an event when explicitly specified', fakeAsync(() => {
|
||||
c.valueChanges.subscribe({next: (value: any) => { throw 'Should not happen'; }});
|
||||
|
||||
c.updateValue('newValue', {emitEvent: false});
|
||||
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('valueChanges & statusChanges', () => {
|
||||
var c: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { c = new Control('old', Validators.required); });
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(c.value).toEqual('new');
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to invalid', fakeAsync(() => {
|
||||
c.statusChanges.subscribe({
|
||||
next: (status: any) => {
|
||||
expect(c.status).toEqual('INVALID');
|
||||
expect(status).toEqual('INVALID');
|
||||
}
|
||||
});
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should fire an event after the status has been updated to pending', fakeAsync(() => {
|
||||
var c = new Control('old', Validators.required, asyncValidator('expected'));
|
||||
|
||||
var log: any[] /** TODO #9100 */ = [];
|
||||
c.valueChanges.subscribe({next: (value: any) => log.push(`value: '${value}'`)});
|
||||
|
||||
c.statusChanges.subscribe({next: (status: any) => log.push(`status: '${status}'`)});
|
||||
|
||||
c.updateValue('');
|
||||
tick();
|
||||
|
||||
c.updateValue('nonEmpty');
|
||||
tick();
|
||||
|
||||
c.updateValue('expected');
|
||||
tick();
|
||||
|
||||
expect(log).toEqual([
|
||||
'' +
|
||||
'value: \'\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'nonEmpty\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'INVALID\'',
|
||||
'value: \'expected\'',
|
||||
'status: \'PENDING\'',
|
||||
'status: \'VALID\'',
|
||||
]);
|
||||
}));
|
||||
|
||||
// TODO: remove the if statement after making observable delivery sync
|
||||
it('should update set errors and status before emitting an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.valueChanges.subscribe((value: any /** TODO #9100 */) => {
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
async.done();
|
||||
});
|
||||
c.updateValue('');
|
||||
}));
|
||||
|
||||
it('should return a cold observable',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
c.updateValue('will be ignored');
|
||||
c.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual('new');
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c.updateValue('new');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('setErrors', () => {
|
||||
it('should set errors on a control', () => {
|
||||
var c = new Control('someValue');
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(c.valid).toEqual(false);
|
||||
expect(c.errors).toEqual({'someError': true});
|
||||
});
|
||||
|
||||
it('should reset the errors and validity when the value changes', () => {
|
||||
var c = new Control('someValue', Validators.required);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
c.updateValue('');
|
||||
|
||||
expect(c.errors).toEqual({'required': true});
|
||||
});
|
||||
|
||||
it('should update the parent group\'s validity', () => {
|
||||
var c = new Control('someValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not reset parent\'s errors', () => {
|
||||
var c = new Control('someValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
expect(g.errors).toEqual({'someGroupError': true});
|
||||
});
|
||||
|
||||
it('should reset errors when updating a value', () => {
|
||||
var c = new Control('oldValue');
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
g.setErrors({'someGroupError': true});
|
||||
c.setErrors({'someError': true});
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(c.errors).toEqual(null);
|
||||
expect(g.errors).toEqual(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ControlGroup', () => {
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var g = new ControlGroup({'one': new Control('111'), 'two': new Control('222')});
|
||||
expect(g.value).toEqual({'one': '111', 'two': '222'});
|
||||
});
|
||||
|
||||
it('should be empty when there are no child controls', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.value).toEqual({});
|
||||
});
|
||||
|
||||
it('should support nested groups', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '222'}});
|
||||
|
||||
(<Control>(g.controls['nested'].find('two'))).updateValue('333');
|
||||
|
||||
expect(g.value).toEqual({'one': '111', 'nested': {'two': '333'}});
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding and removing controls', () => {
|
||||
it('should update value and validity when control is added', () => {
|
||||
var g = new ControlGroup({'one': new Control('1')});
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
|
||||
g.addControl('two', new Control('2', Validators.minLength(10)));
|
||||
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
});
|
||||
|
||||
it('should update value and validity when control is removed', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('1'), 'two': new Control('2', Validators.minLength(10))});
|
||||
expect(g.value).toEqual({'one': '1', 'two': '2'});
|
||||
expect(g.valid).toBe(false);
|
||||
|
||||
g.removeControl('two');
|
||||
|
||||
expect(g.value).toEqual({'one': '1'});
|
||||
expect(g.valid).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls['one'].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlGroup({'one': c}, null, simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: any /** TODO #9100 */, g: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
g = new ControlGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(g.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(g.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('optional components', () => {
|
||||
describe('contains', () => {
|
||||
var group: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
group = new ControlGroup(
|
||||
{
|
||||
'required': new Control('requiredValue'),
|
||||
'optional': new Control('optionalValue')
|
||||
},
|
||||
{'optional': false});
|
||||
});
|
||||
|
||||
// rename contains into has
|
||||
it('should return false when the component is not included',
|
||||
() => { expect(group.contains('optional')).toEqual(false); });
|
||||
|
||||
it('should return false when there is no component with the given name',
|
||||
() => { expect(group.contains('something else')).toEqual(false); });
|
||||
|
||||
it('should return true when the component is included', () => {
|
||||
expect(group.contains('required')).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.contains('optional')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include an inactive component into the group value', () => {
|
||||
var group = new ControlGroup(
|
||||
{'required': new Control('requiredValue'), 'optional': new Control('optionalValue')},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue'});
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.value).toEqual({'required': 'requiredValue', 'optional': 'optionalValue'});
|
||||
});
|
||||
|
||||
it('should not run Validators on an inactive component', () => {
|
||||
var group = new ControlGroup(
|
||||
{
|
||||
'required': new Control('requiredValue', Validators.required),
|
||||
'optional': new Control('', Validators.required)
|
||||
},
|
||||
{'optional': false});
|
||||
|
||||
expect(group.valid).toEqual(true);
|
||||
|
||||
group.include('optional');
|
||||
|
||||
expect(group.valid).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var g: any /** TODO #9100 */, c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control('old1');
|
||||
c2 = new Control('old2');
|
||||
g = new ControlGroup({'one': c1, 'two': c2}, {'two': true});
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
|
||||
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is excluded',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual({'one': 'old1'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
g.exclude('two');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is included',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
g.exclude('two');
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
g.include('two');
|
||||
}));
|
||||
|
||||
it('should fire an event every time a control is updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var loggedValues: any[] /** TODO #9100 */ = [];
|
||||
|
||||
g.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
loggedValues.push(value);
|
||||
|
||||
if (loggedValues.length == 2) {
|
||||
expect(loggedValues).toEqual([
|
||||
{'one': 'new1', 'two': 'old2'}, {'one': 'new1', 'two': 'new2'}
|
||||
]);
|
||||
async.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
c2.updateValue('new2');
|
||||
}));
|
||||
|
||||
xit('should not fire an event when an excluded control is updated',
|
||||
inject(
|
||||
[AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// hard to test without hacking zones
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getError', () => {
|
||||
it('should return the error when it is present', () => {
|
||||
var c = new Control('', Validators.required);
|
||||
var g = new ControlGroup({'one': c});
|
||||
expect(c.getError('required')).toEqual(true);
|
||||
expect(g.getError('required', ['one'])).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return null otherwise', () => {
|
||||
var c = new Control('not empty', Validators.required);
|
||||
var g = new ControlGroup({'one': c});
|
||||
expect(c.getError('invalid')).toEqual(null);
|
||||
expect(g.getError('required', ['one'])).toEqual(null);
|
||||
expect(g.getError('required', ['invalid'])).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new Control('value');
|
||||
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should set the parent group\'s status to pending', fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
var g = new ControlGroup({'one': c});
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should run the parent group\'s async validator when children are pending',
|
||||
fakeAsync(() => {
|
||||
var c = new Control('value', null, asyncValidator('expected'));
|
||||
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.find(['one']).errors).toEqual({'async': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ControlArray', () => {
|
||||
describe('adding/removing', () => {
|
||||
var a: ControlArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */, c3: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
a = new ControlArray([]);
|
||||
c1 = new Control(1);
|
||||
c2 = new Control(2);
|
||||
c3 = new Control(3);
|
||||
});
|
||||
|
||||
it('should support pushing', () => {
|
||||
a.push(c1);
|
||||
expect(a.length).toEqual(1);
|
||||
expect(a.controls).toEqual([c1]);
|
||||
});
|
||||
|
||||
it('should support removing', () => {
|
||||
a.push(c1);
|
||||
a.push(c2);
|
||||
a.push(c3);
|
||||
|
||||
a.removeAt(1);
|
||||
|
||||
expect(a.controls).toEqual([c1, c3]);
|
||||
});
|
||||
|
||||
it('should support inserting', () => {
|
||||
a.push(c1);
|
||||
a.push(c3);
|
||||
|
||||
a.insert(1, c2);
|
||||
|
||||
expect(a.controls).toEqual([c1, c2, c3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
it('should be the reduced value of the child controls', () => {
|
||||
var a = new ControlArray([new Control(1), new Control(2)]);
|
||||
expect(a.value).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('should be an empty array when there are no child controls', () => {
|
||||
var a = new ControlArray([]);
|
||||
expect(a.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should run the validator when the value changes', () => {
|
||||
var simpleValidator = (c: any /** TODO #9100 */) =>
|
||||
c.controls[0].value != 'correct' ? {'broken': true} : null;
|
||||
|
||||
var c = new Control(null);
|
||||
var g = new ControlArray([c], simpleValidator);
|
||||
|
||||
c.updateValue('correct');
|
||||
|
||||
expect(g.valid).toEqual(true);
|
||||
expect(g.errors).toEqual(null);
|
||||
|
||||
c.updateValue('incorrect');
|
||||
|
||||
expect(g.valid).toEqual(false);
|
||||
expect(g.errors).toEqual({'broken': true});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('dirty', () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => { expect(a.dirty).toEqual(false); });
|
||||
|
||||
it('should be false after changing the value of the control', () => {
|
||||
c.markAsDirty();
|
||||
|
||||
expect(a.dirty).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pending', () => {
|
||||
var c: Control;
|
||||
var a: ControlArray;
|
||||
|
||||
beforeEach(() => {
|
||||
c = new Control('value');
|
||||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it('should be false after creating a control', () => {
|
||||
expect(c.pending).toEqual(false);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
|
||||
it('should be true after changing the value of the control', () => {
|
||||
c.markAsPending();
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(true);
|
||||
});
|
||||
|
||||
it('should not update the parent when onlySelf = true', () => {
|
||||
c.markAsPending({onlySelf: true});
|
||||
|
||||
expect(c.pending).toEqual(true);
|
||||
expect(a.pending).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valueChanges', () => {
|
||||
var a: ControlArray;
|
||||
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => {
|
||||
c1 = new Control('old1');
|
||||
c2 = new Control('old2');
|
||||
a = new ControlArray([c1, c2]);
|
||||
});
|
||||
|
||||
it('should fire an event after the value has been updated',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(a.value).toEqual(['new1', 'old2']);
|
||||
expect(value).toEqual(['new1', 'old2']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event after the control\'s observable fired an event',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var controlCallbackIsCalled = false;
|
||||
|
||||
|
||||
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
|
||||
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(controlCallbackIsCalled).toBe(true);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
c1.updateValue('new1');
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is removed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual(['old1']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
a.removeAt(1);
|
||||
}));
|
||||
|
||||
it('should fire an event when a control is added',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
a.removeAt(1);
|
||||
|
||||
a.valueChanges.subscribe({
|
||||
next: (value: any) => {
|
||||
expect(value).toEqual(['old1', 'old2']);
|
||||
async.done();
|
||||
}
|
||||
});
|
||||
|
||||
a.push(c2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
it('should return null when path is null', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is empty', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find([])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is invalid', () => {
|
||||
var g = new ControlGroup({});
|
||||
expect(g.find(['one', 'two'])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return a child of a control group', () => {
|
||||
var g = new ControlGroup(
|
||||
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
|
||||
|
||||
expect(g.find(['nested', 'two']).value).toEqual('222');
|
||||
expect(g.find(['one']).value).toEqual('111');
|
||||
expect(g.find('nested/two').value).toEqual('222');
|
||||
expect(g.find('one').value).toEqual('111');
|
||||
});
|
||||
|
||||
it('should return an element of an array', () => {
|
||||
var g = new ControlGroup({'array': new ControlArray([new Control('111')])});
|
||||
|
||||
expect(g.find(['array', 0]).value).toEqual('111');
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncValidator', () => {
|
||||
it('should run the async validator', fakeAsync(() => {
|
||||
var c = new Control('value');
|
||||
var g = new ControlArray([c], null, asyncValidator('expected'));
|
||||
|
||||
expect(g.pending).toEqual(true);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(g.errors).toEqual({'async': true});
|
||||
expect(g.pending).toEqual(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControl, Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
|
||||
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
import {EventEmitter} from '../../src/facade/async';
|
||||
import {normalizeAsyncValidator} from '../../src/forms-deprecated/directives/normalize_validator';
|
||||
|
||||
export function main() {
|
||||
function validator(key: string, error: any) {
|
||||
return function(c: AbstractControl) {
|
||||
var r = {};
|
||||
(r as any)[key] = error;
|
||||
return r;
|
||||
};
|
||||
}
|
||||
|
||||
class AsyncValidatorDirective {
|
||||
constructor(private expected: string, private error: any) {}
|
||||
|
||||
validate(c: any): {[key: string]: any;} {
|
||||
return Observable.create((obs: any) => {
|
||||
const error = this.expected !== c.value ? this.error : null;
|
||||
obs.next(error);
|
||||
obs.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('Validators', () => {
|
||||
describe('required', () => {
|
||||
it('should error on an empty string',
|
||||
() => { expect(Validators.required(new Control(''))).toEqual({'required': true}); });
|
||||
|
||||
it('should error on null',
|
||||
() => { expect(Validators.required(new Control(null))).toEqual({'required': true}); });
|
||||
|
||||
it('should not error on a non-empty string',
|
||||
() => { expect(Validators.required(new Control('not empty'))).toEqual(null); });
|
||||
|
||||
it('should accept zero as valid',
|
||||
() => { expect(Validators.required(new Control(0))).toEqual(null); });
|
||||
});
|
||||
|
||||
describe('minLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.minLength(2)(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.minLength(2)(new Control('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on short strings', () => {
|
||||
expect(Validators.minLength(2)(new Control('a'))).toEqual({
|
||||
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.maxLength(2)(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.maxLength(2)(new Control('aa'))).toEqual(null); });
|
||||
|
||||
it('should error on long strings', () => {
|
||||
expect(Validators.maxLength(2)(new Control('aaa'))).toEqual({
|
||||
'maxlength': {'requiredLength': 2, 'actualLength': 3}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('pattern', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(''))).toEqual(null); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(null))).toEqual(null); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaAA'))).toEqual(null); });
|
||||
|
||||
it('should error on failure to match string', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaa0'))).toEqual({
|
||||
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
|
||||
const c = Validators.composeAsync(
|
||||
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]);
|
||||
|
||||
let value: any = null;
|
||||
c(new Control()).then((v: any) => value = v);
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true});
|
||||
}));
|
||||
|
||||
describe('compose', () => {
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.compose(null)).toBe(null); });
|
||||
|
||||
it('should collect errors from all the validators', () => {
|
||||
var c = Validators.compose([validator('a', true), validator('b', true)]);
|
||||
expect(c(new Control(''))).toEqual({'a': true, 'b': true});
|
||||
});
|
||||
|
||||
it('should run validators left to right', () => {
|
||||
var c = Validators.compose([validator('a', 1), validator('a', 2)]);
|
||||
expect(c(new Control(''))).toEqual({'a': 2});
|
||||
});
|
||||
|
||||
it('should return null when no errors', () => {
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new Control(''))).toEqual(null);
|
||||
});
|
||||
|
||||
it('should ignore nulls', () => {
|
||||
var c = Validators.compose([null, Validators.required]);
|
||||
expect(c(new Control(''))).toEqual({'required': true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeAsync', () => {
|
||||
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
|
||||
return (c: any /** TODO #9100 */) => {
|
||||
var emitter = new EventEmitter();
|
||||
var res = c.value != expected ? response : null;
|
||||
Promise.resolve(null).then(() => {
|
||||
emitter.emit(res);
|
||||
// this is required because of a bug in ObservableWrapper
|
||||
// where callComplete can fire before callEmit
|
||||
// remove this one the bug is fixed
|
||||
setTimeout(() => { emitter.complete(); }, 0);
|
||||
});
|
||||
|
||||
return emitter;
|
||||
};
|
||||
}
|
||||
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.composeAsync(null)).toEqual(null); });
|
||||
|
||||
it('should collect errors from all the validators', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([
|
||||
asyncValidator('expected', {'one': true}), asyncValidator('expected', {'two': true})
|
||||
]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true, 'two': true});
|
||||
}));
|
||||
|
||||
it('should return null when no errors', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('expected'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual(null);
|
||||
}));
|
||||
|
||||
it('should ignore nulls', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);
|
||||
|
||||
var value: any /** TODO #9100 */ = null;
|
||||
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
|
||||
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual({'one': true});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MaxLengthValidator, MinLengthValidator} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
|
||||
// #docregion min
|
||||
@Component({
|
||||
selector: 'min-cmp',
|
||||
directives: [MinLengthValidator],
|
||||
template: `
|
||||
<form>
|
||||
<p>Year: <input ngControl="year" minlength="2"></p>
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class MinLengthTestComponent {
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion max
|
||||
@Component({
|
||||
selector: 'max-cmp',
|
||||
directives: [MaxLengthValidator],
|
||||
template: `
|
||||
<form>
|
||||
<p>Year: <input ngControl="year" maxlength="4"></p>
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class MaxLengthTestComponent {
|
||||
}
|
||||
// #enddocregion
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NG_VALIDATORS} from '@angular/common';
|
||||
import {bootstrap} from '@angular/platform-browser-dynamic';
|
||||
|
||||
class MyApp {}
|
||||
let myValidator: any = null;
|
||||
|
||||
// #docregion ng_validators
|
||||
bootstrap(MyApp, [{provide: NG_VALIDATORS, useValue: myValidator, multi: true}]);
|
||||
// #enddocregion
|
|
@ -257,15 +257,10 @@ export abstract class AbstractControl {
|
|||
this._updateControlsErrors(emitEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated - use get() instead
|
||||
*/
|
||||
find(path: Array<string|number>|string): AbstractControl { return _find(this, path, '/'); }
|
||||
|
||||
get(path: Array<string|number>|string): AbstractControl { return _find(this, path, '.'); }
|
||||
|
||||
getError(errorCode: string, path: string[] = null): any {
|
||||
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
|
||||
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.get(path) : this;
|
||||
if (isPresent(control) && isPresent(control._errors)) {
|
||||
return StringMapWrapper.get(control._errors, errorCode);
|
||||
} else {
|
||||
|
@ -432,18 +427,6 @@ export class FormControl extends AbstractControl {
|
|||
this.setValue(value, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use setValue() instead.
|
||||
*/
|
||||
updateValue(value: any, options: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
} = {}): void {
|
||||
this.setValue(value, options);
|
||||
}
|
||||
|
||||
reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
this.markAsPristine({onlySelf: onlySelf});
|
||||
this.markAsUntouched({onlySelf: onlySelf});
|
||||
|
|
|
@ -365,66 +365,6 @@ export function main() {
|
|||
}));
|
||||
});
|
||||
|
||||
// deprecated function
|
||||
// TODO(kara): remove these tests when updateValue is removed
|
||||
describe('updateValue', () => {
|
||||
var g: any /** TODO #9100 */, c: any /** TODO #9100 */;
|
||||
beforeEach(() => {
|
||||
c = new FormControl('oldValue');
|
||||
g = new FormGroup({'one': c});
|
||||
});
|
||||
|
||||
it('should update the value of the control', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(c.value).toEqual('newValue');
|
||||
});
|
||||
|
||||
it('should invoke ngOnChanges if it is present', () => {
|
||||
var ngOnChanges: any /** TODO #9100 */;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => ngOnChanges = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue');
|
||||
|
||||
expect(ngOnChanges).toEqual(['invoked', 'newValue']);
|
||||
});
|
||||
|
||||
it('should not invoke on change when explicitly specified', () => {
|
||||
var onChange: any /** TODO #9100 */ = null;
|
||||
c.registerOnChange((v: any /** TODO #9100 */) => onChange = ['invoked', v]);
|
||||
|
||||
c.updateValue('newValue', {emitModelToViewChange: false});
|
||||
|
||||
expect(onChange).toBeNull();
|
||||
});
|
||||
|
||||
it('should update the parent', () => {
|
||||
c.updateValue('newValue');
|
||||
expect(g.value).toEqual({'one': 'newValue'});
|
||||
});
|
||||
|
||||
it('should not update the parent when explicitly specified', () => {
|
||||
c.updateValue('newValue', {onlySelf: true});
|
||||
expect(g.value).toEqual({'one': 'oldValue'});
|
||||
});
|
||||
|
||||
it('should fire an event', fakeAsync(() => {
|
||||
|
||||
c.valueChanges.subscribe(
|
||||
{next: (value: any) => { expect(value).toEqual('newValue'); }});
|
||||
|
||||
c.updateValue('newValue');
|
||||
tick();
|
||||
}));
|
||||
|
||||
it('should not fire an event when explicitly specified', fakeAsync(() => {
|
||||
c.valueChanges.subscribe({next: (value: any) => { throw 'Should not happen'; }});
|
||||
|
||||
c.updateValue('newValue', {emitEvent: false});
|
||||
|
||||
tick();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('reset()', () => {
|
||||
let c: FormControl;
|
||||
|
||||
|
@ -1878,41 +1818,6 @@ export function main() {
|
|||
}));
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
it('should return null when path is null', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find(null)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is empty', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find([])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return null when path is invalid', () => {
|
||||
var g = new FormGroup({});
|
||||
expect(g.find(['one', 'two'])).toEqual(null);
|
||||
});
|
||||
|
||||
it('should return a child of a control group', () => {
|
||||
var g = new FormGroup({
|
||||
'one': new FormControl('111'),
|
||||
'nested': new FormGroup({'two': new FormControl('222')})
|
||||
});
|
||||
|
||||
expect(g.find(['nested', 'two']).value).toEqual('222');
|
||||
expect(g.find(['one']).value).toEqual('111');
|
||||
expect(g.find('nested/two').value).toEqual('222');
|
||||
expect(g.find('one').value).toEqual('111');
|
||||
});
|
||||
|
||||
it('should return an element of an array', () => {
|
||||
var g = new FormGroup({'array': new FormArray([new FormControl('111')])});
|
||||
|
||||
expect(g.find(['array', 0]).value).toEqual('111');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should return null when path is null', () => {
|
||||
var g = new FormGroup({});
|
||||
|
|
|
@ -431,8 +431,8 @@ export function main() {
|
|||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
form.find('login').valueChanges.subscribe(
|
||||
() => { expect(form.find('login').dirty).toBe(true); });
|
||||
form.get('login').valueChanges.subscribe(
|
||||
() => { expect(form.get('login').dirty).toBe(true); });
|
||||
|
||||
const loginEl = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
loginEl.value = 'newValue';
|
||||
|
@ -460,10 +460,10 @@ export function main() {
|
|||
loginEl.value = 'newValue';
|
||||
dispatchEvent(loginEl, 'input');
|
||||
|
||||
expect(form.find('login').pristine).toBe(false);
|
||||
expect(form.get('login').pristine).toBe(false);
|
||||
|
||||
form.find('login').valueChanges.subscribe(
|
||||
() => { expect(form.find('login').pristine).toBe(true); });
|
||||
form.get('login').valueChanges.subscribe(
|
||||
() => { expect(form.get('login').pristine).toBe(true); });
|
||||
|
||||
dispatchEvent(formEl, 'reset');
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule, FORM_PROVIDERS} from '@angular/common';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {APP_INITIALIZER, ApplicationModule, ExceptionHandler, NgModule, NgZone, OpaqueToken, PLATFORM_COMMON_PROVIDERS, PlatformRef, ReflectiveInjector, RootRenderer, assertPlatform, createPlatform, createPlatformFactory, getPlatform, platformCore} from '@angular/core';
|
||||
|
||||
import {BROWSER_SANITIZATION_PROVIDERS} from './browser';
|
||||
|
@ -83,7 +83,7 @@ function setupWebWorker(): void {
|
|||
*/
|
||||
@NgModule({
|
||||
providers: [
|
||||
FORM_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS, Serializer,
|
||||
BROWSER_SANITIZATION_PROVIDERS, Serializer,
|
||||
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
|
||||
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
||||
WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer},
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
'index': 'index.js',
|
||||
'@angular/core': '/packages-dist/core/bundles/core.umd.js',
|
||||
'@angular/common': '/packages-dist/common/bundles/common.umd.js',
|
||||
'@angular/forms': '/packages-dist/forms/bundles/forms.umd.js',
|
||||
'@angular/compiler': '/packages-dist/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser':
|
||||
'/packages-dist/platform-browser/bundles/platform-browser.umd.js',
|
||||
|
@ -54,6 +55,7 @@
|
|||
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/upgrade': {main: 'index.js', defaultExtension: 'js'}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<model-driven-forms>
|
||||
<reactive-forms>
|
||||
Loading...
|
||||
</model-driven-forms>
|
||||
</reactive-forms>
|
||||
|
||||
<script src="../bootstrap.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ControlGroup, FORM_DIRECTIVES, FormBuilder, NgFor, NgFormModel, NgIf, Validators} from '@angular/common';
|
||||
import {AbstractControl} from '@angular/common';
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, Directive, Host} from '@angular/core';
|
||||
import {isPresent, print} from '@angular/core/src/facade/lang';
|
||||
import {AbstractControl, FormBuilder, FormGroup, FormGroupDirective, REACTIVE_FORM_DIRECTIVES, Validators} from '@angular/forms';
|
||||
import {bootstrap} from '@angular/platform-browser-dynamic';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Custom validator.
|
||||
*/
|
||||
|
@ -52,11 +53,11 @@ class ShowError {
|
|||
controlPath: string;
|
||||
errorTypes: string[];
|
||||
|
||||
constructor(@Host() formDir: NgFormModel) { this.formDir = formDir; }
|
||||
constructor(@Host() formDir: FormGroupDirective) { this.formDir = formDir; }
|
||||
|
||||
get errorMessage(): string {
|
||||
var form: ControlGroup = this.formDir.form;
|
||||
var control = form.find(this.controlPath);
|
||||
var form: FormGroup = this.formDir.form;
|
||||
var control = form.get(this.controlPath);
|
||||
if (isPresent(control) && control.touched) {
|
||||
for (var i = 0; i < this.errorTypes.length; ++i) {
|
||||
if (control.hasError(this.errorTypes[i])) {
|
||||
|
@ -75,66 +76,66 @@ class ShowError {
|
|||
|
||||
|
||||
@Component({
|
||||
selector: 'model-driven-forms',
|
||||
selector: 'reactive-forms',
|
||||
viewProviders: [FormBuilder],
|
||||
template: `
|
||||
<h1>Checkout Form (Model Driven)</h1>
|
||||
<h1>Checkout Form (Reactive)</h1>
|
||||
|
||||
<form (ngSubmit)="onSubmit()" [ngFormModel]="form" #f="ngForm">
|
||||
<form (ngSubmit)="onSubmit()" [formGroup]="form" #f="ngForm">
|
||||
<p>
|
||||
<label for="firstName">First Name</label>
|
||||
<input type="text" id="firstName" ngControl="firstName">
|
||||
<input type="text" id="firstName" formControlName="firstName">
|
||||
<show-error control="firstName" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="middleName">Middle Name</label>
|
||||
<input type="text" id="middleName" ngControl="middleName">
|
||||
<input type="text" id="middleName" formControlName="middleName">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" id="lastName" ngControl="lastName">
|
||||
<input type="text" id="lastName" formControlName="lastName">
|
||||
<show-error control="lastName" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="country">Country</label>
|
||||
<select id="country" ngControl="country">
|
||||
<select id="country" formControlName="country">
|
||||
<option *ngFor="let c of countries" [value]="c">{{c}}</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="creditCard">Credit Card</label>
|
||||
<input type="text" id="creditCard" ngControl="creditCard">
|
||||
<input type="text" id="creditCard" formControlName="creditCard">
|
||||
<show-error control="creditCard" [errors]="['required', 'invalidCreditCard']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="amount">Amount</label>
|
||||
<input type="number" id="amount" ngControl="amount">
|
||||
<input type="number" id="amount" formControlName="amount">
|
||||
<show-error control="amount" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" ngControl="email">
|
||||
<input type="email" id="email" formControlName="email">
|
||||
<show-error control="email" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="comments">Comments</label>
|
||||
<textarea id="comments" ngControl="comments">
|
||||
<textarea id="comments" formControlName="comments">
|
||||
</textarea>
|
||||
</p>
|
||||
|
||||
<button type="submit" [disabled]="!f.form.valid">Submit</button>
|
||||
</form>
|
||||
`,
|
||||
directives: [FORM_DIRECTIVES, NgFor, ShowError]
|
||||
directives: [REACTIVE_FORM_DIRECTIVES, NgFor, ShowError]
|
||||
})
|
||||
class ModelDrivenForms {
|
||||
class ReactiveForms {
|
||||
form: any /** TODO #9100 */;
|
||||
countries = ['US', 'Canada'];
|
||||
|
||||
|
@ -158,5 +159,5 @@ class ModelDrivenForms {
|
|||
}
|
||||
|
||||
export function main() {
|
||||
bootstrap(ModelDrivenForms);
|
||||
bootstrap(ReactiveForms);
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {FORM_DIRECTIVES, NgFor, NgIf} from '@angular/common';
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, EventEmitter, Injectable, Input, Output} from '@angular/core';
|
||||
import {ListWrapper} from '@angular/core/src/facade/collection';
|
||||
import {FORM_DIRECTIVES} from '@angular/forms';
|
||||
import {bootstrap} from '@angular/platform-browser-dynamic';
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {FORM_DIRECTIVES, NgFor, NgIf} from '@angular/common';
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, Injectable} from '@angular/core';
|
||||
import {FORM_DIRECTIVES} from '@angular/forms';
|
||||
import {bootstrap} from '@angular/platform-browser-dynamic';
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ControlGroup, FORM_DIRECTIVES, NG_VALIDATORS, NgControl, NgFor, NgForm, NgIf, Validators} from '@angular/common';
|
||||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, Directive, Host} from '@angular/core';
|
||||
import {isPresent, print} from '@angular/core/src/facade/lang';
|
||||
import {FORM_DIRECTIVES, FormGroup, NG_VALIDATORS, NgControl, NgForm, Validators} from '@angular/forms';
|
||||
import {bootstrap} from '@angular/platform-browser-dynamic';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A domain model we are binding the form controls to.
|
||||
*/
|
||||
|
@ -79,8 +81,8 @@ class ShowError {
|
|||
constructor(@Host() formDir: NgForm) { this.formDir = formDir; }
|
||||
|
||||
get errorMessage(): string {
|
||||
var form: ControlGroup = this.formDir.form;
|
||||
var control = form.find(this.controlPath);
|
||||
var form: FormGroup = this.formDir.form;
|
||||
var control = form.get(this.controlPath);
|
||||
if (isPresent(control) && control.touched) {
|
||||
for (var i = 0; i < this.errorTypes.length; ++i) {
|
||||
if (control.hasError(this.errorTypes[i])) {
|
||||
|
@ -106,49 +108,49 @@ class ShowError {
|
|||
<form (ngSubmit)="onSubmit()" #f="ngForm">
|
||||
<p>
|
||||
<label for="firstName">First Name</label>
|
||||
<input type="text" id="firstName" ngControl="firstName" [(ngModel)]="model.firstName" required>
|
||||
<input type="text" id="firstName" name="firstName" [(ngModel)]="model.firstName" required>
|
||||
<show-error control="firstName" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="middleName">Middle Name</label>
|
||||
<input type="text" id="middleName" ngControl="middleName" [(ngModel)]="model.middleName">
|
||||
<input type="text" id="middleName" name="middleName" [(ngModel)]="model.middleName">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" id="lastName" ngControl="lastName" [(ngModel)]="model.lastName" required>
|
||||
<input type="text" id="lastName" name="lastName" [(ngModel)]="model.lastName" required>
|
||||
<show-error control="lastName" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="country">Country</label>
|
||||
<select id="country" ngControl="country" [(ngModel)]="model.country">
|
||||
<select id="country" name="country" [(ngModel)]="model.country">
|
||||
<option *ngFor="let c of countries" [value]="c">{{c}}</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="creditCard">Credit Card</label>
|
||||
<input type="text" id="creditCard" ngControl="creditCard" [(ngModel)]="model.creditCard" required credit-card>
|
||||
<input type="text" id="creditCard" name="creditCard" [(ngModel)]="model.creditCard" required credit-card>
|
||||
<show-error control="creditCard" [errors]="['required', 'invalidCreditCard']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="amount">Amount</label>
|
||||
<input type="number" id="amount" ngControl="amount" [(ngModel)]="model.amount" required>
|
||||
<input type="number" id="amount" name="amount" [(ngModel)]="model.amount" required>
|
||||
<show-error control="amount" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" ngControl="email" [(ngModel)]="model.email" required>
|
||||
<input type="email" id="email" name="email" [(ngModel)]="model.email" required>
|
||||
<show-error control="email" [errors]="['required']"></show-error>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="comments">Comments</label>
|
||||
<textarea id="comments" ngControl="comments" [(ngModel)]="model.comments">
|
||||
<textarea id="comments" name="comments" [(ngModel)]="model.comments">
|
||||
</textarea>
|
||||
</p>
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {FORM_DIRECTIVES, NgFor} from '@angular/common';
|
||||
import {NgFor} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {FORM_DIRECTIVES} from '@angular/forms';
|
||||
|
||||
import {Store, Todo, TodoFactory} from './services/TodoStore';
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ System.config({
|
|||
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
|
||||
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
|
||||
|
|
|
@ -44,7 +44,6 @@ var specFiles: any =
|
|||
'@angular/core/test/zone/**',
|
||||
'@angular/core/test/fake_async_spec.*',
|
||||
'@angular/forms/test/**',
|
||||
'@angular/common/test/forms-deprecated/**',
|
||||
'@angular/router/test/route_config/route_config_spec.*',
|
||||
'@angular/router/test/integration/bootstrap_spec.*',
|
||||
'@angular/integration_test/symbol_inspector/**',
|
||||
|
|
|
@ -1,59 +1,3 @@
|
|||
/** @experimental */
|
||||
export declare abstract class AbstractControl {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
dirty: boolean;
|
||||
errors: {
|
||||
[key: string]: any;
|
||||
};
|
||||
pending: boolean;
|
||||
pristine: boolean;
|
||||
root: AbstractControl;
|
||||
status: string;
|
||||
statusChanges: Observable<any>;
|
||||
touched: boolean;
|
||||
untouched: boolean;
|
||||
valid: boolean;
|
||||
validator: ValidatorFn;
|
||||
value: any;
|
||||
valueChanges: Observable<any>;
|
||||
constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn);
|
||||
find(path: Array<string | number> | string): AbstractControl;
|
||||
getError(errorCode: string, path?: string[]): any;
|
||||
hasError(errorCode: string, path?: string[]): boolean;
|
||||
markAsDirty({onlySelf}?: {
|
||||
onlySelf?: boolean;
|
||||
}): void;
|
||||
markAsPending({onlySelf}?: {
|
||||
onlySelf?: boolean;
|
||||
}): void;
|
||||
markAsTouched(): void;
|
||||
setErrors(errors: {
|
||||
[key: string]: any;
|
||||
}, {emitEvent}?: {
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
setParent(parent: ControlGroup | ControlArray): void;
|
||||
updateValueAndValidity({onlySelf, emitEvent}?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare abstract class AbstractControlDirective {
|
||||
control: AbstractControl;
|
||||
dirty: boolean;
|
||||
errors: {
|
||||
[key: string]: any;
|
||||
};
|
||||
path: string[];
|
||||
pristine: boolean;
|
||||
touched: boolean;
|
||||
untouched: boolean;
|
||||
valid: boolean;
|
||||
value: any;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare const APP_BASE_HREF: OpaqueToken;
|
||||
|
||||
|
@ -64,16 +8,6 @@ export declare class AsyncPipe implements OnDestroy {
|
|||
transform(obj: Observable<any> | Promise<any> | EventEmitter<any>): any;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
onChange: (_: any) => void;
|
||||
onTouched: () => void;
|
||||
constructor(_renderer: Renderer, _elementRef: ElementRef);
|
||||
registerOnChange(fn: (_: any) => {}): void;
|
||||
registerOnTouched(fn: () => {}): void;
|
||||
writeValue(value: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare const COMMON_DIRECTIVES: any[];
|
||||
|
||||
|
@ -84,60 +18,6 @@ export declare const COMMON_PIPES: (typeof AsyncPipe | typeof SlicePipe | typeof
|
|||
export declare class CommonModule {
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class Control extends AbstractControl {
|
||||
constructor(value?: any, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn);
|
||||
registerOnChange(fn: Function): void;
|
||||
updateValue(value: any, {onlySelf, emitEvent, emitModelToViewChange}?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
emitModelToViewChange?: boolean;
|
||||
}): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class ControlArray extends AbstractControl {
|
||||
controls: AbstractControl[];
|
||||
length: number;
|
||||
constructor(controls: AbstractControl[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn);
|
||||
at(index: number): AbstractControl;
|
||||
insert(index: number, control: AbstractControl): void;
|
||||
push(control: AbstractControl): void;
|
||||
removeAt(index: number): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class ControlContainer extends AbstractControlDirective {
|
||||
formDirective: Form;
|
||||
name: string;
|
||||
path: string[];
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class ControlGroup extends AbstractControl {
|
||||
controls: {
|
||||
[key: string]: AbstractControl;
|
||||
};
|
||||
constructor(controls: {
|
||||
[key: string]: AbstractControl;
|
||||
}, optionals?: {
|
||||
[key: string]: boolean;
|
||||
}, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn);
|
||||
addControl(name: string, control: AbstractControl): void;
|
||||
contains(controlName: string): boolean;
|
||||
exclude(controlName: string): void;
|
||||
include(controlName: string): void;
|
||||
registerControl(name: string, control: AbstractControl): void;
|
||||
removeControl(name: string): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface ControlValueAccessor {
|
||||
registerOnChange(fn: any): void;
|
||||
registerOnTouched(fn: any): void;
|
||||
writeValue(obj: any): void;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare const CORE_DIRECTIVES: Type<any>[];
|
||||
|
||||
|
@ -156,48 +36,6 @@ export declare class DecimalPipe implements PipeTransform {
|
|||
transform(value: any, digits?: string): string;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class DefaultValueAccessor implements ControlValueAccessor {
|
||||
onChange: (_: any) => void;
|
||||
onTouched: () => void;
|
||||
constructor(_renderer: Renderer, _elementRef: ElementRef);
|
||||
registerOnChange(fn: (_: any) => void): void;
|
||||
registerOnTouched(fn: () => void): void;
|
||||
writeValue(value: any): void;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export declare class DeprecatedFormsModule {
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface Form {
|
||||
addControl(dir: NgControl): void;
|
||||
addControlGroup(dir: NgControlGroup): void;
|
||||
getControl(dir: NgControl): Control;
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup;
|
||||
removeControl(dir: NgControl): void;
|
||||
removeControlGroup(dir: NgControlGroup): void;
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare const FORM_DIRECTIVES: Type<any>[];
|
||||
|
||||
/** @experimental */
|
||||
export declare const FORM_PROVIDERS: Type<any>[];
|
||||
|
||||
/** @experimental */
|
||||
export declare class FormBuilder {
|
||||
array(controlsConfig: any[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): ControlArray;
|
||||
control(value: Object, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): Control;
|
||||
group(controlsConfig: {
|
||||
[key: string]: any;
|
||||
}, extra?: {
|
||||
[key: string]: any;
|
||||
}): ControlGroup;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class HashLocationStrategy extends LocationStrategy {
|
||||
constructor(_platformLocation: PlatformLocation, _baseHref?: string);
|
||||
|
@ -265,31 +103,6 @@ export declare class LowerCasePipe implements PipeTransform {
|
|||
transform(value: string): string;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class MaxLengthValidator implements Validator {
|
||||
constructor(maxLength: string);
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class MinLengthValidator implements Validator {
|
||||
constructor(minLength: string);
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare const NG_ASYNC_VALIDATORS: OpaqueToken;
|
||||
|
||||
/** @experimental */
|
||||
export declare const NG_VALIDATORS: OpaqueToken;
|
||||
|
||||
/** @experimental */
|
||||
export declare const NG_VALUE_ACCESSOR: OpaqueToken;
|
||||
|
||||
/** @stable */
|
||||
export declare class NgClass implements DoCheck {
|
||||
initialClasses: string;
|
||||
|
@ -300,53 +113,6 @@ export declare class NgClass implements DoCheck {
|
|||
ngDoCheck(): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare abstract class NgControl extends AbstractControlDirective {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
name: string;
|
||||
validator: ValidatorFn;
|
||||
valueAccessor: ControlValueAccessor;
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgControlGroup extends ControlContainer implements OnInit, OnDestroy {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
control: ControlGroup;
|
||||
formDirective: Form;
|
||||
path: string[];
|
||||
validator: ValidatorFn;
|
||||
constructor(parent: ControlContainer, _validators: any[], _asyncValidators: any[]);
|
||||
ngOnDestroy(): void;
|
||||
ngOnInit(): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgControlName extends NgControl implements OnChanges, OnDestroy {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
control: Control;
|
||||
formDirective: any;
|
||||
model: any;
|
||||
path: string[];
|
||||
validator: ValidatorFn;
|
||||
viewModel: any;
|
||||
constructor(_parent: ControlContainer, _validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
ngOnDestroy(): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgControlStatus {
|
||||
ngClassDirty: boolean;
|
||||
ngClassInvalid: boolean;
|
||||
ngClassPristine: boolean;
|
||||
ngClassTouched: boolean;
|
||||
ngClassUntouched: boolean;
|
||||
ngClassValid: boolean;
|
||||
constructor(cd: NgControl);
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class NgFor implements DoCheck, OnChanges {
|
||||
ngForOf: any;
|
||||
|
@ -357,64 +123,6 @@ export declare class NgFor implements DoCheck, OnChanges {
|
|||
ngOnChanges(changes: SimpleChanges): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgForm extends ControlContainer implements Form {
|
||||
control: ControlGroup;
|
||||
controls: {
|
||||
[key: string]: AbstractControl;
|
||||
};
|
||||
form: ControlGroup;
|
||||
formDirective: Form;
|
||||
ngSubmit: EventEmitter<{}>;
|
||||
path: string[];
|
||||
submitted: boolean;
|
||||
constructor(validators: any[], asyncValidators: any[]);
|
||||
addControl(dir: NgControl): void;
|
||||
addControlGroup(dir: NgControlGroup): void;
|
||||
getControl(dir: NgControl): Control;
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup;
|
||||
onSubmit(): boolean;
|
||||
removeControl(dir: NgControl): void;
|
||||
removeControlGroup(dir: NgControlGroup): void;
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgFormControl extends NgControl implements OnChanges {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
control: Control;
|
||||
form: Control;
|
||||
model: any;
|
||||
path: string[];
|
||||
update: EventEmitter<{}>;
|
||||
validator: ValidatorFn;
|
||||
viewModel: any;
|
||||
constructor(_validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgFormModel extends ControlContainer implements Form, OnChanges {
|
||||
control: ControlGroup;
|
||||
directives: NgControl[];
|
||||
form: ControlGroup;
|
||||
formDirective: Form;
|
||||
ngSubmit: EventEmitter<{}>;
|
||||
path: string[];
|
||||
submitted: boolean;
|
||||
constructor(_validators: any[], _asyncValidators: any[]);
|
||||
addControl(dir: NgControl): void;
|
||||
addControlGroup(dir: NgControlGroup): void;
|
||||
getControl(dir: NgControl): Control;
|
||||
getControlGroup(dir: NgControlGroup): ControlGroup;
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
onSubmit(): boolean;
|
||||
removeControl(dir: NgControl): void;
|
||||
removeControlGroup(dir: NgControlGroup): void;
|
||||
updateModel(dir: NgControl, value: any): void;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class NgIf {
|
||||
ngIf: any;
|
||||
|
@ -426,20 +134,6 @@ export declare abstract class NgLocalization {
|
|||
abstract getPluralCategory(value: any): string;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgModel extends NgControl implements OnChanges {
|
||||
asyncValidator: AsyncValidatorFn;
|
||||
control: Control;
|
||||
model: any;
|
||||
path: string[];
|
||||
update: EventEmitter<{}>;
|
||||
validator: ValidatorFn;
|
||||
viewModel: any;
|
||||
constructor(_validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgPlural {
|
||||
ngPlural: number;
|
||||
|
@ -453,15 +147,6 @@ export declare class NgPluralCase {
|
|||
constructor(value: string, template: TemplateRef<Object>, viewContainer: ViewContainerRef, ngPlural: NgPlural);
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class NgSelectOption implements OnDestroy {
|
||||
id: string;
|
||||
ngValue: any;
|
||||
value: any;
|
||||
constructor(_element: ElementRef, _renderer: Renderer, _select: SelectControlValueAccessor);
|
||||
ngOnDestroy(): void;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class NgStyle implements DoCheck {
|
||||
ngStyle: {
|
||||
|
@ -509,14 +194,6 @@ export declare class PathLocationStrategy extends LocationStrategy {
|
|||
replaceState(state: any, title: string, url: string, queryParams: string): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class PatternValidator implements Validator {
|
||||
constructor(pattern: string);
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class PercentPipe implements PipeTransform {
|
||||
transform(value: any, digits?: string): string;
|
||||
|
@ -536,33 +213,11 @@ export declare abstract class PlatformLocation {
|
|||
abstract replaceState(state: any, title: string, url: string): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class RadioButtonState {
|
||||
checked: boolean;
|
||||
value: string;
|
||||
constructor(checked: boolean, value: string);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export declare class ReplacePipe implements PipeTransform {
|
||||
transform(value: any, pattern: string | RegExp, replacement: Function | string): any;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class RequiredValidator {
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
onChange: (_: any) => void;
|
||||
onTouched: () => void;
|
||||
value: any;
|
||||
constructor(_renderer: Renderer, _elementRef: ElementRef);
|
||||
registerOnChange(fn: (value: any) => any): void;
|
||||
registerOnTouched(fn: () => any): void;
|
||||
writeValue(value: any): void;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare class SlicePipe implements PipeTransform {
|
||||
transform(value: any, start: number, end?: number): any;
|
||||
|
@ -582,25 +237,3 @@ export interface UrlChangeEvent {
|
|||
export interface UrlChangeListener {
|
||||
(e: UrlChangeEvent): any;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export interface Validator {
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class Validators {
|
||||
static compose(validators: ValidatorFn[]): ValidatorFn;
|
||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn;
|
||||
static maxLength(maxLength: number): ValidatorFn;
|
||||
static minLength(minLength: number): ValidatorFn;
|
||||
static nullValidator(c: AbstractControl): {
|
||||
[key: string]: boolean;
|
||||
};
|
||||
static pattern(pattern: string): ValidatorFn;
|
||||
static required(control: AbstractControl): {
|
||||
[key: string]: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ export declare abstract class AbstractControl {
|
|||
constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn);
|
||||
clearAsyncValidators(): void;
|
||||
clearValidators(): void;
|
||||
/** @deprecated */ find(path: Array<string | number> | string): AbstractControl;
|
||||
get(path: Array<string | number> | string): AbstractControl;
|
||||
getError(errorCode: string, path?: string[]): any;
|
||||
hasError(errorCode: string, path?: string[]): boolean;
|
||||
|
@ -209,12 +208,6 @@ export declare class FormControl extends AbstractControl {
|
|||
emitModelToViewChange?: boolean;
|
||||
emitViewToModelChange?: boolean;
|
||||
}): void;
|
||||
/** @deprecated */ updateValue(value: any, options?: {
|
||||
onlySelf?: boolean;
|
||||
emitEvent?: boolean;
|
||||
emitModelToViewChange?: boolean;
|
||||
emitViewToModelChange?: boolean;
|
||||
}): void;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
|
|
Loading…
Reference in New Issue