markdown Angular2 Snippets - 表格(反应式方法)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Angular2 Snippets - 表格(反应式方法)相关的知识,希望对你有一定的参考价值。


# Forms

## Reactive approach

* in app.module, instead of the `FormsModule` you need the `ReactiveFormsModule`:

```javascript
...
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    HttpModule
  ],
```

* In template driven approach we imported `NgForm`, that automatically created a `FormGroup` wrapper for our form (in Angular a form is just a group of controls)
* In reactive approach, `FormGroup` is created programmatically and sychronized/connected with our DOM.
* We use `FormControl` to create our form controls (for angular, they're simply "controls"), no matter if it is a radio button, text input, etc.

```javascript
...
import { FormGroup } from '@angular/forms';

@Component({
...
})
export class AppComponent {
  genders = ['male', 'female'];
  signUpForm: FormGroup; // new property of type 'FormGroup' that will hold our form.

  // We'll initialize our form in a method inside ngOnInit, 
  // because we need to do it before rendering our template:

  ngOnInit() {
	  this.signUpForm = new FormGroup({ // pass a javascript object that defines our controls with key/value pairs. 
	  	// Put your keys between ' ' to make sure that the name is kept during minification 
	  	// (because we are referencing it in our html code)
	  	// 
	  	//  We create our controls with 'FormControl'. Arguments:
	  	//  - first: initial state/value of this control
	  	//  - second: validator or [array of validators] you want to apply to this control
	  	//  - third: potential asynchronous validators
	    'usename': new FormControl(null),
	    'email': new FormControl(null),
	    'gender': new FormControl('male'),
	  });
	}

}
```

* To synchronize our HTML code with our form we have to give the `form` tag directives to override the default Angular behaviour and receive our form instructions from our code:

```html
<form [formGroup]="signUpForm"> // property binding the 'formGroup' directive we tell Angular,  'don't create a form for me, use my own formGroup'
  <div class="form-group">
    <label for="username">Username</label>
    <input
      type="text"
      id="username"
      formControlName="username" // tell angular what's the name of this input in my form code
      class="form-control">
  </div>
```

* To submit the form we still use `ngSubmit` because we still want to react to the native HTML form `submit` event. But in this case we don't need the form local reference here, because we created the form on our code:

`<form [formGroup]="signUpForm" (ngSubmit)="onSubmit()">`

```javascript
onSubmit() {
  console.log(this.signUpForm);
}
```

* This is the cool thing about the reactive approach: whatever you set in the object argument you pass to the `FormGroup`, you'll get out as a value when you submit the form, so you can bind it to your own model in your app, and make sure that your form structure matches the desired model structure.


### Adding validation

* Second argument in FormGroup allows you to configure one or several (array) validators via the `Validators` object:

```javascript
this.signUpForm = new FormGroup({
  'usename': new FormControl(null, Validators.required), // don't add parenthesis after the call: you're only passing the reference to this method. 
  // Angular will execute it when detects changes in this form control.
  'email': new FormControl(null, [Validators.required, Validators.email]),
..
});
```

* To display validation messages, we need to access the overall form and then with the `.get()` method access our controls easily. 
* You specify the control name or a path to the control (in a nested form object). 
* Then, `valid` and `touched` holds this control state:

```html
<span class="help-block"
  *ngIf="!signupForm.get('email').valid && signupForm.get('email').touched">Please enter a valid email!</span>
```

* For the overall form we can simply check for `valid` and `touched`:

```html
<span class="help-block"
  *ngIf="!signupForm.valid && signupForm.touched">Please enter valid data!</span>
```


### Grouping controls

```javascript
this.signUpForm = new FormGroup({
  'userData': new FormGroup({
    'username': new FormControl(null, Validators.required),
    'email': new FormControl(null, [Validators.required, Validators.email]),
  }),
  'gender': new FormControl('male'),
});
```

* we have to replicate this in our html to keep in sync, and also update the routes to our controls:

```html
<form [formGroup]="signUpForm" (ngSubmit)="onSubmit()">
  <div formGroupName="userData">
    <div class="form-group">
      <label for="username">Username</label>
      <input ... formControlName="username">
      <span class="help-block"
        *ngIf="!signUpForm.get('userData.username').valid && signUpForm.get('userData.username').touched">Please enter a valid username!</span> 
    </div>
  </div>
```

### Arrays of form controls (FormArray):

* Example: allow the user to add form controls:

```javascript
this.signUpForm = new FormGroup({
  ...
  'gender': new FormControl('male'),
  'hobbies': new FormArray([]), // 'FormArray' holds an array of 'FormControls' 
  // (you can initialize it with an empty array or with some form controls)
});

onAddHobby() {
  const control = new FormControl(null, Validators.required);
  (<FormArray>this.signUpForm.get('hobbies')).push(control); // we explictly cast to 'FormArray' type twith < > and placing all in parenthesis 
  // -this tells typescript that everything between parenthesis is treated as FormArray
}
```

* In our HTML, to keep in sync:

```html
<div formArrayName="hobbies"> //we need 'formArrayName', as we needed 'formGroupName', 'formControlName'...
  <button type="button" class="btn btn-light" 
    (click)="onAddHobby()">Add hobby</button>
  <div class="form-group"
    *ngFor="let hobbyControl of signUpForm.get('hobbies').controls; let i = index"> //we loop over the controls in this formArray
    <input type="text" class="form-control" [formControlName]="i"> // property binding because I'm not passing a string here
  </div>
</div>
```


### Creating custom validators:

* Validators included with angular: [https://angular.io/api/forms/Validators](https://angular.io/api/forms/Validators)
* A validator is just a function that is executed by Angular automatically when a form control changes, to check its validity. Let's create a custom validator to avoid the use of forbidden usernames:

```javascript
forbiddenUsernames = ['Oscar', 'Anna'];

// our custom validator function:
// - arguments: the form control that we want to check
// - must return an object with a key:string and a value:boolean
// - if validation is successful, omit the return statement or return null.
forbiddenNames(control: FormControl): {[s: string]: boolean} {
  if (this.forbiddenUsernames.indexOf(control.value) !== -1) { // if the form control value is in the array of forbiddenUsernames
    return {'nameIsForbidden': true}; // any short error code you want
  }
  return null; // DON'T return {'nameIsForbidden': false}
}
```

Add our validator to the field:

```javascript
this.signUpForm = new FormGroup({
...
  'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]), // remember to only pass a reference (not calling the method), 
  // and also .bind(this) -if not, when Angular executes this function itself, 'this' wont be referring to our class
``` 

** You can also create your validators in external files, so you can re-use them. **

custom-validators.ts

```javascript
export class CustomValidators {
  static forbiddenProjectNames(control: FormControl): {[s: string]: boolean} {
  ...
    } 
  // 'static' keyword will create a method associated with the class, and not with an instance of the class. 
  // In other words, you can only reach a static method using the name of the class. 
  // So you don't have to instantiate the class to be able to access its methods.
}
```

Then in your Reactive Form definition:

```javascript
//...import custom-validator...
this.myReactiveForm = new FormGroup({
  'projectName': new FormControl(null, [Validators.required, CustomValidators.forbiddenProjectNames]),
...
```    


### Using error codes

Angular stores the form errors on the individual controls, in an `errors` object:

```html
<p class="help-block"
  *ngIf="!signUpForm.get('userData.username').valid && signUpForm.get('userData.username').touched">
  <span 
  *ngIf="signUpForm.get('userData.username').errors['nameIsForbidden']">This name is not allowed</span>
  <span 
  *ngIf="signUpForm.get('userData.username').errors['required']">This field is required</span>
</p> 
```

*You could improve this example mapping the errors to an object with messages.*

### Creating a custom async validator

* If you need to reach to a web server to check for some validations, you need *asynchronous* validators who are able to wait to a response

```javascript
forbiddenEmails(control: FormControl): Promise<any> | Observable<any> { // in this case we don't expect an object to be returned, but a Promise or Observable
    const promise = new Promise<any>((resolve, reject) => {
      setTimeout(() => {
        if (control.value === 'test@test.com') {
          resolve({'emailIsForbidden': true}); // since I am in a promise, I don't 'return', I 'resolve'
        } else {
          resolve(null);
        }
      }, 1500);
    });
    return promise;
  }
```

Now to add it to our validators:

```javascript
'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails), // you add it in the third argument (async validators)
``` 

*When checking the validity, the input field gets applied `ng-pending` class.

### Reacting to status or value changes

* For example in async validation it changes from `invalid` to `pending` and then to `valid`. You can observe this state changes in your code, both for the whole form and for individual form controls:

```javascript
ngOnInit() {
	...
	// listen to value changes (for the whole form)
	this.signupForm.valueChanges.subscribe(
	  (value) => console.log(value); // prints the form object with all the control values
	);
	// listen to status changes (for the whole form)
	this.signupForm.statusChanges.subscribe(
	  (status) => console.log(status); // prints INVALID / PENDING / VALID
	);
	// listen to value changes (for a single form control)
	this.signupForm.get('username').valueChanges.subscribe(
	  (value) => console.log(value);
	);
}
```

### Setting and patching values

* You can set/change values for your whole form or for individual controls:

```javascript
// Set initial values (whole form)
this.signUpForm.setValue({
  'userData': {
    'username': 'Max',
    'email': 'max@test.com'
  },
  'gender': 'male',
  'hobbies': []
});
// Set initial value of a control
this.signUpForm.patchValue({
  'userData': {
    'username': 'Anna',
  }
});
```

You can also reset the form on submit with:
```javascript
this.signUpForm.reset()
```

*If you don't want to clear for example the radiobuttons, remember that you can pass an object to `reset()` to reset to specific values.*

以上是关于markdown Angular2 Snippets - 表格(反应式方法)的主要内容,如果未能解决你的问题,请参考以下文章

markdown [快照] #snippet

markdown Snippet Utile

markdown [查看寻呼机不完整,添加片段示例] #android_snippet #android

Visual Studio Code配置Markdown文档的snippet不生效的解决

markdown Angular2 Snippets - 路由

markdown Angular2 Snippets - Observables