مطالب
مراحل ارتقاء پروژه‌های Angular از نسخه‌ی 6.0 به 7.0
مراحل ارتقاء پروژه‌های Angular از نگارش 6 به 7 آن به شرح زیر هستند:
1- به روز رسانی Angular CLI
ابتدا نیاز است نگارش قبلی را حذف و سپس نگارش جدید را نصب کنید:
npm uninstall -g @angular/cli
npm cache verify
# if npm version is < 5 then use `npm cache clean` 
npm install -g @angular/cli@latest
البته Angular 7 پشتیبانی از  Node 10 را اضافه کرده است (بیشتر؛ دانلود Node). بنابراین پیش از اجرای دستورات فوق بهتر است NodeJS خود را نیز به روز کنید:
npm i -g npm

2- به روز رسانی RxJS (اگر در نگارش 6 آن‌را تکمیل نکرده‌اید)
1-تعویض کردن HttpModule با HttpClientModule و سرویس Http با سرویس HttpClient
2-حذف کردن ویژگی‌های منسوخ شده از RxJS 6 با اجرای دستور زیر:
npm install -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json
3-حذف rxjs-compat بعد از بروزرسانی RxJS 6
 
3- به روز رسانی TypeScript
Angular 7 از تایپ اسکریپت 3.1 استفاده می‌کند (بیشتر). به همین جهت نیاز است وابستگی‌های سراسری سیستم خود را مانند TypeScript، پس از نصب CLI جدید، به نحو زیر به روز کنید:
npm update -g

4- به روز رسانی وابستگی‌های اصلی پروژه
برای به‌‌روز رسانی به نسخه 7 (core framework و CLI)، دستورات زیر را اجرا کنید:
ng update @angular/cli
ng update @angular/core
ng update rxjs
و اگر از Angular Material نیز استفاده می‌کنید، نیاز به اجرای دستور زیر را نیز خواهید داشت:
ng update @angular/material
اگر در اینجا خطای  peer dependency را مشاهده کردید، از سوئیچ force-- در انتهای دستورات، استفاده کنید.
و یا به صورت خلاصه دستور زیر تمام مراحل فوق را به صورت یکجا انجام می‌دهد:
 ng update --all --force
 
5- به روز رسانی Service worker 

اگر شما از Service worker  مربوط به Angular استفاده می‌کنید، بجای versionedFiles از files استفاده کنید. رفتار همان است و تغییر نکرده‌است.
 
6- به روز رسانی وابستگی‌های ثالث پروژه
برای به روز رسانی سایر وابستگی‌های پروژه‌، می‌توان از بسته‌ی npm-check-updates استفاده کرد:
npm install npm-check-updates -g
ncu -u
npm install
دستور دوم تمام شماره نگارش‌های بسته‌های موجود در فایل package.json را به صورت خودکار به آخرین نگارش آن‌ها روز رسانی می‌کند و دستور سوم این بسته‌های جدید را دریافت و نصب خواهد کرد.  
مطالب دوره‌ها
تزریق وابستگی‌های AutoMapper در لایه سرویس برنامه
اگر مطلب «Refactoring به تزریق وابستگی‌ها» را به خاطر داشته باشید، جهت تشخیص وابستگی‌های یک کلاس، کار از بررسی کلمات new و همچنین فراخوانی‌های استاتیک، شروع می‌شود و ... متد استاتیک Mapper.Map کتابخانه‌ی AutoMapper نیز از همین دست است. در ادامه قصد داریم بجای فراخوانی مستقیم Mapper.Map از اینترفیس IMappingEngine به عنوان تامین کننده‌ی متد Map استفاده کنیم. همچنین کلاس‌های Profile نوشته شده را نیز به صورت خودکار به برنامه اضافه نمائیم.


تنظیمات IoC Container مختص به AutoMapper

در ذیل یک کلاس Registry مخصوص StructureMap را مشاهده می‌کنید که جهت کپسوله کردن اطلاعات خاص AutoMapper تهیه شده‌است. می‌توان این اطلاعات را در داخل تنظیمات new Container خود قرار داد و یا می‌توان آن‌ها را جهت شلوغ نشدن سایر تنظیمات IoC Container، به یک کلاس Registry منتقل کرد:
public class AutomapperRegistry : Registry
{
    public AutomapperRegistry()
    {
        var platformSpecificRegistry = PlatformAdapter.Resolve<IPlatformSpecificMapperRegistry>();
        platformSpecificRegistry.Initialize();
 
        For<ConfigurationStore>().Singleton().Use<ConfigurationStore>()
            .Ctor<IEnumerable<IObjectMapper>>().Is(MapperRegistry.Mappers);
 
        For<IConfigurationProvider>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
 
        For<IConfiguration>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
 
        For<ITypeMapFactory>().Use<TypeMapFactory>();
 
        For<IMappingEngine>().Singleton().Use<MappingEngine>()
                             .SelectConstructor(() => new MappingEngine(null));
 
        this.Scan(scanner =>
        {
            scanner.AssembliesFromApplicationBaseDirectory();
 
            scanner.ConnectImplementationsToTypesClosing(typeof(ITypeConverter<,>))
                   .OnAddedPluginTypes(t => t.HybridHttpOrThreadLocalScoped());
 
            scanner.ConnectImplementationsToTypesClosing(typeof(ValueResolver<,>))
                .OnAddedPluginTypes(t => t.HybridHttpOrThreadLocalScoped());
        });
    }
}
هدف اصلی، وهله سازی خودکار IMappingEngine است و برای رسیدن به آن، باید تمام وابستگی‌های کلاس MappingEngine را مانند IConfigurationProvider و سایر مواردی که مشاهده می‌کنید، مشخص کرد. پس از این تنظیمات، کلاس ObjectFactory سفارشی برنامه به شکل ذیل جهت معرفی AutomapperRegistry تغییر خواهد کرد:
public static class SmObjectFactory
{
    private static readonly Lazy<Container> _containerBuilder =
        new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
    public static IContainer Container
    {
        get { return _containerBuilder.Value; }
    }
 
    private static Container defaultContainer()
    {
        var container = new Container(cfg =>
        {
            cfg.AddRegistry<AutomapperRegistry>();
            cfg.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AddAllTypesOf<Profile>().NameBy(item => item.FullName);
            });
        });
 
        configureAutoMapper(container);
 
        return container;
    }
 
    private static void configureAutoMapper(IContainer container)
    {
        var configuration = container.TryGetInstance<IConfiguration>();
        if (configuration == null) return;
        //saying AutoMapper how to resolve services
        configuration.ConstructServicesUsing(container.GetInstance);
        foreach (var profile in container.GetAllInstances<Profile>())
        {
            configuration.AddProfile(profile);
        }
    }
}
در اینجا علاوه بر معرفی AutomapperRegistry، یک مورد دیگر نیز اضافه شده‌است: یافتن خودکار کلاس‌هایی از نوع Profile و همچنین فراخوانی متد AddProfile کتابخانه‌ی AutoMapper به صورت خودکار. به این ترتیب دیگر نیازی نخواهد بود تا در ابتدای کار برنامه، متد Mapper.Initialize را جهت معرفی کلاس‌های Profile فراخوانی کرد و این‌کار به صورت خودکار توسط متد configureAutoMapper انجام می‌شود.


تغییرات لایه سرویس برنامه جهت استفاده از IoC Container

اکنون که IoC Container ما با نحوه‌ی یافتن وابستگی‌های IMappingEngine آشنا شده‌است، تنها کافی است این اینترفیس را در سازنده‌ی کلاس سرویس خود تزریق کنیم:
public class UsersService : IUsersService
{
    private readonly IMappingEngine _mappingEngine;
 
    public UsersService(IMappingEngine mappingEngine)
    {
        _mappingEngine = mappingEngine;
    }
 
    public UserViewModel GetName(int id)
    {
        var dbUser1 = new User
        {
            Id = 1,
            Name = "Test",
            RegistrationDate = DateTime.Now.AddDays(-10)
        };
 
        var uiUser = new UserViewModel();
        _mappingEngine.Map(source: dbUser1, destination: uiUser);
        return uiUser;
    }
}
و پس از آن از متد Map این اینترفیس بجای فراخوانی مستقیم Mapper.Map می‌توان استفاده کرد. به این ترتیب وابستگی مورد نیاز این کلاس، از طریق سازنده‌ی آن به آن تزریق شده‌است و دیگر فراخوانی‌های استاتیک را در اینجا مشاهده نمی‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample03.zip
مطالب
Angular Material 6x - قسمت سوم - طرحبندی برنامه
پس از نصب بسته‌ی Angular Material و آشنایی با سیستم Angular Flex Layout برای پوشش کمبود سیستم طرحبندی آن، در این قسمت طرح ابتدایی دفترچه تلفن این سری را پیگیری می‌کنیم تا به طراحی زیر برای حالت‌های دسکتاپ و موبایل برسیم:



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


ایجاد ماژول مدیریت تماس‌ها

در قسمت اول، برنامه را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد کردیم که نتیجه‌ی آن تولید فایل src\app\app-routing.module.ts می‌باشد:
 ng new MaterialAngularClient --routing
در ادامه ماژول مخصوص مدیریت تماس‌ها را ایجاد می‌کنیم که به آن feature module هم گفته می‌شود. برای این منظور دستور زیر را اجرا کنید:
 ng g m ContactManager -m app.module --routing


این دستور ماژول جدید contact-manager را به همراه تنظیمات ابتدایی مسیریابی و همچنین به روز رسانی app.module، برای درج آن، ایجاد می‌کند. البته در این حالت نیاز است به app.module.ts مراجعه کرد و محل درج آن‌را تغییر داد:
import { ContactManagerModule } from "./contact-manager/contact-manager.module";

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    CoreModule,
    SharedModule.forRoot(),
    ContactManagerModule,
    AppRoutingModule
  ],
})
export class AppModule { }
به صورت پیش‌فرض ContactManagerModule، پس از AppRoutingModule ذکر می‌شود و چون مسیر catch all را در ادامه در AppRoutingModule قرار می‌دهیم، دیگر هیچگاه مسیریابی‌های ContactManagerModule پردازش نخواهند شد. به همین جهت باید آن‌را پیش از AppRoutingModule قرار داد.
سپس دستور زیر را اجرا می‌کنیم تا کامپوننت contact-manager-app در ماژول contact-manager ایجاد شود:
 ng g c contact-manager/ContactManagerApp --no-spec
با این خروجی:
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.html (38 bytes)
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.ts (319 bytes)
CREATE src/app/contact-manager/contact-manager-app/contact-manager-app.component.css (0 bytes)
UPDATE src/app/contact-manager/contact-manager.module.ts (436 bytes)
همانطور که ملاحظه می‌کنید این دستور کار به روز رسانی contact-manager.module را نیز جهت معرفی این کامپوننت جدید انجام داده‌است.
این کامپوننت به عنوان میزبان سایر کامپوننت‌هایی که در مقدمه‌ی بحث عنوان شدند، عمل می‌کند. این کامپوننت‌ها را به صورت زیر در پوشه‌ی components ایجاد می‌کنیم:
ng g c contact-manager/components/toolbar --no-spec
ng g c contact-manager/components/main-content --no-spec
ng g c contact-manager/components/sidenav --no-spec
با این خروجی:



تنظیمات مسیریابی برنامه

در ادامه به src\app\app-routing.module.ts مراجعه کرده و این ماژول جدید را به صورت lazy load معرفی می‌کنیم:
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

const routes: Routes = [
  { path: "contactmanager", loadChildren: "./contact-manager/contact-manager.module#ContactManagerModule" },
  { path: "", redirectTo: "contactmanager", pathMatch: "full" },
  { path: "**", redirectTo: "contactmanager" }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا تنظیمات صفحه‌ی پیش‌فرض برنامه و همچنین not found و یا catch all را نیز مشاهده می‌کنید که هر دو به contactmanager تنظیم شده‌اند.

سپس تنظیمات مسیریابی ماژول مدیریت تماس‌ها را در فایل src\app\contact-manager\contact-manager-routing.module.ts به صورت زیر تغییر می‌دهیم:
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

import { MainContentComponent } from "./components/main-content/main-content.component";
import { ContactManagerAppComponent } from "./contact-manager-app/contact-manager-app.component";

const routes: Routes = [
  {
    path: "", component: ContactManagerAppComponent,
    children: [
      { path: "", component: MainContentComponent }
    ]
  },
  { path: "**", redirectTo: "" }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ContactManagerRoutingModule { }
در اولین بار بارگذاری این ماژول، کامپوننت ContactManagerApp بارگذاری می‌شود. همچنین مسیر not found نیز به همان مسیر ریشه‌ی این کامپوننت تنظیم شده‌است.
کامپوننت ContactManagerApp که کار هاست سایر کامپوننت‌های این ماژول را بر عهده دارد، دارای router-outlet خاص خود خواهد بود. به همین جهت برای آن children تعریف شده‌است که مسیر پیش‌فرض آن، بارگذاری کامپوننت MainContent است.
بنابراین نیاز است به فایل contact-manager-app\contact-manager-app.component.html مراجعه و ابتدا منوی کنار صفحه را به آن افزود:
 <app-sidenav></app-sidenav>
app-sidenav همان selector کامپوننت sidenav است که در فایل sidenav\sidenav.component.ts قابل مشاهده‌است.
سپس در قالب sidenav\sidenav.component.html، کار تعریف toolbar و همچنین router-outlet را انجام می‌دهیم:
<app-toolbar></app-toolbar>
<router-outlet></router-outlet>
بر اساس مسیریابی که تعریف کردیم، router-outlet کار نمایش Main Content را انجام می‌دهد.


معرفی Angular Material به ماژول جدید مدیریت تماس‌ها

در قسمت اول، یک فایل material.module.ts را ایجاد کردیم که به همراه تعریف تمامی کامپوننت‌های Angular Material بود. سپس آن‌را به shared.module.ts افزودیم که حاوی تعریف ماژول فرم‌ها و همچنین Flex Layout نیز هست. به همین جهت برای معرفی این‌ها به این ماژول جدید تنها کافی است در فایل src\app\contact-manager\contact-manager.module.ts در قسمت imports، کار معرفی SharedModule صورت گیرد:
import { SharedModule } from "../shared/shared.module";

@NgModule({
  imports: [
    CommonModule,
    SharedModule,
    ContactManagerRoutingModule
  ]
})
export class ContactManagerModule { }
تا اینجا پیش از ادامه‌ی کار، فرمان ng serve -o را صادر کنید تا مطمئن شویم همه چیز به درستی قابل دریافت و کامپایل است.


پس از اجرای برنامه مشاهده می‌کنید که ابتدا ماژول مدیریت تماس‌ها بارگذاری شده‌است و سپس contact-manager-app عمل و sidenav را بارگذاری کرده و آن نیز سبب نمایش کامپوننت toolbar و سپس main-content شده‌است.


تنظیم طرحبندی برنامه توسط کامپوننت‌های Angular Material و همچنین Flex Layout


پس از این تنظیمات اکنون نوبت به تنظیم طرحبندی برنامه‌است و آن‌را با قراردادن کامپوننت Sidenav بسته‌ی Angular Material شروع می‌کنیم:
<mat-sidenav-container *ngIf="shouldRun">
   <mat-sidenav mode="side" opened>
      Sidenav content
   </mat-sidenav>
   Primary content
</mat-sidenav-container>
از کامپوننت Sidenav عموما برای طراحی منوی راهبری سایت استفاده می‌شود. این کامپوننت در سه حالت قابل تنظیم است که بر روی نحوه‌ی نمایش Sidenav content و Primary content تاثیرگذار است:
- Over: قسمت Sidenav content بر روی Primary content قرار می‌گیرد.
- Push: قسمت Sidenav content قسمت Primary content را از سر راه خود بر می‌دارد.
- Side:  قسمت Sidenav content در کنار Primary content قرار می‌گیرد.

در اینجا از حالت Side، در صفحات نمایشی بزرگ (اولین تصویر این قسمت) و از حالت Over، در صفحات نمایشی موبایل (مانند تصویر زیر) استفاده خواهیم کرد.



در ابتدا کدهای کامل هر سه کامپوننت و سپس توضیحات آن‌ها را مشاهده خواهید کرد:


تنظیم margin در CSS اصلی برنامه

زمانیکه sidenav و toolbar را بر روی صفحه قرار می‌دهیم، فاصله‌ای بین آن‌ها و لبه‌های صفحه مشاهده می‌شود. برای اینکه این فاصله را به صفر برسانیم، به فایل src\styles.css مراجعه کرده و margin بدنه‌ی صفحه را به صفر تنظیم می‌کنیم:
@import "~@angular/material/prebuilt-themes/indigo-pink.css";

body {
  margin: 0;
}


طراحی قالب main content

<mat-card>
  <h1>Main content</h1>
</mat-card>
برای نمایش main-content از کامپوننت ساده‌ی card استفاده شده‌است که به فایل main-content\main-content.component.html اضافه خواهد شد. این قسمت در نهایت توسط router-outlet نمایش داده می‌شود.


طرای منوی راهبری واکنشگرا

sidenav\sidenav.component.css 
 sidenav\sidenav.component.html 
.app-sidenav-container {
  position: fixed;
}

.app-sidenav {
  width: 240px;
}

.wrapper {
  margin: 50px;
}
<mat-sidenav-container fxLayout="row"
         fxFill>
  <mat-sidenav #sidenav 
    fxFlex="1 1 100%" [opened]="!isScreenSmall"
    [mode]="isScreenSmall ? 'over' : 'side'">
    <mat-toolbar color="primary">
      Contacts
    </mat-toolbar>
    <mat-list>
      <mat-list-item>Item 1</mat-list-item>
      <mat-list-item>Item 2</mat-list-item>
      <mat-list-item>Item 3</mat-list-item>
    </mat-list>
  </mat-sidenav>

  <mat-sidenav-content fxLayout="column" fxFlex="1 1 100%" fxFill>
    <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
    <div>
      <router-outlet></router-outlet>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>

- نمای کلی صفحه در این قسمت طراحی شده‌است. sidenav-container که در برگیرنده‌ی اصلی است، به fxLayout از نوع row تنظیم شده‌است. یعنی mat-sidenav و mat-sidenav-content دو ستون آن‌را از چپ به راست تشکیل می‌دهند و درون یک ردیف، سیلان خواهند یافت. همچنین می‌خواهیم این container کل صفحه را پر کند، به همین جهت از fxFill استفاده شده‌است. این fxFill اعمال شده‌ی به container، زمانی عمل خواهد کرد که position آن در css، به fixed تنظیم شود که اینکار در css این قالب و در کلاس app-sidenav-container آن انجام شده‌است.
- سپس toolbar و همچنین router-outlet که main content را نمایش می‌دهند، داخل sidenav-content قرار گرفته‌اند و هر دو با هم، ستون دوم این طرحبندی را تشکیل می‌دهند. به همین جهت fxLayout آن به column تنظیم شده‌است (ستون اول آن، لیست تماس‌ها است و ستون دوم آن، از دو ردیف toolbar و main-content تشکیل می‌شود).
- اگر دقت کنید یک template reference variable به نام sidenav# به container اعمال شده‌است. از آن، جهت باز و بسته کردن sidenav استفاده می‌شود:
<app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
زمانیکه در toolbar بر روی دکمه‌ی منوی همبرگری کلیک شود، متد sidenav.toggle فراخوانی شده و این مورد سبب نمایان شدن مجدد sidenav خواهد شد. در این مورد در ادامه بیشتر بحث می‌کنیم.
- mat-sidenav از دو قسمت تشکیل شده‌است. بالای آن توسط mat-toolbar صرفا کلمه‌ی Contacts نمایش داده می‌شود و سپس ذیل آن، لیست فرضی تماس‌ها توسط کامپوننت mat-list قرار گرفته‌اند (تا فعلا خالی نباشد. در قسمت‌های بعدی آن‌را پویا خواهیم کرد). رنگ تولبار آن‌را ("color="primary) نیز به primary تنظیم کرده‌ایم تا خاکستری پیش‌فرض آن نباشد.
- کار کلاس mat-elevation-z10 این است که بین sidenav و main-content یک سایه‌ی سه بعدی را ایجاد کند که آن‌را در تصاویر مشاهده می‌کنید. عددی که پس از z قرار می‌گیرد، میزان عمق سایه را مشخص می‌کند.
- این قسمت از sidenav به همراه دو خاصیت opened و همچنین mode است که به مقدار isScreenSmall عکس العمل نشان می‌دهند:
<mat-sidenav  [opened]="!isScreenSmall" [mode]="isScreenSmall ? 'over' : 'side'">
در اینجا می‌خواهیم اگر اندازه‌ی صفحه xs شد، حالت over بجای حالت پیش‌فرض side تنظیم شود. یعنی در حالت موبایل و اندازه‌ی صفحه‌ی کوچک، sidenav در صورت فراخوانی متد sidenav.toggle در toolbar، بر روی قسمتی از صفحه ظاهر شود و نه در کنار آن که مخصوص حالت تمام صفحه است. همچنین می‌خواهیم اگر اندازه‌ی صفحه کوچک بود، sidenav بسته شود و نمایان نباشد. به همین جهت خاصیت opened آن به isScreenSmall تنظیم شده‌است. مدیریت خاصیت isScreenSmall در کدهای این کامپوننت به صورت زیر انجام می‌شود:

محتویات فایل sidenav\sidenav.component.ts:
import { Component, OnDestroy, OnInit } from "@angular/core";
import { MediaChange, ObservableMedia } from "@angular/flex-layout";
import { Subscription } from "rxjs";

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

  isScreenSmall = false;
  watcher: Subscription;

  constructor(private media: ObservableMedia) {
    this.watcher = media.subscribe((change: MediaChange) => {
      this.isScreenSmall = change.mqAlias === "xs";
    });
  }

  ngOnInit() {
  }

  ngOnDestroy() {
    this.watcher.unsubscribe();
  }
}
ObservableMedia را در انتهای قسمت دوم این سری بررسی کردیم. کار آن گوش فرادادن به تغییرات اندازه‌ی صفحه‌است. زمانیکه mqAlias آن برای مثال مساوی xs شد، یعنی در حالت موبایل قرار داریم. در این حالت مقدار خاصیت isScreenSmall به true تنظیم می‌شود و برعکس. با توجه به اینکه این media یک Observable است، نیاز است کار unsubscribe از آن نیز همواره در کدها وجود داشته باشد که نمونه‌ای از آن در متد ngOnDestroy صورت گرفته‌است.
تاثیر خاصیت isScreenSmall بر روی دو خاصیت opened و mode کامپوننت sidenav را در دو تصویر زیر مشاهده می‌کنید. اگر اندازه‌ی صفحه کوچک شود، ابتدا sidenav مخفی می‌شود. اگر کاربر بر روی دکمه‌ی منوی همبرگری کلیک کند، سبب نمایش مجدد sidenav، اینبار با حالت over و بر روی محتوای زیرین آن خواهد شد:




طراحی نوار ابزار واکنشگرا

کدهای قالب و css تولبار (ستون دوم طرحبندی کلی صفحه) را در ادامه مشاهده می‌کنید:
  toolbar\toolbar.component.css    toolbar\toolbar.component.html 
 
.sidenav-toggle {
  padding: 0;
  margin: 8px;
  min-width:56px;
}
 
<mat-toolbar color="primary">
  <button mat-button fxHide fxHide.xs="false" 
              class="sidenav-toggle" (click)="toggleSidenav.emit()">
    <mat-icon>menu</mat-icon>
  </button>

  <span>Contact Manager</span>
</mat-toolbar>

با توجه به استفاده‌ی از fxHide، یعنی دکمه‌ی نمایش منوی همبرگری در تمام حالات مخفی خواهد بود. برای لغو آن و نمایش آن در حالت موبایل، از حالت واکنشگرای آن یعنی fxHide.xs استفاده می‌کنیم (قسمت «کار با API واکنشگرای Angular Flex layout» در مطلب قبلی این سری). به این ترتیب زمانیکه کاربر اندازه‌ی صفحه را کوچک می‌کند و یا اندازه‌ی واقعی صفحه‌ی نمایش او کوچک است، این دکمه نمایان خواهد شد.
همچنین در sidenav یک چنین تعریفی را داریم:
 <app-toolbar (toggleSidenav)="sidenav.toggle()"></app-toolbar>
بروز رخ‌داد toggleSidenav سبب خواهد شد که متد sidenav.toggle فراخوانی شود و سبب نمایش sidenav در اندازه‌های کوچک صفحه‌ی نمایشی گردد. این رخ‌داد سفارشی را نیز به رخ‌داد click دکمه‌ی همبرگری تولبار متصل کرده‌ایم که با کلیک بر روی آن، کار emit آن صورت می‌گیرد. این emit نیز سبب خواهد شد تا sidenav.toggle متصل به سمتی دیگر، فعال شود. نحوه‌ی تعریف این رخ‌داد سفارشی را در کدهای کامپوننت تولبار، در ادامه مشاهده می‌کنید:

محتویات فایل toolbar\toolbar.component.ts:
import { Component, EventEmitter, OnInit, Output } from "@angular/core";

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

  @Output() toggleSidenav = new EventEmitter<void>();

  constructor() { }

  ngOnInit() {
  }
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-02.zip
برای اجرای آن نیز ابتدا فایل restore.bat و سپس فایل ng-serve.bat را اجرا کنید. پس از اجرای برنامه، یکبار آن‌را در حالت تمام صفحه و بار دیگر با کوچک‌تر کردن اندازه‌ی مرورگر آزمایش کنید. در حالتیکه به اندازه‌ی موبایل رسیدید، بر روی دکمه‌ی همبرگری نمایان شده کلیک کنید تا عکس العمل آن و نمایش مجدد sidenav را در حالت over، مشاهده نمائید.
نظرات مطالب
EF Code First #12
یک سوال:
در نگارش سوم Structure map، شما از دستور زیر استفاده کردید:
private void btnHomeLoanRequest_Click(object sender, System.EventArgs e)
        {
            using (var container = ObjectFactory.Container.GetNestedContainer()) // کانتکست را به صورت خودکار دیسپوز می‌کند
            {
                var uow = container.GetInstance<IUnitOfWork>();
                var employeeService = container.GetInstance<IEmployeeService>();
که با توجه به توضیح شما (^) نیازی به استفاده آن در متد فوق نیست و با تزریق frmMain در program.cs این وابستگی‌ها بوسیله container تزریق و تخریب می‌شود؟
مطالب
Gulp #3
در قسمت اول گالپ را معرفی کردیم و در مقاله قبلی به نوشتن اولین تسک با گالپ پرداختیم. در این قسمت می‌خواهیم با نصب bower، پروژه‌ی workflow بوت استرپ راستچین شده را انجام دهیم.

نصب bower



bower یک مدیریت پکیج سمت Front end است و از مزیای استفاده از آن می‌توان به موارد زیر اشاره کرد:
  • ساده کردن تعریف وابستگی‌های منابع پروژه با تعریف یک فایل bower.json
  • نیازی به commit کردن واستگی‌های پروژه نیست.
  • با ذکر ورژن مربوط به وابستگی یا محدوده‌ی قابل قبول برای آن، به روز رسانی منابع به سادگی با یک دستور انجام می‌شود.
  • وابستگی های وابسته به یک منبع  را نیز نصب می‌کند. برای مثال زمانیکه بوت استرپ را به عنوان وابستگی پروژه تعریف می‌کنیم، وابستگی آن یعنی jquery را چون در فایل bower.json بوت استرپ تعریف شده‌است، به صورت خودکار دانلود می‌کند.
  • در نهایت افراد هم تیمی یا توسعه دهندگان دیگر به راحتی با زدن دستور bower install تمام وابستگی‌های پروژه را می‌توانند نصب کنند.
برای نصب آن کافی است دستور زیر را بزنید و بعد از نصب نیز دستور خط دوم را در مسیر پروژه وارد کنید تا یک فایل bower.json را برایمان بسازد. برای اینکار به سوال‌هایی که می‌پرسد باید جواب دهیم. تنها نکته‌ای که قابل ذکر است، پاسخ به سوال ? what types of modules does this package expose است که باید گزینه‌ی Node را انتخاب کنید. 
sudo npm install -g bower
bower init
حال می‌خواهیم وابستگی‌های پروژه را نصب کنیم که عبارتند از bootstrap-sass,fontawesome,bootstrap-rtl :
bower install bootstrap-sass-official --save  
bower install fontawesome --save
bower install bootstrap-rtl --save
نکته : عبارت save-- وابستگی مربوطه را به bower.json اضافه می‌کند. اگر نصب با موفقیت صورت گرفته باشد، پکیج‌های مربوطه را می‌توانید در فولدر bower_components در root پروژه مشاهده کنید.

نصب پلاگین‌های مورد نیاز gulp

ما می‌خواهیم که بوت استرپ و نگارش sass آن‌را کامپایل کنیم و همچنین وابستگی‌های bower پروژه را از طریق گالپ نصب کنیم تا نیازی به زدن bower install نباشد و توسعه دهنده‌ی پروژه فقط با زدن npm install، تمام وابستگی‌های پروژه‌ی ما را نصب کند. می‌توان تمام پلاگین‌ها را پشت سر هم با یک دستور نصب کرد و یا به صورت جداگانه این کار را انجام داد.
sudo npm install gulp gulp-ruby-sass gulp-notify gulp-bower --save-dev
نکته۱:پلاگین gulp-notify به منظور نشان دادن خطاها در ترمینال است؛ تا در صورت وجود اشتباه در کامپایل فایل‌های Sass، کل روند گالپ متوقف نشود.
نکته ۲ : برای اینکه کامپایل sass انجام شود نیاز به Ruby دارید. برای ویندوز می‌توانید از روبی اینستالر استفاده کنید.

نوشتن تسک‌ها برای گالپ

به قسمت مهم و هیجان انگیز کار رسیدیم! همان طور در مقاله قبلی گفتیم، ابتدا باید ماژول‌هایی را که نصب کردیم، include کنیم به این صورت:
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    notify = require('gulp-notify'),
    bower = require('gulp-bower');
برای اینکه دسترسی به مسیر‌های مهم پروژه آسان‌تر شود، آن‌ها را درون یک شیء نگه داری می‌کنیم.
var config = {
    sassPath = './resources/sass',
    bowerDir = './bower_components'
}
تسک باور را اضافه می‌کنیم تا کار bower install را خودکار کنیم. مزیت این کار این است اگر یک هم تیمی، پکیج جدیدی را در حین توسعه‌ی پروژه نصب کرد، بدون اینکه لازم باشد تا در جایی از پروژه، بقیه را از آن مطلع کنید، فقط با زدن gulp خیالتان راحت شود که تمام کارهایی که باید انجام دهید، گالپ برایتان انجام می‌دهد.
// create a task to do bower install
gulp.task('bower', function() {
    return bower()
      .pipe(gulp.dest(config.bowerDir))
});
در گام بعدی، تسک جاوا اسکریپت را اضافه می‌کنیم. یعنی جی کوری و فایل bootstrap.js را به مسیر public/js می‌آوریم. فولدر public برای جدا سازی فایل‌های نهایی از فایل‌های توسعه است و به همین صورت برای فونت آیکن‌های fontawesome.

// Copy js files to public folder
gulp.task('js', function() {
    return gulp.src([config.bowerDir + '/bootstrap-sass-official/assets/javascripts/bootstrap.min.js',
                    config.bowerDir + '/jquery/dist/jquery.min.js'
            ])
        .pipe(gulp.dest('./public/js'));

});

// Copy fontawesome icons to public/fonts folder
gulp.task('icons', function() {
    return gulp.src(config.bowerDir + '/fontawesome/fonts/**.*')
        .pipe(gulp.dest('./public/fonts'));
});

:برای سی اس اس هم تسک مربوطه‌اش را به صورت زیر می‌نویسیم

gulp.task('css', function() {
    return sass(config.sassPath + '/style.scss', { // Our coustom sass
      style: 'compressed',  // minify css
      loadPath: [ // load paths to easy use import in resources/sass
        './resources/sass',
        config.bowerDir + '/bootstrap-sass-official/assets/stylesheets', // bootstrap sass files
        config.bowerDir + '/fontawesome/scss' // awesome icons sass files
      ]
    })
});
 حال تعریف می‌کنیم که اگر خطایی در حین کامپایل رخ داد، آن را به ما نشان دهد و در نهایت فایل کامپایل و فشرده شده را در مسیر خروجی قرار می‌دهیم. کد‌ها را به صورت زیر به روز می‌کنیم
gulp.task('css', function() {
    return sass(config.sassPath + '/style.scss', { // Our coustom sass
      style: 'compressed',  // minify css
      loadPath: [ // load paths to easy use import in resources/sass
        './resources/sass',
        config.bowerDir + '/bootstrap-sass-official/assets/stylesheets', // bootstrap sass files
        config.bowerDir + '/fontawesome/scss' // awesome icons sass files
      ]
    })
        .on('error', notify.onError(function(error) {
            return 'Error: ' + error.message;
        }))
      .pipe(gulp.dest('./public/css'));
});
اینجا ما می‌خواهیم که کار‌ها را خودکار سازی کنیم تا با تغییر و ذخیره‌ی مجدد فایل‌های سس، تسک سی اس اس را انجام دهد. برای این کار کد‌های زیر را اضافه می‌کنیم
// Rerun the task when a file changes
gulp.task('watch', function() {

    gulp.watch(config.sassPath + '/**/*.scss', ['css']); 

});
در نهایت برای سادگی می‌توانیم مجموعه‌ای از وظایف را در یک تسک تعریف کنیم تا به راحتی و با زدن تنها یک دستور در ترمینال، کار‌ها خودکار سازی شوند
// Run this task with :  gulp
// OR gulp default
gulp.task('default', ['bower', 'icons', 'css','js']);

بسیار خوب؛ ما توانستیم پایه‌ی ورک فلو یمان را بسازیم. در مقاله‌ی بعدی از پلاگین‌های دیگری برای بهینه سازی کارهایمان کمک خواهیم گرفت

مخزن گیت هاب 
اشتراک‌ها
Visual Studio Code 1.34 منتشر شد

Welcome to the April 2019 release of Visual Studio Code. During April, we were busy with the Preview release of the Remote Development extensions. These extensions let you work with VS Code over SSH on a remote machine or VM, in Windows Subsystem for Linux (WSL), or inside a Docker container. 

Visual Studio Code 1.34 منتشر شد
اشتراک‌ها
آشنایی با IBM Swift Sandbox

The IBM Swift Sandbox is an interactive website that lets you write Swift code and execute it in a server environment – on top of Linux! Each sandbox runs on IBM Cloud in a Docker container. In addition, both the latest versions of Swift and its standard library are available for you to use. 

آشنایی با IBM Swift Sandbox
بازخوردهای دوره
تزریق وابستگی‌ها
- کار آزاد سازی منابع را به صورت خودکار GC انجام خواهد داد. فقط وهله سازی در اینجا توسط IoC Container انجام می‌شود. مابقی مسایل مانند قبل است.
- خیر. برای نمونه همین سایت جاری از StructerMap استفاده می‌کند و مشکلی هم از لحاظ میزان مصرف حافظه وجود ندارد. اساسا ایجاد چند شیء در سازنده یک کلاس آنچنان حافظه‌ای را مصرف نمی‌کنند مگر اینکه سازنده‌های این کلاس‌ها دارای تخصیص منابع قابل توجهی باشند. بنابراین سازنده‌های تعریف شده را سبک درنظر بگیرید و طراحی کنید.
- یک قسمت را به StructerMapاختصاص خواهم داد؛ به صورت جداگانه.
مطالب
ایجاد دامنه‌های سفارشی در IIS Express
اگر شما در حال راه اندازی برنامه‌ای هستید که از یک زیر دامنه یا نام دامنه برای شناسایی یک کاربر یا زیرمجموعه دینامیک استفاده می‌کند، ممکن است در تلاش برای آزمایش قابلیت‌های زیر دامنه به صورت محلی مشکلی داشته باشید. در این مقاله قصد داریم یک وب سایت با استفاده از domain و subdomains  را به صورت محلی بر روی IIS Express اجرا کنیم.

اولا، اجازه بدهید نگاهی به تنظیم یک دامنه محلی داشته باشیم. زمانیکه شما برنامه محلی را اجرا می‌کنید IIS Express به صورت محلی، پورتی خاص را به برنامه اختصاص می‌دهد:


فرض کنید می‌خواهیم برای سایت خود، درگاه بانک را راه اندازی کنیم. برنامه را به صورت محلی اجرا کرده و  زمانیکه قصد ارتباط با بانک را دارید، با پیامی که دامنه شما در سیستم پرداخت بانکی ثبت نشده، مواجه می‌شوید. در اینجا بانک انتظار دارد که ما از طریق دامنه‌ای که قبلا در سیستم پرداخت بانک ثبت کرده‌ایم برای مثال (www.elemarket.ir) با آن ارتباط برقرار کنیم؛ ولی به دلیل ارتباط به صورت محلی با یک‌چنین دامنه‌ای (localhost:59395) روبه‌رو شده و پیغام عدم امکان برقراری ارتباط را میدهد.

حال قصد داریم با تغییر دامنه به صورت سفارشی، این مشکل را برطرف کرده، تا درحقیقت  قبل از قرار دادن سایت بر روی سرور و تست عملیات بتوانیم به صورت محلی نتیجه را دریافت کنیم.


استفاده از Telerik Fiddler برای ایجاد یک دامنه‌ی سفارشی

برای این کار می‌توانید از برنامه‌ی سبک وزن Telerik Fiddler استفاده کنید و تنها کافیست به قسمت Tools>Host برنامه بروید و آدرس محلی برنامه (localhost:59395 ) و آدرس دامنه‌ی مورد نظر را وارد کنید تا برنامه هم به صورت local و هم توسط یک دامنه‌ی سفارشی، در دسترس باشد. برنامه‌ی Fiddler را باز نگه داشته و به ویژوال استودیو بازگرید.
 


در نهایت، پیکربندی IIS Express خود را با اتصالهای جدید به روز کنید. شما معمولا میتوانید پیکربندی IIS Express Application خود را در این مسیر پیدا کنید .
  C: \ Users \ YOUR_USERNAME \ Documents \ IISExpress \ config / applicationhost.config
 اگر از نسخه‌های ویژوال استودیو 2015 به بعد استفاده میکنید، فایل applicationhost.config  شما در داخل پوشه‌ی برنامه شما  vs\config\applicationhost.config.\~
در دسترس است.
فایل را باز کرده و گره <sites> را جستجو کنید. شما باید بتوانید درخواست خود را در فهرست سایت‌ها مشاهده کنید. ما قصد داریم HTTP binding را با تغییر localhost به نام دامنه‌ی سفارشی خود به‌روز رسانی کنیم. در اینجا HTTP binding به صورت پیشفرض بر روی localhost میباشد:


پس از  تغییر localhost و ذخیره کردن تنظیمات، بررسی کنید تا IIS Express در حال اجرا نباشد. حال برنامه را اجرا کنید.


نکته:
اگر هنگام اجرای برنامه با خطای
  “Unable to create the virtual directory. You must use specify ‘localhost’ for the server name”
و یا
 " Invalid URL:the hostame could not be parsed "
مواجه شدید، برنامه را بسته و در حالت Run as Administrator اجرا کنید.

بعد از اجرای برنامه به طور پیشفرض بر روی همان پورت localhost اجرا شده، حال به دامنه‌ی مورد نظر که ایجاد کرده‌اید بروید:


در اینجا بعد از تلاش برای ارتباط با بانک، دیگر با پیام «دامنه شما در سیستم پرداخت بانکی ثبت نشده‌است» مواجه نشده و با موفقیت امکان تست برنامه را داریم:


 
مطالب
امکان تعریف قالب‌ها در Angular با دایرکتیو ng-template
معرفی دایرکتیو ng-template

همانطور که از اسم آن نیز مشخص است، ng-template به معنای قالب انگیولار است و هدف از آن، ارائه‌ی قسمتی از قالب نهایی یک کامپوننت می‌باشد. فریم ورک Angular از دایرکتیو ng-template در پشت صحنه‌ی دایرکتیوهای ساختاری مانند ngIf، ngFor و ngSwitch استفاده می‌کند. برای مثال، قسمت if‌، تبدیل به یک ng-template شده و else آن نیز تبدیل به یک ng-template ضمنی دیگر خواهد شد.


روش فعالسازی و نمایش قالب‌ها

باید دقت داشت که تعریف یک ng-template سبب رندر هیچگونه خروجی در صفحه نمی‌شود و باید به طریقی درخواست فعالسازی و رندر آن‌را ارائه داد.
<div class="lessons-list" *ngIf="lessons else loading">
... 
</div>

<ng-template #loading>
     <div>Loading...</div>
</ng-template>
یکی از روش‌های معمول نمایش قالب‌ها، استفاده از ngIf/else است. در این مثال اگر آرایه‌ی فرضی دروس دارای عضوی باشد، div مرتبط نمایش داده می‌شود؛ در غیراینصورت، قالبی که توسط یک template reference variable به نام loading مشخص شده‌است، نمایش داده خواهد شد (loading‌# در اینجا).
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کننده‌ی div اصلی تشکیل می‌شود که از دید ما پنهان است.

استفاده از ngIf برای نمایش یک قالب، یکی از روش‌های کار با آن‌ها است. روش دیگر، استفاده از ng-container است:
<ng-container *ngTemplateOutlet="loading"></ng-container>
در اینجا دایرکتیو ساختاری ngTemplateOutlet، قالبی را که توسط loading# مشخص شده‌است، وهله سازی کرده و به درون ng-container تزریق می‌کند که در این حالت سبب نمایش آن نیز خواهد شد.


سطوح دسترسی در قالب‌ها

اکنون این سؤال مطرح است: «آیا یک قالب میدان دید متغیرهای خاص خودش را دارد؟ این قالب به چه متغیرهایی دسترسی دارد؟»
درون بدنه یک تگ ng-template، به همان متغیرهایی که در قالب خارجی آن قابل دسترسی هستند، دسترسی خواهیم داشت؛ برای نمونه در مثال فوق به همان متغیر lessons. به عبارتی تمام وهله‌های ng-templateها، به همان متغیرهای زمینه‌ی قالبی که درون آن جای‌گرفته‌اند، دسترسی دارند. به علاوه هر قالب می‌تواند متغیرهای خاص خود را نیز تعریف کند.
در ادامه قالب یک کامپوننت را به صورت ذیل فرض کنید:
<ng-template #estimateTemplate let-lessonsCounter="estimate">
     <div> Approximately {{lessonsCounter}} lessons ...</div>
</ng-template>
<ng-container *ngTemplateOutlet="estimateTemplate;context:ctx">
</ng-container>
با کدهای ذیل
export class AppComponent {
     totalEstimate = 10;
     ctx = {estimate: this.totalEstimate};
}
در اینجا قالب تعریف شده، توسط پیشوند -let دارای یک متغیر ورودی به نام lessonsCounter شده‌است (می‌تواند چندین متغیر ورودی داشته باشد). شکل کلی آن به صورت "let-{{templateVariableName}}=”contextProperty است.
 این متغیر lessonsCounter تنها داخل این قالب است که قابل مشاهده و دسترسی می‌باشد و نه خارج از آن. مقدار این متغیر نیز توسط عبارت estimate تامین می‌شود. این عبارت زمانیکه ng-container  سبب وهله سازی estimateTemplate می‌شود، توسط شیء ویژه‌ای به نام context مقدار دهی خواهد شد.
برای اینکه عبارت estimate در قالب، قابل استخراج از شیء context باشد، باین دقیقا خاصیتی به همین نام در این شیء تعریف شده باشد (و برای سایر متغیرها نیز به همین ترتیب). به همین جهت است که خاصیت عمومی ctx در کلاس AppComponent به صورت یک شیء دارای خاصیت estimate تعریف شده‌است تا بتوان نگاشتی را بین این مقدار و عبارت estimate برقرار کرد.

نکته 1: اگر در اینجا متغیری تعریف شود، اما محل تامین آن مشخص نگردد، به دنبال خاصیتی به نام implicit$ خواهد گشت. برای مثال در قالب ذیل، متغیر default تعریف شده‌است؛ اما عبارت تامین کننده‌ی آن مشخص نیست:
    <ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container>
    <ng-template #templateRef let-default>
      <div>
        '{{default}}'
      </div>
    </ng-template>
در این حالت مقدار default از خاصیت implicit$ شیء منتسب به context دریافت می‌شود:
export class AppComponent {
  exampleContext = {
    $implicit: 'default context property when none specified'
  };
}

نکته 2:
نحوه‌ی تعریف شیء context را به صورت ذیل نیز می‌توان مشخص کرد:
 [ngOutletContext]="exampleContext"


دسترسی به قالب‌ها در کدهای کامپوننت‌ها

در اینجا قالبی را مشاهده می‌کنید که توسط یک template reference variable به نام defaultTabButtons مشخص شده‌است:
<ng-template #defaultTabButtons>
</ng-template>
برای دسترسی به آن در کدهای کامپوننت مرتبط، می‌توان از طریق تعریف یک ViewChild هم نام با این متغیر استفاده کرد:
export class AppComponent implements OnInit {
   @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>;
   ngOnInit() {
      console.log(this.defaultTabButtonsTpl);
   }
}
در اینجا متغیر defaultTabButtonsTpl با ویژگی ViewChild مزین شده‌است. البته این یک روش عمومی برای دسترسی به تمام عناصر DOM در کدهای یک کامپوننت می‌باشد.

یکی از کاربردهای این قابلیت، امکان تعویض پویای قالب‌های یک دربرگیرنده‌است:
<ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons">
</ng-container>
توسط دایرکتیو ساختاری ngTemplateOutlet می‌توان در زمان اجرا، قالب‌های مختلفی را توسط کدهای کامپوننت مشخص کرد.
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی می‌شود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر می‌خواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم می‌توان یک خاصیت عمومی مزین شده‌ی با Input از نوع TemplateRef را مشخص کرد:
 @Input() headerTemplate: TemplateRef<any>;
در این حالت این کامپوننت ویژه می‌تواند به صورت ذیل، قالب خودش را با انتساب به این خاصیت عمومی دریافت کند:
 <tab-container [headerTemplate]="defaultTabButtons"></tab-container>