در پست قبلی component های modal خود را ایجاد کردیم. اکنون زمان آن رسیده است تا از آنها در پروژه خود استفاده کنیم.
ایجاد یک موجودیت owner جدید و همچنین اعتبار سنجی فرم، هدف ما در این مقاله میباشد.
اگر می خواهید تمام آموزشهای لازم و پایه مربوط به آموزش گام به گام Angular را ببینید ، لطفاً روی این لینک کلیک کنید: مقدمه آموزش گام به گام Angular.
آماده سازی کامپوننت Owner
با ایجاد component خود در داخل پوشه owner
، کار خود را شروع میکنیم. برای انجام این کار، دستور Angular CLI زیر را اجرا میکنیم:
1 |
ng g component owner/owner-create --skip-tests |
سپس، ما میخواهیم ماژول مسیریابی owner را با افزودن یک route جدید، به این صورت اصلاح کنیم:
1 |
{ path: 'create', component: OwnerCreateComponent } |
ما قصد داریم وقتیکه روی لینک “create” در داخل فایل owner-list.component.html کلیک می کنیم، برنامه ما را به صفحه creation هدایت کند.
بنابراین، اجازه دهید تگ <a> را در فایل owner-list.component.html به این صورت تغییر دهیم:
1 |
<a [routerLink]="['/owner/create']">Create owner</a> |
درباره اعتبارسنجی فرم و ReactiveFormsModule
اکنون میتوانیم شروع به نوشتن کد برای ایجاد entity خود و اعتبارسنجی فرم کنیم. دو نوع اعتبار سنجی در Angular وجود دارد: اعتبار سنجی قالب محور و اعتبار سنجی فرم واکنشی. در پروژه خود، ما از اعتبارسنجی فرم واکنشی استفاده می کنیم زیرا خواندن یک فایل HTML راحت تر است. علاوه بر این، این نوع از اعتبارسنجی، فایلهای HTML را با خطوط کدهای زیاد «کثیف» نکرده و تمام اعتبارسنجی در component قرار میگیرد که نگهداری را آسانتر میسازد.
درست قبل از اینکه component خود را اصلاح کنیم، باید فایل owner.module.ts را اصلاح کنیم. ReactiveFormsModule را اینجا import میکنیم زیرا این ماژولی است که از اعتبارسنجی فرم واکنشی پشتیبانی می کند:
1 2 3 4 5 6 7 8 9 10 11 12 |
import { ReactiveFormsModule } from '@angular/forms'; ... @NgModule({ ... imports: [ CommonModule, OwnerRoutingModule, SharedModule, ReactiveFormsModule ] |
علاوه بر این، باید یک interface جدید ایجاد کنیم:
1 2 3 4 5 |
export interface OwnerForCreation{ name: string; dateOfBirth: string; address: string; } |
قسمت اعتبارسنجی فرم در HTML
با اصلاح فایل create.component.html، به کار خود ادامه میدهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<div class="container-fluid"> <form [formGroup]="ownerForm" autocomplete="off" novalidate (ngSubmit)="createOwner(ownerForm.value)"> <div class="card card-body bg-light mb-2 mt-2"> <div class="row mb-3"> <label for="name" class="col-form-label col-md-2">Name of the owner: </label> <div class="col-md-5"> <input type="text" formControlName="name" id="name" class="form-control" /> </div> <div class="col-md-5"> <em *ngIf="validateControl('name') && hasError('name', 'required')">Name is required</em> <em *ngIf="validateControl('name') && hasError('name', 'maxlength')">Maximum allowed length is 60 characters.</em> </div> </div> <div class="mb-3 row"> <label for="dateOfBirth" class="col-form-label col-md-2">Date of birth: </label> <div class="col-md-5"> <input type="text" formControlName="dateOfBirth" id="dateOfBirth" class="form-control" readonly bsDatepicker/> </div> <div class="col-md-5"> <em *ngIf="validateControl('dateOfBirth') && hasError('dateOfBirth', 'required')">Date of birth is required</em> </div> </div> <div class="mb-3 row"> <label for="address" class="col-form-label col-md-2">Address: </label> <div class="col-md-5"> <input type="text" formControlName="address" id="address" class="form-control" /> </div> <div class="col-md-5"> <em *ngIf="validateControl('address') && hasError('address', 'required')">Address is required</em> <em *ngIf="validateControl('address') && hasError('address', 'maxlength')">Maximum allowed length is 100 characters.</em> </div> </div> <br><br> <div class="mb-3 row"> <div class="offset-5 col-md-1"> <button type="submit" class="btn btn-info" [disabled]="!ownerForm.valid">Save</button> </div> <div class="col-md-1"> <button type="button" class="btn btn-danger" (click)="redirectToOwnerList()">Cancel</button> </div> </div> </div> </form> </div> |
حال این کد را توضیح میدهیم. در عنصر form، ما formGroup را با نام ownerForm ایجاد می کنیم. این form group شامل تمام کنترل هایی است که باید در فرم خود اعتبارسنجی کنیم. علاوه بر این، با (ngSubmit) زمانی که کاربر دکمه submit را کلیک میکند، تابعی را فراخوانی میکنیم. مقدار ownerForm را به عنوان پارامتری برای این تابع ارسال میکنیم که شامل تمام کنترلها به همراه دادههای مورد نیاز برای اعتبارسنجی میباشد.
یک ویژگی formControlName در داخل هر کنترل وجود دارد. این attribute، نشان دهنده نام کنترلی است که ما قصد داریم آن را در داخل ownerForm اعتبارسنجی کنیم و یک attribute اجباری میباشد. علاوه بر این، پیام های خطا در صورت وجود در تگهای <em> نمایش داده می شود.
یکی از مواردی که باید به آن توجه کرد عنصر ورودی DateOfBirth است. در اینجا ما از bsDatepicker
directive از ngx-bootstrap برای پیوست کردن datepicker به کامپوننت input مورد نظر استفاده کرده ایم. برای اینکه این درست کار کند، باید ماژول BsDatepickerModule را داخل فایل owner.module.ts اضافه کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; ... @NgModule({ ... imports: [ CommonModule, OwnerRoutingModule, SharedModule, ReactiveFormsModule, BsDatepickerModule.forRoot() ] |
عملکردهای بسیار بیشتری با ngx-bootstrap’s date picker وجود دارد که میتوانیم از آنها استفاده کنیم، و برای آشنایی با آنها، می توانید این مستندات را بخوانید.
در مورد خطاها و دکمه های روی فرم
خطاها فقط در صورتی روی صفحه نمایش داده میشوند که توابع ()validateControl و ()hasError مقدار true را به عنوان نتیجه برگردانند.
تابع ()validateControl بررسی میکند که آیا کنترل نامعتبر است یا خیر و تابع ()hasError بررسی میکند که بر اساس کدام قانون اعتبارسنجی، خطا وجود دارد (required, max length…). هر دو تابع validateControl و hasError توابع سفارشی هستند که می خواهیم در فایل component (.ts) پیاده سازی کنیم. همچنین یک دکمه submit نیز وجود دارد که تا زمانی که فرم معتبر نباشد غیرفعال خواهد بود و یک دکمه cancel که کاربر را از این فرم creation ریدایرکت می کند.
قسمت اعتبارسنجی فرم در Component (.ts)
اکنون، ما باید logic تمام توابع فراخوانی شده در فایل template را پیاده سازی کنیم. ما میخواهیم تعداد زیادی logic را به فایل owner-create.component.ts اضافه کنیم، بنابراین سعی میکنیم توضیحات را به چند قسمت تقسیم کنیم. کار خود را با import ها شروع میکنیم:
1 2 3 4 5 6 7 8 9 10 11 |
import { SuccessModalComponent } from './../../shared/modals/success-modal/success-modal.component'; import { DatePipe } from '@angular/common'; import { Router } from '@angular/router'; import { ErrorHandlerService } from './../../shared/services/error-handler.service'; import { OwnerRepositoryService } from './../../shared/services/owner-repository.service'; import { Component, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { Owner } from 'src/app/_interfaces/owner.model'; import { HttpErrorResponse } from '@angular/common/http'; import { OwnerForCreation } from 'src/app/_interfaces/ownerForCreation.model'; import { ModalOptions, BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; |
تعداد زیادی import اینجا وجود دارد، و ما در سراسر کد خواهیم دید که چرا از هر یک از آنها استفاده می کنیم.
حالا اجازه دهید قسمت دیگری از این فایل را بررسی کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export class OwnerCreateComponent implements OnInit { errorMessage: string = ''; ownerForm: FormGroup; bsModalRef?: BsModalRef; constructor(private repository: OwnerRepositoryService, private errorHandler: ErrorHandlerService, private router: Router, private datePipe: DatePipe, private modal: BsModalService) { } ngOnInit(): void { this.ownerForm = new FormGroup({ name: new FormControl('', [Validators.required, Validators.maxLength(60)]), dateOfBirth: new FormControl('', [Validators.required]), address: new FormControl('', [Validators.required, Validators.maxLength(100)]) }); } } |
اینجا به محض اینکه کامپوننت mounts می شود، متغیر FormGroup خود را با نام ownerForm با تمام FormControl ها initialize می کنیم. توجه داشته باشید که کلیدهای موجود در آبجکت ownerForm همان نام های موجود در ویژگی formControlName برای تمام فیلدهای input در فایل html هستند که از نوع اجباری میباشند. علاوه بر این، آنها دقیقا همنام ویژگی های داخل ابجکت owner (address, dateOfBirth و name) میباشند.
هنگامی که یک form control جدید را نمونه سازی میکنیم، value آن control را به عنوان پارامتر اول و آرایه Validators را به عنوان پارامتر دوم ارائه میدهیم، که این آرایه، تمام قوانین اعتبارسنجی را برای control های ما در خود نگه میدارد.
Handle کردن خطاها
اکنون، ما قصد داریم آن دو تابع را که در فایل template خود برای handle کردن خطاهای اعتبارسنجی فراخوانی کرده ایم را اینجا اضافه کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
validateControl = (controlName: string) => { if (this.ownerForm.get(controlName).invalid && this.ownerForm.get(controlName).touched) return true; return false; } hasError = (controlName: string, errorName: string) => { if (this.ownerForm.get(controlName).hasError(errorName)) return true; return false; } |
در متد ()validateControl، بررسی میکنیم که آیا کنترل فعلی نامعتبر است و touche شده است یا خیر (اگر کاربر اصلاً cursor را داخل کنترل قرار نداده باشد، نمیخواهیم خطایی نشان دهیم). علاوه بر این، تابع ()hasError بررسی می کند که کنترل فعلی کدام قانون اعتبار سنجی را نقض کرده است.
فرآیند ایجاد، DatePipe، و فراخوانی Modal
برای ادامه، تابع createOwner را که در رویداد (ngSubmit) فراخوانی کرده بودیم را اینجا اضافه می کنیم:
1 2 3 4 |
createOwner = (ownerFormValue) => { if (this.ownerForm.valid) this.executeOwnerCreation(ownerFormValue); } |
این تابع، form value را به عنوان ورودی گرفته و بررسی میکند که آیا form معتبر است یا خیر. که آگر form معتبر باشد، در نتیجه تابع private دیگری به نام executeOwnerCreation را فراخوانی میکند و این form value گرفته شده را به عنوان پارامتر به این تابع ارسال میکند.
طبق گفته مورد نظر، تابع private مورد نظر را ایجاد کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
private executeOwnerCreation = (ownerFormValue) => { const owner: OwnerForCreation = { name: ownerFormValue.name, dateOfBirth: this.datePipe.transform(ownerFormValue.dateOfBirth, 'yyyy-MM-dd'), address: ownerFormValue.address } const apiUrl = 'api/owner'; this.repository.createOwner(apiUrl, owner) .subscribe({ next: (own: Owner) => { const config: ModalOptions = { initialState: { modalHeaderText: 'Success Message', modalBodyText: `Owner: ${own.name} created successfully`, okButtonText: 'OK' } }; this.bsModalRef = this.modal.show(SuccessModalComponent, config); this.bsModalRef.content.redirectOnOk.subscribe(_ => this.redirectToOwnerList()); }, error: (err: HttpErrorResponse) => { this.errorHandler.handleError(err); this.errorMessage = this.errorHandler.errorMessage; } }) } |
در اینجا ما یک آبجکت OwnerForCreation جدید ایجاد می کنیم که می خواهیم آن را با درخواست POST خود به API مورد نظر ارسال کنیم. توجه داشته باشید که در این مثال، استفاده از date pipe فقط به فایل های HTML محدود نمی شود. البته، برای اینکه این date pipe در یک فایل component کار کند، باید DatePipe را داخل فایل app.module.ts ایمپورت کنیم و آن را در آرایه “providers” قرار دهیم:
1 2 3 4 5 6 7 8 9 10 11 |
import { DatePipe } from '@angular/common'; @NgModule({ declarations: [ ... ], imports: [ ... ], providers: [DatePipe], bootstrap: [AppComponent] |
زمانیکه آبجکت ownerForCreation را ایجاد کردیم، سپس apiUrl را ساخته و تابع createOwner
repository را فراخوانی میکنیم. اکنون، از آنجایی که ما از نوع OwnerForCreation داریم استفاده می کنیم، و نه از نوع Owner، پس باید این نوع را در داخل تابع repository تغییر دهیم:
1 |
public createOwner = (route: string, owner: OwnerForCreation) => |
بسیار خب. حالا به توضیحات logic خودمان برمیگردیم.
در داخل تابع subscribe – داخل ویژگی success response – next را از API میگیریم و آبجکت initial state را برای success modal خود ایجاد کرده و سپس با ارائه SuccessModalComponent و آبجکت config
به عنوان پارامترها، modal را نشان میدهیم. علاوه بر این، ما اینجا از آبجکت bsModalRef برای subscribe به رویداد redirectOnOk که از SucessModalComponent پس از کلیک روی دکمه Ok ارسال میکنیم، استفاده میکنیم.
Redirection
با این subscription، تابع redirectToOwnerList را فراخوانی می کنیم. ما با کلیک بر روی دکمه Cancel در فرم خود، همین تابع را نیز فراخوانی می کنیم. بنابراین، این تابع را به این صورت اضافه میکنیم:
1 2 3 |
redirectToOwnerList = () => { this.router.navigate(['/owner/list']); } |
این کد، کدی آشنا برای navigate کردن به component قبلی میباشد. راه دیگری برای انجام این کار نیز وجود دارد که باید Location را از angular/common@ ایمپورت کرده و آن را در داخل سازنده inject کنید و سپس فقط کافیست که تابع ()back را در این ویژگی inject شده فراخوانی کنید (()location.back). اینکه کدام راه را انتخاب میکنید کاملاً به شما بستگی دارد.
اکنون فقط فایل root CSS خود (styles.css) را تغییر دهید تا پیامهای <em> را با رنگ قرمز و سبک bold نشان دهیم و اگر ورودیها نامعتبر باشند، آنها را به رنگ قرمز wrap میکنیم:
1 2 3 4 5 6 7 |
em{ color: #e71515; font-weight: bold; } .ng-invalid.ng-touched{ border-color: red; } |
بررسی نتایج
حال، اگر به کامپوننت CreateOwner برویم، هنگامی که داخل هر عنصر ورودی کلیک کنیم و آن را خالی رها کنیم، پیام های خطای خود را به این صورت می بینیم:
البته، میتوانیم اعتبارسنجی از نوع max length را نیز آزمایش کنیم.
پس از پر کردن تمام فیلدها و کلیک بر روی دکمه ذخیره، پیام موفقیت آمیز را مشاهده خواهیم کرد:
وقتی روی دکمه OK کلیک کنیم، به صفحه owner-list هدایت می شویم و owner جدید در لیست قرار می گیرد.
نمایش کامپوننت Error Modal
در سرویس ErrorHandler، ما میخواهیم تابع handleOtherError را به این صورت اصلاح کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 |
private handleOtherError = (error: HttpErrorResponse) => { this.createErrorMessage(error); const config: ModalOptions = { initialState: { modalHeaderText: 'Error Message', modalBodyText: this.errorMessage, okButtonText: 'OK' } }; this.modal.show(ErrorModalComponent, config); } |
البته باید در همین فایل import ها را نیز اضافه کنیم:
1 2 |
import { ErrorModalComponent } from './../modals/error-modal/error-modal.component'; import { BsModalService, ModalOptions } from 'ngx-bootstrap/modal'; |
حال اگر خطایی غیر از 500 یا 404 ظاهر شد، یک پیام modal را به کاربر نشان می دهیم. برای آزمایش این رفتار، میتوانیم با تغییر عمل CreateOwner در پروژه Web API، خطا را به پروژه خود تحمیل کنیم:
1 2 3 4 5 |
[HttpPost] public IActionResult CreateOwner([FromBody] OwnerForCreationDto owner) { return BadRequest("Bad request from the server while creating owner"); ... |
نتیجه گیری
با خواندن این پست، موارد زیر را یاد گرفتید:
- انواع مختلف اعتبارسنجی در Angular
- نحوه استفاده از اعتبارسنجی فرم واکنشی در فایل HTML
- نحوه استفاده از اعتبارسنجی فرم واکنشی در فایل component
- روشی برای ایجاد یک entity جدید در سمت client
از اینکه این پست را خواندید از شما سپاسگزارم، امیدوارم برای شما مفید واقع قرار گرفته باشد.
در قسمت بعدی این آموزش گام به گام، با ارسال درخواست PUT به server، قسمت آپدیت پروژه را می نویسیم.