2017年3月16日 星期四

[Angular] Reactive Forms (Model-driven)

 Angular    Model-driven Forms    Reactive Forms    Forms  


Introduction


We learned how to use Template driven Forms to design our form quickly. However if there are dynamic fields or rows in the form, it is recommended to use Reactive Forms (Model driven Forms) to implement.



Environment

Angular 2.4.0
Angular CLI 1.0.0-beta.31




Implement



Import ReactiveFormModule

app.module.ts

import { ReactiveFormsModule, FormsModule } from '@angular/forms';

@NgModule({
    imports: [
        //...
        FormsModule,
        ReactiveFormsModule
    ],
    //...
})

            


Create Form Model

In Reactive Forms, we could design our model with FormControl, FormGroup and FormArray.

l   FormGroup: Aggregates the values of each child FormControl into one object.
l   FormControl: Tracks the value and validation status of an individual form control.
l   FormArray: Could contains one or more FormGroup or/and FormControl.




Let’s have a look at the following form,



The sample shows that we could insert a new row with fields by clicking [New] button.
So the form model should be designed like this,




reactive.component.ts

import { AbstractControl, FormGroup, FormArray, FormControl, FormBuilder, Validators, NgForm } from '@angular/forms';

@Component({
    selector: 'reactive-form',
    templateUrl: './reactive-form.component.html',
})
export class ReactiveFormComponent implements OnInit {

    private form: FormGroup;

    constructor(
        private fb: FormBuilder) {

        //Set the model-driven with default data
        this.form = this.fb.group({
            user: this.fb.control('JB', [Validators.required]),
            links: this.fb.array([

                this.fb.group({
                    'title': ['github', [Validators.required, Validators.minLength(30)]],
                    'photo': ['assets/images/Links/github.png'],
                    'uri': ['www.github.com', Validators.required],
                    'target': [null, Validators.required]
                })
            ])
        })
    }
}


reactive.component.html

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

    <span>Current User</span>
    <input type="text" class="form-control" formControlName="user" />

    <div formArrayName="links">
        <table class="table bordered">
            <thead>
                <tr>
                    <th>Image</th>
                    <th>Title/th>
                    <th>Uri</th>
                    <th>Window Open</th>
                    <th></th>
                </tr>
            </thead>
            <tbody>
                <tr [formGroupName]="i" *ngFor="let link of form.controls.links.controls; let i = index">
                    <td>
                        <img *ngIf="link.controls.photo.value" style="width:38px;height:19px;" src="{{link.controls.photo.value}}" />
                        <input type="file" class="form-control" style="max-width: 200px"></td>
                    </td>
                    <td>
                        <input type="text" class="form-control" formControlName="title" />
                    </td>
                    <td>
                        <input type="text" class="form-control" formControlName="uri" />
                    </td>
                    <td>
                        <select class="form-control" formControlName="target">
                            <option *ngFor="let lt of linkTargets" [ngValue]="lt">{{lt.name}}</option>
                        </select>
                    </td>
                    <td>
                        <input type="button" class="btn btn-danger" (click)="removeLink(i)" value="刪除" />
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</form>


Notice:
u  On the form element, use [formGroup]="form" to bind the FormGroup on it.
u  Use formGroupName, formControlName, formArrayName to map our Form Model.
u  We can get the FormControl object in a FormGroup by this way,
 [FormGroup object].controls.[FormControl name]


Result:





Initialize Form Model

Now we will use the ajax call to get the data and fill the Form Model with the them.

Initialize Form

constructor(
    private fb: FormBuilder) {
    // initialize form
    this.form = new FormGroup({
        links: new FormArray([])
    });
}

PS. From now on, we will skip and remove the FormControl: user, which was used for demo the structure in the previous sample.


Initialize Form Model

private initFormGroup() {

    let links: FormArray = <FormArray>this.form.controls['links'];

    let linkObservable$ = this.linkSvc.getAll();

    //Subscribe the observer to linkObservable$
    linkObservable$.subscribe((data: Link[]) => {

      //Push FormGroup to FormArray
      data.forEach((lk: Link) => {

            links.push(this.fb.group({
                'title': [lk.title, [Validators.required, Validators.maxLength(30)]],
                'photo': [lk.photo],
                'uri': [lk.uri, [HttpsValidator, Validators.required]],
                'target': [lk.target, Validators.required]
            }));
        })

    });
}


Result:





Append/Remove FormGroup in FormArray

We will complete the [New] and [Remove] functions.

u  New:  Inserting a new inputs row. In other words, we are going to append a new FormGroup to the FormArray.
u  Remove: Remove a FormGroup from the FormArray.


TS

//Create a link
private insertLink() {

    let linkArry: FormArray = <FormArray>this.form.controls['links'];
    linkArry.insert(0, this.fb.group({
        'title': ['', [Validators.required, Validators.minLength(30)]],
        'photo': [''],
        'uri': ['', Validators.required],
        'target': [null, Validators.required]
    }));

}

//Remove a link
private removeLink(index: number) {
    let linkArry: FormArray = <FormArray>this.form.controls['links'];
    linkArry.removeAt(index);
}


Demo





Form Validation

The last st Of course we can add some Form validations on Reactive Form just like Template-driven Form.

<tr [formGroupName]="i" *ngFor="let link of form.controls.links.controls; let i = index">
    <td [ngClass]="{'has-error': link.controls.title.invalid}">
        <input type="text" class="form-control" formControlName="title" />
        <span class="label label-danger" *ngIf="link.controls.uri.dirty && link.controls.uri.invalid">Title is required!</span>
    </td>
<!-- ... -->




Submit

The last step, we are going to make sure that we can get the Reactive Form’s submit data correctly.

HTML

<form [formGroup]="form" novalidate (ngSubmit)="onSubmit(form)">
    <!-- ... -->
</form>


TS

private onSubmit(form: NgForm) {
    if (form && form.value) {
        console.log(form.value;
    }
}


Result:





Summary


Both Template driven Forms and Reactive Forms are useful in Angular. If we want more flexibility on the form, use Reactive Forms. But for static form, I suggest using Template driven Forms for quick implement.
 

Reference




沒有留言:

張貼留言