مطالب
Angular Material 6x - قسمت پنجم - کار با Data Tables
در این قسمت قصد داریم اطلاعات یادداشت‌های کاربران را توسط کامپوننت mat-table نمایش دهیم که به همراه قابلیت‌هایی مانند صفحه بندی، مرتب سازی و فیلتر کردن داده‌ها است.


کامپوننت mat-table

کار کامپوننت mat-table نمایش اطلاعات در ردیف‌ها و ستون‌ها است. به همراه آن mat-paginator برای نمایش UI صفحه بندی اطلاعات، دایرکتیو matSort و mat-sort-header برای افزودن رابط کاربری مرتب سازی اطلاعات و امکان تغییر منبع داده آن برای فیلتر کردن داده‌ها، نیز وجود دارند.


افزودن کامپوننت جدید notes برای نمایش یادداشت‌های کاربران

برای نمایش لیست یادداشت‌های هر شخص، کامپوننت جدید Notes را به صورت زیر در پوشه‌ی components ایجاد می‌کنیم:
 ng g c contact-manager/components/notes --no-spec
علت اینجا است که نمی‌خواهیم کامپوننت نمایش جزئیات شخص را بیش از اندازه شلوغ کنیم. بنابراین به قالب کامپوننت main-content (فایل main-content.component.html) مراجعه کرده و selector این کامپوننت را در آنجا درج می‌کنیم:
      <mat-tab-group>
        <mat-tab label="Bio">
          <p>
            {{user.bio}}
          </p>
        </mat-tab>
        <mat-tab label="Notes">
          <app-notes [notes]="user.userNotes"></app-notes>
        </mat-tab>
      </mat-tab-group>
همانطور که ملاحظه می‌کنید app-notes در برگه‌ی دوم کامپوننت mat-tab-group درج شده‌است. همچنین قصد داریم لیست userNotes جاری را به خاصیت notes آن نیز ارسال کنیم. به همین جهت به کامپوننت notes مراجعه کرده و این ورودی را ایجاد می‌کنیم:
import { Component, Input, OnInit } from "@angular/core";
import { UserNote } from "../../models/user-note";

@Component({
  selector: "app-notes",
  templateUrl: "./notes.component.html",
  styleUrls: ["./notes.component.css"]
})
export class NotesComponent implements OnInit {

  @Input() notes: UserNote[];
فعلا جهت بررسی صحت عملکرد آن به قالب این کامپوننت (فایل notes.component.html) مراجعه کرده و آن‌را به صورت json نمایش می‌دهیم:
 <p>
  {{notes | json}}
</p>



تکمیل کامپوننت Notes توسط یک data table

در ادامه قصد داریم این اطلاعات خام را توسط یک data table نمایش دهیم. به همین جهت ابتدا به مستندات mat-table مراجعه کرده و همانند قبل، مثالی را پیدا می‌کنیم که به منظور ما نزدیک‌تر باشد. سپس کدهای آن‌را به برنامه اضافه کرده و سفارشی سازی می‌کنیم. در ابتدا مثال basic آن‌را دقیقا به همان نحوی که هست کپی کرده و سپس آن‌را تغییر می‌دهیم:
محتوای فایل notes.component.ts
import { Component, Input, OnInit } from "@angular/core";
import { MatTableDataSource } from "@angular/material";

import { UserNote } from "../../models/user-note";

@Component({
  selector: "app-notes",
  templateUrl: "./notes.component.html",
  styleUrls: ["./notes.component.css"]
})
export class NotesComponent implements OnInit {

  @Input() notes: UserNote[];

  displayedColumns = ["position", "title", "date"];
  dataSource: MatTableDataSource<UserNote>;

  constructor() { }

  ngOnInit() {
    this.dataSource = new MatTableDataSource<UserNote>(this.notes);
  }

}
در اینجا برای نمایش یک mat-table، نیاز به یک منبع داده وجود دارد که روش تعریف آن‌را توسط MatTableDataSource از نوع UserNote مشاهده می‌کنید.
سپس این منبع داده در قسمت ngOnInit بر اساس ورودی آرایه‌ی notes که از کامپوننت main-content مقدار دهی می‌شود، تامین خواهد شد.
displayedColumns نیز لیست ستون‌ها را مشخص می‌کند.

محتوای فایل notes.component.html
<div class="example-container mat-elevation-z8" fxLayout="column">
  <mat-table #table [dataSource]="dataSource">
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.id}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="title">
      <mat-header-cell *matHeaderCellDef> Title </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.title}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="date">
      <mat-header-cell *matHeaderCellDef> Date </mat-header-cell>
      <mat-cell *matCellDef="let note"> {{note.date | date:'yyyy-MM-dd'}} </mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
  </mat-table>
</div>
در اینجا ترتیب ردیف‌ها بر اساس mat-row انتهای جدول مشخص می‌شود. بنابراین مهم نیست که ng-container matColumnDef‌ها چه ترتیبی دارند.
سپس به ازای هر ستون، یک ng-container اضافه شده‌است. matColumnDef معادل نام‌های displayedColumns خواهد بود. matCellDef نیز بر اساس متغیر حلقه‌ای که بر روی منبع داده تشکیل می‌شود، تعریف خواهد شد. این تعریف امکان دسترسی به مقدار آن‌را در ادامه میسر می‌کند.
در این حالت اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:


افزودن صفحه بندی به mat-table یادداشت‌های یک کاربر

اگر مجددا به مستندات mat-table مراجعه کنیم، مثالی در مورد mat-paginator نیز دارد که جهت نمایش رابط کاربری صفحه بندی مورد استفاده قرار می‌گیرد. بنابراین از مثال آن جهت تکمیل این قسمت ایده می‌گیریم:
  </mat-table>

  <mat-paginator #paginator [pageSize]="2" [pageSizeOptions]="[2, 4, 6]">
  </mat-paginator>
</div>
پس از بسته شدن تگ mat-table، کامپوننت mat-paginator به صفحه اضافه می‌شود که pageSize آن تعداد ردیف‌های در هر صفحه را مشخص می‌کند و pageSizeOptions سبب نمایش یک دراپ داون برای انتخاب تعداد ردیف‌های هر صفحه توسط کاربر خواهد شد.
در ادامه به کدهای کامپوننت مراجعه کرده و توسط ViewChild به template reference variable ایی به نام paginator دسترسی پیدا می‌کنیم:
export class NotesComponent implements OnInit, AfterViewInit {

  dataSource: MatTableDataSource<UserNote>;
  
  @ViewChild(MatPaginator) paginator: MatPaginator;

  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
  }

}
سپس مطابق مستندات آن، این کامپوننت باید به خاصیت paginator منبع داده‌ی data table در رخ‌داد ngAfterViewInit، متصل شود.
اکنون اگر برنامه را اجرا کنیم، صفحه بندی فعال شده‌است:



افزودن جستجو و فیلتر کردن اطلاعات به mat-table یادداشت‌های یک کاربر

مستندات mat-table به همراه مثال filtering نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
ابتدا فیلد ورود اطلاعات جستجو، پیش از Mat-table به قالب کامپوننت اضافه می‌شود:
<div class="example-container mat-elevation-z8" fxLayout="column">
  <div class="example-header">
    <mat-form-field>
      <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
    </mat-form-field>
  </div>
سپس متد applyFilter که به ازای هر keyup فعال می‌شود، در کدهای کامپوننت به نحو زیر تکمیل خواهد شد:
  applyFilter(filterValue: string) {
    this.dataSource.filter = filterValue.trim().toLowerCase(); // MatTableDataSource defaults to lowercase matches
  }
همین اندازه تنظیم سبب فعالسازی جستجو بر روی جدول می‌شود:



افزودن مرتب سازی اطلاعات به mat-table یادداشت‌های یک کاربر

مستندات mat-table به همراه مثال sorting نیز هست که از آن جهت تکمیل این قسمت به نحو ذیل ایده خواهیم گرفت:
برای فعالسازی مرتب سازی اطلاعات، در قالب کامپوننت، به mat-table، دایرکتیو matSort و به هر ستونی که نیاز است مرتب سازی شود، دایرکتیو mat-sort-header را به mat-header‌ها اضافه می‌کنیم:
  <mat-table #table [dataSource]="dataSource" matSort>
    <ng-container matColumnDef="position">
      <mat-header-cell *matHeaderCellDef mat-sort-header> No. </mat-header-cell>
در کدهای کامپوننت نیز ابتدا توسط ViewChild به matSort دسترسی پیدا می‌کنیم و سپس آن‌را به خاصیت sort منبع داده در رخ‌داد ngAfterViewInit، متصل خواهیم کرد:
export class NotesComponent implements OnInit, AfterViewInit {

  dataSource: MatTableDataSource<UserNote>;
  
  @ViewChild(MatSort) sort: MatSort;

  ngAfterViewInit() {
    this.dataSource.sort = this.sort;
  }
}
نتیجه‌ی این تغییرات را در تصویر زیر با فعالسازی مرتب سازی بر روی ستون Title مشاهده می‌کنید:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-04.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
مطالب
Angular Material 6x - قسمت دوم - معرفی Angular Flex layout
در این سری قصد داریم یک برنامه‌ی ساده‌ی دفترچه تلفن را توسط Angular 6x و کامپوننت‌های متریال آن ایجاد کنیم؛ اما Grid جزئی از بسته‌ی Angular Material نیست. بنابراین برای طرحبندی برنامه و قرار دادن المان‌های مختلف در مکان‌های تعیین شده‌ی صفحه، از Angular FlexBox Module استفاده خواهیم کرد که محصور کننده‌ی CSS 3 FlexBox است.


آشنایی با Flex Layout Box Model

برای طراحی ظاهر یک برنامه‌ی وب نیاز است عناصر آن‌را در مکان‌های مختلفی از صفحه قرار داد که به آن Layout گفته می‌شود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS

امروزه دیگر آنچنان روش‌های 1 و 2 به صورت مستقیم مورد استفاده قرار نمی‌گیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی می‌کنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی می‌شود.


FlexBox CSS، سیلان المان‌های قرار گرفته‌ی در داخل آن‌را سبب می‌شود. در اینجا یک container اصلی وجود دارد که در برگیرنده‌ی المان‌ها است. در تصویر فوق دو محور را مشاهده می‌کنید. محور افقی از چپ به راست ادامه پیدا می‌کند. محور عمودی نحوه‌ی ارتباط عناصر را مشخص می‌کند.
اکنون این سؤال مطرح می‌شود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلول‌ها نگاه کرد. برای مثال عناصر قرار گرفته‌ی درون Container یا به صورت افقی درون آن گسترده شده و قرار می‌گیرند و یا به صورت عمودی.


نحوه‌ی تفکر و کارکرد با FlexBox چگونه است؟

در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که می‌تواند افقی و یا عمودی باشد.
ب) اندازه‌ی المان‌ها که می‌تواند ثابت و یا نسبی باشد.

یک Container، جهت سیلان عناصر درون آن‌را مشخص می‌کند. المان‌های آن، اندازه، فاصله‌ی از یکدیگر و ترتیب قرارگیری را ارائه می‌دهند. یک flex container می‌تواند شامل چندین flex container تو در تو نیز باشد.


نحوه‌ی سیلان عناصر در FlexBox چگونه است؟

برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:


نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:


سپس نحوه‌ی سیلان عناصر درون Containerهای تعریف شده را مشخص می‌کنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازه‌های مشخص دو عنصر قبلی، به سطر دوم منتقل شده‌است.


در ادامه به قسمت میانی می‌رسیم که آن نیز دارای سیلان افقی از چپ به راست است:


در اینجا نیز می‌توان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازه‌ی آن‌ها در یک سطر جا نشده‌اند، به سطر بعدی منتقل خواهند شد:


و تمام این سیلان‌ها و انتقال به سطرهای بعدی بر اساس اندازه‌ی المان‌ها صورت می‌گیرد:


البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیه‌ی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار می‌گیرد، دارای سیلان خاص خودش است و اندازه‌های آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیه‌ی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، این‌ها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.


معرفی کتابخانه‌ی Angular Flex Layout

برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگ‌های مرورگرها و تفاوت‌های پیاده سازی‌های آن‌ها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشده‌است.

جهت رفع این مشکلات و محدودیت‌ها، تیم Angular کتابخانه‌ای را به نام Angular Flex Layout مخصوص نگارش‌های جدید Angular طراحی کرده‌است. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده می‌شود.

مزایای کار با کتابخانه‌ی Angular flex layout
- یک کتابخانه‌ی متکی به خود و مستقل است و برای کار با آن الزامی به استفاده‌ی از Angular Material نیست.
- به همراه هیچ فایل CSS جانبی ارائه نمی‌شود.
- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شده‌است.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق می‌کند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شده‌است.


نصب و تنظیم کتابخانه‌ی Angular Flex layout

برای نصب این کتابخانه، در ریشه‌ی پروژه دستور زیر را صادر کنید:
 npm install @angular/flex-layout --save
سپس ماژول آن‌را باید به shared.module.ts اضافه کرد:
import { FlexLayoutModule } from "@angular/flex-layout";

@NgModule({
  imports: [
    FlexLayoutModule
  ],
  exports: [
    FlexLayoutModule
  ]
})
export class SharedModule {
}


کار با API استاتیک Angular Flex layout

API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شده‌است که به HTML قالب کامپوننت‌ها اضافه می‌شود.
- از data binding پشتیبانی می‌کند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق می‌کند. Inline CSS تزریق شده به ویژگی‌های styles هر المان تزریق می‌شوند و موارد مشابه را در صورت وجود بازنویسی می‌کنند.
- از تشخیص تغییرات پشتیبانی می‌کند.
- به همراه ویژگی‌های fxHide و fxShow است.
- کارآیی مطلوبی دارد.

در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده می‌شود:
- fxLayout جهت‌های flex را مشخص می‌کند.
<div fxLayout="row" 
     fxLayout.xs="column"></div>
- fxLayout می‌تواند دارای مقداری مانند row، column و row-reverse و column-reverse باشد. برای مثال مقدار row-reverse‌، نمایش از راست به چپ را سبب می‌شود.
- fxLayoutWrap مشخص می‌کند که آیا المان‌ها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
<div fxLayoutWrap></div>
- fxLayoutGap فاصله‌ی بین المان‌ها را مشخص می‌کند.
<div fxLayoutGap="10px"></div>
- fxLayoutAlign نحوه‌ی چیدمان المان را تعیین می‌کند.
<div fxLayoutAlign="start stretch"></div>

چند مثال:


و یا حالت راست به چپ آن به صورت زیر است:


و برای تعریف آیتم‌های قرار گرفته‌ی درون containers می‌توان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المان‌ها
<div fxFlex="1 2 calc(15em + 20px)"></div>
در اینجا سه مقداری که ذکر می‌شوند (و یا تنها یک مقدار) چنین معنایی را به همراه دارند:
 fxFlex="grow shrink basis"
و یا
 fxFlex="basis"
- grow به این معنا است که آیتم جاری در صورت وجود فضا (طراحی واکنشگرا و واکنش نشان دادن به اندازه‌ی صفحه)، نسبت به سایر المان‌ها تا چه اندازه‌ای می‌تواند بزرگ شود.
- shrink به این معنا است که اگر به اندازه‌ی کافی فضا وجود نداشت، این المان نسبت به سایر المان‌های دیگر تا چه اندازه‌ای می‌تواند کوچک شود.
- basis به معنای اندازه‌ی پیش‌فرض المان است.

در اینجا اندازه‌ها برحسب پیکسل، درصد و یا calcs, em, cw, vh می‌توانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.

- fxflexorder برای تعیین ترتیب قرارگیری یک المان
<div fxFlexOrder="2"></div>
-  fxflexoffset برای تعیین فاصله یک المان از container آن
 <div fxFlexOffset="20px"></div>
-  fxflexAlign برای تعیین محل قرارگیری المان
 <div fxFlexAlign="center"></div>
- fxflexfill برای تعیین اینکه این المان کل ردیف یا ستون را پر خواهد کرد
 <div fxFlexFill></div>

چند مثال:


در اینجا سه نمایشی را که در ذیل تعریف div‌ها مشاهده می‌کنید بر اساس تغییر اندازه‌ی صفحه حاصل شده‌اند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازه‌ی صفحه و دسترسی به مقدار فضای بیشتر، بزرگ‌تر شده‌است.

یک مثال کامل
اگر علاقمند باشید تا توانمندی‌های angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینه‌‌های مختلف، کد معادل angular flex layout آن نیز تولید می‌شود.
همچنین wiki خود پروژه نیز به همراه مثال‌های بیشتری است:
https://github.com/angular/flex-layout/wiki



کار با API واکنشگرای Angular Flex layout


در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازه‌ی صفحه و یا اندازه‌ی وسیله‌ی نمایشی، تغییر اندازه و همچنین موقعیت می‌دهند و این تغییرات بر اساس انطباق با viewport وسیله‌ی نمایشی صورت می‌گیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار می‌دهد.
اگر علاقمند هستید تا اندازه‌های واکنشگرای استاندارد متریال را ملاحظه کنید، می‌توانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازه‌ای کمتر از 600px در گروه extra small قرار می‌گیرد (با مخفف xs). از 600px تا 1024px در بازه‌ی small (با مخفف sm)، از 1024px تا 1440px در بازه‌ی medium (با مخفف md) و از 1440px تا 1920px در بازه‌ی large (با مخفف lg) و بیشتر از آن در بازه‌ی xlrage قرار می‌گیرند (با مخفف xl). این اعداد و بازه‌ها، پایه‌ی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازه‌ها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شده‌اند و مورد استفاده قرار می‌گیرند. همچنین اگر اندازه‌های مدنظر از این بازه‌ها کمتر باشند، می‌توان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازه‌های مورد نیاز بیش از این اندازه‌ها باشند می‌توان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخفف‌ها «media query alias» هم گفته می‌شود و اکنون که لیست آن‌ها مشخص است، تنها کافی است آن‌ها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
fxLayout.sm = "..."
fxLayoutAlign.md = "..."
fxHide.gt-sm = "..."
برای نمونه فرض کنید یک چنین طرحبندی دسکتاپی را داریم:


معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:

که در اینجا دو container را ملاحظه می‌کنید. ابتدا Container بیرونی جهت ارائه‌ی ستونی از سه المان اضافه شده‌است. سپس یک Container میانی برای  تعریف ردیفی از سه المان تعریف شده‌است. توسط روش "fxFlex="grow shrink basis نیز اندازه‌های آن‌ها مشخص شده‌اند.

اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آن‌را تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟


برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه می‌کنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازه‌ی xs، محل قرارگیری المان‌های این ستون را هم مشخص می‌کنیم:


همانطور که ملاحظه می‌کنید کار کردن با این API بسیار ساده‌است و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهده‌ی اثر آن‌ها بهتر است اندازه‌ی مرورگر را کوچک و بزرگ کنید.


مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازه‌ی آن

علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمت‌های مختلف صفحه بر اساس اندازه‌ی صفحه‌ی نمایشی نیز هست:
 <div fxShow fxHide.xs="false" fxHide.lg="true"></div>
در اینجا fxShow سبب نمایش این div در حالت عادی می‌شود (پیش‌فرض آن xl، md و sm است). اما اگر اندازه‌ی صفحه lg باشد، fxHide.lg تنظیم شده‌ی به true سبب مخفی سازی آن خواهد شد و در اندازه‌ی xs مجددا نمایش داده می‌شود.


تغییر اندازه‌ی قسمتی از صفحه بر اساس اندازه‌ی آن

در مثال زیر اگر اندازه‌ی صفحه gt-sm باشد (بیشتر از small)، اندازه‌ی این div به 100 درصد بجای 50 درصد حالت‌های دیگر، تنظیم می‌شود:
 <div fxFlex="50%" fxFlex.gt-sm="100%"></div>

حالت‌های ویژه‌ی طراحی واکنشگرا در Angular Flex Layout

در API واکنشگرای آن حالت‌های ویژه‌ی fxshow, fxhide, ngclass  و  ngstyle نیز درنظر گرفته شده‌اند که امکان فعالسازی آن‌ها در اندازه‌های مختلف صفحه مسیر است:
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div>
<div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div>
<div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div>
<div [ngStyle.xs]="{color: 'blue'}"></div>


امکان کار با API واکنشگرا از طریق برنامه نویسی

برای این منظور می‌توان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:


در اینجا به فعالسازی یک بازه‌ی خاص گوش فرا خواهیم داد. برای مثال اگر اندازه‌ی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.



برای مطالعه‌ی بیشتر
قسمت‌های عمده‌ای از مطلب جاری، از ویدیوی زیر که توسط نویسنده‌ی اصلی angular flex layout تهیه شده‌است، گردآوری شدند.
 
مطالب
بازسازی کد: ارتباط یک طرفه و دو طرفه بین کلاس ها
در طراحی شیء گرا، ارتباط کلاس‌ها امری عادی است. در صورتیکه فقط یکی از کلاس‌ها نیاز به شیء کلاس دیگری را داشته باشد، این ارتباط یک طرفه نامیده می‌شود و زمانیکه هر دو کلاس به اشیاء یکدیگر نیاز داشته باشند، دو طرفه نامیده می‌شود.
زمانیکه ارتباطی به صورت یک طرفه پیاده سازی شده باشد، ممکن است پس از مدتی نیاز به ارتباط برعکس بین کلاس‌ها بوجود بیاید. در این صورت ارتباط دوطرفه را با اضافه کردن یک خصوصیت ایجاد می‌کنیم. به طور مثال نرم افزاری را در نظر بگیرید که در آن دو موجودیت مشتری و سفارش وجود دارند و این موجودیت‌ها به صورت زیر طراحی شده‌اند:   

 
public class Customer 
{ 
    public string Name { get; set; } 
} 
public class Order 
{ 
    public Customer Customer { get; set; } 
}
در این طراحی، از شیء سفارش می‌توان در صورت نیاز به شیء مشتری مربوط به آن رسید. اما با در دست داشتن شیء مشتری، نمی‌توان به سفارش‌های آن دسترسی داشت؛ زیرا رفرنسی به سفارش در آن وجود ندارد. به عبارتی طراحی بالا نمایانگر شرایطی است که در آن زمان ثبت قرارداد می‌توانیم مشتری جدیدی را نیز ایجاد کنیم.
در صورت نیاز به ارتباط دوطرفه، برای ایجاد آن در این شرایط، خصوصیتی را که نشان دهنده سفارشات مشتری باشد، به کلاس مشتری اضافه می‌کنیم. در نتیجه، طراحی به صورت زیر تغییر می‌کند. این شرایط ممکن است زمانی بوجود بیاید که نیاز داریم با در دست داشتن شیء یک مشتری بتوانیم سفارشات مورد نظر وی را ایجاد کنیم. 

public class Customer 
{ 
    public string Name { get; set; } 
    public ICollection<Order> Orders { get; set; } 
} 
public class Order 
{ 
    public Customer Customer { get; set; } 
}

همان طور که با مثالهای آورده شده قابل مشاهده است، تصمیم به استفاده از ارتباط یک طرفه یا دوطرفه، تصمیمی صرفا فنی نیست و به شرایط قواعد کسب و کار نیز ارتباط می‌یابد. 

اگر از ORM در طراحی لایه دسترسی داده استفاده شود، معمولا ارتباط‌های دوطرفه در نگاه اول مشکل ساز به نظر نمی‌رسند، یا حتی لازم خواهند بود (برای استفاده از امکانات mapping بر اساس قرارداد). اما ارتباطات دو طرفه معمولا امکان دسترسی به اقلام اطلاعاتی را بدون در نظر گرفتن قواعد کسب و کاری لازم، بوجود می‌آورند. یکی از اشکالات اصلی طراحی با ارتباط دو طرفه ارتباط بیش از اندازه کلاس‌ها با یکدیگر است. در این صورت ممکن است تغییر در یک کلاس، به کلاس‌های دیگری نفوذ کند که این مورد خود یک الگوی کد بد بو است.

عموما با توجه به هزینه‌ای که ارتباط دو طرفه ایجاد می‌کند، بهتر است استفاده از آن، تا آخرین لحظه نیاز به تعویق بیفتد و اولویت طراحی با ارتباط یک طرفه باشد.  

مطالب
بازسازی کد: جایگزینی آرایه با شیء (Replace array with object)
از آرایه برای ذخیره سازی آیتم‌های مشابه استفاده می‌شود. این تشابه باید علاوه بر اینکه در نوع داده‌ای آیتم‌ها رعایت شود، باید از نظر مفهومی نیز رعایت شود.
زمانیکه از یک آرایه برای نگهداری المنت‌های غیر مشابه استفاده می‌شود، نیاز به چنین بازسازی کدی است. به طور مثال آرایه‌ای که آیتم اول آن "نام" و آیتم دوم آن "امتیاز" است. قطعا کار با چنین آرایه‌ای بسیار مشکل خواهد بود. زمانیکه یک آرایه را از نوع داده‌ای عمومی‌تری (مثلا object در سی شارپ) تعریف و انواع داده‌ای متفاوت را در آیتم‌های آن نگهداری کنیم، اوضاع بسیار بدتر خواهد شد. 
محور اصلی بازسازی کد "جایگزینی آرایه با شیء" ایجاد یک کلاس، برای ذخیره اطلاعات آرایه است. به این صورت که برای هر آیتم آرایه، یک خصوصیت در کلاس مربوطه ایجاد می‌شود. 
به طور مثال به آرایه زیر توجه نمایید:
var row = new string[3]; 
row[0] = "Liverpool"; 
row[0] = "15";
در آرایه بالا، آیتم اول نشان دهنده نام تیم و آیتم دوم نشان دهنده امتیاز تیم است. با وجود اینکه این مثال کمی غیر واقعی به نظر میرسد، اما چنین مثال‌هایی در برنامه نویسی روزمره ممکن است به اشکال مختلفی مشاهده شود. مانند استفاده از dictionary برای دریافت اطلاعات فرم وب، استفاده از Tuple (در زبان سی شارپ) برای انتقال اطلاعات و … 
در این مثال طراحی بهتر، ایجاد یک کلاس یا ساختار (بسته به شرایط کلی مسئله) برای نشان دادن امتیاز تیم است:  
public class Performance 
{ 
       public string TeamName { get; set; } 
       public int Score { get; set; } 
}
همانطور که مشاهده می‌کنید، به ازای هر یک از آیتم‌های آرایه، خصوصیتی در کلاس جدید ایجاد شده‌است. همچنین انتخاب انواع داده‌ای نیز در طراحی جدید، ساده‌تر و اصولی‌تر انجام خواهد شد.
تمامی استفاده‌ها از آرایه‌ها، در دسته بندی این نوشتار برای بازسازی کد قرار نمی‌گیرند. آرایه‌هایی که اصل مشابه بودن آیتم‌ها را رعایت می‌کنند، معمولا نیازی به بازسازی کد ندارند. به طور مثال در نرم افزارهای فروشگاه اینترنتی، خصوصیات کالا به صورت داینامیک ذخیره شده و احتمالا برای دسترسی و مدیریت آن، از آرایه یا لیست استفاده می‌شود. اما با کمی دقت خواهیم دید، این استفاده از آرایه، با تعریف مشابه بودن آیتم‌ها همخوانی دارد. زیرا تمامی آیتم‌های آرایه به طور مثال از نوع خصوصیت کالا هستند. همچنین عملا امکان بازسازی و ایجاد کلاس در این مثال وجود ندارد؛ زیرا خصوصیات کالاها در زمان توسعه مشخص نیستند و در زمان اجرای برنامه تنظیم می‌شوند. 
مطالب
بازسازی کد: جایگزینی داده با شیء (Replace data with object)
بازسازی کد جایگزینی داده با شیء، معمولا در طراحی موجودیت‌های قابل ذخیره و بازیابی سیستم‌های اطلاعاتی مورد نیاز قرار می‌گیرید. این بازسازی کد معمولا زمانی مورد نیاز است که آیتم داده‌ای نیاز به اطلاعات بیشتر یا رفتاری خاص دارد. در این صورت باید آن آیتم داده‌ای را به شیء از کلاس یا ساختار (struct) تبدیل کرد. 
معمولا زمانیکه توسعه محصول انجام می‌گیرد، ممکن است آیتم‌های داده‌ای در ابتدا ساده دیده شوند و طراحی ساده‌ای برای آنها در نظر گرفته شود. به طور مثال در یک سیستم فرضی رسیدگی به تیکت، ممکن است با اقلام اطلاعاتی مانند آیتم‌های زیر روبرو باشیم:  
  •  شماره تلفن، به صورت رشته کاراکتری 
  • آدرس، به عنوان رشته کاراکتری 
  • نام مسئول رسیدگی به تیکت، به صورت رشته کاراکتری  
با توجه به مثال بالا، در طراحی اولیه AgentName، فیلدی از نوع رشته کاراکتری برای نگهداری نام مسئول رسیدگی به تیکت در نظر گرفته شده است (فرض می‌کنیم در این طراحی موضوعات مربوط به نرمال سازی پایگاه‌های داده را در نظر نگرفته‌ایم و تکرار شدن نام مسئول رسیدگی به تیکت اشکالی نداشته‌است). کلاس زیر نشان دهنده چنین طراحی‌ای است.  

اما بعد از سپری شدن مدتی از توسعه محصول ممکن است اقلام اطلاعاتی خاصی بر روی هر یک از آیتم‌های بالا نیاز شود. به طور مثال برای آدرس نیاز باشد اطلاعات استان و شهر جداگانه قابل ذخیره سازی و گزارش گیری باشند و یا در کنار نام مسئول رسیدگی به تیکت، شماره تلفن او نیز وجود داشته باشد.

در چنین شرایطی، یک اقدام ممکن، افزودن اقلام اطلاعاتی مورد نیاز در همان مکان آیتم قبلی است؛ به طور مثال اگر نام مسئول بر روی موجودیت تیکت باشد، شماره تلفن مسئول نیز در همان موجودیت تیکت اضافه شود.  

راه حل مناسب‌تر برای حل این نوع مشکلات ایجاد کلاس خاص آیتم اطلاعاتی و استفاده از شیء آن به‌جای مقدار مربوطه است. به طور مثال به طراحی زیر دقت نمایید.  در طراحی زیر کلاس دیگری به نام Agent ایجاد و در کلاس تیکت از آن استفاده کرده‌ایم.  

این بازسازی کد دو مزیت کلی دارد:  

  • راه را برای توسعه آینده آیتم‌های داده‌ای باز می‌کند
  • از تکرار آیتم‌های داده‌ای جلوگیری می‌کند (به طور مثال زمانیکه از پایگاه داده‌های رابطه‌ای جهت ذخیره سازی، استفاده شود)  
در مثال بالا علارغم اینکه قادر بودیم آیتم اطلاعاتی مسئول رسیدگی را به صورت ساختار (struct) تعریف کنیم، این آیتم اطلاعاتی را به صورت کلاس تعریف کردیم. تعریف به صورت کلاس امکان استفاده از رفرنس را به‌جای مقدار شیء، به ما خواهد داد. در اکثر بازسازی‌های کد، استفاده از کلاس‌ها مزیت‌های بیشتری نسبت به استفاده از ساختار دارد. برای مطالعه بیشتر در این مورد می‌توانید به اینجا مراجعه نمایید.  
مطالب
بازسازی کد: پنهان سازی delegate یا Hide delegate

زمانی نیاز به این بازسازی کد به‌وجود می‌آید که استفاده کننده‌ی از کلاس‌ها، درگیر جزییات بیش از اندازه‌ی کلاس‌ها می‌شود. به طور مثال به نمودار بالا توجه نمایید.

در این نمودار تکه کدی مدل شده است که در آن ClientClass استفاده کننده از امکانات دو کلاس دیگر است. برای بدست آوردن مدیر یک شخص در این طراحی نیاز است ابتدا ClientClass اطلاعات مربوط به department یک شخص را با استفاده از متد GetDepartment بدست آورد. سپس با استفاده از متد GetManager در کلاس Department اقدام به دریافت اطلاعات مدیر نماید.

در طراحی بالا، برای دریافت اطلاعات مدیر یک فرد، با تکه کدی مانند زیر روبرو خواهیم شد:  

var manager = person.GetDepartment().GetManager();

یکی از اصلی‌ترین اصول طراحی کلاس‌ها، کپسوله سازی اعضای کلاس، از استفاده کنندگان بیرونی آن است. کپسوله سازی به این معنی است که کلاس کمترین نیاز را به اطلاع از دیگر بخش‌های سیستم داشته باشد. بنابراین در زمان تغییر آن بخش‌ها دیگر نیازی نیست کلاس استفاده کننده از ریز تغییرات اطلاع پیدا کند.

روش کلی بازسازی کد پنهان سازی delegate، ایجاد یک کلاس (یا استفاده از کلاس‌های موجود) به عنوان سرویس دهنده یا server است. در این کلاس به ازای کارکردهایی که نیاز به استفاده از چندین شیء یا متد را داشته باشند، یک متد ایجاد می‌کنیم. این متد روال لازم برای فراخوانی‌ها را خود مدیریت و پیاده سازی می‌کند.

در مثال ذکر شده‌ی در ابتدای نوشتار می‌توان کلاس سرویس دهنده‌ی کارکرد دریافت مدیر را کلاس Person دانست. با این ترتیب بازسازی کد، رابطه‌ی بین کلاس‌ها را به صورت زیر تغییر می‌دهد.  

با کمی توجه در نمودار می‌توان متوجه شد متد موجود در کلاس Person به متد GetManager تغییر کرده است. زیرا در این کارکرد برای دریافت مدیر به کلاس Person رجوع می‌کنیم و نیازی نیست مستقیما به کلاس Department رجوع کنیم. مدیریت کردن نحوه دریافت مدیر یک Person نیز بر عهده این متد است.

همچنین برای دریافت مدیر یک شخص با چنین تکه کدی روبرو خواهیم شد:  

var manager = person.GetManager();

همان طور که قبلا نیز ذکر شد، یکی از مزایای عمده این روش طراحی، مخفی کردن اطلاعات اضافی، از دید استفاده کنندگان کلاس است که در این مثال، نحوه دقیق دریافت مدیر است.  

مطالب
بازسازی کد: جابجایی متد (Move method)
معمولا زمانیکه متدی از امکانات کلاس دیگری غیر از کلاسی که در آن تعریف شده است استفاده می‌کند، نیاز به چنین بازسازی کدی داریم. روش کلی این بازسازی کد، انتقال متد به کلاسی است که بیشترین تعلق را به آن دارد! 
جابجایی متد یکی از موارد پر تکرار و مهم در امر بازسازی کد است. این بازسازی در مراحل انجام دیگر بازسازی‌های کد، مانند شکستن کلاس نیز استفاده می‌شود. با این روش ساده می‌توان کلاس‌هایی با مسئولیت‌های محدود و مشخص را توسعه داد.  

مراحل انجام این بازسازی کد  

  1. تمامی امکانات کلاس مبدا را که متد مورد نظر از آنها استفاده می‌کند، بررسی نمایید که آیا آنها نیز نیاز به انتقال دارند یا خیر. 
  2. کلاس‌های پدر و فرزند کلاس مبدا را برای یافتن تعاریف دیگری از متد مورد نظر بررسی نمایید. اگر تعاریف دیگری وجود داشتند به راحتی نمی‌توان متد را جابجا کرد. در این صورت اگر قصد جابجایی داشتید، باید به فکر جابجایی رابطه چند ریختی موجود نیز باشید. 
  3. متد را در کلاس مقصد ایجاد نمایید. 
  4. بدنه متد را به متد مقصد منتقل نمایید و تمامی امکانات استفاده شده در آن را طوری تغییر دهید که در کلاس جدید کار کند. اگر متد، نیاز به اشاره‌ای به کلاس مبدا داشت، باید تعیین نمایید که به چه صورت این اشاره انجام شود. اگر مکانیزم مدیریت خطایی (exception handling) در متد مبدا پیاده سازی شده بود، تعیین کنید که آیا متد مبدا نیز کماکان امر مدیریت خطا را انجام خواهد داد، یا به متد مقصد انتقال خواهد یافت. 
  5. کد کلاس مقصد را کامپایل و تست نمایید. 
  6. اگر متد مبدا را به عنوان فراخوان متد مقصد نگه داشتید، باید تصمیم بگیرید که کلاس مقصد در آن متد به چه صورت استفاده خواهد شد. 
  7. فراخوانی متد مقصد را به بدنه متد مبدا اضافه کنید. 
  8. کد را کامپایل و تست نمایید.
  9. در مورد سرنوشت متد مبدا تصمیم گیری نمایید که آیا نیازی به وجود آن هست یا خیر. در صورتیکه از متد مبدا در مکان‌های زیادی استفاده شده یا متد در کتابخانه یا فریم ورکی است که کنترلی بر روی استفاده کنندگان آن وجود ندارد، احتمالا باقی ماندن متد به عنوان صرفا فراخوان، ایده خوبی باشد. 
  10. اگر متد مبدا را حذف کردید تمامی استفاده از آن را باید به متد مقصد تغییر دهید. توجه داشته باشید ممکن است سناریو ساختن کلاس جدید با کلاس قدیمی متفاوت باشد. 
  11. مجددا کد را کامپایل و تست نمایید.  

مثال: فرض کنید نرم افزاری برای مدیریت رویدادها و شرکت کنندگان آن‌ها تهیه کرده‌ایم. در این نرم افزار، کلاسی با نام Event وجود دارد و کلاسی نیز با نام Person که نام آنها کاملا نمایانگر استفاده آنها است.
بخشی از بدنه این کلاس‌ها به صورت زیر است:  
public class Event 
{ 
    public List<Person> Participants { get; internal set; } 
} 
public class Person 
{ 
    public int Id { get; private set; } 
    public void Participate(Event ev) 
    { 
        var isParticipatedAlready = ev.Participants.Any(ff => ff.Id == Id); 
        if (isParticipatedAlready) 
            return; 
        ev.Participants.Add(this); 
    } 
}
در کد مربوط به کلاس Person، شاهد متدی هستیم که عمل ثبت‌نام فرد را در یک رویداد انجام می‌دهد. اما با دقت به این متد مشاهده می‌کنیم که بدنه این متد بیشتر از اعضای کلاس Event استفاده می‌کند. حتی این استفاده باعث شده است که خصوصیت Participants از کلاس Event به صورت public تعریف شود که خود مشکل دیگری در این طراحی است.
در چنین شرایطی، بازسازی کد جابجایی متد می‌تواند در راستای انتقال مسئولیت‌های مناسب هر کلاس به بدنه آن و بهبود طراحی کمک کند. بعد از بازسازی کد شاهد چنین طراحی‌ای هستیم:  
public class Event 
{ 
    protected List<Person> Participants { get; set; } 
    public void Participate(Person person) 
    { 
        var isParticipatedAlready = Participants.Any(ff => ff.Id == person.Id); 
        if (isParticipatedAlready) 
            return; 
        Participants.Add(person); 
    } 
} 
public class Person 
{ 
    public int Id { get; private set; } 
}
بازسازی‌ای که انجام شد، دو تاثیر را بر روی طراحی این کلاس‌ها داشته است:
   اول: جایگذاری بهتر و منطقی‌تر مسئولیت‌های یک کلاس 
   دوم: کپسوله سازی آسان‌تر کلاس ها 

بازسازی کد جابجایی متد، سنگ بنای بیشتر بازسازی‌های مورد نیاز در فعالیت‌های روزمره تولید یا نگهداری نرم افزار است. علارغم این که این بازسازی ساده به نظر می‌رسد، در مجموعه کدهای پیچیده، انجام این بازسازی ممکن است امری طاقت فرسا شود.  
مطالب
پیاده سازی مکانیسم سعی مجدد (Retry)
فرض کنید در برنامه‌ای که نوشته‌اید، قصد فراخونی یک وب سرویس را دارید. به طور قطع نمی‌توان همیشه انتظار داشت این سرویس مورد نظر بدون هیچ مشکلی اجرا شود و خروجی مورد نظر را بدهد. برای نمونه ممکن است در لحظه فراخوانی متد مورد نظر، اختلالی در شبکه رخ دهد و فراخوانی سرویس شما با مشکل مواجه شود. در چنین مواقعی دو مورد را پیش‌رو داریم: 
- یک: اعلام نتیجه عدم موفق بودن فراخوانی.
- دو: یک (یا چند) بار دیگر، سعی در فراخوانی سرویس مورد نظر کنیم.
مکانیسم سعی مجدد فقط و فقط محدود به فراخوانی وب سرویس‌ها نمی‌شود. برای نمونه می‌توان به ارسال ایمیل، خطا در اجرای یک کوئری T_SQL و مورادی از این قبیل اشاره کرد.
چرا باید سعی مجدد را پیاده سازی کنیم؟ 
عدم وجود امکان سعی مجدد هیچ چیزی را از پروژه شما سلب نمیکند؛ ولی وجود آن یک امکان را به پروژه شما اضافه میکند که تا حدودی باعث سهولت در استفاده از نرم افزار شما خواهد شد.
نباید‌های مکانیسم سعی مجدد
با خواندن مطالب فوق شاید به این موضوع فکر کنید که مکانیسم سعی مجدد امکان خوبی برای پروژه است و همه بخش‌های پروژه را درگیر این مکانیز کنیم. واقعیت این است که استفاده از مکانیسم سعی مجدد بهتر است محدود به بخش هایی از پروژه شود و نه کل پروژه.
پیش نیاز‌ها
تا به این مرحله با «مکانیسم سعی مجدد» بیشتر آشنا شدیم. برای پیاده سازی یک «سعی مجدد» نیازمندیم یک سری موارد را بدانیم:
یک: میزان تعداد دفعات تلاش 
دو: اختلاف بین هر دو  تلاش مجدد (وقفه)
سه: مقدار افزایش وقفه
چهار: سعی مجدد بر اساس نوع Exception

سه مورد اول از لیست  بالا تقریبا برای یک پیاده سازی سعی مجدد پاسخگو می‌باشد. در ادامه ابتدا قصد داریم یک «سعی مجدد ساده» را نوشته و سپس به معرفی یکی از کتابخانه‌های پرکاربرد آن می‌پردازیم.
قطعه کد زیر را در نظر بگیرد که شبیه ساز ارسال ایمیل می‌باشد:
 public class Mailer
    {
        public static bool SendEmail()
        {
            Console.WriteLine("Sending Mail ...");

            // simulate error
            Random rnd = new Random();
            var rndNumber = rnd.Next(1, 10);
            if (rndNumber != 3) // *
                throw new SmtpFailedRecipientException();

            Console.WriteLine("Mail Sent successfully");
            return true;
        }
    }
خط *  برای شبیه  سازی وقوع یک خطا استفاده شده است.
 قطعه کد زیر برای پیاده سازی مکانیزم سعی مجدد می‌باشد:
 public static class Retry
    {
        public static void Do(Action action,TimeSpan retryInterval,int maxAttemptCount = 3)
        {
            Do<object>(() =>
            {
                action();
                return null;
            }, retryInterval, maxAttemptCount);
        }

        public static T Do<T>(Func<T> action,TimeSpan retryInterval,int maxAttemptCount = 3)
        {
            var exceptions = new List<Exception>();

            for (int attempted = 0; attempted < maxAttemptCount; attempted++)
            {
                try
                {
                    if (attempted > 0)
                    {
                        Thread.Sleep(retryInterval);
                    }
                    return action();
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            throw new AggregateException(exceptions);
        }
    }
قطعه کد فوق ساده‌ترین حالت پیاده سازی Retry می‌باشد که به تعداد MaxAttemptCount سعی در فراخوانی متد مورد نظر خواهد کرد.
یادآوری: متد Do با پارامتر Action در پارامتر اول جهت توابعی که مقدار خروجی ندارند می‌باشد.
همانطور که ذکر شد مقدار Interval بهتر است طبق یک مقدار از پیش تعیین شده افزایش یابد تا درخواست‌های ما با بازه زمانی خیلی کوتاهی نسبت به هم اجرا نشوند. برای رفع این مشکل بعد از Sleep می‌توان مقدار Interval را به صورت زیر افزایش داد:
retryInterval= retryInterval.Add(TimeSpan.FromSeconds(10));
همانطور که بیان کردیم ، قطعه کد نوشته شده فوق برای انجام یک Retry بسیار ساده می‌باشد. موارد دیگری را می‌توان به Retry فوق اضافه نمود. برای نمونه اگر Exception رخ داده شده از نوع مورد نظر ما بود، مجدد Retry کند، در غیر اینصورت از ادامه کار منصرف شود. برای نوشتن هندل کردن نوع Exception می‌توانیم از کتابخانه Polly استفاده کنیم.

کتابخانه Polly
Polly یکی از کتابخانه‌های پرکاربرد است و یکی از امکانات آن، «مکانیسم سعی مجدد» آن، به صورت زیر می‌باشد:


در ساده‌ترین حالت، استفاده از Polly همانند زیر است:

var policy = Policy.Handle<SmtpFailedRecipientException>().Retry();
            policy.Execute(Mailer.SendEmail);

متد Retry، دارای Overload‌های مختلفی است که یکی از آنها مقدار تعداد دفعات تلاش را دریافت می‌کند؛ همانند:

var policy = Policy.Handle<SmtpFailedRecipientException>().Retry(5);

لازم به ذکر است که باید دقیقا Exception مورد نظر را در بخش Config به کار ببرید. برای نمونه اگر کد فوق را همانند زیر به کار ببرید، در صورتیکه متد ارسال ایمیل با خطایی مواجه شود، هیچ تلاشی برای اجرای مجدد نخواهد کرد:

   var policy = Policy.Handle<SqlException>().Retry(5);

برای نمونه می‌توان از متد ForEver آن استفاده کرد تا زمانیکه متد مورد نظر Success نشده باشد، سعی در اجرای آن کند:

Policy
  .Handle<DivideByZeroException>()
  .RetryForever()

جهت کسب اطلاعات بیشتر می‌توانید در مخزن کد آن با سایر قابلیت‌های کتابخانه Polly بیشتر آشنا شوید: Github

مطالب
ساخت تم سفارشی در انگیولار متریال ۲ - بخش دوم

در قسمت قبل با نحوه ساخت تم سفارشی در انگیولار متریال ۲، آشنا شدیم. در این قسمت نحوه ساخت چند تم دیگر در کنار تم اصلی، ساخت تم به ازای هر کامپوننت و نحوه تعویض تم از طریق کد را دنبال خواهیم کرد.

ساخت تم اضافی در کنار تم اصلی

ساخت تم اضافی در انگیولار متریال ۲ بسیار ساده است. شما می‌توانید با استفاده مجدد از تابع angular-material-theme داخل یک کلاس CSS، صاحب یک تم اضافی دیگر شوید. برای نمونه در اینجا فایل my-custom-theme.scss را باز کرده و به شکل زیر تغییر می‌دهیم.

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

/*تعریف تم اضافی در کنار تم اصلی*/
$alternate-primary: mat-palette($mat-light-blue);
$alternate-accent: mat-palette($mat-yellow, 500);
$alternate-warn: mat-palette($mat-red, 500);
$alternate-theme: mat-dark-theme($alternate-primary, $alternate-accent);
.alternate-theme {
  @include angular-material-theme($alternate-theme);
}

با این‌کار در کنار تم روشن اصلی، یک تم مشکی به صورت اضافی داخل کلاس CSS به نام alternate-theme تعریف کرده‌ایم. در این حالت تمامی کامپوننتهایی که داخل المنت با کلاس alternate-theme قرار گرفته‌اند، از تم مشکی تعریف شده استفاده خواهند کرد.

با تغییر فایل app.component.html به شکل زیر: 

<md-card>
  <md-card-header>
    <md-card-title>تم اصلی</md-card-title>
  </md-card-header>
  <button md-raised-button color="primary">
    Primary
  </button>
  <button md-raised-button color="accent">
    Accent
  </button>
  <button md-raised-button color="warn">
    Warning
  </button>
</md-card>

<div>
  <md-card>
    <md-card-header>
      <md-card-title>تم اضافی</md-card-title>
    </md-card-header>
    <md-card-content>
      <button md-raised-button color="primary">
        Primary
      </button>
      <button md-raised-button color="accent">
        Accent
      </button>
      <button md-raised-button color="warn">
        Warning
      </button>
    </md-card-content>
  </md-card>
</div>

تصویر زیر را در خروجی خواهید داشت.

 

به همین روش می‌توانید تعداد دلخواهی از تم‌ها را بسازید. همچنین می‌توانید هر تم اضافی را در یک فایل Sass تعریف کنید و از این طریق تم‌های مختلف را از هم جدا کنید. در این حالت به این نکته توجه داشته باشید که نباید mat-core@ در سرتاسر برنامه بیش از یکبار بارگذاری شده باشد. 

ساخت تم به ازای هر کامپوننت

با استفاده از mixin به نام angular-material-theme خروجی تولید شده بر روی تمامی کامپوننت‌های انگیولار متریال ۲ اعمال خواهد شد. اگر از تمامی کامپوننت‌های انگیولار متریال ۲ استفاده نمی‌کنید، می‌توانید برای کاهش حجم فایل CSS تولید شده از mixin مخصوص به هر کامپوننت استفاده کنید. همچنین برای ساخت تم‌های متفاوت به ازای هر کامپوننت نیز می‌توانید از این روش استفاده کنید. 

برای این کار تمامی مراحلی که برای ساخت تم مورد نیاز بود، باید طی شود. فقط به جای استفاده از mixin به نام angular-material-theme بایستی به طریق زیر عمل شود. 

اول: بارگذاری mixin با نام mat-core-them. این mixin تمامی استایل‌های مشترک رفتاری (مانند موج (ripple) در هنگام کلیک) برای کامپوننت‌ها را در بر دارد. این mixin خروجی تابع mat-light-theme یا mat-dark-theme را به عنوان ورودی دریافت می‌کند.

دوم: بارگذاری mixin مربوط به هر کامپوننت. برای مثال برای دکمه از mixin به نام mat-button-theme و برای checkbox از mixin به نام mat-checkbox-theme می‌توانید استفاده کنید. در زیر لیست mixin‌ها به ازای کامپوننت‌های مختلف ذکر شده است. 

mat-autocomplete-theme
mat-button-theme
mat-button-toggle-theme
mat-card-theme
mat-checkbox-theme
mat-chips-theme
mat-datepicker-theme
mat-dialog-theme
mat-grid-list-theme
mat-icon-theme
mat-input-theme
mat-list-theme
mat-menu-theme
mat-progress-bar-theme
mat-progress-spinner-theme
mat-radio-theme
mat-select-theme
mat-sidenav-theme
mat-slide-toggle-theme
mat-slider-theme
mat-tabs-theme
mat-toolbar-theme
mat-tooltip-theme

در مثال زیر می‌خواهیم تمامی کامپوننت‌ها به جز کامپوننت دکمه، تم سبز(در گروه Primary) و دکمه‌ها نیز تم آبی داشته باشند. کافی است کدهای زیر را در فایل Sass خود وارد کنید. 

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

با توجه به اینکه mat-material-theme در داخل خود mat-button-theme را بارگذاری می‌کند دو نتیجه زیر را می‌توان گرفت.

اول: اگر mat-material-theme بعد از هر کدام از mixinهای مربوط به کامپوننت‌ها نوشته شود، تمامی Cssهای تولید شده به ازای کامپوننت را دوباره نویسی کرده و عملا هیچ کدام کارایی نخواهند داشت. برای مثال کافی است فایل Sass خود را به شکل زیر تغییر دهید. در این صورت تم مربوط به دکمه کاریی نخواهد داشت. 

@import '~@angular/material/theming';
@include mat-core();

/* تعریف تم برای دکمه */
$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-ligth-theme($button-primary, $button-accent);
@include mat-button-theme($button-theme);

$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);
@include mat-material-theme($my-app-theme);

دوم: همانطور که گفتیم mat-button-theme در mat-material-theme قبلا بارگذاری شده است. با بارگذاری دوباره توسط mat-button-theme کدهای CSS که قبلا برای دکمه تولید شده‌اند را از نو دوباره می‌نویسد و این باعث بزرگ شدن حجم فایل Css تولید شده خواهد شد. پس بهتر است هنگام استفاده از mixinهای مختص کامپوننت‌ها از mat-material-theme استفاده نکنیم. 

  جهت بهبود کدهای قبلی بهتر است از کدهای زیر استفاده کنیم. 
@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent: mat-palette($mat-amber, 500, A100, A400);
$my-app-warn: mat-palette($mat-deep-orange);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

$button-primary: mat-palette($mat-light-blue);
$button-accent: mat-palette($mat-yellow, 500);
$button-warn: mat-palette($mat-red, 500);
$button-theme: mat-light-theme($button-primary, $button-accent);

@include mat-core-theme($my-app-theme);
@include mat-autocomplete-theme($my-app-theme);

@include mat-button-theme($button-theme);

@include mat-button-toggle-theme($my-app-theme);
@include mat-card-theme($my-app-theme);
@include mat-checkbox-theme($my-app-theme);
@include mat-chips-theme($my-app-theme);
@include mat-datepicker-theme($my-app-theme);
@include mat-dialog-theme($my-app-theme);
@include mat-grid-list-theme($my-app-theme);
@include mat-icon-theme($my-app-theme);
@include mat-input-theme($my-app-theme);
@include mat-list-theme($my-app-theme);
@include mat-menu-theme($my-app-theme);
@include mat-progress-bar-theme($my-app-theme);
@include mat-progress-spinner-theme($my-app-theme);
@include mat-radio-theme($my-app-theme);
@include mat-select-theme($my-app-theme);
@include mat-sidenav-theme($my-app-theme);
@include mat-slide-toggle-theme($my-app-theme);
@include mat-slider-theme($my-app-theme);
@include mat-tabs-theme($my-app-theme);
@include mat-toolbar-theme($my-app-theme);
@include mat-tooltip-theme($my-app-theme);

تعویض تم از طریق کد

فرض کنید یک تم پیش فرض و یک تم اضافی به نام alternate-theme دارید. برای تعویض تم از طریق کد کافی است کلاس المنت پدر در صفحه html خود را از طریق [ngClass] با نام تم، مقدار دهی کنید. کدهای داخل app.component.ts را به شکل زیر تغییر می‌دهیم. 

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  themes = [
    {value: 'alternate-theme', text: 'تم مشکی'},
    {value: '', text: 'تم سفید'},
  ];
  activeTheme = '';
}

آرایه‌ای جهت نمایش در کامپوننت md-select با دو مقدار تم پیش فرض و تم با نام 'alternate-theme تعریف می‌کنیم. همچنین متغیری با نام activeTheme را تعریف می‌کنیم. این متغیر در هر لحظه نام تم اعمال شده را در خود نگهداری می‌کند. مقدار اولیه این متغیر تم اصلی است.

کامپوننت md-select را به شکل زیر به فایل app.component.html به تگ main اضافه می‌کنیم. 

<md-select dir="rtl" [(ngModel)]="activeTheme" placeholder="تعویض تم">
    <md-option *ngFor="let theme of themes" [value]="theme.value">
            {{ theme.text }}
    </md-option>
</md-select>
حالا کافی است کل تگهای موجود در app.component.html را داخل یک تگ div به شکل زیر قرار دهیم. 
<div [ngClass]="activeTheme">
بعد از اجرای برنامه می‌توانید با تعویض تم از طریق کامپوننت md-select، تم صفحه را تعویض کنید. 

کدهای این قسمت را، از اینجا دریافت کنید: ساخت تم سفارشی در انگولار متریال ۲ - بخش دوم
مطالب
ساخت تم سفارشی در انگیولار متریال ۲ - بخش اول

در  قسمت قبل  بیان شد همراه با نصب Angular Material، تعدادی تم از قبل ساخته شده نیز نصب خواهند شد که شامل یکسری استایل با رنگهای مشخصی هستند. این تم‌ها عبارتند از:

  • indigo-pink
  • deeppurple-amber
  • purple-green
  • pink-bluegrey 

انگیولار متریال ۲ علاوه بر این، امکاناتی برای ایجاد تم سفارشی را نیز فراهم کرده است. در این بخش قصد داریم برای قالب نمونه‌ای که قبلا ایجاد کرده بودیم یک تم سفارشی بسازیم.


مقدمه

تم در انگیولار متریال، از ترکیب چند پالت رنگی، ساخته می‌‌شود. پالت‌های رنگ را در طراحی متریال ( Material Design ) در  اینجا می‌توانید مشاهده کنید. انگیولار متریال رنگهای مورد استفاده خود را در گروه‌های زیر دسته بندی کرده است. 

  • Primary : این پالت رنگی به صورت گسترده در بخشهای مختلف صفحه و کامپوننت‌ها مورد استفاده قرار می‌گیرد.
  • Accent : این پالت رنگی برای دکمه‌های شناور و همچنین المنتهای تعاملی مورد استفاده قرار می‌گیرد.
  • Warn : این پالت رنگی برای مشخص کردن حالت‌های خطا، مورد استفاده قرار می‌گیرد.
  • Foreground : این پالت رنگی برای متون و آیکونها مورد استفاده قرار می‌گیرد.
  • Background : این پالت رنگی برای المنت‌های پس زمینه مورد استفاده قرار می‌گیرد.

در انگیولار متریال تمامی تم‌ها در زمان build به صورت استاتیک تولید می‌شوند. این قابلیت با خارج کردن چرخه تولید تم از چرخه راه‌اندازی برنامه، باعث بهبود در راه‌اندازی خواهد شد. 


تعریف تم سفارشی

برای ساخت تم سفارشی نیاز به یک فایل Sass خواهیم داشت. پس در مسیر /src یک فایل Sass را  با نام my-custom-theme.scss ایجاد می‌کنیم (شما می‌توانید از هر نام دیگری برای فایل Sass استفاده کنید). اگر از AngularCLI برای برنامه‌های خود استفاده می‌کنید، بایستی فایل Sass ایجاد شده را به لیست استایل‌ها در فایل angular-cli.json اضافه کنید. این کار باعث می‌شود AngularCLI این فایل Sass را در زمان build به css کامپایل کند. 

نکته: استفاده از فایل Sass برای ساختن تم سفارشی به این معنی نیست که شما از Sass برای سایر Style های برنامه خود استفاده کنید.

"styles": [
  "styles.css",
  "my-custom-theme.scss"
],

اگر از AngularCLI استفاده نمی‌کنید، شما نیاز به ابزاری برای کامپایل فایل Sass به css خواهید داشت.  ابزارهای بسیاری در این زمینه وجود دارند از جمله: gulp-sass و grunt-sass . ولی ساده‌ترین ابزار برای این کار node-sass می‌باشد. کافی است بعد از نصب، دستور زیر را اجرا کنید تا فایل sass به css کامپایل شود. فایل css تولید شده را مستقیما در صفحه index.html خود می‌توانید استفاده کنید.

node-sass src/my-custom-theme.scss dist/my-custom-theme.css

در فایل تم ایجاد شده ( my-custom-theme.scss ) ابتدا بایستی فایل Sass اصلی انگیولار متریال را وارد کنید.

@import '~@angular/material/theming';

در قدم بعدی mixin تعریف شده با نام mat-core  را در فایل Sass  انگیولار متریال، وارد می‌کنیم. این mixin شامل تمامی Styleهای مشترکی است که توسط کامپوننت‌های مختلف استفاده می‌شود. 

@include mat-core();

نکته: مطمئن شوید فقط یک بار این mixin را در سرتاسر برنامه خود وارد کرده باشید. در غیر این صورت، فایل css تولید شده شامل یکسری Style تکراری خواهد بود و این باعث بزرگ و حجیم شدن فایل css نهایی خواهد شد. 


تا اینجا فایل تم ایجاد شده اینگونه خواهد بود:  

@import '~@angular/material/theming';
@include mat-core();

حالا نوبت تعریف تم سفارشی است. ولی قبل از آن باید با سیستم رنگها در طراحی متریال ( Material Design ) آشنایی داشته باشیم. در طراحی متریال ۱۹ پالت رنگی با نام‌های مختلف وجود دارند. برای ۱۶ پالت رنگی، ۱۴ طیف رنگی و برای ۳ پالت رنگی دیگر، ۱۰ طیف رنگی در نظر گرفته شده است. هر کدام از این طیف‌های رنگی، دارای یک مقدار عددی است. یعنی یک رنگ در سیستم طراحی متریال متشکل از یک نام رنگ و یک شماره طیف رنگ است که مقدار پیش فرض طیف رنگ، عدد ۵۰۰ می‌باشد. 


حالا با استفاده از تابع mat-palette تعریف شده در فایل Sass انگیولار متریال، سه متغیر را برای رنگهای Primary ، Accent و Warn در فایل my-custom-theme.scss ، به شکل زیر تعریف می‌کنیم. 

$my-app-primary: mat-palette($mat-indigo);
$my-app-accent:  mat-palette($mat-pink, 500, A100, A400);
$my-app-warn:    mat-palette($mat-deep-orange);

تابع mat-palette در فایل Sass اصلی انگیولار متریال، به شکل زیر تعریف شده است. 

@function mat-palette($base-palette, $default: 500, $lighter: 100, $darker: 700)

این تابع یک پارامتر اجباری دارد و بقیه پارامترها اختیاری هستند.

  • base-palette $: نام رنگ را دریافت می‌کند. این پارامتر اجباری است و باید مشخص شود. 
  • default$: با این پارامتر، طیف پیش‌فرض رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 500 است.
  • lighter$: با این پارامتر، طیف روشن رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 100 است.
  • darker$: با این پارامتر، طیف تیره رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 700 است.

در قدم آخر با استفاده از تابع mat-light-theme یا mat-dark-theme، رنگهای تعریف شده در مرحله قبل را ترکیب کرده و نتیجه را به عنوان ورودی به mixin به نام angular-material-theme  ارسال و بارگذاری می‌کنیم. 

تابع mat-light-theme و mat-dark-theme سه پارامتر را دریافت می‌کند. پارارمتر اول پالت رنگ ایجاد شده توسط تابع mat-palette برای گروه Primary ، پارامتر دوم پالت رنگ ایجاد شده برای گروه Accent و پارامتر سوم پالت رنگ ایجاد شده برای گروه Warn را دریافت می‌کند. دو پارامتر اول اجباری و پارامتر سوم اختیاری با مقدار پیش فرض mat-palette($mat-red) می‌باشد. 

شکل کلی فایل Sass در نهایت به شکل زیر خواهد بود. 

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent:  mat-palette($mat-amber, 500, A100, A400);
$my-app-warn:    mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

برای استفاده از پالت رنگ‌های ایجاد شده، از خصوصیت color در المنت‌های انگولار متریال استفاده می‌کنیم. برای نمونه بعد از تغییر فایل Sass به شکل بالا و حذف لینک تم از پیش ساخته شده که در پست قبلی به Style.cs اضافه کرده بودیم، می‌توانیم کار خود را به صورت زیر آزمایش کنیم. در فایل app.component.html در تگ main کدهای زیر را اضافه کنید. 

<md-card>
  <button md-raised-button color="primary">
    Primary
  </button>
  <button md-raised-button color="accent">
    Accent
  </button>
  <button md-raised-button color="warn">
    Warning
  </button>
</md-card>

خروجی زیر را مشاهده خواهید کرد. 

همچنین می‌توانید به جای استفاده از تابع mat-light-theme از تابع mat-dark-theme استفاده کنید. دراین صورت خروجی زیر را خواهید دید. 

در بخش بعدی نحوه ساخت چند تم دیگر را در کنار تم اصلی، ساخت تم به ازای هر کامپوننت و نحوه تعویض تم از طریق کد را دنبال خواهیم کرد. 

کدهای این قسمت را از اینجا دریافت کنید:  ساخت-تم-سفارشی-در-انگولار-متریال-۲---بخش-اول.rar