با مطالعهی فصل ۹-۱ به اهمیت و ویژگی فرمها پی بردید و سپس با ارائهی یک مثال کاربردی نحوه استفاده از برخی امکانات موجود در ماژول NgForm را فرا گرفتید. در فصل گذشته رویکرد Template-Driven (کار کردن با قالب HTML برای ایجاد فرمها) را فرا گرفتید. اما فراموش نکردهاید که برای کار با فرمها در انگولار ۴ دو رویکرد وجود دارد. در این بخش قصد داریم به رویکرد دوم (Reactive Forms) پرداخته و فصل ۹ را به اتمام برسانیم. با ما همراه باشید.
مطابق فصل گذشته این مبحث را نیز با یک مثال ساده آغاز کرده و سپس امکانات مورد نیاز را به آن اضافه خواهیم کرد. بنابراین برای همراه بودن با مسیر آموزشی ما ابتدا مثال را از لینک پیوست شده در انتهای همین صفحه دریافت کنید و سپس سایر مراحل ارائه شده را مطالعه بفرمایید. پس از اجرای این فایل صفحهای مشابه زیر در اختیار شما قرار میگیرد که شامل یک فرم نام کاربری، ایمیل و جنسیت است.
بسیار عالی همانطور که ملاحظه میکنید این فرم شامل یک فیلد برای نام کاربری، یک فیلد برای ایمیل و فیلد دیگری برای تعیین جنسیت است. همچنین یک دکمه «ثبت» برای ارسال اطلاعات در انتهای فرم تعبیه کردهایم.
در کدنویسی فرمها با رویکرد Reactive، اکثر دستورها سمت کامپوننت نوشته خواهد شد. این رویکرد به شما کمک میکند تا فرمهایی با پیچیدگیهای خاص را به سادگی تولید کنید. برای درک بهتر مثال فوق را در اختیار داشته و سپس آن را ارتقاء میدهیم. بنابراین فایل app.component.ts را باز کرده و در ابتدا یک ویژگی از نوع FormGroup تعریف میکنیم. کلاس FormGroup مشخص میکند که کدام اطلاعات از کنترلر فرمها مدنظر ماست. در حالت کلیتر FormGroup به ویژگیای نسبت داده میشود که شامل تمام فرم است. بنابراین در اینجا نام این ویژگی را signupForm قرار میدهیم:
import {Component} from '@angular/core'; import {FormGroup} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { genders = ['مرد', 'زن']; signupForm: FormGroup; }
همچنین توجه داشته باشید که برای تولید فرمها با رویکرد Reactive همواره باید فایل app.module.ts را ویرایش کنیم زیرا ماژول FormsModule دیگر برای ما کاربردی ندارد (چون برای کار با قالب و رویکرد Template-Driven است). بنابراین در این فایل داریم:
import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {HttpModule} from '@angular/http'; import {ReactiveFormsModule} from "@angular/forms"; import {AppComponent} from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, HttpModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
همانطور که ملاحظه کردید ماژول ReactiveFormsModule را به فایل ماژولها اضافه کردیم. این ماژول شامل تمام ابزارهایی است که برای ساخت فرم بر پایه رویکرد Reactive نیاز داریم.
حال برای فعالکردن فرم در صفحه موردنظر باید ابتدا هوک ngOnInit را فعال کرده تا به هنگام فعال شدن کامپوننت فرم موردنظر ما نیز فعال شود. بنابراین در فایل app.component.ts داریم:
import {Component, OnInit} from '@angular/core'; import {FormControl, FormGroup} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { genders = ['مرد', 'زن']; signupForm: FormGroup; ngOnInit(){ this.signupForm = new FormGroup({ 'username': new FormControl(null), 'email': new FormControl(null), 'gender': new FormControl('مرد'), }) } }
همانطور که ملاحظه میکنید ابتدا یک شیء جدید از کلاس FormGroup ایجاد کردیم که شامل تمام فرم است سپس از روی کلاس FormControl برای هر ورودی یا فیلد یک شیء جدید تولید و درون آرگومان آن مقدار Null (خالی) را ارسال کردهایم. تا مقدار پیشفرض هر فیلد ورودی خالی باشد. البته در نظر داشته باشید میتوانید با نوشتن هر عبارت دلخواه به صورت string درون علامت " "، مقدار پیشفرض را برای ورودی خود در نظر بگیرید. اکنون در فیلد جنسیت مقدار پیشفرض «مرد» میباشد.
حال باید ارتباط بین قالب HTML و کلاس کامپوننت را برقرار کنیم. بنابراین فایل app.component.html را باز میکنیم و داخل تگ <form> دستور [FormGroup] را قرار داده تا به انگولار اطلاع دهیم که این گروه فرم ماست و مقدار آن را باید برابر با مقدار ویژگی FormGroup در کنترلر کامپوننت یعنی signupForm قرار دهید. همچنین باید برای انگولار هر فیلد ورودی را معرفی کنید و بگویید که به کدام FormControl در کلاس کامپوننت مربوط میشود. پس در این فایل باید تغییرات زیر را اعمال کنیم:
<div class="container"> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form [formGroup]="signupForm"> <div class="form-group"> <label for="username">نام کاربری</label> <input type="text" id="username" formControlName="username" class="form-control"> </div> <div class="form-group"> <label for="email">ایمیل</label> <input type="text" id="email" formControlName="email" class="form-control"> </div> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label> </div> <button class="btn btn-primary" type="submit">ثبت</button> </form> </div> </div> </div>
بسیار عالی در این حالت فرم HTML شما به انگولار آغشته شده است. حال میخواهیم هنگامیکه روی دکمهی «ثبت» کلیک شد. اطلاعاتی در اختیار ما قرار بگیرد. برای انجام این کار همانند رویکرد Template-Driven باید از دستور ویژگی ngSubmit استفاده کرده و سپس آن را به یک متد onSubmit واگذار کنیم. تا به اینجای کار همه چیز دقیقا مشابه رویکرد Template-Driven است ولی در رویکرد Reactive نیازی به تعریف یک قالب لوکال نیست. زیرا فرم ما هم اکنون توسط انگولار شناخته شده است. بنابراین ابتدا در فایل app.component.html به تگ <form> تغییرات زیر را اضافه خواهیم کرد:
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()"> ... </form>
سپس درون فایل app.component.ts متد onSubmit را تعریف میکنیم:
onSubmit(){ console.log(this.signupForm); }
پس از وارد کردن آدرس http://localhost:4200 در صفحه کنسول شما اطلاعات جامعی درباره این فرم در قالب FormGroup در اختیارتان قرار میگیرد.
برای اعتبارسنجی در این رویکرد باید به آرگومانهای سازنده پیشفرض کلاس FormControl در کلاس کامپوننت اشاره کنیم. آرگومان دوم این سازنده برای اعتبارسنجی فرمها مورد استفاده قرار میگیرد. برای درک بهتر از این موضوع دستورهای زیر را درون فایل app.component.ts اجرا میکنیم:
import {Component, OnInit} from '@angular/core'; import {FormControl, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { genders = ['مرد', 'زن']; signupForm: FormGroup; ngOnInit(){ this.signupForm = new FormGroup({ 'username': new FormControl(null, Validators.required), 'email': new FormControl(null, [Validators.required, Validators.email]), 'gender': new FormControl('مرد'), }) } onSubmit(){ console.log(this.signupForm); } }
خب تا اینجای کار همه چیز به درستی اعمال شده است. حالا ما میخواهیم هنگامیکه اعتبارسنجی با خطا روبهرو شد متن پیام خطا نمایش داده شود. برای انجام اینکار از یک دستور شرطی if استفاده کرده و مقدار عبارت شرطی را برابر با اطلاعاتی که از formControlها بدست میاوریم قرار میدهیم. متد get اطلاعات موجود در یک FormControl را در اختیار میگذارد. بنابراین در فایل app.component.html خواهیم داشت:
<div class="container"> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form [formGroup]="signupForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="username">نام کاربری</label> <input type="text" id="username" formControlName="username" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched"> لطفا نام کاربری خود را وارد کنید. </span> <div class="form-group"> <label for="email">ایمیل</label> <input type="text" id="email" formControlName="email" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('email').valid && signupForm.get('email').touched"> لطفا ایمیل خود را وارد کنید. </span> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label> </div> <span class="help-block" *ngIf="!signupForm.valid && signupForm.touched"> لطفا اطلاعات خود را به صورت کامل وارد کنید. </span> <button class="btn btn-primary" type="submit">ثبت</button> </form> </div> </div> </div>
همچنین برای زیباتر شدن فایل app.component.css را به صورت زیر تنظیم کنید:
input.ng-invalid.ng-touched{ border: 1px solid red; }
در صورتیکه تمام مراحل فوق را به درستی انجام داده باشید خروجی شما به صورت زیر خواهد بود:
حال فرض کنید میخواهیم یک فرم تو در تو درست کنیم. یعنی چی؟ یعنی میخواهیم همانند رویکرد اول مثلا دادههای مربوط به نام کاربری و ایمیل را درون یک متغییر به نام userData در اختیار داشته باشیم در این حالت باید ابتدا در فایل app.component.ts یک FormGroup ایجاد کنیم و سپس دادهها را به آن منتقل نماییم:
ngOnInit(){ this.signupForm = new FormGroup({ 'userData' : new FormGroup({ 'username': new FormControl(null, Validators.required), 'email': new FormControl(null, [Validators.required, Validators.email]) }), 'gender': new FormControl('مرد'), }) }
سپس فایل app.component.html را به صورت زیر ویرایش کنیم. توجه داشته باشید که از یک تگ <div> جدید استفاده کردهایم و ویژگی formGroupName را برای آن فعال کرده تا نام این گروه فرم جدید را به آن معرفی کنیم:
<div class="container"> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form [formGroup]="signupForm" (ngSubmit)="onSubmit()"> <div formGroupName="userData"> <div class="form-group"> <label for="username">نام کاربری</label> <input type="text" id="username" formControlName="username" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched"> لطفا نام کاربری خود را وارد کنید. </span> <div class="form-group"> <label for="email">ایمیل</label> <input type="text" id="email" formControlName="email" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched"> لطفا ایمیل خود را وارد کنید. </span> </div> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label> </div> <span class="help-block" *ngIf="!signupForm.valid && signupForm.touched"> لطفا اطلاعات خود را به صورت کامل وارد کنید. </span> <button class="btn btn-primary" type="submit">ثبت</button> </form> </div> </div> </div>
حال اگر این مثال را در آدرس http://localhost:4200 اجرا کنیم مجددا با خروجی قبلی روبهرو خواهیم شد چون فقط یک کلاس فرم جدید به این مجموعه اضافه کردهایم.
گاهی نیاز داریم که یک یا ده یا صد عدد از فرمکنترل ها را درون یک آرایه در اختیار داشته باشیم. مثلا فرض کنید میخواهیم یک دکمه به نام «افزودن تگ» در صفحه قرار دهیم تا با هر بار کلیک کردن روی آن یک فرم کنترل یا ورودی Input ارسال و ذخیره گردد. در این حالت باید از FormArrayها استفاده کنیم. بنابراین در فایل app.component.html داریم:
<div class="container"> <div class="row"> <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2"> <form [formGroup]="signupForm" (ngSubmit)="onSubmit()"> <div formGroupName="userData"> <div class="form-group"> <label for="username">نام کاربری</label> <input type="text" id="username" formControlName="username" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched"> لطفا نام کاربری خود را وارد کنید. </span> <div class="form-group"> <label for="email">ایمیل</label> <input type="text" id="email" formControlName="email" class="form-control"> </div> <span class="help-block" *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched"> لطفا ایمیل خود را وارد کنید. </span> </div> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label> </div> <span class="help-block" *ngIf="!signupForm.valid && signupForm.touched"> لطفا اطلاعات خود را به صورت کامل وارد کنید. </span> <div formArrayName="tags"> <h4>تگ های شما</h4> <button class="btn btn-primary" (click)="onAddTag()" >افزودن تگ</button> <div class="form-group" *ngFor="let tagControl of signupForm.get('tags').controls; let i = index"> <input type="text" class="form-control" [formControlName]="i"> </div> </div> <button class="btn btn-primary" type="submit">ثبت</button> </form> </div> </div> </div>
حال باید این آرایهی فرم کنترلر را داخل فایل app.component.ts تعریف کنیم. برای انجام اینکار داریم:
import {Component, OnInit} from '@angular/core'; import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { genders = ['مرد', 'زن']; signupForm: FormGroup; ngOnInit(){ this.signupForm = new FormGroup({ 'userData' : new FormGroup({ 'username': new FormControl(null, Validators.required), 'email': new FormControl(null, [Validators.required, Validators.email]) }), 'gender': new FormControl('مرد'), 'tags': new FormArray([]) }) } onSubmit(){ console.log(this.signupForm); } onAddTag(){ const control = new FormControl(null, Validators.required); (<FormArray>this.signupForm.get('tags')).push(control); } }
برای تفهیم این موضوع فرض کنید میخواهیم یک محدودیت برای انتخاب نام کاربری درنظر بگیریم. یعنی کاربر به هنگام انتخاب نام کاربری نمیتواند این نام را انتخاب کند. بنابراین فایل app.component.ts را باز کرده و در نهایت یک ویژگی به نام forbiddenUsername تعریف میکنیم که مقادیری را به عنوان نام کاربری ممنوعه یا غیرمجاز دریافت میکند:
forbiddenUsername = ['roxo', 'admin']
سپس باید یک متد برای ارائهی فرمانهای کنترلی و محدودکننده ایجاد کنیم. بنابراین در ادامه همین فایل خواهیم داشت:
import {Component, OnInit} from '@angular/core'; import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { genders = ['مرد', 'زن']; signupForm: FormGroup; forbiddenUsername = ['roxo', 'admin'] ngOnInit(){ this.signupForm = new FormGroup({ 'userData' : new FormGroup({ 'username': new FormControl(null, [Validators.required, this.forbiddenName]), 'email': new FormControl(null, [Validators.required, Validators.email]) }), 'gender': new FormControl('مرد'), 'tags': new FormArray([]) }) } onSubmit(){ console.log(this.signupForm); } onAddTag(){ const control = new FormControl(null, Validators.required); (<FormArray>this.signupForm.get('tags')).push(control); } forbiddenName(control: FormControl): {[s: string]: boolean}{ if(this.forbiddenUsername.indexOf(control.value)){ return {'nameIsForbidden': true} } return {'nameIsForbidden': false} } }
همانطور که ملاحظه میکنید یک متد به نام forbiddenName با ورودی control ایجاد کردیم که پس از پردازش نام کاربری و مقایسه آن با اسامیای که درون ویژگی forbiddenUsername قرار دارند، خروجیای به صورت باینری یا بولین نمایش میدهد حال این اعتبار سنجی را در قسمت مربوط به username و درون یک آرایه اعمال کردیم.
اگر آدرس http://localhost:4200 را باز کنید متوجه خواهید شد که خطایی در صفحه کنسول بوجود آمده است. این خطا برای این است که کلمه کلیدی this درون اعتبارسنجی this.forbiddenName به این کلاس اشاره نمیکند بلکه به کلاس Validators اشاره خواهد کرد و این اشتباه است چون متد forbiddenName درون این کلاس قرار دارد. بنابراین برای حل این مشکل از متد bind استفاده کرده و this را در این کلاس مورد استفاده قرار میدهیم:
import {Component, OnInit} from '@angular/core'; import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { genders = ['مرد', 'زن']; signupForm: FormGroup; forbiddenUsername = ['roxo', 'admin'] ngOnInit(){ this.signupForm = new FormGroup({ 'userData' : new FormGroup({ 'username': new FormControl(null, [Validators.required, this.forbiddenName.bind(this)]), 'email': new FormControl(null, [Validators.required, Validators.email]) }), 'gender': new FormControl('مرد'), 'tags': new FormArray([]) }) } onSubmit(){ console.log(this.signupForm); } onAddTag(){ const control = new FormControl(null, Validators.required); (<FormArray>this.signupForm.get('tags')).push(control); } forbiddenName(control: FormControl): {[s: string]: boolean}{ if(this.forbiddenUsername.indexOf(control.value) !== -1){ return {'nameIsForbidden': true} } return {'nameIsForbidden': false} } }
بسیار عالی حال اگر نام کاربری را درون فرم برابر roxo یا admin قرار دهید در المان div مربوط به ورودی username کلاس ng-invalid نمایش داده میشود.
یکی از ابزارهای جالبی که در کار کردن با فرمها در حالت Reactive مورد توجه است استفاده از ویژگی error در هر فرم کنترلر میباشد که در ادامه به توضیح آن میپردازیم.
اگر فرم مثال قبلی را در آدرس http://localhost:4200 پر کنید و سپس روی دکمهی ثبت کلیک نمایید. در صفحه کنسول یک شیء FormGroup برای شما ایجاد میشود که با کلیک کردن روی آن یک ویژگی به نام errors در اختیار شما قرار میگیرد. با استفاده از این ویژگی میتوان مشخص کرد که کدام متن خطا برای کاربر به نمایش گذاشته شود. به عنوان مثال برای فیلد نام کاربری در فایل app.component.html خواهیم داشت:
<span class="help-block" *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched"> <span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']">!انتخاب این نام کاربری مجاز نیست</span> <span *ngIf="signupForm.get('userData.username').errors['required']">لطفا نام کاربری خود را وارد کنید.</span> </span>
در واقع با استفاده از ویژگی errors به هر خطایی که مدنظرمان است دست پیدا کرده و متناسب با آن پیامی را برای مخاطب ارسال میکنیم.
همانطور که در رویکرد Template-Driven مشاهده کردید میتوان تنها یک فیلد از فرم را برای پیشنهاد نام کاربری تغییر داد و نیازی به ایجاد شیء از تمام فیلدها نیست. در رویکرد Reactive نیز این متد برقرار است و شما میتوانید با استفاده از آن تنها یک فیلد را بروزرسانی کنید.
بسیار عالی به شما عزیزان تبریک میگوییم. با مطالعهی فصل ۹ توانایی ساخت انواع فرمهای پیشرفته را کسب کردید. در ادامهی این سری از دورههای مجموعه آموزشی انگولار ۴ به آموزش مباحثی چون pipe، درخواستهای Http در فصول آینده میپردازیم. با ما همراه باشید.
توجه: دوستان عزیز آموزش ویدیویی انگولار ۵ از مقدماتی تا پیشرفته به زبان فارسی را میتوانید با کلیک روی اینجا یاد بگیرید. (این دوره در حال برگزاری است)
فصل ۱
فصل ۲
فصل ۳: خطایابی (Debugging) در انگولار ۴
فصل ۴
فصل ۵
فصل ۶
فصل ۷
فصل ۸: معرفی Observable یا مشاهده کنندهها در انگولار ۴
فصل ۹
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.