OVERVIEW
Razor components are specific building blocks within the Blazor framework. They can perform many roles: representing a specific piece of the user interface, a view component, or a tag helper; or representing a layout or an entire page. In Razor Components Succinctly, you will explore how to create and work with both simple and advanced Razor components. Longtime Succinctly author Ed Freitas will show you how to write a basic component using one-way data binding and events, and then two-way data binding, event callbacks, life cycle methods, and component references. Finally, you'll see how to enable component reuse by creating a component template.
TABLE OF CONTENTS
Razor Components
Setup and Fundamentals
Component Fundamentals
Component Features
Using Components
Templating
Author
Ed Freitas
ISBN
978-1-64200-211-9
Published on
April 16, 2021
Pages
102
ایجاد برنامههای جدید توسط Angular CLI
دستور خط فرمان ابتدایی ایجاد یک برنامهی جدید توسط Angular CLI به صورت ذیل است
> ng new my-app
پس از اجرای این دستور، برنامهی جدید ایجاد شده، در پوشهی جدید my-app قرار میگیرد.
گزینهی دیگر این دستور، استفاده از پرچم dry-run است:
> ng new my-app --dry-run
>ng new my-app --dry-run installing ng You specified the dry-run flag, so no changes will be written. create .editorconfig create README.md create src\app\app.component.css create src\app\app.component.html . . . Project 'my-app' successfully created.
گزینهی دیگر دستور ng new را که در قسمت قبل ملاحظه کردید:
> ng new my-app --skip-install
برای مشاهدهی سایر پرچمهای مرتبط با دستور ng new میتوان از پرچم help استفاده کرد:
> ng new --help
بررسی فایل angular-cli.json.
فایل angular-cli.json. حاوی تنظیمات Angular CLI است.
در ابتدای این فایل، نام برنامهی جدید را مشاهده میکنید. این نام، همانی است که توسط دستور ng new my-app تعیین گردید.
"project": { "name": "my-app" },
"apps": [ { "root": "src", "outDir": "dist",
یکی از تنظیمات مهم این فایل، مقدار prefix است:
"prefix": "app",
@Component({ selector: 'app-root',
تغییر این مقدار صرفا بر روی کامپوننتهای جدید تولید شدهی توسط Angular CLI تاثیرگذار خواهند بود. اگر میخواهید در ابتدای کار تولید یک برنامه، این مقدار را مشخص کنید، میتوان از پرچم prefix استفاده کرد و در صورت عدم ذکر آن، مقدار پیش فرض app برای آن درنظر گرفته میشود:
> ng new my-project --skip-install --prefix myCompany
عدم ایجاد مخزن Git به همراه ng new
با صدور فرمان ng new، کار ایجاد یک مخزن Git نیز به صورت خودکار انجام خواهد شد. برای نمونه اگر خواستید برنامهای را بدون نصب وابستگیها، بدون ایجاد تستها و بدون ایجاد مخزن git آن تولید کنید، میتوان از دستور ذیل استفاده کرد:
> ng new my-project --skip-install --skip-git --skip-tests --skip-commit
استفادهی از sass بجای css توسط Angular CLI
سیستم Build همراه با Angular CLI مبتنی بر webpack است و به خوبی قابلیت پردازش فایلهای sass را نیز دارا است. اگر خواستید حالت پیش فرض تولید فایلهای css این ابزار را که در فایل angular-cli.json. نمونهای از آن ذکر شدهاست، به همراه فایلهایی مانند app.component.css، به sass تغییر دهید:
"styles": [ "styles.css" ], "defaults": { "styleExt": "css", "component": {} }
> ng new my-project --skip-install --style sass
"styles": [ "styles.sass" ], "defaults": { "styleExt": "sass", "component": {} }
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.sass'] })
انجام تنظیمات مسیریابی پیش فرض پروژه جدید توسط Angular CLI
حالت پیش فرض تولید برنامههای جدید Angular CLI به همراه تنظیمات مسیریابی آن نیست. اگر علاقمند هستید تا مبحث مسیریابی را خلاصه کرده و به سرعت تنظیمات ابتدایی مسیریابی را توسط این ابزار تولید کنید، میتوان پرچم routing را نیز در اینجا ذکر کرد:
> ng new my-project --skip-install --routing
const routes: Routes = [ { path: '', children: [] } ];
imports: [ BrowserModule, FormsModule, HttpModule, AppRoutingModule ],
اجرای ابتدایی یک برنامهی مبتنی بر Angular CLI
پس از انتخاب پرچمهای مناسب جهت ایجاد یک پروژهی جدید مبتنی بر Angular CLI و همچنین نصب وابستگیهای آنها و یا عدم ذکر پرچم skip-install، اکنون نوبت به اجرای این پروژهاست. به همین جهت از طریق خط فرمان به ریشهی پوشهی برنامهی جدید ایجاد شده، وارد شوید. سپس دستور ذیل را صادر کنید:
>ng serve -o
http://localhost:4200/
نکتهی جالب این وب سرور در این است که تغییرات شما را به صورت خودکار دنبال کرده و بلافاصله ارائه میدهد. برای مثال فایل src\app\app.component.html را گشوده و به صورت ذیل تغییر دهید:
<h1> Test {{title}} </h1>
تغییر پیش فرضهای عمومی Angular CLI
تا اینجا مشاهده کردیم که اگر بخواهیم مقدار prefix پیش فرض را که به app تنظیم شدهاست به myCompany تغییر دهیم، یا میتوان از پرچم prefix در ابتدای کار فراخوانی دستور ng new استفاده کرد و یا میتوان فایل angular-cli.json. را نیز دستی ویرایش نمود. برای تغییر عمومی و سراسری مقدار پیش فرض app میتوان از دستور ng set استفاده کرد:
>ng set apps.prefix myCompany >ng set apps.prefix myCompany -g
و یا اگر بخواهید نوع شیوهنامهی مورد استفاده را ویرایش کنید، میتوان از یکی از دو دستور ذیل استفاده کرد (اولی محلی است و دومی عمومی):
>ng set defaults.styleExt sass >ng set defaults.styleExt sass -g
اجرای امکانات Linting پروژههای مبتنی بر Angular CLI
برای بررسی کیفیت کدهای نوشته شده، میتوان از امکانات Linting استفاده کرد. برای این منظور تنها کافی است دستور ذیل را در ریشهی پروژه اجرا نمود:
> ng lint
> ng lint --help
> ng lint --format stylish
>ng lint --fix
یک مثال: فایل src\app\app.component.ts را باز کنید و به عمد تعدادی مشکل را در آن ایجاد نمائید. برای نمونه دو سطر ابتدایی آنرا به صورت ذیل تغییر دهید:
import { Component } from '@angular/core' let number = 10;
>ng lint --format stylish /src/app/app.component.ts[3, 5]: Identifier 'number' is never reassigned; use 'const' instead of 'let'. /src/app/app.component.ts[1, 42]: Missing semicolon Lint errors found in the listed files.
اکنون اگر دستور ng lint --fix را فراخوانی کنیم، تغییرات ذیل به فایل src\app\app.component.ts اعمال خواهند شد:
import { Component } from '@angular/core'; const number = 10;
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders
نگاهی به تاریخچهی Angular
سری آموزشی مقدماتی Web API
Our beginner's guide to building Web APIs with ASP.NET Core is designed to provide you with the foundation you need to start building Web APIs with .NET in a collection of short, pragmatic collection of videos. Web APIs have become a critical component in almost every type of software we use today. In this introductory video series, we will walk you through the fundamental concepts you need to know to get started with building Web APIs using ASP.NET Core. We will cover topics such as routing, validation, working with data, and much more.
معرفی C#/WinRT Version 1.0
Today is the official GA release for .NET 5, and along with it we are excited to share the latest updates with our recent release of C#/WinRT version 1.0. C#/WinRT provides WinRT projection support for .NET 5 based apps. The Windows SDK leverages this technology and is now integrated with the .NET 5.0 SDK to expose Windows APIs through the new Target Framework Monikers. In addition to the Windows SDK support added for .NET 5, C#/WinRT itself allows component authors to build their own .NET 5 projections using the CsWinRT NuGet package.
4.Visual Studio 2017 15.9 منتشر شد
These are the customer-reported issues addressed in 15.9.4:
- Visual Studio 15.9 duplicate loads open files on solution reload.
- All users can now connect to on-premise TFS servers through Team Explorer.
- Visual Studio 15.8.3 no longer expands metadata in ItemDefinitionGroup for project-defined items during GUI builds (worked in Visual Studio 15.8.2).
- Visual Studio has multiple tabs for the same file.
- System.ArgumentException: The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG)).
- LNK4099 PDB not found.
- Asset Catalog empty.
- /analyze fails for C++ code using /ZW.
- C++ compiler code optimization bug.
- Xamarin.iOS can't select image asset for Image View.
- iOS projects referencing a shared project containing image assets in an asset catalog fail to load on windows.
- Image not populating on iOS splashscreen in VS 15.8.6.
- Possible bad codegen on union/bitfield assignment in VS2017 15.8.
- Fix C# UWP Store 1201 Submission Issue.
- Fix C# UWP package creation error APPX1101: Payload contains two or more files with the same destination path 'System.Runtime.CompilerServices.Unsafe.dll'.
- Error MT2002: Failed to resolve 'System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder' reference from 'System.Threading.Tasks.Extensions...'" when building a Xamarin.iOS project.
- Redirecting to a relative url doesn't work when using AndroidClientHandler.
- Debug information for typedefs of unnamed enums compiled with the C compiler is now restored.
- The spectre-mitigated x86 version of delayimp.lib is now built with /Qspectre mitigations enabled.
- Changes were made to how Asset Catalogs in Xamarin.iOS projects are loaded in order to reduce solution load time.
- We have updated Xamarin.Forms templates to use the latest version.
- We have fixed an issue with ASP.NET Core Web Applications being debugged through Kestrel that would show the error message "Unable to configure HTTPS endpoint. No server certificate was specified...".
- Enabling the AppInsights site extension in App Service from Visual Studio now happens through the use of specific Application Settings.
Security Advisory Notices
پروژه دات نت را از طریق این آدرس دریافت کرده و آن را در حالت اجرا بگذارید.
سپس یک سرویس جدید را در پروژه Angular خود اجرا کنید و متد زیر را به آن اضافه کنید:
login():any{ let urlSearchParams = new URLSearchParams(); urlSearchParams.append('username', 'Vahid'); urlSearchParams.append('password', '1234'); urlSearchParams.append('grant_type', 'password'); let body = urlSearchParams.toString(); let headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); return this._http.post('http://localhost:9577/login', body, { headers: headers }) .map(res => res.json()); }
username=vahid&password=1234
از آنجاکه پروژه انگیولار ساخته شده در آدرسی دیگر و جدا از پروژهی دات نت قرار دارد و همینطور که میبینید آدرس کامل آن را به این دلیل قرار دادم، ممکن است خطای CORS را دریافت کنید که میتوانید آن را با نصب یک بسته نیوگتی حل کنید.
همچنین برای تست و انجام یک عمل مرتبط با توکن، متد زیر را هم به آن اضافه میکنیم:
ApiAdmin(token:any):any{ let headers = new Headers(); headers.append('Authorization', 'bearer ' + token); return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers }) .map(res => res.json()); }
کد کل سرویس، الان به شکل زیر شده است:
import { Http, Headers, URLSearchParams } from '@angular/http'; import { Injectable } from '@angular/core'; import { Observable } from "rxjs/Observable"; import "rxjs/Rx"; @Injectable() export class MemberShipService { constructor(private _http: Http) { } ApiAdmin(token: any): any { let headers = new Headers(); headers.append('Authorization', 'bearer ' + token); return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers }) .map(res => res.json()); } login(): any { let urlSearchParams = new URLSearchParams(); urlSearchParams.append('username', 'Vahid'); urlSearchParams.append('password', '1234'); urlSearchParams.append('grant_type', 'password'); let body = urlSearchParams.toString(); let headers = new Headers(); headers.append('Content-Type', 'application/x-www-form-urlencoded'); return this._http.post('http://localhost:9577/login', body, { headers: headers }) .map(res => res.json()); } }
Login(){ this._service.login() .subscribe(res => { localStorage.setItem('access_token', res.access_token); localStorage.setItem('refresh_token', res.refresh_token); } , error => console.log(error)); }
در متد بعدی که قرار است توسط آن صحت اعتبارسنجی مورد آزامایش قرار بگیرد، کدهای زیر را مینویسیم:
CallApi() { let item = localStorage.getItem("access_token"); if (item == null) { alert('please Login First.'); return; } this._service.ApiAdmin(item) .subscribe(res => { alert(res); } , error => console.log(error)); }
اعتبارسنجی در مسیریابی
یکی از روشهایی که انگیولار باید بررسی کند این است که کاربر جاری با توجه به نقشهایی که ممکن است داشته باشد، یا اعتبار سنجی شده است یا خیر و میزان دسترسی او به کامپوننتها، باید مشخص گردد. برای این مورد خصوصیتی به مسیریابی اضافه شده است به نام CanActivate که از شما یک کلاس را که در آن اینترفیس CanActivate پیاده سازی شده است، درخواست میکند. در اینجا ما یک Guard را با نام LoginGuard ایجاد میکنیم، تا تنها کاربرانی که لاگین کردهاند، به این صفحه دسترسی داشته باشند:
import { CanActivate } from '@angular/router'; import { Injectable } from '@angular/core'; @Injectable() export class LoginGuard implements CanActivate { constructor() { } canActivate() { let item = localStorage.getItem('access_token'); if (item == null) return false; return true; } }
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard]}
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard]}
در یک گارد ممکن است کاربر لاگین نکرده باشد و شما نیاز دارید او را به صفحه لاگین هدایت کنید. در این صورت گارد لاگین را به شکل زیر بازنویسی میکنیم:
import { Router } from '@angular/router'; import { CanActivate } from '@angular/router'; import { Injectable } from '@angular/core'; @Injectable() export class LoginGuard implements CanActivate { constructor(public router: Router) { } canActivate() { let item = localStorage.getItem('access_token'); if (item == null) { this.router.navigate(['/app']); return false; } return true; } }
همچنین گارد میتواند اطلاعات مسیر درخواست شده را خوانده و بر اساس آن به شما پاسخ بدهد. به عنوان مثال پارامترهایی را که به سمت مسیر مورد نظر هدایت میشود، بخواند و بر اساس آن، تصمیم گیری کند. به عنوان نمونه کاربر به مسیر ذکرشده دسترسی دارد، ولی با Id که در مسیر ارسال کرده است، دسترسی ندارد:
import { Router } from '@angular/router'; import { CanActivate, ActivatedRouteSnapshot } from '@angular/router'; import { Injectable } from '@angular/core'; @Injectable() export class SecondGuard implements CanActivate { constructor(public router: Router) { } canActivate(route: ActivatedRouteSnapshot) { let id = route.params['id']; if (id == 1) { return false; } return true; } }
متد CanActivate میتواند پارامترهای زیادی را به عنوان ورودی دریافت کند که یکی از آنها ActivatedRouteSnapshot است که اطلاعات خوب و مفیدی را در مورد مسیر ارسال شده از طرف کاربر دارد و با استفاده از آن در اینجا میتوانیم پارامترهای ارسالی را دریافت کنیم. در اینجا ذکر کردهایم که اگر پارامتر Id که بر مبنای مسیر زیر است، برابر 1 بود، مقدار برگشتی برابر false خواهد بود و دسترسی به کامپوننت در اینجا ممکن نخواهد بود.
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard] }
Async Streaming در ASP.NET Core
معرفی سرویس MatDialog
توسط سرویس MatDialog میتوان modal dialogs بستهی Angular Material را نمایش داد که به همراه طراحی متریال و پویانمایی مخصوص آن است.
let dialogRef = dialog.open(UserProfileComponent, { height: '400px’, width: '600px’ });
dialogRef.afterClosed().subscribe(result => { console.log(`Dialog result: ${result}`); }); dialogRef.close('value');
در این مثال اگر dialogRef را با متد close و پارامتر value فراخوانی کنیم، سبب بسته شدن این دیالوگ خواهیم شد. این پارامتر در قسمت Dialog result پیام دریافتی پس از بسته شدن دیالوگ نیز قابل دسترسی است.
کامپوننتهایی که توسط سرویس MatDialog نمایش داده میشوند، میتوانند توسط سرویس جنریک MatDialogRef، صفحهی دیالوگ باز شده را ببندند:
@Component({/* ... */}) export class YourDialog { constructor(public dialogRef: MatDialogRef<YourDialog>) { } closeDialog() { this.dialogRef.close('Value….!’); } }
نحوهی طراحی یک دیالوگ نیز به کمک تعدادی کامپوننت و دایرکتیو میسر است:
<h2 mat-dialog-title>Delete all</h2> <mat-dialog-content>Are you sure?</mat-dialog-content> <mat-dialog-actions> <button mat-button mat-dialog-close>No</button> <!-- Can optionally provide a result for the closing dialog. --> <button mat-button [mat-dialog-close]="true">Yes</button> </mat-dialog-actions>
ایجاد دکمهی نمایش دیالوگ افزودن تماسها و کاربران جدید
قبل از هر کاری نیاز است دکمهی افزودن یک کاربر جدید را به صفحه اضافه کنیم. برای اینکار یک منوی ویژه را در سمت راست، بالای صفحه ایجاد میکنیم. بنابراین ابتدا به مستندات toolbar و menu مراجعه میکنیم تا با نحوهی تعریف دکمهها و منوها به toolbar آشنا شویم. سپس فایل قالب toolbar\toolbar.component.html را به صورت زیر تکمیل میکنیم:
<mat-toolbar color="primary"> <button mat-button fxHide fxHide.xs="false" (click)="toggleSidenav.emit()"> <mat-icon>menu</mat-icon> </button> <span>Contact Manager</span> <span fxFlex="1 1 auto"></span> <button mat-button [matMenuTriggerFor]="menu"> <mat-icon>more_vert</mat-icon> </button> <mat-menu #menu="matMenu"> <button mat-menu-item>New Contact</button> </mat-menu> </mat-toolbar>
سپس ابتدا یک mat-button را با آیکن more_vert (آیکن علامت بیشتر عمودی) تعریف کردهایم:
این دکمه توسط ویژگی matMenuTriggerFor به template reference variable ایی به نام menu متصل شدهاست تا با کلیک بر روی آن، این mat-menu را نمایش دهد:
ایجاد دیالوگ افزودن تماسها و کاربران جدید
پس از تعریف دکمه و منویی که سبب نمایش عبارت افزودن یک تماس جدید میشوند، به رخداد کلیک آن متدی را جهت نمایش صفحهی دیالوگ جدید اضافه میکنیم:
<button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
ng g c contact-manager/components/new-contact-dialog --no-spec
import { NewContactDialogComponent } from "./components/new-contact-dialog/new-contact-dialog.component"; @NgModule({ declarations: [ NewContactDialogComponent], entryComponents: [ NewContactDialogComponent ] }) export class ContactManagerModule { }
import { Component, EventEmitter, OnInit, Output } from "@angular/core"; import { MatDialog } from "@angular/material"; import { NewContactDialogComponent } from "../new-contact-dialog/new-contact-dialog.component"; @Component({ selector: "app-toolbar", templateUrl: "./toolbar.component.html", styleUrls: ["./toolbar.component.css"] }) export class ToolbarComponent implements OnInit { @Output() toggleSidenav = new EventEmitter<void>(); constructor(private dialog: MatDialog) { } ngOnInit() { } openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe(result => { console.log("The dialog was closed", result); }); } }
تکمیل قالب کامپوننت تماس جدید
در ادامه میخواهیم فرم افزودن یک تماس جدید را به همراه فیلدهای ورودی آن، به قالب new-contact-dialog.component.html اضافه کنیم:
<h2 mat-dialog-title>Add new contact</h2> <mat-dialog-content> <div fxLayout="column"> </div> </mat-dialog-content> <mat-dialog-actions> <button mat-button color="primary" (click)="save()"> <mat-icon>save</mat-icon> Save </button> <button mat-button color="primary" (click)="dismiss()"> <mat-icon>cancel</mat-icon> Cancel </button> </mat-dialog-actions>
import { Component, OnInit } from "@angular/core"; import { MatDialogRef } from "@angular/material"; @Component() export class NewContactDialogComponent implements OnInit { constructor( private dialogRef: MatDialogRef<NewContactDialogComponent> ) { } ngOnInit() { } save() { } dismiss() { this.dialogRef.close(null); } }
تا اینجا اگر برنامه را اجرا کنیم، به چنین شکلی خواهیم رسید:
تکمیل فیلدهای ورود اطلاعات فرم ثبت یک تماس جدید
تا اینجا ساختار فرم دیالوگ ثبت اطلاعات جدید را تکمیل کردیم. این فرم، به شیء user متصل خواهد شد. همچنین لیستی از avatars را هم جهت انتخاب، نمایش میدهد. به همین جهت این دو خاصیت عمومی را به کدهای کامپوننت آن اضافه میکنیم:
import { User } from "../../models/user"; @Component() export class NewContactDialogComponent implements OnInit { avatars = ["user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8"]; user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };
الف) فیلد نمایش و انتخاب avatar کاربر
<mat-form-field> <mat-select placeholder="Avatar" [(ngModel)]="user.avatar"> <mat-select-trigger> <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.avatar }} </mat-select-trigger> <mat-option *ngFor="let avatar of avatars" [value]="avatar"> <mat-icon svgIcon="{{avatar}}"></mat-icon> {{ avatar }} </mat-option> </mat-select> </mat-form-field>
در اینجا از کامپوننت mat-select برای انتخاب avatar کاربر استفاده شدهاست که نتیجهی نهایی انتخاب آن به خاصیت user.avatar متصل شدهاست.
گزینههای این لیست (mat-option) بر اساس آرایهی avatars که در کامپوننت تعریف کردیم، تامین میشوند که در اینجا از mat-icon برای نمایش آیکن مرتبط نیز استفاده شدهاست. در این مورد در قسمت قبل چهارم، بخش «بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material» بیشتر توضیح داده شدهاست.
کار mat-select-trigger، سفارشی سازی برچسب نمایشی این کنترل است.
ب) فیلد دریافت نام کاربر به همراه اعتبارسنجی آن
<mat-form-field> <input matInput placeholder="Name" #name="ngModel" [(ngModel)]="user.name" required> <mat-error *ngIf="name.invalid && name.touched">You must enter a name</mat-error> </mat-form-field>
در اینجا فیلد نام کاربر، به user.name متصل و همچنین توسط ویژگی required، پر کردن آن الزامی اعلام شدهاست. به همین جهت در تصویر فوق یک ستاره را نیز کنار آن مشاهده میکند که به صورت خودکار توسط Angular Material نمایش داده شدهاست.
از کامپوننت mat-error برای نمایش خطاهای اعتبارسنجی یک فیلد استفاده میشود که نمونهای از آنرا در اینجا با بررسی خواص invalid و touched فیلد نام که بر اساس ویژگی required فعال میشوند، مشاهده میکنید. بدیهی است در اینجا به هر تعدادی که نیاز است میتوان mat-error را قرار داد.
ج) فیلد دریافت تاریخ تولد کاربر توسط یک date picker
<mat-form-field> <input matInput [matDatepicker]="picker" placeholder="Born" [(ngModel)]="user.birthDate"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker> </mat-form-field>
در اینجا از کامپوننت mat-datepicker برای انتخاب تاریخ تولید یک شخص استفاده شدهاست و نتیجهی آن به خاصیت user.birthDate متصل خواهد شد.
برای افزودن آن ابتدا یک mat-datepicker را به mat-form-field اضافه میکنیم. سپس یک template reference variable را به آن نسبت خواهیم داد. از آن هم در فیلد ورودی با انتساب آن به ویژگی matDatepicker و هم در کامپوننت mat-datepicker-toggle که سبب نمایش آیکن انتخاب تقویم میشود، در ویژگی for آن استفاده خواهیم کرد.
د) فیلد چند سطری دریافت توضیحات و شرححال کاربر
<mat-form-field> <textarea matInput placeholder="Bio" [(ngModel)]="user.bio"></textarea> </mat-form-field>
در اینجا برای دریافت توضیحات چندسطری، از یک text area استفاده شدهاست که به خاصیت user.bio متصل است.
بنابراین همانطور که ملاحظه میکنید، روش طراحی فرمهای Angular Material ویژگیهای خاص خودش را دارد:
- دایرکتیو matInput را میتوان به المانهای استاندارد input و textarea اضافه کرد تا داخل mat-form-field نمایش داده شوند. این mat-form-field است که کار اعمال CSS ویژهی طراحی متریال را انجام میدهد و امکان نمایش پیامهای خطای اعتبارسنجی و پویانمایی ورود اطلاعات را سبب میشود.
- قسمت mat-dialog-content را توسط fxLayout به حالت ستونی تنظیم کردیم:
<mat-dialog-content> <div fxLayout="column"> </div> </mat-dialog-content>
برای مثال اگر خواستید المانهای فرم با فاصلهی بیشتری از هم قرار بگیرند، میتوان از fxLayoutGap استفاده کرد که در مورد آن در قسمت دوم «معرفی Angular Flex layout» بیشتر توضیح داده شد.
تکمیل سرویس کاربران جهت ذخیرهی اطلاعات تماس کاربر جدید
در ادامه میخواهیم با کلیک کاربر بر روی دکمهی Save، ابتدا این اطلاعات به سمت سرور ارسال و سپس در سمت سرور ذخیره شوند. پس از آن، Id این کاربر جدید به سمت کلاینت بازگشت داده شود، دیالوگ جاری بسته و در آخر این شیء جدید به لیست تماسهای نمایش دادهی شدهی در sidenav اضافه گردد.
الف) تکمیل سرویس Web API سمت سرور
در ابتدا متد Post را به Web API برنامه جهت ذخیره سازی اطلاعات User ارسالی از سمت کلاینت اضافه میکنیم. کدهای کامل آنرا از فایل پیوستی انتهای بحث میتوانید دریافت کنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers { [Route("api/[controller]")] public class UsersController : Controller { private readonly IUsersService _usersService; public UsersController(IUsersService usersService) { _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } [HttpPost] public async Task<IActionResult> Post([FromBody] User user) { if (!ModelState.IsValid) { return BadRequest(ModelState); } await _usersService.AddUserAsync(user); return Created("", user); } } }
ب) تکمیل سرویس کاربران سمت کلاینت
سپس به فایل user.service.ts مراجعه کرده و دو تغییر زیر را به آن اضافه میکنیم:
@Injectable({ providedIn: "root" }) export class UserService { private usersSource = new BehaviorSubject<User>(null); usersSourceChanges$ = this.usersSource.asObservable(); constructor(private http: HttpClient) { } addUser(user: User): Observable<User> { const headers = new HttpHeaders({ "Content-Type": "application/json" }); return this.http .post<User>("/api/users", user, { headers: headers }).pipe( map(response => { const addedUser = response || {} as User; this.notifyUsersSourceHasChanged(addedUser); return addedUser; }), catchError((error: HttpErrorResponse) => throwError(error)) ); } notifyUsersSourceHasChanged(user: User) { this.usersSource.next(user); } }
return Created("", user);
بنابراین نیاز است از طریق این سرویس به کامپوننت sidenav، در مورد تغییرات لیست کاربران اطلاعات رسانی کنیم که روش کار آنرا پیشتر در مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» نیز مرور کردهایم. برای این منظور یک BehaviorSubject از نوع User را تعریف کردهایم که اشتراک به آن از طریق خاصیت عمومی usersSourceChanges میسر است. هر زمانیکه متد next آن فراخوانی شود، تمام مشترکین به آن، از افزوده شدن کاربر جدید، به همراه اطلاعات کامل آن مطلع خواهند شد.
ج) تکمیل متد save کامپوننت new-contact-dialog
پس از تکمیل سرویس کاربران جهت افزودن متد addUser به آن، اکنون میتوانیم از آن در کامپوننت دیالوگ افزودن اطلاعات تماس جدید استفاده کنیم:
import { UserService } from "../../services/user.service"; @Component() export class NewContactDialogComponent { user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null }; constructor( private dialogRef: MatDialogRef<NewContactDialogComponent>, private userService: UserService ) { } save() { this.userService.addUser(this.user).subscribe(data => { console.log("Saved user", data); this.dialogRef.close(data); }); } }
د) تکمیل کامپوننت sidenav جهت واکنش نشان دادن به افزوده شدن اطلاعات تماس جدید
اکنون که سرویس کاربران به صفحه دیالوگ افزودن اطلاعات یک تماس جدید متصل شدهاست، نیاز است بتوانیم اطلاعات کاربر جدید را به لیست تماسهای sidenav اضافه کنیم. به همین جهت به sidenav.component مراجعه کرده و مشترک usersSourceChanges سرویس کاربران خواهیم شد:
import { UserService } from "../../services/user.service"; @Component() export class SidenavComponent implements OnInit, OnDestroy { users: User[] = []; subscription: Subscription | null = null; constructor( private userService: UserService) { } ngOnInit() { this.subscription = this.userService.usersSourceChanges$.subscribe(user => { if (user) { this.users.push(user); } }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } }
استفاده از کامپوننت Snackbar جهت نمایش موفقیت آمیز بودن ثبت اطلاعات
متد save کامپوننت دیالوگ یک تماس جدید را به صورت زیر تکمیل کردیم:
save() { this.userService.addUser(this.user).subscribe(data => { console.log("Saved user", data); this.dialogRef.close(data); });
openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe(result => { console.log("The dialog was closed", result); }); }
کدهای کامل این تغییرات را در ذیل مشاهده میکنید:
@Component() export class ToolbarComponent { @Output() toggleSidenav = new EventEmitter<void>(); constructor(private dialog: MatDialog, private snackBar: MatSnackBar, private router: Router) { } openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe((result: User) => { console.log("The dialog was closed", result); if (result) { this.openSnackBar(`${result.name} contact has been added.`, "Navigate").onAction().subscribe(() => { this.router.navigate(["/contactmanager", result.id]); }); } }); } openSnackBar(message: string, action: string): MatSnackBarRef<SimpleSnackBar> { return this.snackBar.open(message, action, { duration: 5000, }); } }
برای گشودن snackbar که نمونهای از آنرا در تصویر فوق ملاحظه میکنید، ابتدا نیاز است سرویس MatSnackBar را به سازندهی کلاس تزریق کرد. سپس توسط آن میتوان یک کامپوننت مستقل را همانند دیالوگها نمایش داد و یا میتوان یک متن را به همراه یک Action منتسب به آن، به کاربر نمایش داد؛ مانند متد openSnackBar که در کامپوننت فوق از آن استفاده میشود. این متد در رخداد پس از بسته شدن dialog، نمایش داده شدهاست.
پارامتر اول آن پیامی است که توسط snackbar نمایش داده میشود و پارامتر دوم آن، برچسب دکمه مانندی است کنار این پیام، که سبب انجام عملی خواهد شد و در اینجا به آن Action گفته میشود. برای مدیریت آن باید متد onAction را فراخوانی کرد و مشترک آن شد. در این حالت اگر کاربر بر روی این دکمهی action کلیک کند، سبب هدایت خودکار او به صفحهی نمایش جزئیات اطلاعات تماس کاربر خواهیم شد. به همین جهت سرویس Router نیز به سازندهی کلاس تزریق شدهاست تا بتوان از متد navigate آن استفاده کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-05.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.