diff --git a/modules/@angular/forms/src/directives.ts b/modules/@angular/forms/src/directives.ts
index 1cb14c5567..278bd26d5c 100644
--- a/modules/@angular/forms/src/directives.ts
+++ b/modules/@angular/forms/src/directives.ts
@@ -16,11 +16,10 @@ 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';
-import {FormGroupName} from './directives/reactive_directives/form_group_name';
+import {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name';
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';
@@ -35,11 +34,10 @@ 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';
-export {FormGroupName} from './directives/reactive_directives/form_group_name';
+export {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name';
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';
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
deleted file mode 100644
index 436c65ddcf..0000000000
--- a/modules/@angular/forms/src/directives/reactive_directives/form_array_name.ts
+++ /dev/null
@@ -1,107 +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, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
-
-import {BaseException} from '../../facade/exceptions';
-import {FormArray} from '../../model';
-import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
-import {ControlContainer} from '../control_container';
-import {ReactiveErrors} from '../reactive_errors';
-import {composeAsyncValidators, composeValidators, controlPath} from '../shared';
-import {AsyncValidatorFn, ValidatorFn} from '../validators';
-
-import {FormGroupDirective} from './form_group_directive';
-import {FormGroupName} from './form_group_name';
-
-export const formArrayNameProvider: any = {
- 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(
- @Optional() @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._checkParentType();
- 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); }
-
- private _checkParentType(): void {
- if (!(this._parent instanceof FormGroupName) && !(this._parent instanceof FormGroupDirective)) {
- ReactiveErrors.arrayParentException();
- }
- }
-}
diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts
index 0d4572a0f4..e1a3786c2b 100644
--- a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts
+++ b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts
@@ -9,7 +9,6 @@
import {Directive, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core';
import {EventEmitter, ObservableWrapper} from '../../facade/async';
-import {BaseException} from '../../facade/exceptions';
import {FormControl} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
import {AbstractFormGroupDirective} from '../abstract_form_group_directive';
@@ -20,10 +19,8 @@ import {ReactiveErrors} from '../reactive_errors';
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared';
import {AsyncValidatorFn, ValidatorFn} from '../validators';
-import {FormArrayName} from './form_array_name';
import {FormGroupDirective} from './form_group_directive';
-import {FormGroupName} from './form_group_name';
-
+import {FormArrayName, FormGroupName} from './form_group_name';
export const controlNameBinding: any = {
provide: NgControl,
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 74a48688fd..26281c7f04 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
@@ -20,8 +20,7 @@ import {NgControl} from '../ng_control';
import {ReactiveErrors} from '../reactive_errors';
import {composeAsyncValidators, composeValidators, setUpControl, setUpFormContainer} from '../shared';
-import {FormArrayName} from './form_array_name';
-import {FormGroupName} from './form_group_name';
+import {FormArrayName, FormGroupName} from './form_group_name';
export const formDirectiveProvider: any = {
provide: ControlContainer,
diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts
index 91e6e94114..e979b7f734 100644
--- a/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts
+++ b/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts
@@ -8,11 +8,13 @@
import {Directive, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
-import {BaseException} from '../../facade/exceptions';
+import {FormArray} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
import {AbstractFormGroupDirective} from '../abstract_form_group_directive';
import {ControlContainer} from '../control_container';
import {ReactiveErrors} from '../reactive_errors';
+import {composeAsyncValidators, composeValidators, controlPath} from '../shared';
+import {AsyncValidatorFn, ValidatorFn} from '../validators';
import {FormGroupDirective} from './form_group_directive';
@@ -85,8 +87,100 @@ export class FormGroupName extends AbstractFormGroupDirective implements OnInit,
/** @internal */
_checkParentType(): void {
- if (!(this._parent instanceof FormGroupName) && !(this._parent instanceof FormGroupDirective)) {
+ if (_hasInvalidParent(this._parent)) {
ReactiveErrors.groupParentException();
}
}
}
+
+export const formArrayNameProvider: any = {
+ 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(
+ @Optional() @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._checkParentType();
+ 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); }
+
+ private _checkParentType(): void {
+ if (_hasInvalidParent(this._parent)) {
+ ReactiveErrors.arrayParentException();
+ }
+ }
+}
+
+function _hasInvalidParent(parent: ControlContainer): boolean {
+ return !(parent instanceof FormGroupName) && !(parent instanceof FormGroupDirective) &&
+ !(parent instanceof FormArrayName);
+}
diff --git a/modules/@angular/forms/src/directives/reactive_errors.ts b/modules/@angular/forms/src/directives/reactive_errors.ts
index 72cb455f79..58a0a06a0d 100644
--- a/modules/@angular/forms/src/directives/reactive_errors.ts
+++ b/modules/@angular/forms/src/directives/reactive_errors.ts
@@ -7,6 +7,7 @@
*/
import {BaseException} from '../facade/exceptions';
+
import {FormErrorExamples as Examples} from './error_examples';
export class ReactiveErrors {
@@ -60,4 +61,4 @@ export class ReactiveErrors {
${Examples.formArrayName}`);
}
-}
\ No newline at end of file
+}
diff --git a/modules/@angular/forms/src/directives/shared.ts b/modules/@angular/forms/src/directives/shared.ts
index bb8b7aa1b0..c03b2e2a63 100644
--- a/modules/@angular/forms/src/directives/shared.ts
+++ b/modules/@angular/forms/src/directives/shared.ts
@@ -22,7 +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 {FormArrayName} from './reactive_directives/form_group_name';
import {SelectControlValueAccessor} from './select_control_value_accessor';
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
import {AsyncValidatorFn, ValidatorFn} from './validators';
diff --git a/modules/@angular/forms/src/forms.ts b/modules/@angular/forms/src/forms.ts
index 1e170def6d..e0c9b1f5c9 100644
--- a/modules/@angular/forms/src/forms.ts
+++ b/modules/@angular/forms/src/forms.ts
@@ -33,10 +33,10 @@ 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';
+export {FormArrayName} from './directives/reactive_directives/form_group_name';
export {FormGroupName} from './directives/reactive_directives/form_group_name';
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts
index 40697b73f2..9d7048b678 100644
--- a/modules/@angular/forms/test/reactive_integration_spec.ts
+++ b/modules/@angular/forms/test/reactive_integration_spec.ts
@@ -350,11 +350,13 @@ 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});
+ inject(
+ [TestComponentBuilder, AsyncTestCompleter],
+ (tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
+ const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
+ const form = new FormGroup({cities: cityArray});
- const t = `
+ const t = `
`;
- tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => {
- fixture.debugElement.componentInstance.form = form;
- fixture.debugElement.componentInstance.cityArray = cityArray;
- fixture.detectChanges();
- tick();
+ tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => {
+ fixture.debugElement.componentInstance.form = form;
+ fixture.debugElement.componentInstance.cityArray = cityArray;
+ fixture.detectChanges();
- 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']});
+ 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');
+ inputs[0].nativeElement.value = 'LA';
+ dispatchEvent(inputs[0].nativeElement, 'input');
- fixture.detectChanges();
- tick();
+ fixture.detectChanges();
- expect(fixture.componentInstance.form.value).toEqual({cities: ['LA', 'NY']});
+ expect(fixture.componentInstance.form.value).toEqual({cities: ['LA', 'NY']});
+ async.done();
+ });
+ }));
- });
- })));
+ it('should support form groups nested in form arrays',
+ inject(
+ [TestComponentBuilder, AsyncTestCompleter],
+ (tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
+ const cityArray = new FormArray([
+ new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}),
+ new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')})
+ ]);
+ const form = new FormGroup({cities: cityArray});
+
+ const t = `
`;
+
+ tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => {
+ fixture.debugElement.componentInstance.form = form;
+ fixture.debugElement.componentInstance.cityArray = cityArray;
+ fixture.detectChanges();
+
+ const inputs = fixture.debugElement.queryAll(By.css('input'));
+ expect(inputs[0].nativeElement.value).toEqual('SF');
+ expect(inputs[1].nativeElement.value).toEqual('CA');
+ expect(inputs[2].nativeElement.value).toEqual('NY');
+ expect(inputs[3].nativeElement.value).toEqual('NY');
+ expect(fixture.componentInstance.form.value).toEqual({
+ cities: [{town: 'SF', state: 'CA'}, {town: 'NY', state: 'NY'}]
+ });
+
+ inputs[0].nativeElement.value = 'LA';
+ dispatchEvent(inputs[0].nativeElement, 'input');
+
+ fixture.detectChanges();
+
+ expect(fixture.componentInstance.form.value).toEqual({
+ cities: [{town: 'LA', state: 'CA'}, {town: 'NY', state: 'NY'}]
+ });
+
+ async.done();
+ });
+ }));
it('should support pushing new controls to form arrays',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {