ng add @angular/material
فعالسازی بررسی مشکلات قالبهای کامپوننتها
برای فعالسازی بررسی مشکلات قالبهای کامپوننتها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{ "compilerOptions": { "experimentalDecorators": true, ... }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "preserveWhiteSpace": false, ... } }
- البته این خاصیت در حین استفادهی از یکی از دستورات ng serve --aot و یا ng build --prod انتخاب میشود.
- مقدار این پرچم در نگارشهای 5x به صورت پیشفرض به false تنظیم شدهاست؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آنرا شروع کنید.
یک مثال: بررسی خاصیت fullTemplateTypeCheck
فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کردهاید که فقط دارای خاصیت name است:
export interface PonyModel { name: string; }
import { PonyModel } from "./pony"; @Component({ selector: "app-detect-common-errors-test", templateUrl: "./detect-common-errors-test.component.html", styleUrls: ["./detect-common-errors-test.component.css"] }) export class DetectCommonErrorsTestComponent implements OnInit { ponyModel: PonyModel = { name: "Pony1" };
<p>Hello {{ponyModel.age}}
در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
\detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینهی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوعهای نال نپذیر عادت دهید. به این ترتیب میتوانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آنها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.
یک نکتهی تکمیلی
افزونهی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارشهای پیش از 5 نیز قابل استفاده است).
قابلیتی که در ادامه از آن برای «قالب پذیر ساختن گرید» استفاده خواهیم کرد، همان نکتهی «امکان تعویض پویای قالبهای یک دربرگیرنده» است که در مطلب «امکان تعریف قالبها در Angular با دایرکتیو ng-template» به آن پرداختیم.
تعریف قالبهای نمایش و ویرایش اطلاعات یک ردیف در گرید طراحی شده
پس از آشنایی با دایرکتیوهای تعریف و کار با قالبها در Angular، اکنون تبدیل بدنهی ثابت جدول، به دو قالب نمایش و ویرایش، سادهاست.
در قسمت دوم این سری، کار رندر بدنهی اصلی گرید توسط همین چند سطر، در قالب آن مدیریت میشود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> </tr> </tbody> </table>
در ادامه قسمت داخلی ngFor را تبدیل به یک ng-container میکنیم تا قالب پذیر شود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <ng-container [ngTemplateOutlet]="loadTemplate(item)" [ngOutletContext]="{ $implicit: item, idx: i }"></ng-container> </tr> </tbody>
در اینجا ngTemplateOutlet این امکان را میدهد تا بتوان توسط کدهای برنامه، قالب هر ردیف را مشخص کرد. متد loadTemplate در کدهای کامپوننت متناظر فراخوانی شده و بر اساس وضعیت هر ردیف، یکی از دو قالب ذیل را بازگشت میدهد:
الف) قالب نمایش معمولی و فقط خواندنی رکوردها
<!--The Html Template for Read-Only Rows--> <ng-template #readOnlyTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> <td> <input type="button" value="Edit" class="btn btn-default btn-xs" (click)="editItem(item)" /> </td> <td> <input type="button" value="Delete" (click)="deleteItem(item)" class="btn btn-danger btn-xs" /> </td> </ng-template>
این قالب در کدهای کامپوننت آن به صورت ذیل قابل دسترسی و انتخاب شدهاست:
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>;
ب) قالب ویرایش اطلاعات هر ردیف که از آن برای افزودن یک ردیف جدید هم میتوان استفاده کرد
شبیه به همان کاری را که برای نمایش ردیفهای فقط خواندنی انجام دادیم، در مورد قالب ویرایش هر ردیف نیز تکرار میکنیم. در اینجا فقط امکان ویرایش نام محصول، قیمت آن و موجود بودن آنرا توسط یکسری input box مهیا کردهایم:
<!--The Html Template for Editable Rows--> <ng-template #editTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.productName" class="form-control" /> </td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.price" class="form-control" /> </td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" [(ngModel)]="selectedItem.isAvailable" /> </td> <td> <input type="button" value="Save" (click)="saveItem()" class="btn btn-success btn-xs" /> </td> <td> <input type="button" value="Cancel" (click)="cancel()" class="btn btn-warning btn-xs" /> </td> </ng-template>
@ViewChild("editTemplate") editTemplate: TemplateRef<any>;
تا اینجا کار تعریف قالبهای این گرید به پایان میرسد. در ادامه کدهای افزودن، ثبت، ویرایش، حذف و لغو را پیاده سازی خواهیم کرد:
خواص عمومی مورد نیاز جهت کار با قالبها و ویرایشهای درون ردیفی
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>; @ViewChild("editTemplate") editTemplate: TemplateRef<any>; selectedItem: AppProduct; isNewRecord: boolean;
به علاوه اگر به قالب editTemplate دقت کنید، مقدار ویرایش شده به [(ngModel)]="selectedItem.productName" انتساب داده میشود. به همین جهت شیء selectedItem نیز تعریف شدهاست.
همچنین نیاز است بدانیم اکنون در حال ویرایش یک ردیف هستیم یا این ردیف، کاملا ردیف جدیدی است. به همین جهت پرچم isNewRecord نیز تعریف شدهاست.
فعالسازی قالب ویرایش هر ردیف
در انتهای هر ردیف، دکمهی ویرایش نیز قرار دارد که به (click) آن، رخداد editItem متصل است:
editItem(item: AppProduct) { this.selectedItem = item; }
loadTemplate(item: AppProduct) { if (this.selectedItem && this.selectedItem.productId === item.productId) { return this.editTemplate; } else { return this.readOnlyTemplate; } }
مدیریت افزودن یک ردیف جدید
دکمهی افزودن یک ردیف جدید به صورت ذیل به قالب اضافه شدهاست:
<div class="panel"> <input type="button" value="Add new product" class="btn btn-primary" (click)="addItem()" /> </div>
addItem() { this.selectedItem = new AppProduct(0, "", 0, false); this.isNewRecord = true; this.queryResult.items.push(this.selectedItem); this.queryResult.totalItems++; }
مدیریت لغو ویرایش هر ردیف
برای اینکه ویرایش هر ردیف را لغو کنیم و قالب آنرا به حالت فقط خواندنی بازگشت دهیم، فقط کافی است selectedItem را به نال تنظیم کنیم:
cancel() { this.selectedItem = null; }
مدیریت حذف هر ردیف
در اینجا با پیاده سازی متد رخدادگردان deleteItem و ارسال id هر ردیف به سرور، کار حذف هر ردیف را انجام خواهیم داد:
deleteItem(item: AppProduct) { this.productsService .deleteAppProduct(item.productId) .subscribe((resp: Response) => { this.getPagedProductsList(); }); }
مدیریت ثبت و یا به روز رسانی هر ردیف
آخرین عملیاتی که باید مدیریت شود، بررسی پرچم isNewRecord است. اگر true بود، کار افزودن یک ردیف جدید صورت گرفته و سپس این پرچم false میشود. اگر false بود، به معنای درخواست به روز رسانی ردیفی مشخص است. در پایان هر دو عملیات selectedItem را نیز true میکنیم و این پایان عملیات باید داخل قسمت دریافت پاسخ از سرور مدیریت شود و نه پس از فراخوانی این متدها؛ چون متدهای subscribe غیرهمزمان بوده و ردیفهای پس از آنها بلافاصله اجرا میشوند.
saveItem() { if (this.isNewRecord) { this.productsService .addAppProduct(this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem.productId = resp.productId; this.isNewRecord = false; this.selectedItem = null; }); } else { this.productsService .updateAppProduct(this.selectedItem.productId, this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem = null; }); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
WPF
- آغاز کار با WPF
- آشنایی با WPF قسمت اول : ساختار سلسله مراتبی
- آشنایی با WPF قسمت دوم: Layouts بخش اول
- آشنایی با WPF قسمت سوم: Layouts بخش دوم
- آشنایی با WPF قسمت چهارم: کنترل ها
- آشنایی با WPF قسمت پنجم : DataContext بخش اول
- آشنایی با WPF قسمت پنجم : DataContext بخش دوم
- آشنایی با WPF قسمت ششم : DataContext بخش سوم
- انقیاد دادهها در WPF بخش اول
- انقیاد دادهها در WPF بخش دوم
- آشنایی با الگوی M-V-VM- قسمت اول
- M-V-VM - قسمت دوم
- آشنایی با الگوی M-V-VM - قسمت سوم
- آشنایی با الگوی M-V-VM - قسمت چهارم
- آشنایی با الگوی M-V-VM - قسمت پنجم
- Expression Blend WPF Tutorial
- طول و عرض WPF
- آموزش رایگان XAML از مایکروسافت
- سری آموزشی PRISM
- ویدیوهای رایگان آموزشی WPF
- ارتقاء از WinForms به WPF
- خلاصهای کاربردی در مورد Observable collection
- دو تنظیم ضروری VS.NET جهت کار با WPF و Silverlight
- معرفی WPF Extended toolkit
- WPF و قالبهایی جهت کنترل DataGrid
- خلاصهای از مبحث نمایش اطلاعات hierarchical در WPF
- WPF4 و ویندوز 7 : به خاطر سپاری لیست آخرین فایلهای گشوده شده توسط برنامه
- نمایش یک فایل PDF در WinForms ، WPF و سیلورلایت
- چند نکته در مورد WPF MediaElement و ویندوز XP
- تعیین Fallback font برای قلمهای فارسی در WPF
- آموزش ایجاد برنامههای چند زبانه در WPF
- آشنایی و استفاده از WCF Data Services در Visualstudio 2012
- بارگذاری UserControl در WPF به کمک الگوی MVVM
- معماری لایه بندی نرم افزار #1
- معماری لایه بندی نرم افزار #2
- معماری لایه بندی نرم افزار #3
- معماری لایه بندی نرم افزار #4
- نحوه نمایش تمام آیکونهای تعریف شده در یک قلم در WPF
- نحوه استخراج آیکونهای یک قلم در WPF
- مقیدسازی (DataBinding) در WPF زمانی که دسترسی به DataContext وجود ندارد
- فراخوانی یک متداز یک کنترل WPF از XAML
- آشنایی با Catel MVVM Frameowork
- استفاده از ItemsControl جهت ساختن کنترلهای پویا در WPF
- ساخت فرمهای generic در WPF و Windows Application
- استفاده از #F در پروژههای WPF
- Markup Extensions در XAML
- Debug کردن Binding در XAML
- Bind کردن Enum به ItemsSource در XAML
- دسترسی به فیلدهای Static در XAML
- نگاهی به درون سیستم Binding در WPF و یافتن مواردی که هنوز در حافظهاند
- بهبود کارآیی کنترلهای لیستی WPF در حین بارگذاری تعداد زیادی از رکوردها
- چگونه تشخیص دهیم UI Virtualization در WPF خاموش شده است؟
- اضافه کردن امکان ویرایش WPF DataGrid در صورت نامعتبر بودن سلول ها
- انقیاد RadioButtonها در WPF به یک Enum
- یکی کردن اسمبلیهای یک پروژهی WPF
- بستن یک پنجره از طریق ViewModel با استفاده از خصوصیتهای پیوست شده هنگام استفاده از الگوی MVVM
- حرکت روی سلولهای دیتا گرید با فشردن کلید Enter در برنامههای WPF
- دسترسی به Collectionها در یک ترد دیگر در WPF
- آموزش WAF
- آموزش WAF (بررسی ساختار همراه با پیاده سازی یک مثال)
- آموزش WAF (بررسی Commandها)
- آموزش WAF (مشاهده تغییرات خواص ViewModel در Controller)
- تصادفی کردن آیتمهای لیست با استفاده از Extension Method
- استفاده از AvalonEdit در WPF
- فرمت شرطی اطلاعات به کمک تریگرها در WPF
- first chance exception چیست؟
- معرفی کتابخانهی OxyPlot
- معرفی DNTProfiler
- چگونه برنامههای دات نت را خارج از ویژوال استودیو دیباگ کنیم؟
- اسکرول روان لیستهای مجازی سازی شده در WPF 4.5
- پیاده سازی INotifyPropertyChanged با استفاده از Unity Container
<services> <service name="MyNewsWCFLibrary.MyNewsService"> <host> <baseAddresses> <add baseAddress="http://localhost:8080/MyNewsWCFLibrary/MyNewsService/" /> </baseAddresses> </host> <!-- Service Endpoints --> <!-- Unless fully qualified, address is relative to base address supplied above --> <endpoint address="" binding="basicHttpBinding" contract="MyNewsWCFLibrary.IMyNewsService"> <!-- Upon deployment, the following identity element should be removed or replaced to reflect the identity under which the deployed service runs. If removed, WCF will infer an appropriate identity automatically. --> <identity> <dns value="localhost" /> </identity> </endpoint> <!-- Metadata Endpoints --> <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. --> <!-- This endpoint does not use a secure binding and should be secured or removed before deployment --> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> </service> </services>
در صورت مشاهده پیام خطا، ویژوال استودیو را ببندید و اینبار به صورت Run as administrator باز کنید.
برای نمونه روی متد AddCategory کلیک کنید. در پنجره نشان داده شده همانند شکل در برابر فیلد CatName مقداری وارد کنید و روی دکمه Invoke کلیک کنید. متد مورد نظر اجرا شده و مقداری که وارد کرده ایم در پایگاه دادهها ذخیره میشود. مقداری که در قسمت پایین دیده میشود خروجی متد است که در اینجا شناسه رکورد درجشده است.
بار دیگر برای مشاهده رکورد درجشده روی متد GetAllCategory کلیک کنید. به علت اینکه این متد ورودی ندارد در قسمت بالا چیزی نشان داده نمیشود. روی دکمه Invoke کلیک کنید. با پیغام خطای زیر روبهرو خواهید شد:
افزودن ویژگی Virtual به tblNews و tblCategory در بخش دوم خواندید؛ باعث میشود که Entity Framework در هنگام اجرا کلاسهایی با عنوان "پروکسیهای پویا" به کلاسهای Address و Customer بیفزاید و بنابراین قابلیت Lazy Loading برای این کلاسها در زمان اجرای برنامه فراهم میگردد.
ولی با افزودن پروکسیهای پویا به کلاسهای ما، این کلاسها قابلیت انتقال خود از طریق سرویسهای WCF را از دست میدهند زیرا پروکسیهای پویا به طور پیشگزیده قابلیت سریالایز و دیسریالایز شدن را ندارند!
خوشبختانه میتوانیم این ویژگی را در کلاس DBContext غیرفعال کنیم. برای این منظور قالب سازندهی آن یا MyNewsModel.Context.tt را از Solution Explorer باز کنید و کد زیر را در آن پیدا کنید:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext { public <#=code.Escape(container)#>() : base("name=<#=container.Name#>") {
سپس در ادامهی آن کدغیرفعالکردن پروکسی پویا را به این شکل بنویسید:
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext { public <#=code.Escape(container)#>() : base("name=<#=container.Name#>") { Configuration.ProxyCreationEnabled = false;
اکنون اگر فایل را ذخیره کنیم سپس فایل MyNewsModel.Context.cs را از Solution Explorer باز کنید؛ خواهید دید که این خط کد در جای خود قرارگرفته است.
بار دیگر پروژه را اجرا کنید روی متد GetAllCategory کلیک کنید. این بار اگر دکمه Invoke را بفشارید با همانند شکل زیر را خواهید دید:
در بخش ششم پیرامون ارتباط جدولهای tblNews و tblCategory و نمایش محتویات وابسته جدول خبر به دسته و تنظیمات آن در t4 و کلاس Service
در بخش هفتم پیرامون میزبانی WCFLibrary در یک Web Application
و در بخش هشتم پیرامون ایجاد یک برنامهی ویندوزی جهت استفاده از سرویسهای WCF خواهم نوشت.
هدف: استفاده از کتابخانهی jsSHA
میخواهیم در یک برنامهی AngularJS 2.0، از کتابخانهی jsSHA استفاده کرده و هش SHA512 یک رشته را محاسبه کنیم.
تامین پیشنیازهای اولیه
میتوان فایلهای این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است اینکار را توسط npm مدیریت کنیم. به همین جهت فایل package.json آنرا گشوده و سپس مدخل متناظری را به آن اضافه کنید:
"dependencies": { // ... "jssha": "^2.1.0", // ... },
بارگذاری فایلهای کتابخانه به صورت پویا
یک روش استفاده از این کتابخانه یا هر کتابخانهی جاوا اسکریپتی، افزودن مدخل تعریف آن به صفحهی index.html است:
<script src="node_modules/jssha/src/sha512.js"></script>
// map tells the System loader where to look for things var map = { // ... 'jssha': 'node_modules/jssha/src' }; // packages tells the System loader how to load when no filename and/or no extension var packages = { // ... 'jssha': { main: 'sha512.js', defaultExtension: 'js' } };
به این ترتیب هر زمانیکه کار import این کتابخانه صورت گیرد، بارگذاری پویای آن انجام خواهد شد. به علاوه ابزارهای بسته بندی و deploy پروژه هم این فایل را پردازش کرده و به صورت خودکار، کار bundling، فشرده سازی و یکی سازی اسکریپتها را انجام میدهند.
استفاده از jsSHA به صورت untyped
پس از دریافت بستههای این کتابخانه و مشخص سازی نحوهی بارگذاری پویای آن، اکنون نوبت به استفادهی از آن است. در اینجا منظور از untyped این است که فرض کنیم برای این کتابخانه، فایلهای typings مخصوص TypeScript وجود ندارند و پس از جستجوی در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped نتوانستهایم معادلی را برای آن پیدا کنیم. بنابراین فایل جدید untyped-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
import { Component, OnInit } from '@angular/core'; var jsSHA = require("jssha"); // ==> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions //declare var jsSHA: any; // ==> this requires adding <script src="node_modules/jssha/src/sha512.js"></script> to the first page manually. @Component({ templateUrl: 'app/using-third-party-libraries/untyped-sha.component.html' }) export class UnTypedShaComponent implements OnInit { hash: String; ngOnInit(): void { let shaObj = new jsSHA("SHA-512", "TEXT"); shaObj.update("This is a test"); this.hash = shaObj.getHash("HEX"); } }
<h1>SHA-512 Hash / UnTyped</h1> <p>String: This is a test</p> <p>HEX: {{hash}}</p>
هر زمانیکه فایلهای typing یک کتابخانهی جاوا اسکریپتی در دسترس نبودند، فقط کافی است از روش declare var jsSHA: any استفاده کنید. در اینجا any به همان حالت استاندارد و بینوع جاوا اسکریپت اشاره میکند. در این حالت برنامه بدون مشکل کامپایل خواهد شد؛ اما از تمام مزایای TypeScript مانند بررسی نوعها و همچنین intellisense محروم میشویم.
در این مثال در hook ویژهای به نام OnInit، کار ساخت شیء SHA را انجام داده و سپس هش عبارت This is a test محاسبه شده و به خاصیت عمومی hash انتساب داده میشود. سپس این خاصیت عمومی، در قالب این کامپوننت از طریق روش interpolation نمایش داده شدهاست.
دو نکتهی مهم
الف) اگر از روش declare var jsSHA: any استفاده کردید، کار بارگذاری فایل sha512.js به صورت خودکار رخ نخواهد داد؛ چون ماژولی را import نمیکند. بنابراین تعاریف systemjs.config.js ندید گرفته خواهد شد. در این حالت باید از همان روش متداول افزودن تگ script این کتابخانه به فایل index.html استفاده کرد.
ب) برای بارگذاری پویای کتابخانهی jsSHA بر اساس تعاریف فایل systemjs.config.js از متد require کمک بگیرید:
var jsSHA = require("jssha");
استفاده از jsSHA به صورت typed
کتابخانهی jsSHA در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jssha دارای فایل d.ts. مخصوص خود است. برای نصب آن از یکی از دو روش ذیل استفاده کنید:
الف) نصب دستی فایلهای typings
npm install -g typings typings install jssha --save --ambient
ب) تکمیل فایل typings.ts
{ "ambientDependencies": { // ... "jssha": "registry:dt/jssha#2.1.0+20160317120654" } }
پس از نصب فایلهای typings این پروژه، به فایل main.ts مراجعه کرده و مدخل ذیل را به ابتدای آن اضافه کنید:
/// <reference path="../typings/browser/ambient/jssha/index.d.ts" />
در ادامه فایل جدید typed-sha.component.ts را با محتوای ذیل به پروژه اضافه کنید:
import { Component, OnInit } from '@angular/core'; //import { jsSHA } from "jssha"; import * as jsSHA from "jssha"; // ===> var jsSHA = require("jssha"); // ===> loads `sha512.js` file dynamically using `systemjs.config.js` file definitions @Component({ templateUrl: 'app/using-third-party-libraries/typed-sha.component.html' }) export class TypedShaComponent implements OnInit{ hash: String; ngOnInit(): void { let shaObj = new jsSHA("SHA-512", "TEXT"); shaObj.update("This is a test"); this.hash = shaObj.getHash("HEX"); } }
در اینجا تنها نکتهی مهم و جدید نسبت به روش قبل (استفاده از jsSHA به صورت untyped)، روش import این کتابخانه است. روش "import * as jsSHA from "jssha به عبارت var jsSHA = require("jssha") ترجمه میشود که در نهایت سبب بارگذاری خودکار فایلهای jssha بر اساس تعاریف مداخل آن در فایل systemjs.config.js میگردد.
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید.
// angular-material-feature.module.ts import { NgModule } from '@angular/core'; // Import angular kendo angular import { MatFormFieldModule, MatInputModule, MatButtonModule, MatButtonToggleModule, MatDialogModule, MatIconModule, MatSelectModule, MatToolbarModule, MatDatepickerModule, DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatTableModule, MatCheckboxModule, MatRadioModule, MatCardModule, fadeInContent, MatListModule, MatProgressBarModule, MatTabsModule } from '@angular/material'; import { MatSidenavModule, MatSlideToggleModule, } from '@angular/material'; import { MatMenuModule } from '@angular/material/menu'; @NgModule({ declarations: [ // KendoGridPaginationComponent ], imports: [ MatSidenavModule, MatSlideToggleModule, MatInputModule, MatFormFieldModule, MatButtonModule, MatButtonToggleModule, MatDialogModule, MatIconModule, MatSelectModule, MatToolbarModule, MatDatepickerModule, MatCheckboxModule, MatRadioModule, MatCardModule, MatMenuModule, MatListModule, MatProgressBarModule, MatTabsModule ], exports: [ MatSidenavModule, MatSlideToggleModule, MatInputModule, MatFormFieldModule, MatButtonModule, MatButtonToggleModule, MatDialogModule, MatIconModule, MatSelectModule, MatToolbarModule, MatDatepickerModule, MatCheckboxModule, MatRadioModule, MatCardModule, MatMenuModule, MatListModule, MatProgressBarModule, MatTabsModule ], providers: [ ] }) export class AngularMaterialFeatureModule { }
// kendo-feature.module.ts import { NgModule } from '@angular/core'; // Import kendo angular ui import { ButtonsModule } from '@progress/kendo-angular-buttons'; import { GridModule, ExcelModule } from '@progress/kendo-angular-grid'; import { DropDownsModule } from '@progress/kendo-angular-dropdowns'; import { InputsModule } from '@progress/kendo-angular-inputs'; import { DateInputsModule } from '@progress/kendo-angular-dateinputs'; import { DialogsModule } from '@progress/kendo-angular-dialog'; import { RTL } from '@progress/kendo-angular-l10n'; import { LayoutModule } from '@progress/kendo-angular-layout'; import { WindowService, WindowRef, WindowCloseResult, DialogService, DialogRef, DialogCloseResult } from '@progress/kendo-angular-dialog'; import { SnotifyModule, SnotifyService, SnotifyPosition, SnotifyToastConfig, ToastDefaults } from 'ng-snotify'; @NgModule({ declarations: [ ], imports: [ ButtonsModule, GridModule, ExcelModule, DropDownsModule, InputsModule, DateInputsModule, DialogsModule, LayoutModule, SnotifyModule, ], exports: [ ButtonsModule, GridModule, ExcelModule, DropDownsModule, InputsModule, DateInputsModule, DialogsModule, LayoutModule, ], providers: [ { provide: 'SnotifyToastConfig', useValue: ToastDefaults }, SnotifyService, // Enable Right-to-Left mode for Kendo UI components { provide: RTL, useValue: true }, ] }) export class KendoFeatureModule { }
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserAnimationsModule, RouterModule, AppRoutingModule, ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
// app.component.ts import { Component, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], encapsulation: ViewEncapsulation.None, }) export class AppComponent { title = 'HowToKeepAngularDeploymentSizeSmall'; constructor() { } }
// app.component.html <router-outlet></router-outlet>
// app-routing.module.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', loadChildren: './projects/home/home.module#HomeModule' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
// home.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import {AngularMaterialFeatureModule , KendoFeatureModule} from '../../modules/index'; import {HomeRoutingModule } from './home.routing'; import { HomeComponent } from './home.component'; import { IndexComponent } from './pages/index/index.component'; import {LoginComponent } from './pages/login/login.component'; @NgModule({ declarations: [ HomeComponent, IndexComponent, LoginComponent ], imports: [ CommonModule, HomeRoutingModule, AngularMaterialFeatureModule ], providers:[ ], entryComponents:[ LoginComponent ] }) export class HomeModule { }
// home.routing.ts import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home.component'; import { IndexComponent } from './pages/index/index.component'; const routes: Routes = [ { path: "", component: HomeComponent, children: [ { path: "", component: IndexComponent, data: { breadcrumb: " Index " } }, { path: "about", loadChildren: "./pages/aboutus/aboutus.module#AboutusModule" }, { path: "blog", loadChildren: "./pages/blog/blog.module#BlogModule" }, { path: "blog-detail", loadChildren: "./pages/blogdetail/blogdetail.module#BlogdetailModule" }, { path: "pricing", loadChildren: "./pages/pricing/pricing.module#PricingModule" }, { path: "contact", loadChildren: "./pages/contact/contact.module#ContactModule" } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) export class HomeRoutingModule { }
// home.component.ts import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; import { LoginComponent } from './pages/login/login.component'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], encapsulation: ViewEncapsulation.None }) export class HomeComponent implements OnInit { constructor( private dialog: MatDialog) { } ngOnInit() { } public toggleModal(){ let dialogRef = this.dialog.open(LoginComponent, { }); } }
// home.component.scss @import '../../../assets/home/css/themify-icons.css'; @import '../../../assets/home/css/font-awesome.min.css'; @import '../../../assets/home/css/set1.css'; @import '../../../assets/home/css/bootstrap.min.css'; @import '../../../assets/home/css/style.css'; @import "~@angular/material/prebuilt-themes/indigo-pink.css";
// home.component.html <!--============================= HEADER =============================--> <div> <div> <div> <div> <div> <nav> <a href="index.html"><img src="../../../assets/home/images/logo.png" alt="logo"></a> <button type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> <span></span> </button> <div id="navbarNavDropdown"> <ul> <li> <a target="_blank" [routerLink]="['/dashboard']">Dashboard</a> </li> <li> <a [routerLink]="['/about']">About</a> </li> <li> <a [routerLink]="['/contact']">Contact</a> </li> <li> <a [routerLink]="['/pricing']">Pricing</a> </li> <li> <a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Blog <span></span> </a> <div> <a [routerLink]="['/blog']">Blog Listing</a> <a [routerLink]="['/blog-detail']">Blog Detail</a> </div> </li> <li> <a (click)='toggleModal()' style="cursor: pointer;">Login</a> </li> <li><a href="add-listing.html"><span></span> Add Listing</a></li> </ul> </div> </nav> </div> </div> </div> </div> </div> <!--//END HEADER --> <router-outlet></router-outlet> <!--============================= FOOTER =============================--> <footer> <div> <div> <div> <div> <i aria-hidden="true"></i> <p>503 Sylvan Ave, Mountain View<br> CA 94041, United States</p> </div> </div> <div> <div> <img src="../../../assets/home/images/footer-logo.png" alt="#"> </div> </div> <div> <ul> <li><a href="#"><i aria-hidden="true"></i></a></li> <li><a href="#"><i aria-hidden="true"></i></a></li> <li><a href="#"><i aria-hidden="true"></i></a></li> <li><a href="#"><i aria-hidden="true"></i></a></li> <li><a href="#"><i aria-hidden="true"></i></a></li> </ul> </div> </div> <div> <div> <div> <p>Copyright © 2017 Listed Inc. All rights reserved</p> <a href="#">Privacy</a> <a href="#">Terms</a> </div> </div> </div> </div> </footer> <!--//END FOOTER -->
کامپوننت mat-table
کار کامپوننت mat-table نمایش اطلاعات در ردیفها و ستونها است. به همراه آن mat-paginator برای نمایش UI صفحه بندی اطلاعات، دایرکتیو matSort و mat-sort-header برای افزودن رابط کاربری مرتب سازی اطلاعات و امکان تغییر منبع داده آن برای فیلتر کردن دادهها، نیز وجود دارند.
افزودن کامپوننت جدید notes برای نمایش یادداشتهای کاربران
برای نمایش لیست یادداشتهای هر شخص، کامپوننت جدید Notes را به صورت زیر در پوشهی components ایجاد میکنیم:
ng g c contact-manager/components/notes --no-spec
<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>
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[];
<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); } }
سپس این منبع داده در قسمت 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>
سپس به ازای هر ستون، یک 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>
در ادامه به کدهای کامپوننت مراجعه کرده و توسط ViewChild به template reference variable ایی به نام paginator دسترسی پیدا میکنیم:
export class NotesComponent implements OnInit, AfterViewInit { dataSource: MatTableDataSource<UserNote>; @ViewChild(MatPaginator) paginator: MatPaginator; ngAfterViewInit() { this.dataSource.paginator = this.paginator; } }
اکنون اگر برنامه را اجرا کنیم، صفحه بندی فعال شدهاست:
افزودن جستجو و فیلتر کردن اطلاعات به 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(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>
export class NotesComponent implements OnInit, AfterViewInit { dataSource: MatTableDataSource<UserNote>; @ViewChild(MatSort) sort: MatSort; ngAfterViewInit() { this.dataSource.sort = this.sort; } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-04.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.