diff --git a/modules/@angular/examples/forms/ts/simpleForm/e2e_test/simple_form_spec.ts b/modules/@angular/examples/forms/ts/simpleForm/e2e_test/simple_form_spec.ts new file mode 100644 index 0000000000..6be39bc1ce --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleForm/e2e_test/simple_form_spec.ts @@ -0,0 +1,43 @@ +/** + * @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 {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; + +describe('simpleForm example', () => { + afterEach(verifyNoBrowserErrors); + let inputs: ElementFinder; + let paragraphs: ElementFinder; + + beforeEach(() => { + browser.get('/forms/ts/simpleForm/index.html'); + inputs = element.all(by.css('input')); + paragraphs = element.all(by.css('p')); + }); + + it('should update the domain model as you type', () => { + inputs.get(0).click(); + inputs.get(0).sendKeys('Nancy'); + + inputs.get(1).click(); + inputs.get(1).sendKeys('Drew'); + + expect(paragraphs.get(0).getText()).toEqual('First name value: Nancy'); + expect(paragraphs.get(2).getText()).toEqual('Form value: { "first": "Nancy", "last": "Drew" }'); + }); + + it('should report the validity correctly', () => { + expect(paragraphs.get(1).getText()).toEqual('First name valid: false'); + expect(paragraphs.get(3).getText()).toEqual('Form valid: false'); + inputs.get(0).click(); + inputs.get(0).sendKeys('a'); + + expect(paragraphs.get(1).getText()).toEqual('First name valid: true'); + expect(paragraphs.get(3).getText()).toEqual('Form valid: true'); + }); + +}); diff --git a/modules/@angular/examples/forms/ts/simpleForm/module.ts b/modules/@angular/examples/forms/ts/simpleForm/module.ts new file mode 100644 index 0000000000..12037114e4 --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleForm/module.ts @@ -0,0 +1,20 @@ +/** + * @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 {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {BrowserModule} from '@angular/platform-browser'; +import {SimpleFormComp} from './simple_form_example'; + +@NgModule({ + imports: [BrowserModule, FormsModule], + declarations: [SimpleFormComp], + bootstrap: [SimpleFormComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/simpleForm/simple_form_example.ts b/modules/@angular/examples/forms/ts/simpleForm/simple_form_example.ts new file mode 100644 index 0000000000..8316973ab2 --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleForm/simple_form_example.ts @@ -0,0 +1,34 @@ +/** + * @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 + */ + +// #docregion Component +import {Component} from '@angular/core'; +import {NgForm} from '@angular/forms'; + +@Component({ + selector: 'example-app', + template: ` +
+ + + +
+ +

First name value: {{ first.value }}

+

First name valid: {{ first.valid }}

+

Form value: {{ f.value | json }}

+

Form valid: {{ f.valid }}

+ `, +}) +export class SimpleFormComp { + onSubmit(f: NgForm) { + console.log(f.value); // { first: '', last: '' } + console.log(f.valid); // false + } +} +// #enddocregion diff --git a/modules/@angular/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts b/modules/@angular/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts new file mode 100644 index 0000000000..de8507a7a2 --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleNgModel/e2e_test/simple_ng_model_spec.ts @@ -0,0 +1,44 @@ +/** + * @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 {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; + +describe('simpleNgModel example', () => { + afterEach(verifyNoBrowserErrors); + let input: ElementFinder; + let paragraphs: ElementFinder; + let button: ElementFinder; + + beforeEach(() => { + browser.get('/forms/ts/simpleNgModel/index.html'); + input = element(by.css('input')); + paragraphs = element.all(by.css('p')); + button = element(by.css('button')); + }); + + it('should update the domain model as you type', () => { + input.click(); + input.sendKeys('Carson'); + + expect(paragraphs.get(0).getText()).toEqual('Value: Carson'); + }); + + it('should report the validity correctly', () => { + expect(paragraphs.get(1).getText()).toEqual('Valid: false'); + input.click(); + input.sendKeys('a'); + + expect(paragraphs.get(1).getText()).toEqual('Valid: true'); + }); + + it('should set the value by changing the domain model', () => { + button.click(); + expect(input.getAttribute('value')).toEqual('Nancy'); + }); + +}); diff --git a/modules/@angular/examples/forms/ts/simpleNgModel/module.ts b/modules/@angular/examples/forms/ts/simpleNgModel/module.ts new file mode 100644 index 0000000000..9f0872c544 --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleNgModel/module.ts @@ -0,0 +1,20 @@ +/** + * @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 {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {BrowserModule} from '@angular/platform-browser'; +import {SimpleNgModelComp} from './simple_ng_model_example'; + +@NgModule({ + imports: [BrowserModule, FormsModule], + declarations: [SimpleNgModelComp], + bootstrap: [SimpleNgModelComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/simpleNgModel/simple_ng_model_example.ts b/modules/@angular/examples/forms/ts/simpleNgModel/simple_ng_model_example.ts new file mode 100644 index 0000000000..3b0c068d54 --- /dev/null +++ b/modules/@angular/examples/forms/ts/simpleNgModel/simple_ng_model_example.ts @@ -0,0 +1,28 @@ +/** + * @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 + */ + +// #docregion Component +import {Component} from '@angular/core'; + +@Component({ + selector: 'example-app', + template: ` + + +

Value: {{ name }}

+

Valid: {{ ctrl.valid }}

+ + + `, +}) +export class SimpleNgModelComp { + name: string = ''; + + setValue() { this.name = 'Nancy'; } +} +// #enddocregion diff --git a/modules/@angular/forms/src/directives/ng_model.ts b/modules/@angular/forms/src/directives/ng_model.ts index fab5a90abb..3296bf40e7 100644 --- a/modules/@angular/forms/src/directives/ng_model.ts +++ b/modules/@angular/forms/src/directives/ng_model.ts @@ -30,24 +30,55 @@ export const formControlBinding: any = { const resolvedPromise = Promise.resolve(null); /** - * Binds a domain model to a form control. + * @whatItDoes Creates a {@link FormControl} instance from a domain model and binds it + * to a form control element. * - * ### Usage + * The {@link FormControl} instance will track the value, user interaction, and + * validation status of the control and keep the view synced with the model. If used + * within a parent form, the directive will also register itself with the form as a child + * control. * - * `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. + * @howToUse * - * ```typescript - * @Component({ - * selector: "search-comp", - * directives: [], - * template: `` - * }) - * class SearchComp { - * searchQuery: string; - * } - * ``` + * This directive can be used by itself or as part of a larger form. All you need is the + * `ngModel` selector to activate it. + * + * It accepts a domain model as an optional {@link @Input}. If you have a one-way binding + * to `ngModel` with `[]` syntax, changing the value of the domain model in the component + * class will set the value in the view. If you have a two-way binding with `[()]` syntax + * (also known as 'banana-box syntax'), the value in the UI will always be synced back to + * the domain model in your class as well. + * + * If you wish to inspect the properties of the associated {@link FormControl} (like + * validity state), you can also export the directive into a local template variable using + * `ngModel` as the key (ex: `#myVar="ngModel"`). You can then access the control using the + * directive's `control` property, but most properties you'll need (like `valid` and `dirty`) + * will fall through to the control anyway, so you can access them directly. You can see a + * full list of properties directly available in {@link AbstractControlDirective}. + * + * The following is an example of a simple standalone control using `ngModel`: + * + * {@example forms/ts/simpleNgModel/simple_ng_model_example.ts region='Component'} + * + * When using the `ngModel` within `
` tags, you'll also need to supply a `name` attribute + * so that the control can be registered with the parent form under that name. + * + * It's worth noting that in the context of a parent form, you often can skip one-way or + * two-way binding because the parent form will sync the value for you. You can access + * its properties by exporting it into a local template variable using `ngForm` (ex: + * `#f="ngForm"`). Then you can pass it where it needs to go on submit. + * + * If you do need to populate initial values into your form, using a one-way binding for + * `ngModel` tends to be sufficient as long as you use the exported form's value rather + * than the domain model's value on submit. + * + * Take a look at an example of using `ngModel` within a form: + * + * {@example forms/ts/simpleForm/simple_form_example.ts region='Component'} + * + * **npm package**: `@angular/forms` + * + * **NgModule**: `FormsModule` * * @stable */