diff --git a/modules/@angular/forms/src/directives.ts b/modules/@angular/forms/src/directives.ts
index cda095a753..150c7b93ce 100644
--- a/modules/@angular/forms/src/directives.ts
+++ b/modules/@angular/forms/src/directives.ts
@@ -16,6 +16,7 @@ import {NgModel} from './directives/ng_model';
import {NgModelGroup} from './directives/ng_model_group';
import {NumberValueAccessor} from './directives/number_value_accessor';
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
+import {FormArrayName} from './directives/reactive_directives/form_array_name';
import {FormControlDirective} from './directives/reactive_directives/form_control_directive';
import {FormControlName} from './directives/reactive_directives/form_control_name';
import {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
@@ -34,6 +35,7 @@ export {NgModel} from './directives/ng_model';
export {NgModelGroup} from './directives/ng_model_group';
export {NumberValueAccessor} from './directives/number_value_accessor';
export {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
+export {FormArrayName} from './directives/reactive_directives/form_array_name';
export {FormControlDirective} from './directives/reactive_directives/form_control_directive';
export {FormControlName} from './directives/reactive_directives/form_control_name';
export {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
@@ -72,4 +74,6 @@ export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
];
export const REACTIVE_FORM_DIRECTIVES: Type[] =
- /*@ts2dart_const*/[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName];
\ No newline at end of file
+ /*@ts2dart_const*/[
+ FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName
+ ];
\ No newline at end of file
diff --git a/modules/@angular/forms/src/directives/ng_form.ts b/modules/@angular/forms/src/directives/ng_form.ts
index afa025285b..201f1f5042 100644
--- a/modules/@angular/forms/src/directives/ng_form.ts
+++ b/modules/@angular/forms/src/directives/ng_form.ts
@@ -19,7 +19,7 @@ import {Form} from './form_interface';
import {NgControl} from './ng_control';
import {NgModel} from './ng_model';
import {NgModelGroup} from './ng_model_group';
-import {composeAsyncValidators, composeValidators, setUpControl, setUpFormGroup} from './shared';
+import {composeAsyncValidators, composeValidators, setUpControl, setUpFormContainer} from './shared';
export const formDirectiveProvider: any =
/*@ts2dart_const*/ {provide: ControlContainer, useExisting: forwardRef(() => NgForm)};
@@ -140,7 +140,7 @@ export class NgForm extends ControlContainer implements Form {
PromiseWrapper.scheduleMicrotask(() => {
var container = this._findContainer(dir.path);
var group = new FormGroup({});
- setUpFormGroup(group, dir);
+ setUpFormContainer(group, dir);
container.registerControl(dir.name, group);
group.updateValueAndValidity({emitEvent: false});
});
diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_array_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_array_name.ts
new file mode 100644
index 0000000000..48b57edf4b
--- /dev/null
+++ b/modules/@angular/forms/src/directives/reactive_directives/form_array_name.ts
@@ -0,0 +1,96 @@
+/**
+ * @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, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
+
+import {FormArray} from '../../model';
+import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
+import {ControlContainer} from '../control_container';
+import {composeAsyncValidators, composeValidators, controlPath} from '../shared';
+import {AsyncValidatorFn, ValidatorFn} from '../validators';
+
+import {FormGroupDirective} from './form_group_directive';
+
+export const formArrayNameProvider: any =
+ /*@ts2dart_const*/ /* @ts2dart_Provider */ {
+ provide: ControlContainer,
+ useExisting: forwardRef(() => FormArrayName)
+ };
+
+/**
+ * Syncs an existing form array to a DOM element.
+ *
+ * This directive can only be used as a child of {@link FormGroupDirective}.
+ *
+ * ```typescript
+ * @Component({
+ * selector: 'my-app',
+ * template: `
+ *
+ *
Angular FormArray Example
+ *
+ * {{ myForm.value | json }} // {cities: ['SF', 'NY']}
+ *
+ * `
+ * })
+ * export class App {
+ * cityArray = new FormArray([
+ * new FormControl('SF'),
+ * new FormControl('NY')
+ * ]);
+ * myForm = new FormGroup({
+ * cities: this.cityArray
+ * });
+ * }
+ * ```
+ *
+ * @experimental
+ */
+@Directive({selector: '[formArrayName]', providers: [formArrayNameProvider]})
+export class FormArrayName extends ControlContainer implements OnInit, OnDestroy {
+ /** @internal */
+ _parent: ControlContainer;
+
+ /** @internal */
+ _validators: any[];
+
+ /** @internal */
+ _asyncValidators: any[];
+
+ @Input('formArrayName') name: string;
+
+ constructor(
+ @Host() @SkipSelf() parent: ControlContainer,
+ @Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
+ @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
+ super();
+ this._parent = parent;
+ this._validators = validators;
+ this._asyncValidators = asyncValidators;
+ }
+
+ ngOnInit(): void { this.formDirective.addFormArray(this); }
+
+ ngOnDestroy(): void { this.formDirective.removeFormArray(this); }
+
+ get control(): FormArray { return this.formDirective.getFormArray(this); }
+
+ get formDirective(): FormGroupDirective { return this._parent.formDirective; }
+
+ get path(): string[] { return controlPath(this.name, this._parent); }
+
+ get validator(): ValidatorFn { return composeValidators(this._validators); }
+
+ get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
+}
diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_group_directive.ts b/modules/@angular/forms/src/directives/reactive_directives/form_group_directive.ts
index a22284dcac..96e7f49a5c 100644
--- a/modules/@angular/forms/src/directives/reactive_directives/form_group_directive.ts
+++ b/modules/@angular/forms/src/directives/reactive_directives/form_group_directive.ts
@@ -12,13 +12,14 @@ import {EventEmitter, ObservableWrapper} from '../../facade/async';
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
import {BaseException} from '../../facade/exceptions';
import {isBlank} from '../../facade/lang';
-import {FormControl, FormGroup} from '../../model';
+import {FormArray, FormControl, FormGroup} 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 {composeAsyncValidators, composeValidators, setUpControl, setUpFormGroup} from '../shared';
+import {composeAsyncValidators, composeValidators, setUpControl, setUpFormContainer} from '../shared';
+import {FormArrayName} from './form_array_name';
import {FormGroupName} from './form_group_name';
export const formDirectiveProvider: any =
@@ -155,16 +156,26 @@ export class FormGroupDirective extends ControlContainer implements Form,
removeControl(dir: NgControl): void { ListWrapper.remove(this.directives, dir); }
- addFormGroup(dir: FormGroupName) {
+ addFormGroup(dir: FormGroupName): void {
var ctrl: any = this.form.find(dir.path);
- setUpFormGroup(ctrl, dir);
+ setUpFormContainer(ctrl, dir);
ctrl.updateValueAndValidity({emitEvent: false});
}
- removeFormGroup(dir: FormGroupName) {}
+ removeFormGroup(dir: FormGroupName): void {}
getFormGroup(dir: FormGroupName): FormGroup { return this.form.find(dir.path); }
+ addFormArray(dir: FormArrayName): void {
+ var ctrl: any = this.form.find(dir.path);
+ setUpFormContainer(ctrl, dir);
+ ctrl.updateValueAndValidity({emitEvent: false});
+ }
+
+ removeFormArray(dir: FormArrayName): void {}
+
+ getFormArray(dir: FormArrayName): FormArray { return this.form.find(dir.path); }
+
updateModel(dir: NgControl, value: any): void {
var ctrl = this.form.find(dir.path);
ctrl.updateValue(value);
diff --git a/modules/@angular/forms/src/directives/shared.ts b/modules/@angular/forms/src/directives/shared.ts
index 417dfe7a32..8bfd7b3f99 100644
--- a/modules/@angular/forms/src/directives/shared.ts
+++ b/modules/@angular/forms/src/directives/shared.ts
@@ -9,7 +9,7 @@
import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions';
import {hasConstructor, isBlank, isPresent, looseIdentical} from '../facade/lang';
-import {FormControl, FormGroup} from '../model';
+import {FormArray, FormControl, FormGroup} from '../model';
import {Validators} from '../validators';
import {AbstractControlDirective} from './abstract_control_directive';
@@ -22,6 +22,7 @@ import {NgControl} from './ng_control';
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
import {NumberValueAccessor} from './number_value_accessor';
import {RadioControlValueAccessor} from './radio_control_value_accessor';
+import {FormArrayName} from './reactive_directives/form_array_name';
import {SelectControlValueAccessor} from './select_control_value_accessor';
import {AsyncValidatorFn, ValidatorFn} from './validators';
@@ -54,7 +55,8 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
}
-export function setUpFormGroup(control: FormGroup, dir: AbstractFormGroupDirective) {
+export function setUpFormContainer(
+ control: FormGroup | FormArray, dir: AbstractFormGroupDirective | FormArrayName) {
if (isBlank(control)) _throwError(dir, 'Cannot find control');
control.validator = Validators.compose([control.validator, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
diff --git a/modules/@angular/forms/src/forms.ts b/modules/@angular/forms/src/forms.ts
index 26c48d14a0..c377402742 100644
--- a/modules/@angular/forms/src/forms.ts
+++ b/modules/@angular/forms/src/forms.ts
@@ -33,6 +33,7 @@ export {NgControlStatus} from './directives/ng_control_status';
export {NgForm} from './directives/ng_form';
export {NgModel} from './directives/ng_model';
export {NgModelGroup} from './directives/ng_model_group';
+export {FormArrayName} from './directives/reactive_directives/form_array_name';
export {FormControlDirective} from './directives/reactive_directives/form_control_directive';
export {FormControlName} from './directives/reactive_directives/form_control_name';
export {FormGroupDirective} from './directives/reactive_directives/form_group_directive';
diff --git a/modules/@angular/forms/test/directives_spec.ts b/modules/@angular/forms/test/directives_spec.ts
index 42883f26e2..849dd199aa 100644
--- a/modules/@angular/forms/test/directives_spec.ts
+++ b/modules/@angular/forms/test/directives_spec.ts
@@ -12,7 +12,7 @@ import {fakeAsync, flushMicrotasks, tick,} from '@angular/core/testing';
import {SpyNgControl, SpyValueAccessor} from './spies';
-import {FormGroup, FormControl, FormControlName, FormGroupName, NgModelGroup, FormGroupDirective, ControlValueAccessor, Validators, NgForm, NgModel, FormControlDirective, NgControl, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, Validator} from '@angular/forms';
+import {FormGroup, FormControl, FormArray, FormArrayName, FormControlName, FormGroupName, NgModelGroup, FormGroupDirective, ControlValueAccessor, Validators, NgForm, NgModel, FormControlDirective, NgControl, DefaultValueAccessor, CheckboxControlValueAccessor, SelectControlValueAccessor, Validator} from '@angular/forms';
import {selectValueAccessor, composeValidators} from '@angular/forms/src/directives/shared';
import {TimerWrapper} from '../src/facade/async';
@@ -376,6 +376,30 @@ export function main() {
});
});
+ describe('FormArrayName', () => {
+ var formModel: FormArray;
+ var formArrayDir: FormArrayName;
+
+ beforeEach(() => {
+ const parent = new FormGroupDirective([], []);
+ formModel = new FormArray([new FormControl('')]);
+ parent.form = new FormGroup({'array': formModel});
+ formArrayDir = new FormArrayName(parent, [], []);
+ formArrayDir.name = 'array';
+ });
+
+ it('should reexport control properties', () => {
+ expect(formArrayDir.control).toBe(formModel);
+ expect(formArrayDir.value).toBe(formModel.value);
+ expect(formArrayDir.valid).toBe(formModel.valid);
+ expect(formArrayDir.errors).toBe(formModel.errors);
+ expect(formArrayDir.pristine).toBe(formModel.pristine);
+ expect(formArrayDir.dirty).toBe(formModel.dirty);
+ expect(formArrayDir.touched).toBe(formModel.touched);
+ expect(formArrayDir.untouched).toBe(formModel.untouched);
+ });
+ });
+
describe('FormControlDirective', () => {
var controlDir: any /** TODO #9100 */;
var control: any /** TODO #9100 */;
diff --git a/modules/@angular/forms/test/integration_spec.ts b/modules/@angular/forms/test/integration_spec.ts
index b05e244800..864386c696 100644
--- a/modules/@angular/forms/test/integration_spec.ts
+++ b/modules/@angular/forms/test/integration_spec.ts
@@ -14,7 +14,7 @@ import {ComponentFixture} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
-import {ControlValueAccessor, FORM_DIRECTIVES, FORM_PROVIDERS, FormControl, FormGroup, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NgControl, NgForm, NgModel, REACTIVE_FORM_DIRECTIVES, Validator, Validators, disableDeprecatedForms, provideForms} from '@angular/forms';
+import {ControlValueAccessor, FORM_DIRECTIVES, FORM_PROVIDERS, FormArray, FormControl, FormGroup, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NgControl, NgForm, NgModel, REACTIVE_FORM_DIRECTIVES, Validator, Validators, disableDeprecatedForms, provideForms} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
@@ -68,7 +68,7 @@ export function main() {
});
}));
- it('should update the control group values on DOM change',
+ it('should update the form group values on DOM change',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
@@ -221,7 +221,7 @@ export function main() {
});
}));
- it('should update DOM elements when rebinding the control group',
+ it('should update DOM elements when rebinding the form group',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
@@ -304,6 +304,77 @@ export function main() {
});
}));
+ it('should support form arrays',
+ fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
+ const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
+ const form = new FormGroup({cities: cityArray});
+
+ const t = ``;
+
+ tcb.overrideTemplate(MyComp8, t)
+ .overrideProviders(MyComp8, providerArr)
+ .createAsync(MyComp8)
+ .then((fixture) => {
+ fixture.debugElement.componentInstance.form = form;
+ fixture.debugElement.componentInstance.cityArray = cityArray;
+ fixture.detectChanges();
+ tick();
+
+ const inputs = fixture.debugElement.queryAll(By.css('input'));
+ expect(inputs[0].nativeElement.value).toEqual('SF');
+ expect(inputs[1].nativeElement.value).toEqual('NY');
+ expect(fixture.componentInstance.form.value).toEqual({cities: ['SF', 'NY']});
+
+ inputs[0].nativeElement.value = 'LA';
+ dispatchEvent(inputs[0].nativeElement, 'input');
+
+ fixture.detectChanges();
+ tick();
+
+ expect(fixture.componentInstance.form.value).toEqual({cities: ['LA', 'NY']});
+
+ });
+ })));
+
+ it('should support pushing new controls to form arrays',
+ fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
+ const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
+ const form = new FormGroup({cities: cityArray});
+
+ const t = ``;
+
+ tcb.overrideTemplate(MyComp8, t)
+ .overrideProviders(MyComp8, providerArr)
+ .createAsync(MyComp8)
+ .then((fixture) => {
+ fixture.debugElement.componentInstance.form = form;
+ fixture.debugElement.componentInstance.cityArray = cityArray;
+ fixture.detectChanges();
+ tick();
+
+ cityArray.push(new FormControl('LA'));
+ fixture.detectChanges();
+ tick();
+
+ const inputs = fixture.debugElement.queryAll(By.css('input'));
+ expect(inputs[2].nativeElement.value).toEqual('LA');
+ expect(fixture.componentInstance.form.value).toEqual({cities: ['SF', 'NY', 'LA']});
+
+ });
+ })));
+
describe('different control types', () => {
it('should support ',
inject(
diff --git a/tools/public_api_guard/forms/index.d.ts b/tools/public_api_guard/forms/index.d.ts
index 55d1e7bca5..663b5f6344 100644
--- a/tools/public_api_guard/forms/index.d.ts
+++ b/tools/public_api_guard/forms/index.d.ts
@@ -118,6 +118,18 @@ export declare class FormArray extends AbstractControl {
removeAt(index: number): void;
}
+export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy {
+ asyncValidator: AsyncValidatorFn;
+ control: FormArray;
+ formDirective: FormGroupDirective;
+ name: string;
+ path: string[];
+ validator: ValidatorFn;
+ constructor(parent: ControlContainer, validators: any[], asyncValidators: any[]);
+ ngOnDestroy(): void;
+ ngOnInit(): void;
+}
+
export declare class FormBuilder {
array(controlsConfig: any[], validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn): FormArray;
control(value: Object, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]): FormControl;
@@ -194,12 +206,15 @@ export declare class FormGroupDirective extends ControlContainer implements Form
submitted: boolean;
constructor(_validators: any[], _asyncValidators: any[]);
addControl(dir: NgControl): void;
+ addFormArray(dir: FormArrayName): void;
addFormGroup(dir: FormGroupName): void;
getControl(dir: NgControl): FormControl;
+ getFormArray(dir: FormArrayName): FormArray;
getFormGroup(dir: FormGroupName): FormGroup;
ngOnChanges(changes: SimpleChanges): void;
onSubmit(): boolean;
removeControl(dir: NgControl): void;
+ removeFormArray(dir: FormArrayName): void;
removeFormGroup(dir: FormGroupName): void;
updateModel(dir: NgControl, value: any): void;
}