مدلهای سمت سرور برنامه
در این مطلب قصد داریم لیست گروهها را به همراه محصولات مرتبط با آنها، توسط دو drop down list نمایش دهیم:
public class Category { public int CategoryId { set; get; } public string CategoryName { set; get; } [JsonIgnore] public IList<Product> Products { set; get; } } public class Product { public int ProductId { set; get; } public string ProductName { set; get; } }
منبع داده JSON سمت سرور
پس از مشخص شدن مدلهای برنامه، اکنون توسط دو اکشن متد، لیست گروهها و همچنین لیست محصولات یک گروه خاص را با فرمت JSON بازگشت میدهیم:
namespace AngularTemplateDrivenFormsLab.Controllers { [Route("api/[controller]")] public class ProductController : Controller { [HttpGet("[action]")] public async Task<IActionResult> GetCategories() { await Task.Delay(500); return Json(CategoriesDataSource.Items); } [HttpGet("[action]/{categoryId:int}")] public async Task<IActionResult> GetProducts(int categoryId) { await Task.Delay(500); var products = CategoriesDataSource.Items .Where(category => category.CategoryId == categoryId) .SelectMany(category => category.Products) .ToList(); return Json(products); } } }
- در اینجا از یک Delay نیز استفاده شدهاست تا بتوان آیکنهای چرخندهی Loading سمت کاربر را در حین کار با عملیاتی زمانبر، بهتر مشاهده کرد.
کدهای سمت کاربر برنامه
کدهای سمت کاربر این مثال در ادامهی همان مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» هستند که بر روی آن این دستورات فراخوانی شدهاست:
>ng g m Product -m app.module --routing
>ng g c product/product-group
>ng g cl product/product >ng g cl product/Category >ng g cl product/product-group-form
export class ProductGroupForm { constructor( public categoryId?: number, public productId?: number ) { } } export class Product { constructor( public productId: number, public productName: string ) { } } export class Category { constructor( public categoryId: number, public categoryName: string ) { } }
سپس سرویسی را جهت دریافت اطلاعات دراپ داونها از سرور تهیه کردهایم:
>ng g s product/product-items -m product.module
import { Injectable } from "@angular/core"; import { Http, Response, Headers, RequestOptions } from "@angular/http"; import { Observable } from "rxjs/Observable"; import "rxjs/add/operator/do"; import "rxjs/add/operator/catch"; import "rxjs/add/observable/throw"; import "rxjs/add/operator/map"; import "rxjs/add/observable/of"; import { Category } from "./category"; import { Product } from "./product"; @Injectable() export class ProductItemsService { private baseUrl = "api/product"; constructor(private http: Http) { } private handleError(error: Response): Observable<any> { console.error("observable error: ", error); return Observable.throw(error.statusText); } getCategories(): Observable<Category[]> { return this.http .get(`${this.baseUrl}/GetCategories`) .map(response => response.json() || {}) .catch(this.handleError); } getProducts(categoryId: number): Observable<Product[]> { return this.http .get(`${this.baseUrl}/GetProducts/${categoryId}`) .map(response => response.json() || {}) .catch(this.handleError); } }
پس از این مقدمات اکنون میتوان کدهای ProductGroupComponent را تکمیل کرد.
ابتدا در متد ngOnInit آن کار دریافت لیست آغازین گروههای محصولات را انجام میدهیم:
export class ProductGroupComponent implements OnInit { categories: Category[] = []; model = new ProductGroupForm(); constructor(private productItemsService: ProductItemsService) { } ngOnInit() { this.productItemsService.getCategories().subscribe( data => { this.categories = data; }, err => console.log("get error: ", err) ); }
اکنون چون این خاصیت در دسترس است، میتوان به قالب این کامپوننت مراجعه کرده و قسمت ابتدایی فرم را تکمیل کرد:
<div class="container"> <h3>Cascading Drop-down Lists</h3> <form #form="ngForm" (submit)="submitForm(form)" novalidate> <div class="form-group"> <label class="control-label">Category</label> <span class="glyphicon glyphicon-refresh glyphicon-spin spinner" *ngIf="categories.length == 0"></span> <select class="form-control" name="categoryCtrl" #categoryCtrl (change)="fetchProducts(categoryCtrl.value)" [(ngModel)]="model.categoryId"> <option value="undefined">Select a Category...</option> <option *ngFor="let category of categories" value="{{category.categoryId}}"> {{ category.categoryName }} </option> </select> </div>
- سپس ngModel به خاصیت categoryId وهلهای از کلاس ProductGroupForm که مدل معادل فرم است، متصل شدهاست.
- همچنین با اتصال به رخداد change، مقدار Id عضو انتخابی به متد fetchProducts ارسال میشود. دسترسی به این Id از طریق یک template reference variable به نام categoryCtrl# انجام شدهاست.
- در آخر، ngFor تعریف شده به ازای هر عضو آرایهی categories، یکبار تگ option را تکرار میکند و در هربار تکرار، مقدار ویژگی value را به categoryId تنظیم میکند و برچسب نمایشی آنرا از categoryName دریافت خواهد کرد.
بنابراین مرحلهی بعدی تکمیل این drop down آبشاری، واکنش نشان دادن به رخداد change و تکمیل متد fetchProducts است:
products: Product[] = []; isLoadingProducts = false; fetchProducts(categoryId?: number) { console.log(categoryId); this.products = []; if (categoryId === undefined || categoryId.toString() === "undefined") { return; } this.isLoadingProducts = true; this.productItemsService.getProducts(categoryId).subscribe( data => { this.products = data; this.isLoadingProducts = false; }, err => { console.log("get error: ", err); this.isLoadingProducts = false; } ); }
- سپس بررسی میکنیم که آیا categoryId دریافتی undefined است یا خیر؟ این مساله دو علت دارد:
الف) اولین عضو drop down انتخاب محصولات را با مقدار undefined مشخص کردهایم:
<option value="undefined">Select a Category...</option>
public categoryId?: number
model = new ProductGroupForm();
- پس از آن همانند قسمت قبل، این categoryId را به سرور ارسال کرده و سپس اطلاعات متناظری را دریافت و به خاصیت عمومی products نسبت دادهایم. همچنین از یک خاصیت عمومی دیگر به نام isLoadingProducts نیز استفاده شدهاست تا مشخص شود چه زمانی کار دریافت اطلاعات از سرور خاتمه پیدا میکند. از آن برای نمایش یک آیکن چرخندهی دیگر استفاده میکنیم:
<div class="form-group"> <label class="control-label">Product</label> <span class="glyphicon glyphicon-refresh glyphicon-spin spinner" *ngIf="isLoadingProducts"></span> <select class="form-control" name="productCtrl" [(ngModel)]="model.productId"> <option value="undefined">Select a Product...</option> <option *ngFor="let product of products" value="{{product.productId}}"> {{ product.productName }} </option> </select> </div>
/* Spinner */ .spinner { font-size:15px; z-index:10 } .glyphicon-spin { -webkit-animation: spin 1000ms infinite linear; animation: spin 1000ms infinite linear; } @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } } @keyframes spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); transform: rotate(359deg); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-06.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run