منابع و مآخذ مرتبط با کتابخانهی Angular Material
در اینجا مآخذ اصلی کار با این کتابخانه را ملاحظه میکنید که شامل اصول طراحی متریال و مخازن اصلی توسعهی آن میباشند:
Material Design Specification
- https://material.io/design
Angular Material
- https://material.angular.io
- https://github.com/angular/material2
مفاهیم پایهی طراحی متریال
چرا «زیبایی» رابط کاربری مهم است؟
در ابتدای معرفی کتابخانهی Angular Material عنوان شد که این مجموعه به همراه تعدادی کامپوننت «زیبا» است. بنابراین این سؤال مطرح میشود که چرا و یا تا چه اندازه «زیبایی» رابط کاربری اهمیت دارد؟ مهمترین دلیل آن بهبود تجربهی کاربری است. بر اساس تحقیقاتی که بر روی کاربران بسیاری صورت گرفتهاست، مشخص شدهاست کاربران، با رابطهای کاربری زیبا نتایج بهتری را از لحاظ کاهش زمان اتمام کار و تعداد خطاهای مرتبط دریافت میکنند.
اما ... طراحی برنامههای زیبا مشکل است. به همین جهت استفاده از کتابخانههای غنی مانند طراحی متریال که این امر را سهولت میبخشند، ضروری است. طراحی متریال یک زبان کامل طراحی برنامههای زیبا است. توسط گوگل طراحی شدهاست و دو هدف اصلی را دنبال میکند:
- وفاداری به اصول کلاسیک طراحی رابط کاربری
- ارائهی تجربهی کاربری یکدست و هماهنگ، در بین وسایل و اندازههای صفحات نمایشی مختلف
اصول پایهی طراحی متریال نیز شامل موارد زیر است:
- «متریال» یک متافور است و بر اساس مطالعهی نحوهی کار با کاغذ، مرکب و ارتباط بین اشیاء در دنیای واقعی پدید آمدهاست.
- اشیاء در دنیای واقعی دارای ارتباطهای ابعادی و حجمی هستند. برای مثال دو برگهی کاغذ یک فضا را اشغال نمیکنند. طراحی متریال برای نمایش این ارتباط سه بعدی بین اشیاء، از نور و سایه استفاده میکند.
- در دنیای واقعی، اشیاء از درون یکدیگر رد نمیشوند. این مورد در طراحی متریال نیز صادق است.
- طراحی متریال به همراه جعبهی رنگ مخصوص و بکارگیری فضاهای خالی و عناوین درشت بسیار مشخص، واضح و عمدی است.
- طراحی متریال به همراه حرکت و پویانمایی، جهت ارائهی مفاهیم مختلف به کاربر، جهت درک بهتر او از برنامه است.
برپایی پیشنیازهای ابتدایی کار با Angular Material
پیش از ادامهی بحث فرض بر این است که آخرین نگارش Angular CLI را نصب کردهاید و اگر پیشتر آنرا نصب کردهاید، یکبار دستور ذیل را اجرا کنید تا تمام وابستگیهای سراسری نصب شدهی در سیستم به صورت خودکار به روز رسانی شوند:
npm update -g
ng new MaterialAngularClient --routing
cd MaterialAngularClient ng serve -o
افزودن کتابخانهی Angular Material به برنامه
در طول این سری از سایت https://material.angular.io زیاد استفاده خواهیم کرد. همواره به روزترین روش افزودن کتابخانهی Angular Material به یک برنامهی موجود را در آدرس https://material.angular.io/guide/getting-started میتوانید مشاهده کنید که خلاصهی آن به صورت زیر است:
البته در Angular 6 روش تفصیلی نصب فوق که شامل 6 مرحلهاست، به صورت زیر هم خلاصه شدهاست:
ng add @angular/material
npm install --save @angular/material @angular/cdk npm install --save @angular/animations npm install --save hammerjs
- همانطور که عنوان شد، طراحی متریال مبتنی بر حرکت و پویانمایی است. به همین جهت تعدادی از کامپوننتهای آن نیاز به بستهی angular/animations را دارند که توسط دستور دوم نصب میشود.
- دستور سوم نیز کامپوننتهای slide و slider را پشتیبانی میکند (Gesture Support). البته پس نصب این وابستگی، نیاز است به فایل src/main.ts مراجعه کرده و یک سطر زیر را نیز افزود:
import "hammerjs";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule ] }) export class AppModule { }
مدیریت بهتر import کامپوننتهای Angular Material
در ادامه به ازای هر کامپوننت Angular Material باید ماژول آنرا به لیست imports افزود که پس از مدتی به یک فایل app.module.ts بسیار شلوغ خواهیم رسید. برای مدیریت بهتر این فایل، از روش مطرح شدهی در مطلب «سازماندهی برنامههای Angular» استفاده خواهیم کرد.
به همین جهت دو پوشهی core و shared را درون پوشهی src/app ایجاد میکنیم:
محتویات فایل src\app\core\core.module.ts به صورت زیر است:
import { CommonModule } from "@angular/common"; import { NgModule, Optional, SkipSelf } from "@angular/core"; import { RouterModule } from "@angular/router"; @NgModule({ imports: [CommonModule, RouterModule], exports: [ // components that are used in app.component.ts will be listed here. ], declarations: [ // components that are used in app.component.ts will be listed here. ], providers: [ /* ``No`` global singleton services of the whole app should be listed here anymore! Since they'll be already provided in AppModule using the `tree-shakable providers` of Angular 6.x+ (providedIn: 'root'). This new feature allows cleaning up the providers section from the CoreModule. But if you want to provide something with an InjectionToken other that its class, you still have to use this section. */ ] }) export class CoreModule { constructor(@Optional() @SkipSelf() core: CoreModule) { if (core) { throw new Error("CoreModule should be imported ONLY in AppModule."); } } }
محتویات فایل src\app\shared\shared.module.ts نیز به صورت زیر است:
import { CommonModule } from "@angular/common"; import { HttpClientModule } from "@angular/common/http"; import { ModuleWithProviders, NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule ], entryComponents: [ // All components about to be loaded "dynamically" need to be declared in the entryComponents section. ], declarations: [ // common and shared components/directives/pipes between more than one module and components will be listed here. ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, ] /* No providers here! Since they’ll be already provided in AppModule. */ }) export class SharedModule { static forRoot(): ModuleWithProviders { // Forcing the whole app to use the returned providers from the AppModule only. return { ngModule: SharedModule, providers: [ /* All of your services here. It will hold the services needed by `itself`. */] }; } }
import { CoreModule } from "./core/core.module"; import { SharedModule } from "./shared/shared.module"; @NgModule({ imports: [ BrowserModule, BrowserAnimationsModule, CoreModule, SharedModule.forRoot(), AppRoutingModule ] }) export class AppModule { }
import { CdkTableModule } from "@angular/cdk/table"; import { NgModule } from "@angular/core"; import { MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatStepperModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, } from "@angular/material"; @NgModule({ imports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ], exports: [ MatAutocompleteModule, MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule, MatChipsModule, MatDatepickerModule, MatDialogModule, MatExpansionModule, MatGridListModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatNativeDateModule, MatPaginatorModule, MatProgressBarModule, MatProgressSpinnerModule, MatRadioModule, MatRippleModule, MatSelectModule, MatSidenavModule, MatSliderModule, MatSlideToggleModule, MatSnackBarModule, MatStepperModule, MatSortModule, MatTableModule, MatTabsModule, MatToolbarModule, MatTooltipModule, CdkTableModule ] }) export class MaterialModule { }
سپس MaterialModule را نیز به قسمتهای imports و exports فایل src\app\shared\shared.module.ts اضافه خواهیم کرد:
import { MaterialModule } from "./material.module"; @NgModule({ imports: [ CommonModule, FormsModule, HttpClientModule, MaterialModule ], exports: [ // common and shared components/directives/pipes between more than one module and components will be listed here. CommonModule, FormsModule, HttpClientModule, MaterialModule ] }) export class SharedModule { }
تا اینجا جهت اطمینان از اجرای برنامه، دستور ng serve -o را از ابتدا اجرا کنید.
افزودن چند کامپوننت مقدماتی متریال به برنامه
بهترین روش کار با این مجموعه، بررسی مستندات آن در سایت https://material.angular.io/components است. برای مثال برای افزودن دکمه، به مستندات آن مراجعه کرده و بر روی دکمهی view source کلیک میکنیم:
سپس کدهای قسمت HTML آنرا به برنامه و فایل app.component.html اضافه خواهیم کرد:
<button mat-button>Click me!</button>
<mat-checkbox>Check me!</mat-checkbox>
البته شکل ظاهری آنها تا اینجا آنچنان مطلوب نیست. برای رفع این مشکل، نیاز است یک قالب را به این کنترلها و کامپوننتها اعمال کرد. به همین جهت فایل styles.css واقع در ریشهی برنامه را گشوده و قالب پیشفرض متریال را به آن اضافه میکنیم:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
پس از اعمال قالب، اکنون است که شکل ظاهری کنترلهای آن بسیار بهتر شدهاند و همچنین کار با آنها به همراه پویانمایی نیز شدهاست:
افزودن آیکنهای متریال به برنامه
مرحلهی آخر این تنظیمات، افزودن آیکنهای متریال به برنامهاست. برای این منظور فایل src\index.html را گشوده و یک سطر ذیل را به head اضافه کنید:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<button mat-button> <mat-icon>face</mat-icon> Click me! </button> <mat-checkbox>Check me!</mat-checkbox>
لیست کامل این آیکنها را به همراه توضیحات تکمیلی آنها، در آدرس ذیل میتوانید ملاحظه کنید:
http://google.github.io/material-design-icons
البته چون ما نمیخواهیم این آیکنها را از وب بارگذاری کنیم، برای نصب محلی آنها ابتدا دستور زیر را در ریشهی پروژه صادر کنید:
npm install material-design-icons --save
همانطور که مشاهده میکنید، برای استفادهی از این فایلهای آیکن فونت محلی، تنها کافی است فایل material-icons.css را به برنامه معرفی کنیم. برای این منظور فایل angular.json را گشوده و قسمت styles آنرا به صورت زیر تکمیل میکنیم:
"styles": [ "node_modules/material-design-icons/iconfont/material-icons.css", "src/styles.css" ],
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-01.zip
برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید.
return-type method-name<type-parameters>(parameters)
public T1 PrintValue<T1, T2>(T1 param1, T2 param2) { Console.WriteLine("values are: parameter 1 = " + param1 + " and parameter 2 = " + param2); return param1; }
public void MyMethod< T >() where T : struct { ... }
- struct: نوع آرگومان ارسالی باید value-type باشد؛ بجز مقادیر غیر NULL.
class C<T> where T : struct {} // value type
- class: نوع آرگومان ارسالی باید reference-type (کلاس، اینترفیس، عامل، آرایه) باشد.
class D<T> where T : class {} // reference type
- ()new: آرگومان ارسالی باید یک سازنده عمومی بدون پارامتر باشد. وقتی این محدوده کننده را با سایر محدود کنندهها به صورت همزمان استفاده میکنید، این محدوده کننده باید در آخر ذکر شود.
class H<T> where T : new() {} // no parameter constructor
public void MyMethod< T >() where T : IComparable, MyBaseClass, new () { ... }
- <base class name>: نوع آرگومان ارسالی باید از کلاس ذکر شده یا کلاس مشتق شده آن باشد.
class B {} class E<T> where T : B {} // be/derive from base class
- <interface name>: نوع آرگومان ارسالی باید اینترفیس ذکر شده یا پیاده ساز آن اینترفیس باشد.
interface I {} class G<T> where T : I {} // be/implement interface
- U: نوع آرگومان ارسالی باید از نوع یا مشتق شده U باشد.
class F<T, U> where T : U {} // be/derive from U
interface I {} class J<T> where T : class, I
interface I {} class J<T, U> where T : class, I where U : I, new() {}
//this method returns if both the parameters are equal public static bool Equals< T > (T t1, Tt2) { return (t1 == t2); }
- Runtime casting
- استفاده از محدود کنندهها
ObsoleteAttribute
ObsoleteAttribute بر روی تمامی عناصر یک برنامه بجز assemblies, modules، پارامترها و مقادیر بازگشتی قابل استفاده است. علامتگذاری یک عنصر به عنوان منسوخ شده، به کاربر استفاده کننده اطلاع میدهد که این عنصر در نسخههای آینده حذف خواهد شد.
با استفاده از پروپرتی Message آن پیامی را به کاربر استفاده کننده نشان خواهد داد و توصیه میشود در این پیام یک راه حل نیز ارائه شود.
پروپرتی IsError در صورتی که مقدار آن به true تعیین شده باشد و کامپایلر در صورتی که عنصری که این خصوصیت بر روی آن تعریف شده است، استفاده شده باشد، در پنجره Error List، پیام مربوط به Obsolete را نشان میدهد. برای مثال پس از استفاده از کلاس زیر، OrderDetailTotal به صورت warning و CalculateOrderDetailTotal به صورت Error در پنجره Error List نشان داده میشود.
public static class ObsoleteExample { // Mark OrderDetailTotal As Obsolete. [ObsoleteAttribute("This property (OrderDetailTotal) is obsolete. Use InvoiceTotal instead.", false)] public static decimal OrderDetailTotal { get { return 12m; } } public static decimal InvoiceTotal { get { return 25m; } } // Mark CalculateOrderDetailTotal As Obsolete. [ObsoleteAttribute("This method is obsolete. Call CalculateInvoiceTotal instead.", true)] public static decimal CalculateOrderDetailTotal() { return 0m; } public static decimal CalculateInvoiceTotal() { return 1m; } }
DefaultValueAttribute
DefaultValueAttribute جهت تعیین مقدار پیش فرض یک پروپرتی استفاده میشود. شما میتوانید یک DefaultValueAttribute را با هر مقداری ایجاد کنید. ایجاد مقدار پیش فرض برای یک پروپرتی باعث نمیشود که مقداردهی اولیهای به آن انجام گیرد؛ برای این کار نیاز به کدنویسی میباشد.
مثال زیر نحوه استفاده و مقداردهی اولیه پروپرتیها را نشان میدهد.
public class DefaultValueAttributeTest { public DefaultValueAttributeTest() { // Use the DefaultValue propety of each property to actually set it, via reflection. foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this)) { var attr = prop.Attributes[typeof(DefaultValueAttribute)] as DefaultValueAttribute; if (attr != null) prop.SetValue(this, attr.Value); } } [DefaultValue(28)] public int Age { get; set; } [DefaultValue("Vahid")] public string FirstName { get; set; } [DefaultValue("Mohammad Taheri")] public string LastName { get; set; } public override string ToString() { return $"{this.FirstName} {this.LastName} is {this.Age}."; } }
DebuggerBrowsableAttribute
در صورت استفاده از DebuggerBrowsableAttribute ، شما میتوانید نحوه نمایش یک عضو را در پنجره متغیرها، در زمان دیباگ، تعیین کنید.public class DebuggerBrowsableTest { [DebuggerBrowsable(DebuggerBrowsableState.Never)] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FirstName { get; set; } [DebuggerBrowsable(DebuggerBrowsableState.Collapsed)] // مقدار پیش فرض public string LastName { get; set; } [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // عدم نمایش در زمان دیباگ در پنجره متغیرها public string FullName => FirstName + " " + LastName; [DebuggerBrowsable( DebuggerBrowsableState.RootHidden )] // تنها در زمانی که یک آرایه یا لیست باشد نمایش داده میشود public string[] FullNameArray => new string[] { FirstName + " " + LastName }; }
اگر از کد مثال بالا استفاده کنید و با استفاده از کلید F11 به صورت خط به خط دستورات را اجرا کنید، مشاهده خواهید کرد متغیر FirstName و FullName در پنجره Autos نشان داده نخواهد شد.
Operator ??
عملگر ?? در صورتی که عملوند سمت چپ آن تهی (null) نباشد، مقدار آن را باز میگرداند و در غیر اینصورت مقدار عملوند سمت راست خود را باز میگرداند. نوعهای تهی پذیر (nullable) میتوانند دارای مقدار و یا به صورت تعریف نشده باشند. عملگر ?? وقتی که یک نوع تهی پذیر به یک نوع غیرتهی پذیر انتساب داده میشود، مقدار پیش فرض آن را باز میگرداند.
int? x = null; int y = x ?? -1; Console.WriteLine("y now equals -1 because x was null => {0}", y); int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int); Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i); string s = DefaultValueOperatorTest.GetStringValue(); Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
I think that JavaScript's loose typing is one of its best features and that type checking is way overrated. TypeScript adds sweetness, but at a price. It is not a price I am willing to pay.
اما به مرور زمان این زبان توفیق بیشتری پیدا کرد تا اینکه امروز پروژههای بسیار جالبی با این زبان در حال توسعه هستند.
چرا باید در مورد Typescript بدانیم؟
زبان Typescript نقاط قوت بسیاری دارد، از جملهی آنها میتوان به موارد زیر اشاره کرد:
- این زبان یکی از مشکلات اصلی JavaScript را که نبودن Type Safety میباشد حل کردهاست. اگر چه زبانی که type safe نباشد بسیاری اوقات مزیت است! زبان typescript در حقیقت یک زبان gradual typing است.
- از آنجایی که typescript یک super set از زبان JavaScript است، برنامهنویس در لحظه از مزایای زبان JavaScript هم بهرهمند است. مهمتر از آن این است که در زبان typescript به اقیانوس کتابخانههای JavaScript دسترسی دارید. این امکان در بسیاری زبانهای دیگر جایگزین JavaScript وجود ندارد. حتی بهتر از آن، میتواند با این کتابخانهها بهصورت type safe برنامه بنویسید. تصور کنید که وقتی با $ در JQuery کار میکنید بتوانید از امکان intellisense استفاده کنید.
- بازهم از آنجا که typescript یک super set از JavaScript است، typescript قرار نیست به اسمبلی کامپایل شود؛ بلکه به زبان شناخته شدهای به نام JavaScript تبدیل میشود. بنابراین حتی میتوان از آن JavaScript نیز یاد گرفت.
- کار با زبان typescript برای کسانی که با java یا سی شارپ آشنا هستند، راحت است. امکاناتی مانند genericها نیز در typescript وجود دارد.
- نقشهی راه typescript با EcmaScript هماهنگ است. بنابراین از یادگرفتن این زبان ضرر نمیکنید چون قابلیتهای این زبان را به احتمال زیاد در نسخهی بعدی EcmaScript خواهید دید.
- این زبان توسط شرکت مایکروسافت پشتیبانی میشود، اوپن سورس است و تجربهی Anders Hejlsberg در زمینهی طراحی زبانهای برنامهنویسی پشتیبان آن!
- پروژههای جالبی که در ادامه به معرفی آنها میپردازیم، با این زبان در حال توسعه هستند.
در این مطلب تعدادی از این پروژهها را که برای خودم جذاب هستند، به شما معرفی میکنم.