آموزش انگولار رایگان (Angular) | از مقدماتی تا پیشرفته


انگولار یک فریمورک مبتنی بر جاوا اسکریپت متن-باز و سمت کلاینت است که به منظور توسعه وباپلیکیشنها مورد استفاده قرار میگیرد. در واقع انگولار یکی از بهترین فریمورکها برای توسعه اپلیکیشنهای تکصفحهای (SPA) محسوب میشود. در این مقاله به آموزش انگولار از طریق معرفی مباحث نظری و بررسی مثالهای عملی به صورت همزمان خواهیم پرداخت.
ویژگیهای انگولار
- انگولار یک فریمورک ساختاری بر مبنای الگوی MVC است.
- انگولار فریمورکی برای توسعه اپلیکیشنهای تکصفحهای (SPA) است.
- این کتابخانه از امکان قالبسازی سمت کلاینت پشتیبانی میکند.
- انگولار امکان اجرای تست unit را فراهم ساخته و از این رو کد انگولار میتواند پیش از توزیع مورد تست قرار گیرد.
پیش از ادامه این مبحث لازم است یادآور شویم که میتوانید انگولار را با استفاده از مجموعه آموزش انگولار، مقدماتی تا پیشرفته
فرادرس یاد بگیرید.
مزیتهای انگولار
چنان که اشاره کردیم انگولار یک فریمورک متن-باز است که به وسیله گوگل نگهداری میشود. این کتابخانه قادر به خوانش بسیار سریع صفحه HTML است که موجب میشود بتوانیم خصوصیتهای سفارشی تگ اضافی را در صفحه جاسازی کنیم. این خصوصیتها به صورت دایرکتیوهایی تفسیر میشوند که به فریمورک انگولار دستور میدهند تا بخشهای ورودی و خروجی صفحه را به یک مدل اتصال دهد که به صورت متغیرهای استاندارد جاوا اسکریپت بازنمایی میشود. مقادیر این متغیرهای جاوا اسکریپت را میتوان به صورت دستی درون کد تعیین کرد یا از منابع استاتیک یا دینامیک JSON شامل دادههای سمت سرور از قبیل REST API یا غیره به دست آورد.
مزایای عمده استفاده از انگولار در وباپلیکیشنها به شرح زیر است:
- انگولار به جای افزودن کد HTML داخلی خاص خود، به صورت مستقیم DOM صفحه را دستکاری میکند که به مراتب سریعتر است.
- «اتصال داده» (Data Binding) در زمان هر تغییر در کنترل یا مقدار رخ نمیدهد. بدین ترتیب چیزی به نام «شنونده تغییر» (Change Listener) وجود ندارد، بلکه در نقاط خاصی از اجرای جاوا اسکریپت این رصد تغییرات انجام مییابند. این موضوع موجب بهبود چشمگیری در عملکرد میشود، چون یک بهروزرسانی منفرد دستهای Model/View جایگزین صدها رویداد تغییر داده آبشاری میشود.
- هیچ نیازی به استفاده از تابعهای observable وجود ندارد. انگولار DOM صفحه را تحلیل میکند و اتصالها را بر مبنای خصوصیتهای عنصر خاص انگولار میسازد. این امر نیازمند کدنویسی کمتری است یعنی کد منسجمتر است، درک آن آسانتر است و خطاهای کمتری دارد.
- امکان بسط قابلیتهایی از قبیل تزریق وابستگی، مسیریابی، انیمیشن، کپسولهسازی نما و موارد دیگر وجود دارد.
- از سوی IDE-های IntelliJ IDEA و Visual Studio.NET پشتیبانی میشود.
- از سوی گوگل و جامعه توسعهدهندگان عالی آن پشتیبانی میشود.
- انگولار راهحلی عالی برای توسعه سریع در سمت فرانت است. این کتابخانه به هیچ پلاگین یا فریمورک دیگری نیاز ندارد.
- انگولار آمادگی تست unit را دارد و این یکی از بهترین مزیتهای این فریمورک است.
چرا انگولار را فریمورک مینامیم؟
پیش از آن که به بررسی مفاهیم انگولار بپردازیم، باید با ماهیت خود آن آشنا شویم. ما چرا به انگولار فریمورک میگوییم؟ بر اساس تعریف دیکشنری، فریمورک یک ساختار حمایتکننده است. این تعریف یکجملهای به خوبی انگولار را توصیف میکند. اما با این وجود، انگولار اکوسیستم بزرگ و مفیدی عرضه میکند که از طریق میتوانیم به ابزارهای مختلفی دست پیدا کرده و به این ترتیب مسائل خود را حل کنیم. همچنین از این ابزارها میتوانیم برای طراحی و سازماندهی اپلیکیشن جدید استفاده کنیم.
تاریخچه انگولار
نخستین نسخه انگولار از سوی شخصی به نام «میشکو هیوِری» (Miško Hevery) که یک توسعهدهنده در گوگل بود، در سالهای 2008 و 2009 ایجاد شد. او یک فریمورک برای تسهیل ساخت وباپلیکیشنها طراحی کرده بود. این فریمورک نه برای توسعهدهندگان وب، بلکه برای استفاده طراحان وب ارائه شده بود که اطلاعات فنی کمی از دانش برنامهنویسی داشتند. به این ترتیب اگر یک وبسرور استاتیک داشتید، میتوانستید یک وباپلیکیشن ساده بسازید. «آدام آبرونز» (Adam abrons) یکی از همکاران میشکو پیشنهاد کرد نام این پروژه انگولار یعنی زاویهدار باشد، زیرا HTML شامل «براکتهای زاویهدار» (Angular) است.
در این زمان مدیر میشکو در شرکت گوگل به نام «براد گرین» (Brad Green) از وی خواست که روی یکی از ابزارهای داخلی گوگل به نام «Google Feedback» کار کند. گوگل یک فریمورک به نام GWT داشت که با جاوا نوشته شده بود و به منظور توسعه ابزارهای داخلی این شرکت مورد استفاده قرار میگرفت. دو توسعهدهنده دیگر به همراه میشکو 17000 خط کد را در طی 6 ماه نوشتند. دلیل این زمان طولانی آن بود که تست کردن این کد کار دشواری بود. برای افزودن یک برچسب در HTML باید کد آن در جاوا نوشته و کامپایل شده به جاوا اسکریپت HTML انتقال مییافت تا در مرورگر وب نمایش یابد. میشکو ادعا کرد که میتواند کل این پروژه را در طی 2 هفته با استفاده از ابزاری که خود به نام انگولار توسعه داده است، بنویسد.
با این که وی نتوانست ادعای خود را اجرا کند و این کار را در طی 3 هفته انجام داد، اما حجم کد به 1500 خط کاهش یافت. با این حال این پروژه از سوی مدیران ارشد گوگل حمایت نشد و لذا به صورت یک کتابخانه متن-باز منتشر شد و کار از سوی توسعهدهندگان خارج از گوگل ادامه یافت.
در ادامه گوگل شرکت DoubleClick را خریداری کرد. این شرکت توسعهدهندگان زیاد جاوا نداشت و بنابراین کار با GWT برای آنها دشوار بود، از این رو به دنبال فناوریهای جایگزینی میگشتند و با میشکو و AngularJs مواجه شدند. بدین ترتیب آنها موفق شدند صفحههای فرود خود را با بهرهگیری از انگولارجیاس با حجمی ده بار کمتر و سرعتی ده بار بیشتر بنویسند. در ابتدا مخالفتهای زیادی وجود داشت، چون همه افراد در گوگل از GWT استفاده میکردند، اما در نهایت افراد زیادی به این فریمورک جدید روی آوردند. نسخه رسمی یعنی AngularJs v1.0 در مه 2011 منتشر شد و در ادامه نسخههای بعدی به ترتیب زیر از سوی گوگل انتشار یافتهاند.
نسخه انگولار | تاریخ |
Angular 2 | 2016/09/14 |
Angular 4 | 2017/03/23 |
Angular 5 | 2017/11/11 |
Angular 6 | 2018/05/03 |
Angular 7 | 2018/10/18 |
Angular 8 | 2019/08/25 |
Angular 9 | 2020/02/06 |
Angular 10 | 2020/05/04 |
نکته مهم: این راهنما بر پایه انگولار نسخه 8 نوشته شده است.
پیشنیازهای آموزش انگولار
برای توسعه یک اپلیکیشن در انگولار باید پیشنیازهای زیر را نصب و پیکربندی کنیم.
- جدیدترین نسخه Node.js
- نصب جدیدترین نسخه تایپ اسکریپت
- نصب یک IDE مانند VSCode یا ویژوال استودیو
- همچنین باید Angular CLI را نصب کنیم تا بتوانیم پروژه انگولار را اجرا کنیم.
برای توسعه اپلیکیشنهای انگولار از زبان برنامهنویسی تایپ اسکریپت استفاده میشود. تایپ اسکریپت یک زبان برنامهنویسی رایگان و متن-باز است که از سوی مایکروسافت توسعه یافته و نگهداری میشد. تایپ اسکریپت یک سوپرست نحوی صریح از جاوا اسکریپت محسوب میشود که قابلیت نوعبندی استاتیک اختیاری را به این زبان افزوده است. «آندرس هیلزبرگ» (Anders Hejlsberg) که رهبر معماری زبان #C و خالق زبانهای دلفی، توربوپاسکال بوده است، روی توسعه تایپ اسکریپت کار کرده است.
شما برای کار روی اپلیکیشنهای انگولار باید با زبان تایپ اسکریپت آشنا باشید و از آن بهره بگیرید.
ایجاد پروژه جدید با Angular CLI
اگر بخواهیم یک پروژه جدید در انگولار ایجاد کنیم، میتوانیم از دستورهای CLI بهره بگیریم. به این منظور باید گامهای زیر را طی کنیم:
- برنامه Command Prompt را باز کرده و یک پوشه ایجاد کنید.
- دستور ng new AngularDemo- را اجرا کنید.
- در زمانی که از شما سؤال شود آیا میخواهید مسیریابی انگولار به این پروژه اضافه شود، مخالفت خود را با وارد کردن حرف N اعلام کنید.
- نوع استایلشیت را به صورت CSS انتخاب کرده و دکمه اینتر را بزنید.
بدین ترتیب Angular CLI فیلدهای لازم برای اجرای پروژههای انگولار را همراه با پکیجهای مرتبط که در پوشه node_modules دانلود میشوند ایجاد میکند.
ساختار پروژههای انگولار
Angular CLI در زمان ایجاد پروژه انگولار، پوشه جدیدی با نام پروژه ایجاد میکند. اینک میتوانید پروژه را در هر ادیتور کدی مانند Visual Studio Code (+) یا Microsoft Visual Studio باز کنید. این پوشه پروژه شامل ساختار زیر است:
پروژه ایجاد شده شامل پوشههای زیر است:
- e2e – این پوشه به منظور تست «سربهسر» (end to end) استفاده میشود و شامل فایلهای پیکربندی مرتبط با اجرای تست unit پروژهها است.
- node_modules – این پوشه شامل پکیجهای دانلود شده بسته به پیکربندی است.
- Src - این پوشه شامل سورس کد اصلی پروژه است و سه زیرپوشه دارد:
- app – پوشه app شامل فایلهای مرتبط با پروژه انگولار مانند کامپوننتها، فایلهای HTML و غیره است.
- assets - پوشه assets شامل هر نوع فایل استاتیک از قبیل تصاویر، استایلشیت، فایلهای کتابخانههای خاص جاوا اسکریپت و غیره است.
- environments - پوشه environments شامل فایلهای مرتبط با محیط است که در زمان توسعه یا بیلد کردن پروژهها مورد نیاز هستند.
فایلهای متفاوت پیکربندی
هرزمان که یک پروژه مبتنی بر انگولار را با استفاده از Angular CLI میسازیم، 3 فایل مختلف پیکربندی ایجاد میشوند که به پیکربندی پروژهها و وابستگیها کمک میکنند. این فایلها به شرح زیر هستند.
فایل tsconfig.json
فایلهای tsconfig.json درون پوشه root پوشه قرار دارند و به این معنی هستند که این پروژه مبتنی بر تایپ اسکریپت است. فایل tsconfig.json فایلهای root و گزینههای کامپایلر مورد نیاز برای کامپایل پروژه را تعیین میکند. یک فایل نمونه tsconfig.json مانند زیر است:
1{
2 "compileOnSave": true,
3 "compilerOptions": {
4 "baseUrl": "./",
5 "outDir": "./dist/out-tsc",
6 "sourceMap": true,
7 "declaration": false,
8 "module": "esnext",
9 "moduleResolution": "node",
10 "emitDecoratorMetadata": true,
11 "experimentalDecorators": true,
12 "importHelpers": true,
13 "target": "es2015",
14 "typeRoots": [
15 "node_modules/@types"
16 ],
17 "lib": [
18 "es2018",
19 "dom"
20 ]
21 }
22}
فایل package.json
فایل package.json اساساً یک فایل JSON است که شامل همه اطلاعات مرتبط با پکیجهای مورد نیاز برای پروژه است. ضمناً به کمک این فایلهای پیکربندی، میتوانیم نام پروژه و نسخه آن را با استفاده از مشخصههای name و version تعیین کنیم. همچنین میتوانیم تعریف build پروژه را با استفاده از این فایل ارائه کنیم.
1{
2 "name": "angular8-demo",
3 "version": "0.0.0",
4 "scripts": {
5 "ng": "ng",
6 "start": "ng serve",
7 "build": "ng build",
8 "test": "ng test",
9 "lint": "ng lint",
10 "e2e": "ng e2e"
11 },
12 "private": true,
13 "dependencies": {
14 "@angular/animations": "~8.0.0",
15 "@angular/common": "~8.0.0",
16 "@angular/compiler": "~8.0.0",
17 "@angular/core": "~8.0.0",
18 "@angular/forms": "~8.0.0",
19 "@angular/platform-browser": "~8.0.0",
20 "@angular/platform-browser-dynamic": "~8.0.0",
21 "@angular/router": "~8.0.0",
22 "rxjs": "~6.4.0",
23 "tslib": "^1.9.0",
24 "zone.js": "~0.9.1"
25 },
26 "devDependencies": {
27 "@angular-devkit/build-angular": "~0.800.0",
28 "@angular/cli": "~8.0.2",
29 "@angular/compiler-cli": "~8.0.0",
30 "@angular/language-service": "~8.0.0",
31 "@types/node": "~8.9.4",
32 "@types/jasmine": "~3.3.8",
33 "@types/jasminewd2": "~2.0.3",
34 "codelyzer": "^5.0.0",
35 "jasmine-core": "~3.4.0",
36 "jasmine-spec-reporter": "~4.2.1",
37 "karma": "~4.1.0",
38 "karma-chrome-launcher": "~2.2.0",
39 "karma-coverage-istanbul-reporter": "~2.0.1",
40 "karma-jasmine": "~2.0.1",
41 "karma-jasmine-html-reporter": "^1.4.0",
42 "protractor": "~5.4.0",
43 "ts-node": "~7.0.0",
44 "tslint": "~5.15.0",
45 "typescript": "~3.4.3"
46 }
47}
فایل angular.json
این فایل به پیکربندی محیط توسعه اپلیکیشن انگولار مربوط است و با داشتن ساختاری مبتنی بر json همه اطلاعات مرتبط با بیلد و توزیع پروژه را در خود جای داده است. این فایل به سیستم اعلام میکند که در زمان استفاده از دستورهای ng build یا ng serve کدام فایلها باید تغییر یابند.
فایل main.ts
این فایل به عنوان نقطه ورودی اپلیکیشن انگولار عمل میکند. این فایل مسئول عملیات بوتاسترپ ماژولهای انگولار است. فایل main.ts شامل برخی گزارههای ایمپورت مرتبط با ماژولها و برخی پیکربندیهای راهاندازی اولیه از قبیل موارد زیر است:
- enableProdMode – این گزینه برای غیر فعال ساختن محیط توسعه انگولار و فعالسازی حالت پروداکشن استفاده میشود. غیر فعالسازی حالت توسعه موجب خاموش شدن assertion-ها و دیگر برسی های مرتبط با مدل درون فریمورک میشود.
- platformBrowserDynamic – این گزینه برای بوتاسترپ کردن اپلیکیشن انگولار در مرورگر مورد نیاز است.
- AppModule – این گزینه نشان میدهد که ماژول به صورت یک ماژول root در اپلیکیشنها عمل میکند.
- environment – این گزینه ثابتهای محیط متفاوت را ذخیره میکند.
1import { enableProdMode } from '@angular/core';
2import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3
4import { AppModule } from './app/app.module';
5import { environment } from './environments/environment';
6
7if (environment.production) {
8 enableProdMode();
9}
10
11platformBrowserDynamic().bootstrapModule(AppModule)
12 .catch(err => console.error(err));
متادیتای ngModule@
در هر اپلیکیشن انگولار دست کم یک فایل ماژول انگولار مورد نیاز است. یک اپلیکیشن انگولار ممکن است شامل بیش از یک ماژول انگولار باشد. ماژولهای انگولار یک فرایند یا سیستم برای مونتاژ عناصر چندگانه انگولار مانند کامپوننتها، دایرکتیوها، pipe-ها، سرویسها و غیره است. این عناصر انگولار میتوانند به ترتیبی ترکیب شوند که همه عناصر بتوانند با همدیگر ارتباط داشته باشند و در نهایت اپلیکیشن را تشکیل دهند.
دکوراتور NgModule@ در انگولار به منظور تعریف کردن کلاس ماژول انگولار استفاده میشود. برخی اوقات، این کلاس به نام کلاس NgModule نامیده میشود. NgModule@ همواره یک شیء metadata میگیرد که به انگولار اعلام میکند چگونه اپلیکیشن را کامپایل کرده و در مرورگر اجرا کند. به این ترتیب برای تعریف ماژول انگولار باید برخی مراحل را به صورت زیر تعریف کنیم:
- ابتدا باید BrowserModule انگولار را در فایل ماژول انگولار ایمپورت کنیم. این کلاس BrowserModule مسئول اجرا کردن اپلیکیشن در مرورگر است.
- در گام بعدی، باید عناصر انگولار مانند کامپوننتها درون ماژول انگولار اعلان کنیم، به طوری که این کامپوننتها یا عناصر میتوانند با ماژول انگولار ارتباط بگیرند.
در گام آخر باید یک کامپوننت انگولار را به صورت کامپوننت root برای ماژول انگولار تعیین کنیم. این کامپوننت همواره به نام کامپوننت بوتاسترپ نیز شناخته میشوند. بنابراین یک ماژول انگولار میتواند شامل صدها کامپوننت باشد. اما از میان این کامپوننتها، یک کامپوننت همواره به عنوان کامپوننت بوتاسترپ شناخته میشود که وقتی ماژول انگولار در مرورگر بوتاسترپ میشود، اجرا خواهد شد.
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3
4import { AppComponent } from './app.component';
5
6@NgModule({
7 declarations: [
8 AppComponent
9 ],
10 imports: [
11 BrowserModule
12 ],
13 providers: [],
14 bootstrap: [AppComponent]
15})
16export class AppModule { }
مثال اول: نخستین برنامه با انگولار
چنان که اشاره کردیم وقتی یک پروژه انگولار را با استفاده از Angular CLI ایجاد میکنیم، پروژههای جدیدی همراه با یک ماژول و فایل کامپوننت پیشفرض ایجاد میشود.
این فایلها به طور معمول درون پوشه app قرار دارند. از این رو ابتدا باید پروژه انگولار را با استفاده از دستور ng serve اجرا کنیم تا خروجی زیر در مرورگر دیده شود:

اکنون برخی تغییرها را در فایل app.component.ts و app.component.ts به صورت زیر ایجاد میکنیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls: ['./app.component.css']
7})
8export class AppComponent {
9 title = 'Welcome to Angular 8 Learning Series...';
10}
فایل app.component.html
1<!--The content below is only a placeholder and can be replaced.-->
2<div style="text-align:center">
3 <h1>
4 Welcome to {{ title }}!
5 </h1>
6 <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdC
7b3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEu
8OSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaW
9xsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHo
10iIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5
11LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
12</div>
اگر اکنون مرورگر را رفرش کنیم، خروجی زیر را مشاهده خواهیم کرد:
کامپوننت چیست؟
کامپوننت اساساً کلاسی است که برای هر عنصر یا کنترل قابل دیدن روی صفحه تعریف میشود.
هر کلاس کامپوننت برخی مشخصهها دارد و با استفاده از آنها میتوانیم رفتار یا ظاهر عنصر را روی صفحه دستکاری کنیم. به این ترتیب میتوانیم کامپوننتهای خود را بسته به الزامات در هر مرحله از اپلیکیشن، ایجاد، بهروزرسانی یا تخریب کنیم. اما در تایپ اسکریپت، کامپوننت اساساً یک کلاس تایپ اسکریپت است که یک دکوراتور ()Component@ دارد. یک کامپوننت از دیدگاه HTML تگ سفارشی HTML تعریف شده از سوی کاربر است که میتواند در مرورگر برای نمایش هر نوع عنصر UI همراه با نوعی منطق تجاری رندر شود.
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls: ['./app.component.css']
7})
8export class AppComponent {
9 title = 'Welcome to Angular 8 Learning Series...';
10}
دکوراتورها به طور عمده تابعهای جاوا اسکریپت هستند که به ترتیبی موجب بهبود کلاس دارای دکوراتور میشوند. کامپوننت یک بلوک مستقل و کامل است که منطق، نما و دادههای مورد نیاز به صورت واحد منفردی درآورده است. شما به عنوان یک توسعهدهنده برای ساخت یک کامپوننت صرفاً باید برخی اشیای پیکربندی مرتبط با تابع دکوراتور را به صورت آرگومان یا پارامتر به آن ارسال کنید. از نظر منطقی هر کامپوننت انگولار خود به صورت یک مفهوم MVC عمل میکند. از آنجا که هر کلاس کامپوننت واحد مستقلی است، قابلیت بالایی برای استفاده مجدد دارد و میتواند بدون ایجاد مشکل در کامپوننتهای دیگر نگهداری شود.
مثالی از یک کامپوننت ابتدایی
اکنون فرض کنید لازم است یک کامپوننت جدید در پروژه انگولار خود ایجاد کنیم. به این منظور هم میتوانیم یک پروژه جدید ایجاد کنیم و هم از همان پروژه بخش قبلی مقاله استفاده کنیم. در پوشه پروژه یک فایل کامپوننت به نام app.component.ts داریم. ابتدا یک کامپوننت با HTML درونخطی ایجاد میکنیم. به این منظور باید تغییرهای زیر را در فایل موجود app.component.ts ایجاد کنیم:
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 template: '<h1>Component is the main Building Block in Angular</h1>'
6})
7export class AppComponent {
8
9}
اگر اینک اپلیکیشن را در مرورگر اجرا کنید، با تصویری مانند زیر مواجه میشوید:
چه لزومی به استفاده از معماری کامپوننت-محور وجود دارد؟
با توجه به روندهای موجود در توسعه اپلیکیشنها، معماری کامپوننت-محور به عنوان یک معماری با بیشترین قابلیت استفاده در آینده توسعه وب مطرح شده است. با کمک گرفتن از این معماری، میتوانیم زمان و هزینه توسعه در حجم بالا را برای هر پروژه توسعه وب بزرگ-مقیاس کاهش دهیم. به همین جهت است که کارشناسان فنی امروزه پیادهسازی این معماری را در توسعه اپلیکیشنهای مبتنی بر وب توصیه میکنند. بنابراین پیش از این که به بررسی تفصیلی کامپوننتها بپردازیم، ابتدا باید دلیل لزوم استفاده از معماری کامپوننت-محور را در توسعه وباپلیکیشنها توضیح دهیم.
- قابلیت استفاده مجدد: فریمورکهای کامپوننت-محور فایده زیادی دارند، زیرا امکان ساخت فیچرهای با قابلیت استفاده مجدد را فراهم میسازند. در این چارچوب، کامپوننتها واحدهای منفردی در فرایند توسعه هستند. این توسعه با استفاده از کامپوننت موجب میشود که یک پیشبینی در خصوص قابلیت استفاده مجدد در چرخههای آتی توسعه داشته باشیم. با توجه به این که فناوری امروزه با سرعت زیادی در حال تغییر است، اگر اپلیکیشنی را با معماری کامپوننت-محور توسعه دهیم در آینده قادر خواهیم بود اجزای مختلف را که به شکل کامپوننت هستند، وارد پروژه کنیم یا آنها را کنار بگذاریم. رویکرد معماری کامپوننت-محور به ما امکان میدهد که اپلیکیشن را در طی زمان بهروز نگهداریم و دیگر نیاز نیست همه چیز را از صفر دوباره بسازیم.
- افزایش سرعت توسعه: توسعه کامپوننت-محور از توسعه مبتنی بر روش agile پشتیبانی میکند. کامپوننتها میتوانند در یک کتابخانه نگهداری شوند و تیم میتواند در طی فرایند توسعه به آنها دسترسی داشته، آنها را مورد استفاده قرار دهید یا تغییر دهد. از سوی دیگر باید توجه داشته باشیم که هر توسعهدهندهای مهارتهای تخصصی معینی دارد. برای نمونه یک فرد میتواند در زمینه جاوا اسکریپت تخصص داشته باشد. فرد دیگر متخصص CSS باشد و یا تخصصهای دیگری داشته باشند. در این چارچوب هر توسعهدهنده متخصص میتواند روی کامپوننت معینی که در حیطه تخصصی وی است کار کند.
- ادغام آسان: در چارچوب کامپوننت-محور میتوان یک ریپازیتوری کتابخانه مرتبط با کامپوننت ایجاد کرد. این ریپازیتوری کامپوننت میتواند به عنوان یک ریپازیتوری کد مرکزی برای توسعه کنونی و همچنین توسعههای آتی مورد استفاده قرار گیرد. همانند ریپازیتوریهای مرکزی کدهای دیگر این کتابخانه را میتوان در هر سیستم کنترل نسخهای نگهداری کرد. به این ترتیب توسعهدهنده میتواند به این ریپازیتوری دسترسی داشته باشد و فیچرها و کارکردهای جدید را بسته به الزامات جدید بهروزرسانی کند و جهت تأیید شدن تحویل دهد.
- بهینهسازی الزام و طراحی: از کتابخانه کامپوننت میتوان به عنوان یک منبع ارجاع کامپوننت UI استفاده کرد. به این ترتیب با بهرهگیری از این منبع، تحلیل اعضای تیم از قبیل مدیر محصول، تحلیلگر کسبوکار و یا رهبران فنی نیازمند صرف وقت کمتری است و نهاییسازی طراحی UI برای الزامات جدید سریعتر انجام مییابد، زیرا از قبل یک دسته کامپوننتهای کاملاً تستشده و با کارکرد صحیح در اختیار داریم. در این صورت تنها کافی است در مورد فرایند بهینهسازی شامل صرفاً منطق تجاری جدید تصمیمگیری شود. به این ترتیب این چارچوب کامپوننت-محور موجب افزایش سرعت چرخه عمر فرایند توسعه میشود.
- کاهش هزینه نگهداری: از آنجا که چارچوب کامپوننت-محور موجب ایجاد قابلیت استفاده مجدد میشود، چنین فریمورکی نیاز به وجود توسعهدهندگان زیاد را در زمان ساخت یک اپلیکیشن جدید از میان برمیدارد. کامپوننتهای مبتنی بر منطق به طور معمول «فاقد زمینه» (context-free) هستند و کامپوننتهای مبتنی بر UI نیز به همراه UI و UX مناسبی عرضه میشوند. از این رو توسعهدهنده میتواند روی ادغام این کامپوننتها در اپلیکیشن و شیوه ایجاد اتصالها بین این نوع کامپوننتها تمرکز کند. ضمناً خصوصیتهای دیگر سیستم از قبیل امنیت، عملکرد، قابلیت نگهداری، پایداری و مقیاسپذیری نیز میتوانند تست شوند.
متادیتای Component@
زمانی که بخواهیم یک کامپوننت جدید در انگولار بسازیم، باید از دکوراتور Component@ استفاده کنیم. دکوراتور Component@ اساساً یک کلاس تایپ اسکریپت به صورت یک شیء Component تعریف میکند. در واقع دکوراتور Component@ یک تابع است که انواع مختلفی از پارامترها را میگیرد. در دکوراتور Component@ میتوانیم مقادیر مشخصههای مختلف را برای نهاییسازی تغییر رفتار کامپوننت تعیین کنیم. رایجترین مشخصههای دکوراتور Component@ به شرح زیر هستند:
- selector – یک کامپوننت میتواند از سوی عبارت سلکتور مورد استفاده قرار گیرد. افراد زیادی با کامپوننتها به عنوان یک تگ سفارشی HTML رفتار میکنند، زیرا در نهایت زمانی که بخواهند از کامپوننت در فایل HTML استفاده کنند، باید یک سلکتور درست مانند تگ HTML داشته باشند.
- template - قالب یا template بخشی از کامپوننت است که در مرورگر رندر میشود. در این مشخصه میتوانیم تگهای HTML یا کد را به صورت مستقیم به نام کد inline ارسال کنیم. این قالبها گاهی اوقات قالبهای درونخطی (Inline) نامیده میشوند. برای نوشتن چندین خط کد، همه کد باید درون نماد backtick (`) قرار گیرد.
- templayeUrl – این مشخصه یک روش دیگر برای رندر کردن تگهای HTML در مرورگر است. این مشخصه همیشه نام فایل HTML را همراه با مسیر فایل مربوطه میگیرد. برخی اوقات آن را قالب خارجی مینامند. استفاده از این مشخصه بسیار بهتر از این است که بخواهیم UI پیچیده را درون کامپوننت طراحی کنیم.
- moduleId – این مشخصه برای به دست آوردن مسیر مرتبط URL قالب یا URL استایل برای اشیای کامپوننت مورد استفاده قرار میگیرد. این مشخصه Id ماژولهای مرتبط را که کامپوننت در آنها الصاق یافته یا تگ شده است را با هم ترکیب میکند.
- styles / stylesUrls – کامپوننتها میتوانند با ارائه CSS سفارشی با استفاده از استایل مخصوص خودشان مورد استفاده قرار گیرند. همچنین میتوانند به فایلهای استایلشیت بیرونی ارجاع دهند و به این ترتیب میتوان از یک استایلشیت برای تنظیم ظاهر چند کامپوننت استفاده کرد. برای ارائه یک استایل درونخطی (Inline) باید از styles و برای ارائه یک مسیر فایل بیرونی باید از styleUrls استفاده کنیم.
- providers – در اپلیکیشنهای واقعی باید انواع مختلفی از سرویسهای سفارشی را در کامپوننت مورد استفاده قرار داده یا تزریق کنیم تا منطق تجاری را برای کامپوننت پیادهسازی کنیم. برای استفاده از سرویسهای تعریف شده کاربر درون کامپوننت، باید یک وهله از سرویس را درون provider عرضه کنیم. از این رو مشخصه provider همواره مقادیر از نوع آرایه را مجاز میداند. به این ترتیب میتوانیم نامهای وهلههای چندگانه سرویس را تعریف کنیم که درون مشخصه با کاما از هم جدا میشوند.
در مثال زیر، شیوه تعریف یک کامپوننت را با استفاده از برخی مشخصهها از قبیل سلکتور و قالب را نشان دادهایم:
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 template: 'Welcome to Angular 8 Learning Series...'
6})
7export class AppComponent {
8}
در مثال زیر نیز شیوه استفاده از مشخصههای دیگر دکوراتور Component@ مانند templateUrls را میبینید:
1import { Component } from '@angular/core';
2
3@Component({
4 moduleId: module.id,
5 selector: 'app-root',
6 templateUrl: './app.component.html'
7})
8export class AppComponent {
9 title = 'Welcome to Angular 8 Learning Series...';
10}
بنابراین در مثال فوق، فایل HTML را برای ذخیرهسازی بخش HTML مرتبط با کامپوننتها جدا خواهیم ساخت. بر اساس مثال فوق، باید دو فایل تایپ اسکریپت و HTML را در مکان یکسانی قرار دهیم. اگر بخواهیم HTML را در مکان مجزایی قرار دهیم، باید از آن به وسیله یک URL نسبی در دکوراتور کامپوننت استفاده کنیم. در بخش زیر نمونه کدی را که در فایل app.component.html نوشته شده است میبینید:
1<div style="text-align:center">
2 <h1>
3 Welcome to {{ title }}!
4 </h1>
5</div>
مثالی از اعمال استایل روی محتوا
اکنون باید استایلهای مورد نظر خود را روی کامپوننت فوق اعمال کنیم. به این منظور تغییرهای زیر را در کامپوننت اعمال میکنیم. ابتدا استایلهای درونخطی را در کامپوننت ایجاد میکنیم.
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 template: '<h1>Component is the main Building Block in Angular</h1> <h2>Angular 8 Samples</h2>',
6 styles: ['h1{color:red;font-weight:bold}','h2{color:blue}']
7})
8export class AppComponent {
9
10}
اینک با اعمال تغییرهای فوق، اپلیکیشن را اجرا میکنیم تا نتیجهای مانند زیر به دست آید:
مثالی از فایل استایلشیت اکسترنال برای کامپوننت
در دموی پیشین از استایلشیت درونخطی برای تزیین محتوای HTML درون کامپوننت استفاده کردیم. این حالت در مواردی که لازم باشد استایلها تنها در یک کامپوننت استفاده شوند، مطلوب است. اما اگر بخواهیم همین استایلها را در همه کامپوننتها اعمال کنیم، باید از استایلشیت اکسترنال درون کامپوننت بهره بگیریم. به این منظور ابتدا باید یک فایل استایلشیت جدید به نام custom.css درون پروژه ایجاد کنیم و سپس کد زیر را درون همان فایل اضافه کنیم:
1/* You can add global styles to this file, and also import other style files */
2h1{
3 color:red;
4 font-weight:bold;
5 font-size: 30px;
6}
7h2{
8 color:blue;
9 font-size: 20px;
10}
11
12p{
13 color:brown;
14 font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
15}
اینک کافی است مسیر ارجاع style.css را در مشخصههای styleUrls درون فایل app.component.ts قرار دهیم.
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 template: '<h1>Component is the main Building Block in Angular</h1> <h2>Angular 8 Samples</h2>',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10}
اینک با بارگذاری مجدد مرورگر، خروجی زیر به دست میآید که همانند مثال قبلی است:
مثالی از استفاده از فایل HTML اکسترنال برای محتوای کامپوننت
همانند استایل اکسترنال، امکان استفاده از فایل HTML اکسترنال نیز برای بخش کد HTML وجود دارد. به این منظور ابتدا باید یک فایل HTML به نام app.component.html در پوشه app اضافه کنیم و کد زیر را در آن بنویسیم:
1<h1>Component is the main Building Block in Angular</h1>
2<h2>Angular 8 Samples</h2>
3<p>
4 Use of <b>External HTML</b> files with the Component
5</p>
در ادامه تغییرهای زیر را در فایل app.component.ts ایجاد میکنیم تا ارجاع مسیر فایل HTML بیرونی را به آن ارسال نماییم.
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10}
اینک با رفرش کردن صفحه مرورگر، خروجی زیر حاصل میشود:
چرخه عمر یک کامپوننت
کامپوننتهای انگولار نیز مانند دیگر فریمورکها رویدادهای خاص چرخه عمری دارند که به طور عمده توسط خود انگولار نگهداری میشوند. در ادامه فهرستی از رویدادهای چرخه عمر هر کامپوننت انگولار را توضیح میدهیم. در انگولار، هر کامپوننت یک چرخه عمر دارد که از مرحله مقداردهی تا تخریب، تعدادی مراحل مختلف را طی میکند. هشت مرحله مختلف در چرخه عمر کامپوننت وجود دارند. هر مرحله یک رویداد قلاب چرخه را فرامیخواند. بنابراین میتوانیم از این رویدادهای چرخه عمر کامپوننت در مراحل مختلف اپلیکیشن برای به دست آوردن کنترل کامل کامپوننتها استفاده کنیم.
- ngOnChanges – این رویداد هر بار که مقدار یک کنترل ورودی در کامپوننت تغییر یابد، اجرا میشود. این رویداد نخستین بار زمانی که یک مشخصهی اتصالیافته (Bound) تغییر یابد، فعال میشود.
- ngOnInit – این رویداد درزمان مقداردهی کامپوننت اجرا میشود. این رویداد تنها یک بار و پس از رویدادهای ()ngOnChanges فراخوانی میشود. این رویداد به طور عمده برای مقداردهی یک کامپوننت مورد استفاده قرار میگیرد.
- ngDoCheck – این رویداد هر بار که مشخصههای ورودی یک کامپوننت بررسی شوند، اجرا میشود. میتوان از این متد چرخه عمر برای پیادهسازی بررسی مقادیر ورودی همانند بررسی منطق سفارشی استفاده کرد.
- ngAfterContentInit – این متد چرخه عمر در زمانی اجرا میشود که هر نوع نمایش محتوا درون نماهای کامپوننت اجرا شود. این متد تنها یک بار و زمانی که همه اتصالهای کامپوننت قرار است برای نخستین بار بررسی شوند، اجرا میشود. این رویداد درست پس از متد ()ngDoCheck اجرا میشود.
- ngAfterContentChecked – این متد قلاب چرخه عمر هر بار که محتوای کامپوننت از سوی سازوکار تشخیص تغییر انگولار بررسی شود، اجرا خواهد شد. این متد پس از متد ()ngAfterContentInit فراخوانی میشود. این متد میتواند روی هر اجرای رویداد ()ngDoCheck نیز اجرا شود.
- ngAfterViewInit – این متد چرخه عمر زمانی اجرا میشود که کامپوننت کار رندرینگ نمایش را تکمیل کرده باشد. این متد چرخه عمری برای مقداردهی نمای کامپوننت و نماهای فرزند آن استفاده میشود. این متد تنها یک بار و پس از ()ngAfterContentChecked فراخوانده میشود. این متد قلاب چرخه عمری روی همه کامپوننتها اعمال میشود.
- ngAfterViewChecked – این متد همواره پس از متد ()ngAterViewInit اجرا میشود. ngAfterViewChecked یک متد چرخه عمری است که اساساً زمانی اجرا میشود که الگوریتم تشخیص تغییر کامپوننتهای انگولار فعال شود. این متد به صورت خودکار با هر بار اجرای ()ngAfterContentChecked اجرا خواهد شد.
- ngOnDestroy – این متد زمانی اجرا خواهد شد که بخواهیم کامپوننتهای انگولار را تخریب کنیم. این متد برای لغو اشتراک observable-ها و جداسازی دستگیرههای رویداد جهت جلوگیری از نشت حافظه بسیار مفید است. این متد تنها یک بار و درست پیش از این که کامپوننت از DOM حذف شود، فراخوانی میشود.
مثالی از چرخه عمر کامپوننت
در این مثال به بررسی رویدادهای چرخه عمر یک کامپوننت میپردازیم. به این منظور کد زیر را در فایل app.component.ts اضافه میکنیم:
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9 data:number=100;
10 constructor() {
11 console.log(`new - data is ${this.data}`);
12 }
13 ngOnChanges() {
14 console.log(`ngOnChanges - data is ${this.data}`);
15 }
16 ngOnInit() {
17 console.log(`ngOnInit - data is ${this.data}`);
18 }
19}
همچنین کد زیر را در فایل app.component.html اضافه میکنیم:
1<span class="setup">Given Number</span>
2<h1 class="punchline">{{ data }}</h1>
با رفرش کردن مرورگر، خروجی زیر به دست میآید:
کامپوننتهای تودرتو
در بخش پیش به بررسی وجوه گوناگون کامپوننتها از قبیل تعریف، متادیتا و رویدادهای چرخه عمری پرداختیم. در زمان توسعه یک اپلیکیشن در موارد متعددی لازم است که یک کامپوننت تودرتو را پیادهسازی کنیم. کامپوننت تودرتو به کامپوننتی گفته میشود که درون کامپوننت دیگری قرار میگیرد و یا آن را میتوان کامپوننت فرزند نامید. نخستین سؤال که پیش میآید این است که آیا فریمورک انگولار از چنین کامپوننتهای پشتیبانی میکند؟ پاسخ مثبت است. ما میتوانیم هر تعداد کامپوننت که دوست داریم درون یک کامپوننت دیگر قرار دهیم. همچنین انگولار به طور کلی از هر سطحی از تودرتوسازی پشتیبانی میکند.
مثالی از کامپوننت تودرتو
چنان که پیشتر اشاره کردیم، در انگولار میتوان هر کامپوننتی را به صورت والد-فرزند توسعه داد. به این منظور باید از سلکتور کامپوننت فرزند درون فایل HTML کامپوننت والد استفاده کنیم. بنابراین ابتدا باید یک کامپوننت فرزند مانند زیر توسعه دهیم.
فایل child.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'child',
5 templateUrl: './child.component.html',
6 styleUrls : ['./custom.css']
7})
8export class ChildComponent {
9
10}
فایل child.component.html
1<h2>It is a Child Component</h2>
2<p>
3 A component is a Reusable part of the application.
4</p>
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10}
فایل app.component.html
1<h1>Demostration of Nested Component in Angular</h1>
2<h3>It is a Parent Component</h3>
3<child></child>
اینک کامپوننت فرزند را به صورت زیر در فایل app.module.ts قرار میدهیم.
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3
4import { AppComponent } from './app.component';
5import { ChildComponent } from './child.component';
6
7@NgModule({
8 declarations: [
9 AppComponent,ChildComponent
10 ],
11 imports: [
12 BrowserModule
13 ],
14 providers: [],
15 bootstrap: [AppComponent]
16})
17export class AppModule { }
اینک با رفرش کردن مرورگر، خروجی زیر حاصل میشود:
بدین ترتیب در این بخش با مفاهیم مختلف مرتبط با کامپوننتهای انگولار از قبیل متادیتا، رویدادهای چرخه عمری و غیره آشنا شدیم و با بررسی مثالهایی به صورت عملی آنها را کدنویسی کردیم. در بخش بعدی با مفهوم «اتصال دادهها» (Data Binding) آشنا خواهیم شد.
اتصال دادهها (Data Binding)
اتصال دادهها یکی از مهمترین ویژگیهای فریمورک انگولار محسوب میشود. زیرا در هر وباپلیکیشنی ارسال و دریافت دادهها همواره نقشی کلیدی در توسعه دارد. در انگولار این کار با کمک مفهوم «اتصال دادهها» به روش بسیار آسانی انجام مییابد.
اتصال دادهها چیست؟
اتصال داده یکی از ظریفترین و مفیدترین قابلیتهای فریمورک انگولار است. توسعهدهندگان با استفاده از این ویژگی نیاز کمتری به کدنویسی در قیاس با هر کتابخانه یا فریمورک دیگر سمت کلاینت مییابند. اتصال دادهها در اپلیکیشن انگولار به همگامسازی خودکار دادهها بین کامپوننتها مدل و «نما» (View) گفته میشود. در انگولار با به خدمت گرفتن مفهوم اتصال دادهها، هر زمان میتوانیم با مدل به صورت یک «منبع منفرد واقعیت» (single-source-of-truth) در وباپلیکیشن رفتار کنیم. به این ترتیب UI یا نما همواره مدل دادهها را در هر حالت نمایش میدهد. توسعهدهندگان به کمک «اتصال دادهها» میتوانند رابطهای بین UI اپلیکیشن و منطق تجاری برقرار سازند. اگر اتصال دادهها را به روش صحیحی برقرار سازیم و دادهها اعلانهای مناسبی در اختیار فریمورک قرار دهند، در این صورت زمانی که کاربر تغییری در دادهها در نما ایجاد میکند، عناصری که به دادهها اتصال یافتهاند، به صورت خودکار این تغییر را بازتاب میدهند.
مفاهیم مقدماتی اتصال دادهها
در مورد هر وباپلیکیشن نیازمند ایجاد نوعی پل ارتباطی بین بکاند که دادهها در آنجا ذخیره شدهاند و فرانتاند که کاربر دادهها را دستکاری میکند، هستیم. کل این فرایند به برخی تعاملهای شبکهای پیدرپی و ارتباط مکرر بین سرور (بکاند) و کلاینت (فرانتاند) وابسته است.
به دلیل ماهیت پیدرپی و تکرارشونده این ارتباط بین کلاینت و سرور، اغلب پلتفرمهای فریمورک وب روی اتصال یکطرفه تمرکز کردهاند. این فرایند اساساً شامل خواندن ورودی از DOM، سریالسازی دادهها، ارسال آن به بکاند یا سرور و انتظار برای پایان فرایند است. سپس، این فرایند DOM را ویرایش میکند تا نشان دهد که خطایی رخ داده است یا نه و یا در صورت موفق بودن فراخوانی، عنصر DOM را بارگذاری مجدد کند.
با این که این فرایند یک وباپلیکیشن سنتی را ارائه میکند، اما همواره باید پردازش دادهها را اجرا کند و تنها برای وباپلیکیشنهایی مفید است که ساختمان داده پیچیده دارند. اگر اپلیکیشن شما قالب ساختمان داده سادهتری دارد و مدلهای آن نسبتاً مسطح هستند، در این صورت این کار اضافی منجر به پیچیدهتر شدن فرایند و کاهش عملکرد اپلیکیشن میشود.
فریمورک انگولار این موضوع را از طریق مفهوم اتصال داده حل میکند. اتصال داده یک فرایند ارتقای مداوم دادهها ایجاد میکند به طوری که وقتی کاربر هر تغییری در اینترفیس ایجاد میکند، به صورت خودکار دادهها را بهروزرسانی میکند و برعکس. به این ترتیب، مدل دادههای اپلیکیشن همواره به صورت یک واحد اتمیک عمل میکند، به طوری که نمای اپلیکیشن میتواند به کمک یک سری از دستگیرههای رویداد پیچیده و شنوندههای رویداد اجرا شود. اما این رویکرد میتواند به سرعت وضعیت بغرنجی ایجاد کند. در فریمورک انگولار این فرایند در ارتباط با دادهها به یکی از بخشهای اصلی معماری آن تبدیل شده است. به این ترتیب انگولار به جای ایجاد یک سری از callback-ها برای مدیریت تغییر دادهها، این کار را به صورت خودکار و بدون نیاز به مداخله برنامهنویس انجام میدهد. این قابلیت موجب ایجاد رفاه زیادی برای برنامهنویس میشود و صرفهجویی زیادی در زمان وی پدید میآورد.
از این رو مهمترین و اصلیترین مزیت اتصال دادهها این است که مدلهای دادهها را به صورت خودکار در ارتباط با نما بهروزرسانی میکند. از این رو زمانی که مدل دادهها بهروزرسانی میشود، به صورت خودکار موجب بهروزرسانی عنصر مرتبط نما در اپلیکیشن خواهد شد. به این ترتیب انگولار یک کپسولهسازی صحیح دادهها در فرانتاند ارائه میکند و الزام دستکاری پیچیده و تخریبی عناصر DOM را کاهش میدهد.
چه لزومی به اتصال داده وجود دارد؟
فریمورک انگولار از روز نخست خود این قابلیت خاص و قدرتمند «اتصال دادهها» را ارائه کرده است که همواره روانی و انعطافپذیری را در هر وباپلیکیشن به ارمغان آورده است. توسعهدهندگان با استفاده از این قابلیت «اتصال داده» میتوانند کنترل بهتری روی فرایند و مراحل مرتبط با پردازش اتصال داده داشته باشند. این فرایند موجب سهولت کار توسعهدهنده و کاهش زمان توسعه با توجه به فریمورکهای دیگر میشود. برخی از دلایل مرتبط با دلیل لزوم وجود اتصال دادهها در هر وباپلیکیشن به شرح زیر هستند:
- به کمک اتصال داده، صفحههای وب مبتنی بر دادهها میتوانند به روشی سریع و کارآمد توسعه یابند.
- ما همواره نتیجه مطلوب را با کمترین حجم کدنویسی به دست میآوریم.
- به دلیل این فرایند، زمان اجرا افزایش مییابد. در نتیجه، کیفیت اپلیکیشن افزایش مییابد.
- ما به کمک event emitter میتوانیم کنترل بهتری روی فرایند اتصال دادهها داشته باشیم.
انواع مختلف اتصال داده
در انگولار با چهار نوع مختلف از فرایندهای اتصال داده مواجه هستیم:
- درونیابی (Interpolation)
- اتصال مشخصه (Property Binding)
- اتصال دوطرفه (Two-Way Binding)
- اتصال رویداد (Event Binding)
درونیابی
درونیابی اتصال دادهها رایجترین و آسانترین روش Data Binding در انگولار محسوب میشود. این قابلیت در نسخههای قبلی فریمورک انگولار نیز وجود دارد. در واقع context بین آکولاد یک عبارت قالبی است که انگولار ابتدا ارزیابی میکند و سپس به رشته تبدیل میکند. درونیابی از عبارتهای آکولادی یعنی {{}} برای رندر مقدار اتصالیافته به قالب کامپوننت استفاده میکند. این مقدار میتواند یک رشته استاتیک، مقدار عددی یا یک شیء از مدل دادهها باشد. در انگولار ما از ساختاری مانند {{firstName}} استفاده میکنیم.
مثال زیر نشان میدهد که چگونه میتوانیم از درونیابی در کامپوننت برای نمایش دادهها در فرانتاند استفاده کنیم.
1<div>
2 <span>User Name : {{userName}}</span>
3</div>
مثالی از درونیابی
در مثال زیر شیوه استفاده یا پیادهسازی درونیابی را در اپلیکیشنهای انگولار برای انواع داده مختلف نشان میدهیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9 public value1: number = 10;
10 public array1: Array<number> = [10, 22, 14];
11 public dt1: Date = new Date();
12
13 public status: boolean = true;
14
15 public returnString(): string {
16 return "String return from function";
17 }
18}
فایل app.component.html
1<div>
2 <span>Current Number is {{value1}}</span>
3 <br /><br />
4 <span>Current Number is {{value1 | currency}}</span>
5 <br /><br />
6 <span>Current Number is {{dt1}}</span>
7 <br /><br />
8 <span>Current Number is {{dt1 | date}}</span>
9 <br /><br />
10 <span>Status is {{status}}</span>
11 <br /><br />
12 <span>{{status ? "This is correct status" :"This is false status"}}</span>
13 <br /><br />
14 <span>{{returnString()}}</span>
15</div>
خروجی کد فوق به صورت زیر است:
اتصال مبتنی بر مشخصه
در انگولار یک مکانیسم اتصال داده دیگر نیز وجود دارد که به نام اتصال شناخته میشود. ماهیت آن مشابه درونیابی است. برخی افراد آن را بنا بر نسخههای قبلی AngularJS، اتصال یکطرفه مینامند. اتصال مشخصه از نمادهای [] برای ارسال دادهها از کامپوننت به قالب HTML استفاده میکند. رایجترین روش برای استفاده از اتصال مشخصه، انتساب هر مشخصه تگ عنصر HTML به [] با استفاده از مقدار مشخصه به صورت زیر است:
1<input type=”text” [value]=”data.name”/>
برای پیادهسازی اتصال مشخصه، کافی است تغییر زیر را در فایل HTML قبلی یعنی interpolation.component.html اعمال میکنیم:
1<div>
2 <input [value]="value1" />
3 <br /><br />
4</div>
مثالی از اتصال داده مبتنی بر مشخصه
در این مثال به بررسی شیوه استفاده از اتصالهای مبتنی بر مشخصه در انگولار میپردازیم. به این منظور باید یک کادر متنی از نوع ورودی در فایل app.component.html اضافه کنیم و این کادر متنی را با استفاده از اتصال مشخصهای به متغیر value1 وصل میکنیم.
فایل app.component.html
1<div>
2 <span>Current Number is {{value1}}</span>
3 <br/><br />
4 Display Value in Input Controls : <input [value]="value1" />
5 <br /><br />
6 <span>Current Number is {{value1 | currency}}</span>
7 <br /><br />
8 <span>Current Number is {{dt1}}</span>
9 <br /><br />
10 <span>Current Number is {{dt1 | date}}</span>
11 <br /><br />
12 <span>Status is {{status}}</span>
13 <br /><br />
14 <span>{{status ? "This is correct status" :"This is false status"}}</span>
15 <br /><br />
16 <span>{{returnString()}}</span>
17</div>
اینک خروجی مثال ما به صورت زیر است:
اتصال رویداد
«اتصال رویداد» (Event Binding) یک تکنیک دیگر اتصال دادهها است که در انگولار استفاده میشود. این تکنیک اتصال دادهها با مقدار عناصر UI کار نمیکند، بلکه با فعالیتهای رویداد عناصر UI مانند رویداد کلیک، رویداد blur و غیره کار میکند. در نسخه قدیمی AngularJS از انواع مختلفی از دایرکتیوها مانند ng-click ،ng-blur برای اتصال هر اکشن رویداد خاص یک کنترل HTML استفاده میکنیم. اما در نسخه کنونی انگولار باید از همان مشخصه عنصر HTML (مانند click، change و غیره) استفاده کنیم و آن را درون پرانتزها قرار دهیم. در انگولار برای مشخصهها از براکت و در مورد رویدادها از پرانتز استفاده میکنیم:
1<div>
2 <input type="submit" value="Submit" (click)="fnSubmit()">
3</div>
مثالی از اتصال دادهها مبتنی بر رویداد
در این مثال به بررسی شیوه پیادهسازی اتصالهای مبتنی بر رویداد در انگولار میپردازیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 public showAlert() : void {
11 console.log('You clicked on the button...');
12 alert("Click Event Fired...");
13 }
14}
فایل app.component.html
1<div>
2 <h2>Demo of Event Binding in Angular 8</h2>
3 <input type="button" value="Click" class="btn-block" (click)="showAlert()" />
4 <br /><br />
5 <input type="button" value="Mouse Enter" class="btn-block" (mouseenter)="showAlert()" />
6</div>
خروجی کد فوق در مرورگر به صورت زیر است:
اتصال دوطرفه
در فریمورک انگولار، رایجترین و مهمترین تکنیکهای اتصال داده، نوعی به نام «اتصال داده دوطرفه» (Two-Way Data Binding) است. اتصال دوطرفه به طور عمده در فیلد نوع ورودی یا هر عنصر فرم که کاربر در مرورگر مقادیری را وارد میکند یا مقداری را ارائه کرده یا کنترلی را تغییر میدهد، استفاده میشود. از سوی دیگر، همین تغییر به صورت خودکار در متغیرهای کامپوننت و برعکس بهروزرسانی میشود. در انگولار یک دایرکتیو به نام ngModel داریم که باید به صورت زیر استفاده شود:
1<input type=”text” [(ngModel)] =”firstName”/>
مثالی از اتصال داده دوطرفه
در این مثال شیوه استفاده از اتصالهای داده دوطرفه را در انگولار بررسی میکنیم. به این منظور باید ابتدا دو کامپوننت زیر را بسازیم:
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 public val: string = "";
11}
فایل app.component.html
1<div>
2 <div>
3 <span>Enter Your Name </span>
4 <input [(ngModel)]="val" type="text"/>
5 </div>
6 <div>
7 <span>Your Name :- </span>
8 <span>{{val}}</span>
9 </div>
10</div>
اکنون زمانی که از ngModel یا اتصال داده دوطرفه در کامپوننتها استفاده کنیم، باید FormsModule انگولار را در فایل ماژول اپلیکیشن به صورت زیر تعریف کنیم، چون FormsModule بدون FormsModule کار نخواهد کرد.
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6
7@NgModule({
8 declarations: [
9 AppComponent
10 ],
11 imports: [
12 BrowserModule,FormsModule
13 ],
14 providers: [],
15 bootstrap: [AppComponent]
16})
17export class AppModule { }
خروجی کد فوق به صورت زیر است:
ما از [] استفاده میکنیم، زیرا در عمل یک اتصال مشخصه و پرانتز برای مفهوم اتصال داده استفاده میشود که نماد اتصال دوطرفه دادهها به صورت [()] است.
NgModel هر دو نوع اتصال مشخصه و اتصال رویداد را اجرا میکند. در واقع، اتصال مشخصه ngModel یعنی [ngModel]) عمل بهروزرسانی عنصر ورودی را با یک مقدار اجرا میکند. در حالی که (ngModel) یعنی رویداد (ngModelChange) به دنیای خارج اطلاع میدهد که تغییری در عنصر dom رخ داده است.
مثال زیر، پیادهسازی اتصال دوطرفه را نشان میدهد. در این مثال، یک متغیر رشته به نام strName تعریف میکنیم و این متغیر را به کنترل Textbox انتساب میدهیم. بنابراین هر زمان که محتوایی را در Textbox تغییر دهیم، مقدار متغیر به صورت خودکار تغییر خواهد یافت.
1<div>
2 <input [(ngModel)]="strName" type="text"/>
3</div>
دکوراتور ()Input@
در فریمورک انگولار همه کامپوننتها به صورت یک کامپوننت «بدون حالت» (Stateless) یا کامپوننت «با حالت» (Statefull) استفاده میشوند. به طور معمول کامپوننتها به روش بیحالت استفاده میشوند، اما برخی اوقات باید از برخی کامپوننتهای باحالت نیز استفاده کنیم. دلیل استفاده از کامپوننت باحالت در یک وباپلیکیشن به جهت ارسال یا بازیابی از کامپوننت کنونی به یکی از کامپوننتهای والد یا فرزند است. بنابراین در این مورد باید به انگولار اطلاع دهیم کدام نوع داده یا چه دادهای میتواند وارد کامپوننت کنونی ما شود. برای پیادهسازی این موضوع باید از دکوراتور ()Input@ در برابر هر متغیر استفاده کنیم. ویژگیهای کلیدی دکوراتور ()Input@ به شرح زیر هستند:
- ()Input@ یک دکوراتور برای نشانهگذاری مشخصه ورودی است. به کمک این مشخصه، میتوانیم یک مشخصه پارامتر ورودی را مانند خصوصیتهای تگ HTML نرمال تعریف کنیم و آن مقدار را به صورت یک اتصال مشخصه به کامپوننت متصل سازیم.
- دکوراتور ()Input@ همواره یک ارتباط یکطرفه دادهای از کامپوننت والد به کامپوننت فرزند برقرار میسازد. با استفاده از این قابلیت میتوانیم برخی از مقادیر را برای مشخصه یک کامپوننت فرزند از کامپوننتهای والد تعیین کنیم.
- مشخصه کامپوننت باید با دکوراتور ()Input@ حاشیهنویسی شود تا به عنوان یک مشخصه ورودی مورد استفاده قرار گیرد. یک کامپوننت میتواند مقداری را از کامپوننت دیگر با استفاده از اتصال مشخصه کامپوننت دریافت کند.
دکوراتور ()Input@ میتواند به صورت هر نوع از مشخصه از قبیل عدد، رشته، آرایه، یا کلاس تعریف شده کاربر حاشیهنویسی شود. برای استفاده از یک نام مستعار برای نام مشخصه اتصالیافته، باید یک نام مستعار به صورت Input(alias)@ انتساب دهیم. در مثال زیر کاربرد Input@ را با نوع داده رشتهای میبینید:
1@Input() caption : string;
2@Input('phoneNo') phoneNo : string;
در مثال فوق، testValue نام مستعار است. نام مستعار زمانی مورد نیاز است که بخواهیم مقداری را به مشخصههای ورودی ارسال کنیم. اگر نام مستعاری یافت نشود، در این صورت باید از نام مشخصه ورودی برای ارسال مقدار بهره بگیریم. بدین ترتیب زمانی که از این کامپوننت استفاده میکنیم، باید مقادیر ورودی را به صورت زیر ارسال کنیم:
1<demo-app [name]="'Debasis Saha'" [phoneNo]="9830098300"></demo-app>
مثالی از ارسال مقدار ورودی به کامپوننت
در این مثال با روش استفاده یا پیادهسازی مشخصه input یک کامپوننت آشنا خواهیم شد. به این منظور باید کامپوننت نخست زیر را توسعه دهیم که مشخصه ورودی آن تعریف خواهد شد.
فایل message.component.ts
1import { Component, Input } from '@angular/core';
2
3@Component({
4 selector: 'message-info',
5 templateUrl: './message.component.html',
6 styleUrls : ['./custom.css']
7})
8export class MessageComponent {
9
10 @Input() public message :string = '';
11
12 @Input('alert-pop') public message1 :string= ''
13
14 public showAlert():void{
15 alert(this.message1);
16 }
17}
فایل message.component.html
1<div>
2 Message : <h2>{{message}}</h2>
3 <input type="button" value="Show Alert" (click)="showAlert()"/>
4</div>
اکنون باید این کامپوننت message-info را در کامپوننت دیگری مصرف کنیم و باید مقدار ورودی را با استفاده از مشخصههای input ارسال کنیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 public val: string = "This is alert popup message";
11
12}
فایل app.component.html
1<div>
2 <message-info [message]="'Demostration of Input Property of a Component'" [alert-pop]="val"></message-info>
3</div>
خروجی مثال فوق به صورت زیر است:
دکوراتور ()Output@
دکوراتور ()Output@ در انگولار به طور معمول به عنوان یک مشخصه خروجی استفاده میشود. از ()Output@ برای تعریف مشخصههای خروجی که اتصالهای دادهای سفارش را دریافت میکنند بهره میگیریم. ()Output@ به عنان یک وهله از Event Emitter استفاده میشود. قابلیتهای کلیدی دکوراتور Event Emitter به شرح زیر هستند:
- دکوراتور ()Output@ یک مشخصه کامپوننت را برای ارسال دادهها از یک کامپوننت (کامپوننت فرزند) به کامپوننت فراخوانیکننده (کامپوننت والد) اتصال میدهد.
- این یک اتصال یک طرفه از فرزند به سمت والد است.
- ()Output@ یک مشخصه کلاس EventEmitter را اتصال میدهد. اگر کامپوننت را به صورت یک کنترل تصور کنیم، در این صورت مشخصه خروجی به عنوان یک رویداد آن کنترل عمل خواهد کرد.
- دکوراتور EventEmitter میتواند گزینههایی برای سفارشیسازی نام مشخصه با استفاده از نام مستعار به صورت Output(alias)@ نیز ارائه کند. در این حالت، اسامی مستعار سفارشی به عنوان یک نام مشخصه اتصال رویداد کامپوننت عمل میکنند.
دکوراتور Output(alias)@ در هر نوع مشخصه از قبیل عدد، رشته، آرایه یا کلاسهای تعریف شده کاربر میتواند وارد شود. برای استفاده از یک اسم مستعار برای نام مشخصه اتصال میتوانیم یک نام مستعار به صورت Output(alias)@ مورد استفاده قرار دهیم. کاربرد Output@ با نوع داده رشتهای به صورت مثال زیر است. توجه کنید که دکوراتور خروجی نیز همانند دکوراتور ورودی، ابتدا باید به صورت زیر اعلان شود:
1@Output('onSubmit') submitEvent = new EventEmitter<any>();
سپس باید emitter را از جایی درون کامپوننت فعال کنیم تا رویداد بتواند از سوی کامپوننت والد مورد ردگیری قرار گیرد:
1this.submitEvent.emit();
اگر بخواهیم هر مقداری را از طریق این event emitter ارسال کنیم، در این صورت باید آن مقدار را به صورت پارامتر از طریق ()emit بفرستیم. مشخصه خروجی در کامپوننت والد به صورت زیر تعریف میشود:
1<demo-app (onSubmit)="receiveData($event)"></demo-app>
مثالی از بازگشت مقدار خروجی از یک کامپوننت
در این مثال به بررسی شیوه استفاده از مشخصه output هر کامپوننت میپردازیم. به این منظور باید تغییرهای زیر را در کامپوننت message-info ایجاد کنیم تا یک مشخصه output را تعریف کنیم.
فایل message.component.ts
1import { Component, Input, EventEmitter, Output } from '@angular/core';
2
3@Component({
4 selector: 'message-info',
5 templateUrl: './message.component.html',
6 styleUrls : ['./custom.css']
7})
8export class MessageComponent {
9
10 @Input() public message :string = '';
11 @Input('alert-pop') public message1 :string= ''
12
13 @Output() onSignup = new EventEmitter<any>();
14
15 public data:any={};
16
17 public showAlert():void{
18 alert(this.message1);
19 }
20
21 public onSubmit() :void{
22 this.onSignup.emit(this.data);
23 }
24}
فایل message.component.html
1<div>
2 Message : <h2>{{message}}</h2>
3 <input type="button" value="Show Alert" (click)="showAlert()"/>
4 <br/><br/>
5 Provide Full Name : <input type="text" [(ngModel)]="data.name">
6 <br/>
7 Provide Email Id : <input type="email" [(ngModel)]="data.email">
8 <br>
9 <input type="button" value="Sign Up" (click)="onSubmit()"/>
10</div>
در بخش فوق، یک مشخصه output به نام ()onSignup درون کامپوننت message-info تعریف میکنیم. اکنون باید این رویداد را در کامپوننت والد به صورت زیر مصرف کنیم.
فایل app.component.html
1<div>
2 <message-info [message]="'Demostration of Input Property of a Component'" [alert-pop]="val" (onSignup)="onSignup($event)"></message-info>
3</div>
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 public val: string = "This is alert popup message";
11
12 public onSignup(data:any):void{
13 let strMessage:string ="Thanks for Signup " + data.name + ". ";
14 strMessage += "Email id " + data.email + " has been registered successfully.";
15 alert(strMessage);
16 }
17}
خروجی مثال فوق به صورت زیر است:
در این بخش تکنیکهای مختلف اتصال داده در انگولار را بررسی کردیم. همچنین مشخصههای ورودی و خروجی کامپوننتها را معرفی کردیم. در بخش بعدی یکی از اجزای مهم دیگر انگولار یعنی دایرکتیوها را بررسی خواهیم کرد.
دایرکتیو چیست؟
«دایرکتیو» (Directive) با تغییر دادن ظاهر، رفتار یا لیآوت عناصر DOM آنها را تغییر میدهد. دایرکتیوها مانند کامپوننتها یکی از بلوکهای سازنده اصلی فریمورک انگولار برای ساخت اپلیکیشنها است. در واقع، بخش بزرگی از کامپوننتهای انگولار در عمل دایرکتیوهایی به همراه قالب (Template) هستند. کامپوننتهای انگولار نقشهای زیادی را که دایرکتیوها بر عهده دارند مصرف میکنند. در انگولار 8 یکی از مشکلات عمده در خصوص تزریق قالب و تزریق وابستگی به کمک کامپوننتها حل شده است و مشکلات مرتبط با ایجاد تغییر در رفتار ژنریک اپلیکیشن نیز به کمک دایرکتیوها حل شده است.
بنابراین اگر تعاریف سطح بالای دایرکتیوها را در نظر بگیریم، دایرکتیوها به عنوان نشانگرهای HTML روی هر عنصر DOM عمل میکنند. از این نظر دایرکتیوها شبیه خصوصیت، نام یا کامنت عنصر و یا کلاس استایل بر مبنای CSS هستند که به کامپایلر انگولار دستور میدهند تا رفتار خاصی را به آن عنصر معین DOMN الصاق کند یا آن عنصر DOM را تغییر دهد.
مفاهیم مقدماتی دایرکتیوها
در فریمورک انگولار، یکی از مهمترین عناصر دایرکتیوها هستند. اگر بخواهیم خود دایرکتیو را تحلیل کنیم، متوجه میشویم که بلوک سازنده اصلی انگولار که کامپوننت شناخته میشود، در واقع دایرکتیو است. به بیان ساده هر کامپوننت انگولار در عمل یک دایرکتیو با قالب HTML سفارشی است. بنابراین در دنیای واقعی زمانی که یک کامپوننت را به عنوان بلوک سازنده اصلی اپلیکیشن انگولار تعریف میکنیم، در واقع میخواهیم بگوییم که دایرکتیوها اجزای اصلی تشکیلدهنده اپلیکیشنهای انگولار هستند.
به طور کلی یک دایرکتیو به تابع مبتنی بر تایپ اسکریپت گفته میشود که کامپایلر انگولار آن را درون عنصر DOM شناسایی کرده و اجرا میکند. دایرکتیوها برای ارائه یا تولید ساختار جدید مبتنی بر HTML استفاده میشوند که قدرت UI را در اپلیکیشن انگولار بسط میدهد. هر دایرکتیو باید یک نام سلکتور مانند کامپوننت داشته باشد. این نام هم میتواند از الگوهای از پیشتعریفشده انگولار مانند ng-if یا یک نام سفارشی انتخاب شده از سوی توسعهدهنده باشد که نشانگر مقصود دایرکتیو باشد. همچنین هر دایرکتیو میتواند به عنوان یک عنصر یا یک خصوصیت یا یک کلاس یا یک کامپوننت در بخش HTML عمل کند.
چرا به دایرکتیوها نیاز داریم؟
دایرکتیوها در فریمورک انگولار همواره سطح بالایی از قابلیت استفاده مجدد کنترلهای UI را در سراسر اپلیکیشن تضمین میکنند. به کمک دایرکتیوها میتوانیم UI-هایی را توسعه دهیم که بخشهای متحرک زیادی داشته باشند و همزمان جریان توسعه را در میان مهندسان مختلف تقسیم کنیم. دلیل اصلی استفاده از دایرکتیوها در هر اپلیکیشن انگولار به شرح زیر است:
- قابلیت استفاده مجدد – دایرکتیو در یک اپلیکیشن انگولار به بخش مستقلی از UI گفته میشود. ما به عنوان توسعهدهنده میتوانیم از دایرکتیو در بخشهای مختلف اپلیکیشن بهره بگیریم. این وضعیت در اپلیکیشنهای بزرگ-مقیاس که چندین سیستم به عناصر کارکردی یکسانی از قبیل کادر جستجو، کنترل تاریخ و غیره نیاز دارند، اهمیت دوچندان مییابد.
- قابلیت خوانایی – دایرکتیوها خوانایی بیشتری در اختیار ما قرار میدهند و میتوانیم کارکرد پروداکشن و گردش دادهها را درک کنیم.
- قابلیت نگهداری – یکی از کاربردهای اصلی دایرکتیو در هر اپلیکیشنی ایجاد قابلیت نگهداری است. به این ترتیب میتوانیم به سادگی دایرکتیو را از اپلیکیشن جدا کنیم و یا یک دایرکتیو قدیمی را با دایرکتیو جدید جایگزین نماییم.
تفاوت کامپوننتها با دایرکتیوها
در جدول زیر تفاوت بین کامپوننتها و دایرکتیوها را توضیح میدهیم.
کامپوننت | دایرکتیو |
کامپوننت با دکوراتور Component@ تعریف میشود. | دایرکتیو با دکوراتور Directive@ تعریف میشود. |
کامپوننت دایرکتیوی است که از یک DOM سایه برای ایجاد رفتار بصری کپسولهسازیشده به نام کامپوننت استفاده میکند. کامپوننتها به طور معمول برای ایجاد ویجتهای UI استفاده میشود. | دایرکتیو به طور عمده برای ارائه رفتار جدید درون عناصر DOM موجود استفاده میشود. |
به کمک کامپوننت میتوانیم اپلیکیشن را به چند بخش کوچکتر تقسیم کنیم. | با کمک دایرکتیو میتوانیم هر نوع کامپوننت که قابلیت استفاده مجدد داشته باشد را طراحی کنیم. |
در DOM مرورگر تنها یک کامپوننت میتواند به عنوان کامپوننت والد فعال شود. در این حالت، کامپوننتهای دیگر به عنوان کامپوننت فرزند عمل میکنند. | درون یک عنصر منفرد DOM، هر تعداد دایرکتیو میتواند مورد استفاده قرار گیرد |
وجود دکوراتور @View یا قالب templateUrl در هر کامپوننت الزامی است. | دایرکتیو از View استفاده نمیکند. |
متادیتای Directive@
دکوراتور Directive@ برای تعریف کردن یک کلاس به صورت دایرکتیو انگولار مورد استفاده قرار میگیرد. ما همواره میتوانیم دایرکتیوها را برای تعیین رفتار سفارشی عناصر درون DOM تعریف کنیم. متادیتای Directive@ شامل گزینههای زیر است:
- Selector – مشخصه سلکتور برای شناسایی دایرکتیو درون قالب HTML استفاده میشود. همچنین به کمک این مشخصه میتوانیم دایرکتیو را از روی عناصر DOM مقداردهی کنیم.
- Inputs – این گزینه برای ارائه یک مجموعه از مشخصههای ورودی متصل به داده از دایرکتیوها استفاده میشود.
- Outputs – این گزینه برای شمارش مشخصههای رویداد از دایرکتیوها استفاده میشود.
- Providers – این گزینه برای تزریق هر نوع ارائهدهنده مانند سرویس یا کامپوننتهای درون دایرکتیوها استفاده میشود.
- exportAs – این گزینه برای تعریف نام دایرکتیوهایی استفاده میشود که میتوانند برای انتساب دایرکتیو به عنوان یک متغیر استفاده شوند.
انواع دایرکتیوها
سه نوع عمده از دایرکتیوها در انگولار وجود دارند:
- Component – شامل دایرکتیوهای دارای قالب است.
- Attribute Directives – دایرکتیوهایی هستند که رفتار یک کامپوننت یا عنصر را تغییر میدهند، اما تأثیری روی قالب ندارند.
- Structural Directives – دایرکتیویهایی هستند که رفتار یک عنصر یا کامپوننت را از طریق تأثیرگذاری روی قالب یا دکوراسیون DOM در UI تغییر میدهند.
دایرکتیو خصوصیت چیست؟
«دایرکتیوهای خصوصیت» (Attribute Directive) به طور عمده برای تغییر دادن ظاهر یا رفتار یک کامپوننت یا یک عنصر نیتیو DOM مورد استفاده قرار میگیرند. دایرکتیوهای خصوصیت عملاً ظاهر یا رفتار یک عنصر را تغییر میدهند. این دایرکتیوها به عنوان یک خصوصیت HTML برای هر تگ HTML عمل میکنند. برخی دایرکتیوهای داخلی خصوصیت مانند ngModel در فریمورک انگولار وجود دارند. اما ما میتوانیم هر نوع دایرکتیو مبتنی بر خصوصیت سفارشی را نیز بسته به نیاز ایجاد کنیم. در این حالت، از نام سلکتور دایرکتیو خصوصیت به عنوان یک خصوصیت درون تگ HTML در بخش کد صفحه استفاده میکنیم.
مثالی از دایرکتیو خصوصیت
در این بخش شیوه استفاده از دایرکتیوهای خصوصیت داخلی را در فریمورک انگولار بررسی میکنیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 showColor: boolean = false;
11
12 constructor() { }
13
14 public changeColor(): void {
15 this.showColor = !this.showColor;
16 }
17}
فایل app.component.html
1<div>
2 <h3>This is a Attribute Directives</h3>
3 <span [class.red]="true">Attribute Change</span><br />
4 <span [ngClass]="{'blue':true}">Attribute Change by Using NgClass</span><br />
5 <span [ngStyle]="{'font-size':'14px','color':'green'}">Attribute Change by Using NgStyle</span>
6 <br /><br />
7 <span [class.cyan]="showColor">Attribute Change</span><br />
8 <span [ngClass]="{'brown':showColor}">Attribute Change by Using NgClass</span><br />
9 <input type="button" value="Change Color" (click)="changeColor()" />
10 <br /><br />
11 <span [class.cyan]="showColor">Attribute Change</span><br />
12 <span [ngClass]="{'cyan':showColor, 'red' : !showColor}">Attribute Change by Using NgClass</span><br />
13 <br />
14</div>
فایل custom.css
1.red {color:red;}
2.blue {color:blue}
3.cyan {color : cyan}
4.brown {color : brown}
اکنون خروجی را در مرورگر بررسی کنید.
دایرکتیوهای داخلی خصوصیت
انگولار برخی دایرکتیوهای داخلی خصوصیت را عرضه کرده است که میتوان برای تغییر دادن استایل یا خصوصیتهای عناصر HTML در DOM مورد استفاده قرار داد. این دایرکتیوهای خصوصیت به شرح زیر هستند:
- ngClass – دایرکتیو ngClass موجب تغییر خصوصیت class متصل به کامپوننت یا عنصر میشود.
- ngStyle – دایرکتیو ngStyle برای تغییر یا ویرایش خصوصیت استایل یک عنصر مورد استفاده قرار میگیرد. این دایرکتیو خصوصیت کاملاً مشابه استفاده از متادیتای استایل در کلاس کامپوننت است.
دایرکتیو ساختاری چیست؟
انواع دیگری از دایرکتیو در فریمورک انگولار وجود دارند که «دایرکتیوهای ساختاری» (Structural Directive) نام دارند. دایرکتیوهای ساختاری به طور عمده برای تغییر الگوی طراحی عناصر DOM در UI مورد استفاده قرار میگیرند. این دایرکتیوها در HTML به عنوان تگ قالب مورد استفاده قرار میگیرند. ما با استفاده از این نوع دایرکتیوها میتوانیم ساختار هر عنصر DOM را تغییر داده و آن عناصر DOM را بازطراحی کرده یا از نو تزیین کنیم.
در فریمورک انگولار برخی دایرکتیوهای ساختاری مانند ngIf ،ngFor و ngSwitch وجود دارند که از سوی سیستم تولید میشوند. همچنین میتوانیم هر نوع دایرکتیو ساختاری دیگر را نیز به صورت سفارشی ایجاد کنیم.
رایجترین نمونه از دایرکتیوهای ساختاری سفارشی، همان کامپوننتها هستند. در واقع ما میتوانیم هر کامپوننت را در صورتی که تغییری در استایل یا طراحی عناصر DOM در UI ایجاد کند، به عنوان یک دایرکتیو ساختاری در نظر بگیریم. همه دایرکتیوهای ساختاری تولید شده از سوی سیستم دارای نام قالب به همراه برخی مشخصههای مقدار هستند که باید در زمان تعریف دایرکتیو در کد HTML ارائه شوند.
دایرکتیوهای داخلی ساختاری
انگولار دایرکتیوهای ساختاری زیر را عرضه کرده است که میتوانند درون یک کامپوننت برای تغییر دادن ساختار یا طراحی عناصر مورد استفاده قرار گیرند.
- ngIf
- ngFor
- ngSwitch
مثالی از کاربرد ngIf
در این مثال به بررسی شیوه استفاده از ngIf در یک اپلیکیشن انگولار میپردازیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 showInfo: boolean = false;
11 caption: string = 'Show Text';
12
13 constructor() { }
14
15 public changeData(): void {
16 this.showInfo = !this.showInfo;
17 if (this.showInfo) {
18 this.caption = 'Hide Text';
19 }
20 else {
21 this.caption = 'Show Text';
22 }
23 }
24}
فایل app.component.html
1<div>
2 <input type="button" value="{{caption}}" (click)="changeData()"/>
3 <br />
4 <h2 *ngIf="showInfo"><span>Demonstrate of Structural Directives - *ngIf</span></h2>
5</div>
خروجی این مثال در مرورگر به صورت زیر است.
مثالی از کاربرد ngFor
در این مثال، کاربرد دایرکتیو ngFor را در یک اپلیکیشن انگولار بررسی میکنیم.
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 productList: Array<string> = ['IPhone','Galaxy 9.0','Blackberry 10Z'];
11
12 constructor() { }
13<b>} </b>
فایل app.component.html
1<div>
2 <h2>Demonstrate ngFor</h2>
3 <ul>
4 <li *ngFor="let item of productList">
5 {{item}}
6 </li>
7 </ul>
8</div>
خروجی این مثال در مرورگر به صورت زیر است.
مثالی از کاربرد ngSwitch
برای مشاهده کاربرد دایرکتیو ngSwitch به مثال زیر توجه کنید.
فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent implements OnInit {
9
10 studentList: Array<any> = new Array<any>();
11
12 constructor() { }
13 ngOnInit() {
14 this.studentList = [
15 { SrlNo: 1, Name: 'Rajib Basak', Course: 'Bsc(Hons)', Grade: 'A' },
16 { SrlNo: 2, Name: 'Rajib Basak1', Course: 'BA', Grade: 'B' },
17 { SrlNo: 3, Name: 'Rajib Basak2', Course: 'BCom', Grade: 'A' },
18 { SrlNo: 4, Name: 'Rajib Basak3', Course: 'Bsc-Hons', Grade: 'C' },
19 { SrlNo: 5, Name: 'Rajib Basak4', Course: 'MBA', Grade: 'B' },
20 { SrlNo: 6, Name: 'Rajib Basak5', Course: 'MSc', Grade: 'B' },
21 { SrlNo: 7, Name: 'Rajib Basak6', Course: 'MBA', Grade: 'A' },
22 { SrlNo: 8, Name: 'Rajib Basak7', Course: 'MSc.', Grade: 'C' },
23 { SrlNo: 9, Name: 'Rajib Basak8', Course: 'MA', Grade: 'D' },
24 { SrlNo: 10, Name: 'Rajib Basak9', Course: 'B.Tech', Grade: 'A' }
25 ];
26 }
27}
فایل app.component.html
1<div>
2 <h2>Demonstrate ngSwitch</h2>
3 <table style="width:100%;border:solid;border-color:blue;border-width:thin;">
4 <thead>
5 <tr >
6 <td>Srl No</td>
7 <td>Student Name</td>
8 <td>Course</td>
9 <td>Grade</td>
10 </tr>
11 </thead>
12 <tbody>
13 <tr *ngFor="let student of studentList;" [ngSwitch]="student.Grade">
14 <td>
15 <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.SrlNo}}</span>
16 <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.SrlNo}}</span>
17 <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.SrlNo}}</span>
18 <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.SrlNo}}</span>
19 </td>
20 <td>
21 <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Name}}</span>
22 <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Name}}</span>
23 <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Name}}</span>
24 <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Name}}</span>
25 </td>
26 <td>
27 <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Course}}</span>
28 <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Course}}</span>
29 <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Course}}</span>
30 <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Course}}</span>
31 </td>
32 <td>
33 <span *ngSwitchCase="'A'" [ngStyle]="{'font-size':'18px','color':'red'}">{{student.Grade}}</span>
34 <span *ngSwitchCase="'B'" [ngStyle]="{'font-size':'16px','color':'blue'}">{{student.Grade}}</span>
35 <span *ngSwitchCase="'C'" [ngStyle]="{'font-size':'14px','color':'green'}">{{student.Grade}}</span>
36 <span *ngSwitchDefault [ngStyle]="{'font-size':'12px','color':'black'}">{{student.Grade}}</span>
37 </td>
38 </tr>
39 </tbody>
40 </table>
41</div>
خروجی این مثال در مرورگر به صورت زیر است:
دایرکتیو سفارشی
برای ایجاد دایرکتیوهای خصوصیت باید از اشیای زیر استفاده کرده و یا آنها را در کلاس کامپوننت دایرکتیو خصوصیت سفارشی تزریق کنیم. برای ایجاد یک دایرکتیو خصوصیت باید موضوعهای زیر را به خاطر داشته باشیم:
- ماژولهای مورد نیاز مانند دایرکتیوها ElementRef و renderer را از کتابخانه مرکزی انگولار ایمپورت کنیم.
- یک کلاس TypeScript ایجاد کنیم.
- از دکوراتور Directive@ در کلاس استفاده کنیم.
- مقدار مشخصه سلکتور را در تابع دکوراتور Directive@ تعریف کنیم. این دایرکتیو با استفاده از مقدار سلکتور روی عناصر مورد استفاده قرار خواهد گرفت.
- در سازنده کلاس، ElementRef و شیء renderer را تزریق کنیم.
- باید ElementRef را در سازنده دایرکتیو تزریق کنیم تا به عنصر DOM دسترسی داشته باشیم.
- همچنین باید رندرر را در سازنده دایرکتیو تزریق کنید تا بتوانید روی استایل عنصر DOM کار کنید.
- در نهایت باید تابع setElementStyle رندرر را فراخوانی کنید. در این تابع باید وهله عنصر کنونی DOM را با کمک ElementRef به صورت یک پارامتر ارسال کنیم و رفتار یا مشخصه عنصر جاری را تعیین کنیم.
- ElementRef – در زمان ایجاد یک دایرکتیو خصوصیت سفارشی باید ElementRef را در سازنده تزریق کنیم تا به عنصر DOM دسترسی داشته باشیم. ElementRef امکان دسترسی به عنصر نیتیو زیربنایی را فراهم میسازد. به کمک ElementRef میتوانیم با استفاده از مشخصه nativeElement آن، دسترسی مستقیمی به عنصر DOM داشته باشیم. در این حالت، ElementRef درست مانند یک سرویس عمل میکند. این تنها چیزی است که برای تعیین رنگ عنصر با استفاده از DOM API نیاز داریم.
- Renderer – در زمان ایجاد یک دایرکتیو خصوصیت سفارشی، Renderer را در سازنده تزریق میکنیم تا به استایل عنصر DOM دسترسی داشته باشیم. در واقع ما تابع setElementStyle رندرر را فراخوانی میکنیم. در این تابع، عنصر کنونی DOM را به کمک شیء setElementStyle ارسال و خصوصیت مورد نیاز عنصر جاری را تعیین میکنیم.
- HostListener – گاهی اوقات باید به مشخصه ورودی درون دایرکتیو خصوصیت دسترسی داشته باشیم تا بتوانیم خصوصیت مرتبطی را درون عنصر DOM اِعمال کنیم. برای به دست آوردن اکشنهای کاربر میتوانیم متدهای مختلفی را فراخوانی کنیم. این متد برای اجرای هر نوع اکشنی استفاده میشود. به این منظور باید متد HostListener@ را به این متد اضافه کنیم.
مثالی از کاربرد دایرکتیوهای سفارشی برای تغییر رنگ
در این مثال یک دایرکتیو مبتنی بر خصوصیت سفارشی میسازیم که رنگ متن انتخابی را در زمان رویداد mouseover تغییر میدهد. به این منظور ابتدا باید دایرکتیو زیر را اجرا کنیم.
فایل app.directive.ts
1import { Directive, ElementRef, Renderer, HostListener, Input } from '@angular/core';
2
3@Directive({
4 selector: '[colorchange]'
5})
6export class ColorChangeDirective {
7 private _defaulColor = 'red';
8 @Input('colorchange') highlightColor: string;
9
10 constructor(private el: ElementRef, private render: Renderer) {
11 }
12
13 @HostListener('mouseenter') onMouseEnter() {
14 console.log(this.highlightColor);
15 this.changecolor(this.highlightColor || this._defaulColor);
16 }
17
18 @HostListener('mouseleave') onMouseLeave() {
19 console.log(this.highlightColor);
20 this.changecolor(null);
21 }
22
23 private changecolor(color: string) {
24 this.render.setElementStyle(this.el.nativeElement, 'color', color);
25 }
26}
سپس از این دایرکتیو سفارشی در کامپوننت app-root به صورت زیر استفاده میکنیم:
فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9
10 public message: string = 'Sample Demostration of Attribute Directives using Custom Directives';
11 public color: string = 'blue';
12
13}
فایل app.component.html
1<div>
2 <input type="radio" name="colors" (click)="color='blue'">blue
3 <input type="radio" name="colors" (click)="color='orange'">orange
4 <input type="radio" name="colors" (click)="color='green'">green
5</div>
6<h1 [colorchange]="color">{{message}}</h1>
اینک دایرکتیو سفارشی که در بخش قبل ساختیم را در AppModule به صورت زیر قرار میدهیم
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { ColorChangeDirective } from './app.directive';
7
8@NgModule({
9 declarations: [
10 AppComponent,ColorChangeDirective
11 ],
12 imports: [
13 BrowserModule,FormsModule
14 ],
15 providers: [],
16 bootstrap: [AppComponent]
17})
18export class AppModule { }
نتیجه نهایی باید به صورت زیر باشد:
بدین ترتیب با مفهوم دایرکتیو در انگولار آشنا شدیم. در بخش بعدی در خصوص Pipe در انگولار توضیح میدهیم. با استفاده از Pipe میتوانیم دادهها را به صورت قالب مورد نظر خودمان درآوریم. هدف اصلی استفاده از Pipe انتقال دادهها درون یک قالب HTML است.
Pipe چیست؟
زمانی که میخواهیم یک اپلیکیشن انگولار بسازیم، همواره کار را از اجزای ساده مانند بازیابی دادهها و انتقال و نمایش آنها در برابر کاربر و از طریق رابط کاربری آغاز میکنیم. بازیابی دادهها از هر نوع منبع داده به صورت کامل به ارائهدهنده سرویس داده از قبیل WEB API و غیره بستگی دارد. از این رو زمانی که دادهها به دست آمد، میتوانیم آنها را مستقیماً به رابط کاربری بفرستیم تا در معرض دید کاربر قرار بگیرد. اما گاهی اوقات فرایند کار به این صورت نیست. برای نمونه در اغلب موارد کاربران ترجیح میدهند که دادهها را در قالب سادهای مانند 15/02/2020 ببینند تا این که یک رشته خام مانند زیر را شاهد باشند:
Wed Feb 15 2017 00:00:00 GMT-0700 (Pacific Daylight Time)
بنابراین بدیهی است که از روی مثال فوق مشخص است که برخی مقادیر دادهای پیش از نمایش برای کاربر، نیازمند ویرایش هستند. همچنین ممکن است یک بخش از اطلاعات در چند نقطه از رابط کاربری مورد نیاز ما باشد. در این حالت، از برخی مشخصههای نوع استایل استفاده میکنیم که به صورت مرکزی ایجاد میشوند و در موارد نیاز مورد استفاده قرار میگیرند. فریمورک انگولار، به این منظور مفهوم Pipe-ها را معرفی کرده است که یک روش قطعی برای نوشتن تبدیلهای نمایش-مقدار است که میتوانند در HTML اعلان شوند.
Pipe-ها در انگولار به صورت کلاسهای تایپ اسکریپت هستند که یک اینترفیس تابع منفرد را پیادهسازی میکنند، یک مقدار ورودی با یک پارامتر اختیاری آرایه میپذیرند و یک مقدار تبدیلیافته بازگشت میدهند. برای اجرای تبدیل مقدار، میتوانیم هر نوع منطق تجاری را بسته به نیاز پیادهسازی کنیم. این Pipe میتواند در هر UI بسته به نتیجه مورد نیاز، برای تبدیل آن نوع خاص به دادهها استفاده شود.
مفهوم ابتدایی Pipe-ها
Pipe-ها اساساً یک روش جالب و پیچیده برای اجرای وظایف مختلف درون قالبها فراهم میسازند. Pipe موجب میشود که کد ما تمیز و ساختیافته باشد. Pipe-ها در انگولار، مقادیر را از عناصر DOM میگیرند و بر اساس منطق تجاری پیادهسازیشده درون Pipe-ها یک مقدار بازگشت میدهند. بنابراین، Pipe-ها یکی از قابلیتهای عالی انگولار محسوب میشوند که از طریق آنها دادههای خود را به UI تبدیل کرده و نمایش میدهیم. اما یک نکته را باید به روشنی درک کنیم و آن این است که پایپها به صورت خودکار مقدار مدل ما را بهروزرسانی نمیکنند.
پایپ تبدیل دادهها را انجام میدهند و آن را به کامپوننت بازگشت میدهد. اگر در ادامه نیاز باشد که UI را بهروزرسانی کنیم، در این صورت باید دادههای مدل را به صورت دستی بهروزرسانی کنیم. به جز این، میتوانیم از پایپها برای هر یک از دلایل زیر استفاده کنیم:
- اگر بخواهیم موقعیت عناصر را به دست آوریم.
- اگر بخواهیم ورودیهای کاربر را در عناصر نوع ورودی ردگیری کنیم.
- اگر بخواهیم کاربر را محدود سازیم تا مقداری را در کنترلهای ورودی وارد کند.
چه نیازی به Pipe داریم؟
در هر اپلیکیشن، پایپ میتواند به دلایل زیر مورد نیاز باشد:
- تنها برخی عناصر فیلترشده آرایه را نمایش دهیم.
- مقداری را ویرایش یا قالببندی کنیم.
- از آنها به عنوان یک تابع استفاده کنیم.
- همه کارهای فوق را به صورت ترکیبی انجام دهیم.
انواع Pipe
در انگولار میتوانیم پایپها را به دو دسته «پایپهای خالص» (Pure Pipes) و «پایپهای ناخالص» (Impure Pipes) تقسیم کنیم.
- پایپهای خالص – پایپهای خالص در انگولار به پایپهایی گفته میشود که همواره برخی آرگومانها را به عنوان مقدار ورودی میگیرند و برخی مقادیر را بسته به مقادیر ورودی به عنوان مقدار خروجی عرضه میکنند. برخی نمونههای پایپهای خالص شامل پایپهای اعشاری، پایپهای تاریخ و غیره هستند. زمانی که از این نوع پایپها در انگولار استفاده کنیم، مقدار ورودی با پیکربندی مرتبط ارائه میشود و یک مقدار قالببندیشده به عنوان خروجی بازگشت مییابد.
- پایپهای ناخالص – پایپهای ناخالص در انگولار به انواعی گفته میشود که مقادیر ورودی میپذیرند، اما انواع متفاوتی از مقدار را بازگشت میدهند که بر اساس حالت مقدار ورودی تعیین میشوند. نمونهای از پایپهای ناخالص شامل async pipes هستند. این پایپها همواره حالت درونی را ذخیره کرده و انواع متفاوتی از مقدار را بسته به حالت درونی و منطق در خروجی بازگشت میدهند.
تفاوت فیلتر با Pipe
مفهوم فیلتر به طور عمده در انگولار نسخه 1.x استفاده میشد. از انگولار 2 به این سو، گوگل مفهوم فیلتر را منسوخ کرده و مفهوم Pipe را جایگزین آن ساخته است. اکنون بسته به کارکرد، فیلتر و پایپها، هر دو کارکرد مشابهی دارند. اما همچنان برخی تفاوتها به شرح زیر وجود دارد:
فیلتر شبیه به تابعها عمل میکنند و میتوانیم ورودی و دیگر پارامترها را به آنها ارسال کنیم و مقدار خروجی قالببندی شده را به ما بازگشت دهند، اما Pipe به عنوان یک عملگر عمل میکند و با پذیرش مقدار ورودی، آن را ویرایش کرده و خروجی مطلوب را بازگشت میدهد.
فیلتر میتواند به صورت مستقیم هر عملیات نوع ناهمگام را اجرا کند. به این منظور باید مقادیر را به صورت دستی تنظیم کنیم. اما Pipe عملیات ناهمگام را خودش مدیریت میکند. در مورد این نوع از عملیات باید از پایپهای نوع ناهمگام استفاده کنیم.
Pipe-های ابتدایی
اغلب پایپها که از سوی انگولار عرضه شدهاند، در نسخههای قبلی نیز وجود دارند. در نسخههای جدید انگولار امکان استفاده از منطق در قالب وجود دارد. به این ترتیب میتوانیم هر تابع را درون کلاس پایپ تعریف کنیم تا تبدیل نوعهای خاص و یا منطق تجاری معینی را پیادهسازی کرده و سپس تابع خاصی را از قالب HTML اجرا کند تا نتیجه مطلوب به دست آید. ساختار پایپ در قالب HTML با یک مقدار ورودی آغاز میشود و سپس نماد پایپ (|) میآید و در ادامه باید نام پایپ ارائه شود.
پارامترهای این پایپ میتوانند با دونقطه (:) از هم جدا شوند. ترتیب اجرای پایپ از راست به چپ است. به طور کلی پایپ صرفاً درون HTML عمل میکند. پایپهای داخلی که بیشترین استفاده را دارند به شرح زیر هستند:
- Currency
- Date
- Uppercase
- Lowercase
- JSON
- Decimal
- Percent
- Async
مثالی از یک Pipe مقدماتی
در این مثال با شیوه استفاده از Pipe-های ابتدایی در انگولار آشنا میشویم:
فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent implements OnInit {
9 public todayDate: Date;
10 public amount: number;
11 public message: string;
12
13 constructor() { }
14
15 ngOnInit(): void {
16 this.todayDate = new Date();
17 this.amount = 100;
18 this.message = "Angular 8.0 is a Component Based Framework";
19 }
20}
- فایل app.component.html
1<div>
2 <h1>Demonstrate of Pipe in Angular 8</h1>
3 <h2>Date Pipes</h2>
4 Full Date : {{todayDate}}<br />
5 Short Date : {{todayDate | date:'shortDate'}}<br />
6 Medium Date : {{todayDate | date:'mediumDate'}}<br />
7 Full Date : {{todayDate | date:'fullDate'}}<br />
8 Time : {{todayDate | date:'HH:MM'}}<br />
9 Time : {{todayDate | date:'hh:mm:ss a'}}<br />
10 Time : {{todayDate | date:'hh:mm:ss p'}}<br />
11
12 <h2>Number Pipes</h2>
13 No Formatting : {{amount}}<br />
14 2 Decimal Place : {{amount |number:'2.2-2'}}
15
16 <h2>Currency Pipes</h2>
17 No Formatting : {{amount}}<br />
18 USD Doller($) : {{amount |currency:'USD':true}}<br />
19 USD Doller : {{amount |currency:'USD':false}}<br />
20 INR() : {{amount |currency:'INR':true}}<br />
21 INR : {{amount |currency:'INR':false}}<br />
22
23 <h2>String Related Pipes</h2>
24 Actual Message : {{message}}<br />
25 Lower Case : {{message | lowercase}}<br />
26 Upper Case : {{message | uppercase}}<br />
27
28 <h2> Percentage Pipes</h2>
29 2 Place Formatting : {{amount | percent :'.2'}}<br /><br />
30</div>
خروجی مرورگر به صورت زیر است:
ساختار پایپها
{{myValue | myPipe:param1:param2 | mySecondPipe:param1}}
Pipe-های سفارشی
در انگولار امکان تعریف پایپهای سفارشی بسته به منطق خاص تجاری وجود دارد. برای پیکربندی پایپهای سفارشی باید از شیء pipe استفاده کنیم. به این منظور باید یک پایپ سفارشی با دکوراتور Pipe@ تعریف کنیم و با افزودن یک مشخصه پایپ به دکوراتور @View یا با نام کلاس پایپ، آن را مورد استفاده قرار دهیم. ما از متد تبدیلی برای انجام هر گونه منطق مورد نیاز برای تبدیل مقداری که به عنوان ورودی ارسال شده استفاده میکنیم. میتوانیم یک آرایه از آرگومانها را به عنوان پارامتر دوم داشته باشیم و هر تعداد که دوست داریم از قالب ارسال کنیم. دکوراتور Pipe@ شامل و مشخصه زیر است:
- Name – این مشخصه شامل نام پایپ است که در عناصر DOM مورد استفاده قرار میگیرد.
- Pure – این مشخصه یک مقدار بولی میپذیرد. این مشخصه تعیین میکند که پایپ از نوع خالص یا ناخالص است. مقدار پیشفرض مشخصه pure به صورت true است یعنی تصور میشود که یک پایپ سفارشی همواره یک پایپ خالص است. از این رو فریمورک انگولار یک پایپ خالص را زمانی اجرا میکند که یک تغییر خالص در مقدار ورودی تشخیص دهد. دادههای تغییرهای خالص میتوانند از نوع مقدماتی (شامل مقادیر منفرد) یا غیر مقدماتی (دادههای شامل نوع دادهای که گروهی از مقادیر را نیز میپذیرد). اگر لازم باشد که یک پایپ را به شکل غیر خالص دربیاوریم، در این صورت باید مقدار false را برای این مشخصه ارسال کنیم. در مورد پایپ ناخالص، فریمورک انگولار، پایپها را در هر بار چرخه تشخیص تغییر در کامپوننت اجرا خواهد کرد. در این حالت، پایپ غالباً در هر بار ضربه کیبورد یا حرکت ماوس فراخوانی میشود.
زمانی که یک کلاس پایپ با استفاده از دکوراتور Pipe@ تعریف میشود، باید اینترفیس PipeTransforms را پیادهسازی کنیم که به طور عمده برای گرفتن مقادیر ورودی (مقادیر اختیاری) و بازگشت مقادیر تبدیلیافته به DOM مورد استفاده قرار میگیرد.
مثالی از Pipe سفارشی
در این بخش یک Pipe سفارشی را تعریف میکنیم در مثال قبل دیدیم که انگولار پایپهای UpperCase و LowerCase را روی انواع رشتهای به صورت پایپهای داخلی خود عرضه کرده است. اما انگولار هیچ پایپی به صورت Proper Case عرضه نکرده است. از این رو این Pipe را تعریف میکنیم تا هر مقدار رشته را به حالت Proper تبدیل کنیم و نتیجه را بازگشت دهیم. به این منظور باید فایلهای کلاس تایپ اسکریپت زیر را تعریف کنیم:
- فایل propercase.pipe.ts
1import { Pipe, PipeTransform } from "@angular/core"
2
3@Pipe({
4 name: 'propercase'
5})
6
7export class ProperCasePipe implements PipeTransform {
8 transform(value: string, reverse: boolean): string {
9 if (typeof (value) == 'string') {
10 let intermediate = reverse == false ? value.toUpperCase() : value.toLowerCase();
11 return (reverse == false ? intermediate[0].toLowerCase() :
12 intermediate[0].toUpperCase()) + intermediate.substr(1);
13 }
14 else {
15 return value;
16 }
17 }
18}
در ادامه پایپ proper case را به صورت زیر در AppModule قرار میدهیم:
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { ColorChangeDirective } from './app.directive';
7import { ProperCasePipe } from './propercase.pipe';
8
9@NgModule({
10 declarations: [
11 AppComponent,ColorChangeDirective,ProperCasePipe
12 ],
13 imports: [
14 BrowserModule,FormsModule
15 ],
16 providers: [],
17 bootstrap: [AppComponent]
18})
19export class AppModule { }
- فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent implements OnInit {
9 public message: string;
10
11 constructor() { }
12
13 ngOnInit(): void {
14 this.message = "This is a Custom Pipe";
15 }
16}
- فایل app.component.html
1<div>
2 <div class="form-horizontal">
3 <h2 class="aligncenter">Custom Pipes - Proper Case</h2><br />
4 <div class="row">
5 <div class="col-xs-12 col-sm-2 col-md-2">
6 <span>Enter Text</span>
7 </div>
8 <div class="col-xs-12 col-sm-4 col-md-4">
9 <input type="text" id="txtFName" placeholder="Enter Text" [(ngModel)]="message" />
10 </div>
11 </div>
12 <div class="row">
13 <div class="col-xs-12 col-sm-2 col-md-2">
14 <span>Result in Proper Case</span>
15 </div>
16 <div class="col-xs-12 col-sm-4 col-md-4">
17 <span>{{message | propercase}}</span>
18 </div>
19 </div>
20 </div>
21</div>
با اجرای این مثال، خروجی زیر را در مرورگر مشاهده خواهید کرد:
Viewchild چیست؟
Viewchild در واقع یکی از قابلیتهای جدید فریمورک انگولار است. این فریمورک به طور عمده به معماری مبتنی بر کامپوننت وابسته است، از این رو زمانی که تلاش کنیم یک صفحه وب یا UI را توسعه دهیم، بدیهی است که این صفحه یا رابط کاربری باید یک کامپوننت باشد که شامل چند کامپوننت تابعی مختلف به عنوان کامپوننتهای فرزند خواهد بود. بنابراین به بیان ساده فریمورک انگولار دارای یک معماری مبتنی بر کامپوننت شامل کامپوننتهای والد و فرزند است.
به این ترتیب برخی موقعیتها وجود دارند که در آنها کامپوننت والد باید با کامپوننت فرزند تعامل پیدا کند. ما در این موقعیتها میتوانیم به روشهای مختلف، ارتباطهایی بین کامپوننتهای والد و فرزند برقرار سازیم. یکی از این روشها دکوراتور Viewchild است.
در صورتی که بخواهیم به وهلهای از کامپوننت فرزند یا یک دایرکتیو کلاس کامپوننت والد دسترسی داشته باشیم، میتوانیم از دکوراتور Viewchild استفاده کنیم. بنابراین زمانی که لازم شود هر نوع متد کامپوننت فرزند را از کامپوننت والد اجرا کنیم، میتوانیم کامپوننت فرزند را به صورت یک Viewchild درون کامپوننت والد تزریق کنیم. در مواردی که بخواهیم به چند فرزند دسترسی داشته باشیم، میتوانیم به جای Viewchild از Viewchildren استفاده کنیم.
برای پیادهسازی ViewChild باید دکوراتور ViewChild@ را در کامپوننت والد مورد استفاده قرار دهیم. این دکوراتور به ما دسترسی به کلاس کامپوننت فرزند را از کامپوننت والد فراهم میسازد. دکوراتور ViewChild اساساً یک تابع یا متد است که نام کلاس کامپوننت را به عنوان ورودی خود میگیرند و سلکتور آن را در قالب کامپوننت مرتبط یافته و متصل میشود. ساختار ابتدایی دکوراتور ViewChild به صورت زیر است:
@ViewChild(ChildComponent, { static:true}) _calculator: ChildComponent;
بنابراین بر اساس مثال فوق، دکوراتور ViewChild شامل متادیتای زیر است:
- Selector – این سلکتور شامل نام دایرکتیو یا کامپوننت است که باید به عنوان ViewChild استفاده شود.
- Static – این مشخصه یک مقدار بولی میگیرد. در صورتی که میخواهد نتیجه کوئری پیش از تشخیص تغییر ریزالو شود، باید مقدار true به آن بدهید. بنابراین زمانی که مقدار true ارسال شود، فریمورک انگولار تلاش میکند تا آن عنصر را در زمان مقداردهی کامپوننت یعنی در متد ngOnInit پیدا کند. اگر مقدار false به این مشخصه بدهیم، انگولار تلاش خواهد کرد تا عنصر را پس از مقداردهی نما یعنی در متد ngAfterViewInit پیدا کند.
مثالی از ViewChild
در این بخش به بررسی شیوه استفاده از دکوراتور ViewChild درون یک کامپوننت میپردازیم. به این منظور ابتدا باید یک کامپوننت فرزند بسازیم که به عنوان یک ViewChild در کامپوننت والد یا روت استفاده میشود.
- فایل child.component.ts
1import { Component, OnInit, Output, EventEmitter } from '@angular/core';
2
3@Component({
4 selector: 'child',
5 templateUrl: 'child.component.html'
6})
7
8export class ChildComponent implements OnInit {
9 private firstNumber: number = 0;
10 private secondNumber: number = 0;
11 private result: number = 0;
12
13 @Output() private addNumber: EventEmitter<number> = new EventEmitter<number>();
14 @Output() private subtractNumber: EventEmitter<number> = new EventEmitter<number>();
15 @Output() private multiplyNumber: EventEmitter<number> = new EventEmitter<number>();
16 @Output() private divideNumber: EventEmitter<number> = new EventEmitter<number>();
17
18 constructor() { }
19
20 ngOnInit(): void {
21 }
22
23 private add(): void {
24 this.result = this.firstNumber + this.secondNumber;
25 this.addNumber.emit(this.result);
26 }
27
28 private subtract(): void {
29 this.result = this.firstNumber - this.secondNumber;
30 this.subtractNumber.emit(this.result);
31 }
32
33 private multiply(): void {
34 this.result = this.firstNumber * this.secondNumber;
35 this.multiplyNumber.emit(this.result);
36 }
37
38 private divide(): void {
39 this.result = this.firstNumber / this.secondNumber;
40 this.divideNumber.emit(this.result);
41 }
42
43 public clear(): void {
44 this.firstNumber = 0;
45 this.secondNumber = 0;
46 this.result = 0;
47 }
48}
- فایل child.component.html
1<div class="ibox-content">
2 <div class="row">
3 <div class="col-md-4">
4 Enter First Number
5 </div>
6 <div class="col-md-8">
7 <input type="number" [(ngModel)]="firstNumber" />
8 </div>
9 </div>
10 <div class="row">
11 <div class="col-md-4">
12 Enter Second Number
13 </div>
14 <div class="col-md-8">
15 <input type="number" [(ngModel)]="secondNumber" />
16 </div>
17 </div>
18 <div class="row">
19 <div class="col-md-4">
20 </div>
21 <div class="col-md-8">
22 <input type="button" value="+" (click)="add()" />
23
24 <input type="button" value="-" (click)="subtract()" />
25
26 <input type="button" value="X" (click)="multiply()" />
27
28 <input type="button" value="/" (click)="divide()" />
29 </div>
30 </div>
31</div>
اینک کامپوننت فرزند را در فایل app.module.ts قرار میدهیم.
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { ChildComponent } from './child.component';
7
8@NgModule({
9 declarations: [
10 AppComponent,ChildComponent
11 ],
12 imports: [
13 BrowserModule, FormsModule
14 ],
15 providers: [],
16 bootstrap: [AppComponent]
17})
18export class AppModule { }
اینک میتوانیم کامپوننت فرزند فوق را در کامپوننت root به صورت زیر استفاده کنیم.
- فایل app.component.ts
1import { Component,ViewChild } from '@angular/core';
2import { ChildComponent } from './child.component';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css']
8})
9export class AppComponent {
10 private result: string = '';
11
12 @ViewChild(ChildComponent, { static:true}) private _calculator: ChildComponent;
13
14 constructor() {
15 }
16
17 private add(value: number): void {
18 this.result = 'Result of Addition ' + value;
19 }
20
21 private subtract(value: number): void {
22 this.result = 'Result of Subtraction ' + value;
23 }
24
25 private multiply(value: number): void {
26 this.result = 'Result of Multiply ' + value;
27 }
28
29 private divide(value: number): void {
30 this.result = 'Result of Division ' + value;
31 }
32
33 private reset(): void {
34 this.result = '';
35 this._calculator.clear();
36 }
37}
- فایل app.component.html
1<h2>Demo of Viewchild</h2>
2<div>
3 <child (addNumber)="add($event)" (subtractNumber)="subtract($event)" (multiplyNumber)="multiply($event)"
4 (divideNumber)="divide($event)" #calculator></child>
5</div>
6<h3>Result</h3>
7<span>{{result}}</span>
8<br />
9<br />
10<input type="button" value="Reset" (click)="reset()" />
خروجی مثال فوق در مرورگر به صورت زیر است:
در این بخش از راهنمای جامع انگولار با مفهوم Pipe در این فریمورک آشنا شدیم. همچنین انواع مختلفی از Pipe-ها از قبیل پایپهای خالص و ناخالص را بررسی کردیم. در ادامه این مقاله به بررسی دو مفهوم دیگر به نامهای کپسولهسازی نما و پروجکشن محتوا خواهیم پرداخت.
کپسولهسازی نما
در این بخش از راهنمای آموزش انگولار به بررسی جنبه دیگری از مدیریت DOM در انگولار میپردازیم. در توسعه وباپلیکیشن، کامپوننت وب نقش مهمی در زمینه طراحی و توسعه UI وب ایفا میکند. کامپوننتهای وب به طور کلی در همه مرورگرهای مدرن به جز Microsoft Edge و IE 11 حضور دارند. اما برای این مرورگرها نیز میتوان از polyfills استفاده کرد. کامپوننتهای وب به طور عمده شامل سه عنصر زیر هستند:
- عناصر سفارشی (Custom Elements) - شامل عناصر کاملاً معتبر HTML
- DOM سایه – شامل CSS جدا شده و API جاوا اسکریپت
- قالبهای تعریفشدهی کاربر
توجه کنید که کامپوننت انگولار ربطی به کامپوننت وب ندارد، اما فریمورک انگولار در عمل از همه قابلیتهای کامپوننتهای وب پشتیبانی میکند. بنابراین در این بخش از مقاله آموزش انگولار به بررسی تکنیکهای مختلف طراحی نماها میپردازیم، به طوری که بتوان با آنها مانند کامپوننتهای وب رفتار کرد.
کامپوننت وب چیست؟
فریمورک انگولار همواره یک مجموعه از کتابخانهها برای توسعه کامپوننتهای کپسولهسازیشده، تزریق وابستگی، قالبهای HTML مبتنی بر اتصال داده و غیره ارائه میکند. با استفاده از این فریمورک، میتوانیم منطق نمایش UI (کامپوننتها) را از منطق تجاری اپلیکیشن (سرویسها و منطق) طوری جدا کنیم که چند توسعهدهنده یا تیمهای مختلف بتوانند همزمان روی بخشهای مختلف یک اپلیکیشن کار کنند. یکی از اجزای کلیدی برای پیادهسازی این رویکرد، کامپوننتهای وب هستند.
کامپوننتهای وب به گروهی از API-های استاندارد گفته میشود که امکان ایجاد تگهای مبتنی بر HTML سفرشی را فراهم میسازند که کارکرد و چرخه عمر کامپوننت خاص خود را دارند. هدف اصلی کامپوننت وب، کپسولهسازی کد برای کامپوننتهای انگولار به صورت یک پکیج با قابلیت استفاده مجدد با بیشترین امکان «همکنشپذیری» (interoperability) است. به بیان ساده کامپوننتهای وب مجموعهای از API-های استاندارد هستند که به ما امکان میدهند تا عناصر سفارشی با قابلیت استفاده مجدد بسازیم که کل کارکردشان از باقی کد جدا شود و بتوانیم به صورت مستقل از آنها در وباپلیکیشن خود استفاده کنیم.
برای تعریف کردن هر کامپوننت وب باید قابلیتهای زیر را در هر کامپوننت انگولار تعریف کنیم، به طوری که به عنوان یک کامپوننت وب عمل کند.
- عناصر سفارشی – این مشخصات به ما کمک میکنند که انواع جدیدی از عناصر DOM را طراحی کنیم.
- DOM سایه – به ما کمک میکند که شیوه استفاده از استایل کپسولهسازیشده و markup را در کامپوننتهای وب تعریف کنیم.
- ماژولهای ES – این مشخصه موجب میشود بتوانیم هر کتابخانه جاوا اسکریپت را تعریف کنیم و گزینههایی برای استفاده مجدد از آنها در اسناد به روش استاندارد فراهم میسازد.
- قالبهای HTML – با استفاده از این مشخصه میتوانیم هر مارکاپ HTML را تعریف کنیم که به صورت نرمال در زمان بارگذاری صفحه در حالت بیاستفاده است، اما متعاقباً در زمان وقوع یک رویداد خاص یا اجرای یک کار معین فعال یا مقداردهی میشود.
مزیتهای کامپوننت وب
ما به عنوان توسعهدهنده ترجیح میدهیم تا حد امکان از کدهای خود در توسعه اپلیکیشن به صورت مجدد استفاده بکنیم. هدف اصلی استفاده از کامپوننتهای وب فراهم ساختن امکان استفاده مجدد از کد است. یک کامپوننت وب اساساً به فرایند استانداردشده ساخت عناصر رابط کاربری کپسولهسازیشده و با قابلیت استفاده مجدد گفته میشود. یک کامپوننت وب همواره قابلیتهای زیر را در فرایند توسعه وب در اختیار ما قرار میدهد
- قابلیت استفاده مجدد
- قابلیت نگهداری
- قابلیت بهرهوری
- قابلیت کپسولهسازی
- قابلیت بسطپذیری
- قابلیت ترکیببندی
اما سؤال همچنان پابرجا است، یعنی چرا باید از کامپوننت وب استفاده کنیم، زیرا همه فریمورکها این قابلیتها را در اختیار ما قرار میدهند. دلیل اصلی استفاده از کامپوننت وب، قابلیت همکنشپذیری است، زیرا هر فریمورک در چارچوب اکوسیستم خودش فعالیت میکند. به طور کلی امکان استفاده از یک کامپوننت انگولار درون فریمورک ریاکت و یا برعکس وجود ندارد و یا اگر امکانپذیر باشد، کار آسانی نیست.
جامعه توسعه فرانتاند، به طور مداوم ابزارها، کتابخانهها و فریمورکهای جدیدی را معرفی میکند و از این رو باید به صورت پیوسته تغییرهایی در اپلیکیشنهای خود اعمال کنیم تا با این تغییرها سازگار شود. بنابراین در مورد اپلیکیشنهایی که هماینک توسعه میدهیم، بزرگترین دغدغه این است که چطور با نسخههای آتی فریمورکها و کتابخانهها سازگار خواهد شد.
در برخی موارد ایجاد این سازگاری کار بسیار دشواری است و در مواردی نیز باید اپلیکیشن را به کل از نو بنویسیم تا با یک فناوری جدید تطبیق پیدا کند. در این حالت، کامپوننتهای وب نقش مهمی در توسعه وباپلیکیشن ایفا میکنند. از آنجا که وباپلیکیشنها بر مبنای پروتکل استاندارد وب توسعه مییابند، میتوانند روی هر اکوسیستمی کار کنند. در ادامه مزیتهای عمده کامپوننتهای وب را جمعبندی میکنیم.
- همکنشپذیری (Interoperability) – کامپوننتهای وب میتوانند فریمورک را ارتقا ببخشند و در پروژههای مختلف با مجموعه فناوریهایمتفاوت مورد استفاده قرار گیرند.
- طول عمر – از آنجا که کامپوننت وب همکنشپذیر است، طول عمر بیشتری دارد و نیازی به بازنویسی آن برای استفاده از فناوریهای جدید وجود ندارد.
- قابلیت انتقال – کامپوننت وب قابلیت انتقال بالایی دارد و در هر محیط با هر فریمورک یا کتابخانهای کار میکند.
با توجه به مزایایی که در بخش فوق برشمردیم، کامپوننت وب همواره قابلیت سازگاری چندگانه دارد که نقش مهمی در محیط دائماً در حال تغییر وب ایفا میکند.
DOM سایه
پیش از آن که در مورد «کپسولهسازی نما» (View Encapsulation) در فریمورک انگولار صحبت کنیم، باید ابتدا مفهوم «DOM سایه» (Shadow DOM) را درک کنیم و همچنین دلیل استفاده از آنها را متوجه شویم. DOM سایه به طور خلاصه یکی از اجزای مهم کامپوننتهای وب است که امکان قرارگیری درخت DOM و کپسولهسازی CSS یا استایل را درون کامپوننتهای وب فراهم میسازد.
به بیان سادهتر به کمک DOM سایه میتوانیم منطق مرتبط با DOM را پشت عناصر دیگر DOM پنهان سازیم. این قابلیت بسیار خوبی است، زیرا در نهایت کامپوننتی توسعه دادهایم که به طور معمول یک عنصر مبتنی بر HTML منفرد (سفارشی یا تعریفشدهی کاربر) عرضه میکند که به همراه منطق و استایل DOM پنهان میتواند روی آن عنصر خاص یعنی کامپوننت وب اعمال شود. برای نمونه میتواند شامل یک عنصر HTML به صورت <”input type=“date> باشد. بنابراین قابلیتی زیبا و عالی است چون یک تگ منفرد داریم و مرورگر کل کنترل انتخابگر تاریخ را برای ما رندر میکند.
حالا که ایدهای اولیه و کلی از مفهوم DOM سایه داریم، به بررسی شیوه استفاده از آن در فریمورک انگولار و به طور خاص درون کامپوننتهای انگولار میپردازیم. همچنان که میدانیم هر کامپوننت انگولار شامل یک کلاس کنترلر با قالب HTML و استایلها است. ما میتوانیم از انواع مختلفی از استایلها برای یک کامپوننت متفاوت استفاده کنیم.
به طور کلی، هر کامپوننت میتواند در صورت داشتن کارکردهای مستقل و ایزوله شده در بخشهای مختلف اپلیکیشن به اشتراک گذاشته شود. معنی این گفته آن است که فریمورک انگولار کامپوننتها را همواره به صورت یک عنصر با قابلیت استفاده مجدد عرضه میکند. با این که در عمل کامپوننتهای انگولار، کامپوننتهای وب محسوب نمیشوند، اما برخی مزیتها دارند که میتوان با استفاده از کامپوننتها وب به دست آورد.
بنابراین زمانی که در انگولار یک کامپوننت میسازیم، یک قالب HTML به shadowRoot انتساب مییابد که اساساً همان DOM سایه مربوط به آن کامپوننت خاص است. به این ترتیب درخت DOM و کپسولهسازی استایل به صورت پیشفرض به دست میآید. حتی در صورتی که مرورگر از DOM سایه پشتیبانی نکند، میتوانیم اپلیکیشن انگولار را اجرا کنیم، زیرا این فریمورک به صورت پیشفرض از DOM سایه نیتیو استفاده نمیکند و در واقع آن را شبیهسازی میکند. دلیل این مسئله آن است که اغلب مرورگرها از قابلیتهای DOM سایه پشتیبانی نمیکنند.
کپسولهسازی نما
فریمورک انگولار به صورت پیشفرض قابلیت کپسولهسازی نما را عرضه میکند. ما میتوانیم آن را از طریق DOM سایه فعال کنیم و یا آن را شبیهسازی نماییم. ما با استفاده از کپسولهسازی نما میتوانیم تعریف کنیم که قالب یا استایلهای مورد استفاده درون کامپوننتها بر کل اپلیکیشن تأثیر داشته باشند یا برعکس آن اتفاق بیفتد.
انواع کپسولهسازی نما
انگولار سه نوع کپسولهسازی نما ارائه کرده است:
- ViewEncapsulation.None – در این نوع از کپسولهسازی هیچ گزینهای برای DOM سایه ارائه نشده است. همچنین استایل دارای دامنه کامپوننت نیست. از این رو کد استایل به بخش هدر DOM منتقل خواهد شد و روی همه گرههای کامپوننت تأثیر میگذارد.
- ViewEncapsulation.Emulated – در انگولار، حالت پیشفرض ViewEncapsulation به این صورت یعنی شبیهسازیشده است. انگولار در این گزینه DOM سایه را برای کامپوننت تولید نخواهد کرد. بنابراین استایل کامپوننت دارای دامنه خود کامپوننت است. در این حالت تنها DOM سایه شبیهسازی میشود و واقعاً ایجاد نخواهد شد. از این رو اپلیکیشنها میتوانند در مرورگرهای فاقد پشتیبانی از DOMshdi نیز اجرا شوند و استایلها نیز دارای دامنه محدود به کامپوننت خواهند بود.
- ViewEncapsulation.ShadowDom – انگولار در این گزینه DOM سایه را برای کامپوننت مورد میسازد. همچنین دارای دامنهای یکسان با بخش مرتبط با استایل کامپوننت است.
مفهوم پروجکشن محتوا
مفهوم پروجکشن محتوا در انگولار به درجه یک عنصر DOM سایه درون کامپوننت کمک میکند. به بیان ساده، اگر بخواهیم عناصر HTML یا کامپوننت دیگری را به صورت دینامیک درون یک کامپوننت درج کنیم، باید از مفهوم پروجکشن محتوا استفاده کنیم. «پروجکشن محتوا» (Content Projection) یکی از مهمترین و مفیدترین قابلیتهای فریمورک انگولار محسوب میشود. این قابلیت به ما کمک میکند که دادهها را از کامپوننت والد به کمک قالب HTML به کامپوننتهای داخلی فرزند بفرستیم. ما در غالب موارد از یک بخش خاص کد در بخشهای مختلف قالبهای کامپوننتهای فرزند استفاده کنیم. این کار با استفاده از پروچکشن محتوا امکانپذیر است.
ngContent
یکی از بهترین رویکردها برای پیادهسازی پروجکشن محتوا در اپلیکیشنهای مبتنی بر فریمورک انگولار استفاده از ngContent است. از ngContent میتوان برای ارسال هر نوع محتوای HTML به کامپوننتهای فرزند استفاده کرد. ngContent نه تنها قالب HTML ساده را ارسال میکند، بلکه «اتصال مشخصه» (property binding) و «فرستندههای رویداد» (event emitters) را نیز ارسال میکند.
ما میتوانیم با استفاده از <ng-content></ng-content> درون یک قالب HTML کامپوننت فرزند یک placeholder تعبیه کنیم که انگولار در آنجا محتوا را رندر کند. قالب HTML که در تگ <ng-content> پروجکت میشود، همواره درون تگ کامپوننت فرزند تعریف خواهد شد.
مقایسه فرزندان نما با فرزندان محتوا
فرزندان نما | فرزندان محتوا |
عناصر فرزندی که درون قالب HTML کامپوننت قرار دارند، به طور معمول View Children نامیده میشوند. | عناصری که بین تگهای آغازی و پایانی عنصر والد یک کامپوننت قرار میگیرند، به نام فرزندان محتوا شناخته میشوند. |
در زمان افزودن عنصری که قرار است به صورت مستقیم در خود کامپوننت استفاده شود، باید از ViewChildren @ استفاده کنیم. | از فرزندان محتوا میتوان برای به دست آوردن یک ارجاع به محتوایی که با استفاده از <ng-content> درون کامپوننت پروجکت شده است، بهره گرفت. |
مثالی از حالت ViewEncapsulation.None
در این بخش ابتدا یک کامپوننت فرزند ایجاد و از آن در کامپوننت ریشه به عنوان یک کامپوننت تودرتو استفاده میکنیم. در ابتدا هیچ گزینهای در ارتباط با نمای کپسولهسازی ارائه نمیکنیم. بنابراین در آغاز تنها یک کامپوننت مبتنی بر رابطه والد-فرزند است. همچنین در کامپوننت ریشه از برخی استایلشیتهای سفارشی مرتبط با فایلها استفاده میکنیم که بعضی استایلهای مبتنی بر CSS را اعمال میکنند.
- فایل ViewEncapsulation.None
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent {
9 private message: string = 'Welcome to Angular 8';
10
11 constructor() {
12 }
13}
- فایل app.component.html
1<h2>Demostration of View Encapsulation</h2>
2
3<div>
4 <h1>{{message}}</h1>
5</div>
6
7<child></child>
- فایل custom.css
1h1{
2 color:red;
3 font-weight:bold;
4 font-size: 30px;
5 text-align: center;
6 text-transform: uppercase;
7}
8h2,h3{
9 color:blue;
10 font-size: 20px;
11}
- فایل child.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'child',
5 templateUrl: 'child.component.html'
6})
7
8export class ChildComponent {
9 public title:string ='This is a Child Component';
10}
- فایل child.component.html
1<div>
2 <h1>{{title}}</h1>
3</div>
اکنون میتوانید نتیجه را در مرورگر بررسی کنید:
بر اساس خروجی فوق، روشن است که از استایلها در کامپوننت ریشه استفاده کردهایم و استایلها روی تگ <h1> اعمال شدهاند. دلیل این که این استایلها روی کامپوننت فرزند اعمال نشدهاند، این است که از این استایل در کامپوننت فرزند استفاده نکردهایم. اینک در کامپوننت ریشه، حالت کپسولهسازی نمای زیر را فعال میکنیم:
1import { Component } from '@angular/core';
2import { ViewEncapsulation } from '@angular/core';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css'],
8 encapsulation:ViewEncapsulation.None
9})
10export class AppComponent {
11 private message: string = 'Welcome to Angular 8';
12
13 constructor() {
14 }
15}
بدین ترتیب خروجی در مرورگر به صورت زیر درمیآید:
چنان که در تصویر فوق میبینید، همان استایلها روی کامپوننت فرزند نیز اعمال شدهاند، زیرا از کپسولهسازی نما استفاده کردهایم. اکنون اگر HTML را در مرورگر بررسی کنیم، میتوانیم کد مرتبط با استایل را که در بخش هدر فایل HTML قرار گرفته است، شناسایی کنیم. به همین دلیل است که این استایل روی هر دو کامپوننت والد و فرزند اعمال شده است.

مثالی از حالت ViewEncapsulation.ShadowDOM
در این بخش به بررسی شیوه استفاده از کپسولهسازی نما با گزینههای DOM سایه خواهیم پرداخت. به این منظور، تغییرهای زیر را در کامپوننت app-root اعمال میکنیم.
1import { Component } from '@angular/core';
2import { ViewEncapsulation } from '@angular/core';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css'],
8 encapsulation:ViewEncapsulation.ShadowDom
9})
10export class AppComponent {
11 private message: string = 'Welcome to Angular 8';
12
13 constructor() {
14 }
15}
اکنون مرورگر را بررسی میکنیم تا ببینیم آیا خروجی همانند قبل است یا نه. اما اگر عنصر HTML را در مرورگر بررسی کنیم، میبینیم که کد مرتبط با استایل دارای دامنهای در چارچوب سلکتور کامپوننت است.

مثالی از ViewEncapsulation.Emulated
در این بخش به بررسی شیوه کپسولهسازی نما در انواع نوع شبیهسازیشده میپردازیم. به این منظور کافی است تغییرهای زیر را روی کامپوننت app-root اعمال کنید. چنان که پیشتر اشاره کردیم، این نوع از کپسولهسازی نما هیچ DOM سایه را درون مرورگر ایجاد نمیکند.
1import { Component } from '@angular/core';
2import { ViewEncapsulation } from '@angular/core';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css'],
8 encapsulation:ViewEncapsulation.Emulated
9})
10export class AppComponent {
11 private message: string = 'Welcome to Angular 8';
12
13 constructor() {
14 }
15}
خروجی این مثال در مرورگر به صورت زیر است:
مثالی از ng-content
در این بخش به بررسی شیوه استفاده از ng-content در اپلیکیشن میپردازیم. به این منظور ابتدا یک کامپوننت به نام modal-window میسازیم که یک صفحه modal بازشونده است و این کامپوننت را در کامپوننت aoo-root خود پیادهسازی میکنیم. مفهوم پنجره modal از روی قالب HTML کامپوننت app-root تعریف میشود.
- فایل modal.component.ts
1import { Component, OnInit, ViewChild, Input } from '@angular/core';
2
3@Component({
4 selector: 'modal-window',
5 templateUrl: 'modal.component.html'
6})
7
8export class ModalComponent implements OnInit {
9 @Input() private display: string = 'none';
10 @Input('header-caption') private header: string = 'Modal';
11
12 constructor() {
13 }
14
15 ngOnInit(): void {
16 }
17
18 private fnClose(): void {
19 this.display = 'none';
20 }
21
22 showModal(): void {
23 this.display = 'block';
24 }
25
26 close(): void {
27 this.fnClose();
28 }
29
30 setModalTitle(args: string): void {
31 this.header = args;
32 }
33}
- فایل modal.component.html
1<div class="modal" id="myModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" [ngStyle]="{'display' : display }">
2 <div class="modal-dialog">
3 <div class="modal-content animated bounceInRight">
4 <div class="modal-header">
5 <button type="button" class="close" (click)="fnClose()">×</button>
6 <h3 class="modal-title">{{header}}</h3>
7 </div>
8 <div class="modal-body">
9 <ng-content select="content-body"></ng-content>
10 </div>
11 <div class="modal-footer">
12 <ng-content select="content-footer"></ng-content>
13 </div>
14 </div>
15 </div>
16</div>
- فایل app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { ModalComponent } from './modal.component';
7
8@NgModule({
9 declarations: [
10 AppComponent,ModalComponent
11 ],
12 imports: [
13 BrowserModule, FormsModule
14 ],
15 providers: [],
16 bootstrap: [AppComponent],
17 schemas: [NO_ERRORS_SCHEMA]
18})
19export class AppModule { }
- فایل app.component.ts
1import { Component, OnInit, ViewChild } from '@angular/core';
2import { ModalComponent } from './modal.component';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css']
8})
9export class AppComponent implements OnInit {
10 private caption: string = 'Custom Modal';
11 @ViewChild('modal',{static:true}) private _ctrlModal: ModalComponent;
12
13 constructor() {
14 }
15
16 ngOnInit(): void {
17 }
18
19 private fnOpenModal(): void {
20 this._ctrlModal.showModal();
21 }
22
23 private fnHideModal(): void {
24 this._ctrlModal.close();
25 }
26}
- فایل app.component.html
1<div>
2 <h2>Demonstrate Modal Window using ngContent</h2>
3 <input type="button" value="Show Modal" class="btn-group" (click)="fnOpenModal()" />
4 <br />
5 <modal-window [header-caption]="caption" #modal>
6 <content-body>
7 <h1>Modal Contain Defined at Parent Component</h1>
8 <p>
9 Lorem ipsum dolor, sit amet consectetur adipisicing elit. Atque natus minima
10 suscipit magnam, quas provident aperiam? Quam maiores saepe placeat soluta, vel qui dolorem dolorum dignissimos veniam iusto facilis totam?
11 </p>
12 </content-body>
13 <content-footer>
14 <input type="button" class="btn-default active" class="btn btn-primary" value="Modal Close" (click)="fnHideModal();" />
15 </content-footer>
16 </modal-window>
17</div>
برای نمایش پنجره modal از CSS bootstrap در فایل index.html به صورت زیر استفاده کردهایم:
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <title>Angular8Demo</title>
6 <base href="/">
7
8 <meta name="viewport" content="width=device-width, initial-scale=1">
9 <link rel="icon" type="image/x-icon" href="favicon.ico">
10 <link href="assets/bootstrap.min.css" rel="stylesheet" type="text/css"
11</head>
12<body>
13 <app-root></app-root>
14</body>
15</html>
اینک خروجی را در مرورگر بررسی میکنیم.
به این ترتیب با مفهوم کپسولهسازی نما در انگولار نیز آشنا شدیم. در بخش بعدی مقاله آموزش انگولار به بررسی یکی از مهمترین مفاهیم فریمورک انگولار یعنی فرمها میپردازیم.
فرمهای انگولار
در این بخش از مقاله آموزش انگولار به بررسی مفهوم فرمهای انگولار میپردازیم و با فرمهای مشتق از قالب و فرمهای واکنشی آشنا خواهیم شد. فرمها همواره بخشی جداییناپذیر از هر وباپلیکیشن محسوب میشوند. فرمها در اپلیکیشن کاربردهای مختلفی از لاگین، تحویل درخواست، ارائه اطلاعات از سوی کاربر، صدور یک سفارش و غیره دارند.
فرمهای انگولار چه هستند؟
زمانی که میخواهیم شروع به توسعه یک وباپلیکیشن بکنیم، متوجه میشویم که بخش زیادی از UI یا اینترفیسها با فرمها ارتباط دارند. این موضوع در خصوص اغلب اپلیکیشنهای سازمانی هم صدق میکند، زیرا اغلب اینترفیسها در واقع یک فرم بزرگ است که شامل چندین زبانه، دیالوگ، شبکه و غیره است. همچنین این نوع فرمها همواره شامل منطق اعتبارسنجی تجاری نه چندان سادهای هستند. ما در یک فریمورک مبتنی بر کامپوننت باید فرمها را به بخشهای کوچک کد و با استفاده مجدد کد تقسیم کنیم تا این کامپوننتها بتوانند در سراسر اپلیکیشن مورد استفاده قرار گیرند. به این ترتیب مزیتهای زیادی در زمینه معماری به دست میآید که از جمله شامل انعطافپذیری و امکان ایجاد تغییر در طراحی است.
فریمورک انگولار از طریق ngModel با فرمها کار میکند. تکنیک اتصال داده دوطرفه مربوط به ngModel در فریمورک انگولار واقعاً ارزشمند است، زیرا یک همگامسازی خودکار بین فرم و اشیای مدل نما فراهم میسازد. برای استفاده از ماژول فرم در انگولار باید FormModule را در ماژول اپلیکیشن خودمان تزریق کنیم.
انواع فرمهای انگولار
انگولار به عنوان یک فریمورک UI با امکانات کامل و مدرن برخی کتابخانههای اختصاصی برای توسعه UI-های پیچیده مبتنی بر فرم ارائه کرده است. در حال حاضر انگولار دو نوع راهبرد ساخت فرم به شرح زیر دارد:
- فرمهای مشتق از قالب
- فرمهای واکنشی
هر دو فناوری فوق به پکیجهای @angular/forms تعلق دارند و به طور کامل مبتنی بر کلاسهای form-control هستند. اما علیرغم این واقعیت، این دو گزینه از نظر فلسفه، شیوه برنامهنویسی و تکنیک متمایز از هم هستند. در بخش زیر به بررسی تفصیلی آیتمهای مرتبط با دو نوع فوق از فرمهای انگولار میپردازیم.
فرمهای مشتق از قالب
فرمهای مشتق از قالب در انگولار به فرمهایی گفته میشود که در آنها میتوان منطق اعتبارسنجی، کنترل و غیره را در بخش HTML کامپوننت نوشت. «قالب» (Template) به طور کامل مسئول تعیین عناصر فرم در UI است و اعتبارسنجی را با استفاده از کنترل پیادهسازی میکند. به کمک قالب میتوانیم گروهی از فرمها (Form Group) را نیز درون قالب HTML عرضه کنیم. فرمهای مشتق از قالب برای اینترفیسهای مبتنی بر سناریوی خاص و سادهای مناسب هستند که در آن میتوان به سهولت از اتصال داده دوطرفه انگولار استفاده کرد.
انگولار برای فرمهای مشتق از قالب برخی دایرکتیوهای خاص فرم ارائه کرده است که میتوانند برای اتصال به دادههای ورودی بدون نیاز به متغیر مدل مورد استفاده قرار گیرند. به جهت این دایرکتیو خاص فرم، میتوانیم کارکرد و رفتار یک فرم ساده HTML را بسط دهیم. در نهایت خود قالب مسئولیت اتصال مقادیر به مدل و اعتبارسنجی فرم را بر عهده میگیرد.
مزایای فرمهای مشتق از قالب
برخی از مزیتهای استفاده از فرمهای مشتق از قالب در فریمورک انگولار به شرح زیر هستند:
- استفاده از آن بسیار آسانتر است.
- این تکنیک در سناریوهای ساده عملکرد خوبی دارد.
- این تکنیک به طور کامل به تکنیکهای اتصال دوطرفه داده مانند ساختار ngModel وابسته است.
- کمترین میزان کدنویسی را در بخش کامپوننت نیاز دارد، زیرا بخش زیاد کار در سمت قالب HTML انجام مییابد.
- این تکنیک به صورت خودکار عنصر فرم و کنترل آن را ردگیری میکند.
با این حال، علیرغم مزایای فوق، فرمهای مشتق از قالب برخی معایب به شرح زیر نیز دارند:
- فرمهای مشتق از قالب در مواردی که طراحی در سمت UI دارای پیچیدگیهایی باشد با مشکل مواجه میشوند.
- امکان اجرای تست Unit روی فرمهای مشتق از قالب وجود ندارد.
مثالی از فرم مشتق از قالب
در این بخش به بررسی شیوه تعریف یک فرم مشتق از قالب در انگولار میپردازیم.
- فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2import { NgForm } from '@angular/forms';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css']
8})
9export class AppComponent implements OnInit {
10 private formData: any = {};
11 private showMessage: boolean = false;
12
13 constructor() {
14 }
15
16 ngOnInit(): void {
17 }
18
19 registerUser(formdata: NgForm) {
20 this.formData = formdata.value;
21 this.showMessage = true;
22 }
23}
- فایل app.component.html
1<h2>Template Driven Form</h2>
2<div>
3 <form #signupForm="ngForm" (ngSubmit)="registerUser(signupForm)">
4 <table style="width:60%;" cellpadding="5" cellspacing="5">
5 <tr>
6 <td style="width :40%;">
7 <label for="username">User Name</label>
8 </td>
9 <td style="width :60%;">
10 <input type="text" name="username" id="username" [(ngModel)]="username" required>
11 </td>
12 </tr>
13 <tr>
14 <td style="width :40%;">
15 <label for="email">Email</label>
16 </td>
17 <td style="width :60%;">
18 <input type="text" name="email" id="email" [(ngModel)]="email" required>
19 </td>
20 </tr>
21 <tr>
22 <td style="width :40%;">
23 <label for="password">Password</label>
24 </td>
25 <td style="width :60%;">
26 <input type="password" name="password" id="password" [(ngModel)]="password" required>
27 </td>
28 </tr>
29 <tr>
30 <td style="width :40%;"></td>
31 <td style="width :60%;">
32 <button type="submit">Sign Up</button>
33 </td>
34 </tr>
35 </table>
36 </form>
37 <div *ngIf="showMessage">
38 <h3>Thanks You {{formData.username}} for registration</h3>
39 </div>
40</div>
اینک خروجی را در مرورگر بررسی میکنیم:
فرمهای مشتق از مدل
فرمهای مشتق از مدل غالباً به نام «فرمهای واکنشی» (Reactive Form) نامیده میشوند. این نوع از فرمها از یک رویکرد مشتق مدل برای مدیریت ورودیها و هدایت آنها به متغیر کامپوننت استفاده میکنند. ما با استفاده از این تکنیک میتوانیم یک فرم ساده کنترل را ایجاد و بهروزرسانی کنیم، از چند کنترل در یک گروه فرم استفاده کنیم و مقادیر کنترل را اعتبارسنجی کرده یا کارهای مشابهی را اجرا کنیم.
فرمهای مشتق از مدل یا واکنشی به طور معمول از یک رویکرد صریح و «تغییرناپذیر» (immutable) برای مدیریت حالت کنترل فرم در هر نقطه از زمان بهره میگیرند. اگر هر گونه تغییری در کنترل فرم رخ بدهد، در این صورت یک «حالت» (State) جدید بازگشت مییابد که انگولار از طریق آن میتواند یکپارچگی متغیرهای مدل را بین این تغییرها حفظ کند. فرمهای واکنشی اساساً مبتنی بر استریمهای observable هستند. از این رو کنترلهای ورودی هر فرم مقدار ورودی را به صورت یک استریم ارائه میکنند که به صورت همگامسازی شده از سوی فرم واکنشی مورد دسترسی قرار میگیرد. ما به کمک فرمهای واکنشی میتوانیم یک پروسه سرراست برای اجرای تست Unit روی کنترلهای فرم داشته باشیم. این کار به سهولت قابل اجرا است، زیرا میتوانیم مطمئن باشیم که دادههای فرم در هر زمان که تقاضا شوند، منسجم و قابل پیشبینی هستند.
فرمهای واکنشی یا فرمهای مشتق از مدل رویکردی متمایز فرمهای مشتق از قالب محسوب میشوند. فرمهای واکنشی همواره قابلیت پیشبینی بیشتری دارند و امکان دسترسی همگام از سوی متغیرهای مدل وجود دارد. همچنین با استفاده از عملگرهای observable قابلیت تغییرناپذیری به دست میآید و رد هر تغییری با استفاده از استریم observables ثبت میشود.
مزیتهای فرمهای مشتق از مدل
مزیتهای فرمهای مشتق از مدل یا واکنشی در انگولار به شرح زیر هستند:
- تعریف فرم در فرمهای واکنشی شامل کدنویسی مرتبط با منطق است که به طور عمده درون بخش تایپ اسکریپت کامپوننت نگهداری میشود. ما با استفاده از این تکنیک میتوانیم کنترلهای فرم را به صورت برنامهنویسیشده با استفاده از کلاس FormGroup یا FormBuilder بسازیم. تگهای فرم HTML در قالب HTML تنها برای قرار دادن یک ارجاع از کلاس form-control مبتنی بر تایپ اسکریپت مورد استفاده قرار میگیرند.
- فرمهای واکنشی کنترل کامل و برنامهنویسیشدهای روی بهروزرسانیهای مقادیر فرم و اعتبارسنجی آنها فراهم میسازد.
- در این تکنیک میتوانیم فرم مبتنی بر ساختار دینامیک را در زمان اجرا بسازیم.
- امکان پیادهسازی اعتبارسنجیهای سفارشی برای فرم وجود دارد.
- از آنجا که کل بخش مبتنی بر فرم در کلاس تایپ اسکریپت یا کامپوننت است، نوشتن تستهای Unit در فرمهای واکنشی بسیار آسانتر است.
علیرغم مزیتهای فوق، فرمهای واکنشی نیز برخی معایب را دارند:
- این تکنیک نیازمند کدنویسی بسیار بیشتری به خصوص در بخش تایپ اسکریپت است.
- درک و نگهداری کد فرمهای واکنشی اندکی دشوارتر است.
- تفاوتهای فرمهای مشتق از قالب و فرمهای واکنشی
در جدول زیر بین فرم مشتق از قالب و فرمهای واکنشی یک مقایسه ارائه شده است.
فرم مشتق از قالب | فرم واکنشی |
فرم مشتق از قالب صراحت کمتری دارد و به طور عمده به وسیله دایرکتیوها ساخته میشود | فرم واکنشی صریحتر است و به طور معمول درون کلاس کامپوننت ساخته میشود. |
فرمهای مشتق از قالب از مدل دادههای غیر ساختیافته پشتیبانی میکنند. | همواره از مدل داده ساختیافته پشتیبانی میکند |
از دایرکتیوها برای پیادهسازی اعتبارسنجی فرم بهره میگیرد | از تابع برای پیادهسازی اعتبارسنجی فرم استفاده میکند |
زمانی که مقدار یک کنترل فرم تغییر یابد، یک مکانیسم ناهمگام برای بهروزرسانی کنترلهای فرم ارائه میکند | زمانی که مقدار کنترلهای فرم تغییر یابد، مکانیسم همگامی برای بهروزرسانی کنترلها عرضه میکند. |
کنترلهای فرم
کلاس FormControl به طور عمده برای انتساب فیلدهای مرتبط با فرم در کامپوننتهای انگولار مورد استفاده قرار میگیرد. این کلاس در متد کلاس FormBuilder نیز استفاده میشود. کلاس FormBuilder اساساً به جهت سهولت استفادهاش کاربرد گستردهای یافته است، زیرا به کمک ارجاعها به FormBuilder به جای کلاس FormBuilder میتوانیم به ورودیهای قالب بدون استفاده از خود فرم دسترسی پیدا کنیم. به طور مشابه، میتوانیم از هر وهله از FormControl برای دسترسی به گروه والد با بهرهگیری از مشخصه ریشه استفاده کنیم. زمانی که یک فرم کنترل تعریف میشود، به دو مشخصه به صورت مقدار اولیه و لیستی از اعتبارسنجها نیاز دارد.
اعتبارسنجی فرم واکنشی
فریمورک انگولار، اعتبارسنجهای زیادی برای اعتبارسنجی مقادیر ورودی کنترل فرم در هر اپلیکیشن ارائه کرده است. این اعتبارسنجها میتوانند همراه با وابستگیهای مرتبط با فرمهای رویهای ایمپورت شوند. به طور کلی رویه رایج برای استفاده از اعتبارسنجهای فرم استفاده از .valid و .untouched برای تعیین نیاز به ایجاد پیام خطا است. همچنین میتوانیم از متد ()hasError به عنوان یک اعتبارسنج داخلی روی عنصر فرم استفاده کنیم تا دادههای آن را اعتبارسنجی کنیم.
اعتبارسنجی سفارشی فرم واکنشی
علیرغم وجود اعتبارسنجیهای داخلی در انگولار، گاهی اوقات لازم است که روشهای اعتبارسنجی سفارشی خودمان را برای مقاصد خاص بنویسیم. انگولار به ما امکان داده است که این کار را با کمترین تلاش انجام دهیم. یک تابع ساده، وهلهای از FormControl را میگیرد و در صورتی که همه چیز صحیح باشد، مقدار null بازگشت میدهد. اگر تست شکست بخورد، یک شیء با مشخصه نامدار دلخواه بازگشت خواهد یافت. در این حالت، باید از نام مشخصه ()hasError. برای تست استفاده کنیم.
1<div [hidden]="!password.hasError('hasSpecialChars')">
2 Your password must have Special Characters like @,#,$, etc!
3</div>
مثالی از فرم مشتق از مدل
در این بخش شیوه توسعه یک فرم واکنشی انگولار را به صورت عملی بررسی میکنیم. به این منظور ابتدا باید یک کامپوننت فرم لاگین به صورت زیر بنویسیم:
- فایل app.component.ts
1import { Component, OnInit, ViewChild } from '@angular/core';
2import { Validators, FormBuilder, FormControl, FormGroup } from '@angular/forms';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css']
8})
9export class AppComponent implements OnInit {
10 private formData: any = {};
11
12 username = new FormControl('', [
13 Validators.required,
14 Validators.minLength(5)
15 ]);
16
17 password = new FormControl('', [
18 Validators.required,
19 hasExclamationMark
20 ]);
21
22 loginForm: FormGroup = this.builder.group({
23 username: this.username,
24 password: this.password
25 });
26
27 private showMessage: boolean = false;
28
29 constructor(private builder: FormBuilder) {
30 }
31
32 ngOnInit(): void {
33 }
34
35 registerUser() {
36 this.formData = this.loginForm.value;
37 this.showMessage = true;
38 }
39}
40
41function hasExclamationMark(input: FormControl) {
42 const hasExclamation = input.value.indexOf('!') >= 0;
43 return hasExclamation ? null : { needsExclamation: true };
44}
- فایل app.component.html
1<h2>Reactive Form Module</h2>
2<div>
3 <form [formGroup]="loginForm" (ngSubmit)="registerUser()">
4 <table style="width:60%;" cellpadding="5" cellspacing="5">
5 <tr>
6 <td style="width :40%;">
7 <label for="username">User Name</label>
8 </td>
9 <td style="width :60%;">
10 <input type="text" name="username" id="username" [formControl]="username">
11 <div [hidden]="username.valid || username.untouched" class="error">
12 <div [hidden]="!username.hasError('minlength')">
13 Username can not be shorter than 5 characters.
14 </div>
15 <div [hidden]="!username.hasError('required')">
16 Username is required.
17 </div>
18 </div>
19 </td>
20 </tr>
21 <tr>
22 <td style="width :40%;">
23 <label for="password">Password</label>
24 </td>
25 <td style="width :60%;">
26 <input type="password" name="password" id="password" [formControl]="password">
27 <div [hidden]="password.valid || password.untouched" class="error">
28 <div [hidden]="!password.hasError('required')">
29 The password is required.
30 </div>
31 <div [hidden]="!password.hasError('needsExclamation')">
32 Your password must have an exclamation mark!
33 </div>
34 </div>
35 </td>
36 </tr>
37 <tr>
38 <td style="width :40%;"></td>
39 <td style="width :60%;">
40 <button type="submit" [disabled]="!loginForm.valid">Log In</button>
41 </td>
42 </tr>
43 </table>
44 </form>
45 <div *ngIf="showMessage">
46 <h3>Thanks You {{formData.username}} for registration</h3>
47 </div>
48</div>
اکنون برای استفاده از فرم واکنشی باید ReactiveFormModule را در فایل app.module.ts به صورت زیر ایمپورت کنیم:
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6
7@NgModule({
8 declarations: [
9 AppComponent
10 ],
11 imports: [
12 BrowserModule, FormsModule, ReactiveFormsModule
13 ],
14 providers: [],
15 bootstrap: [AppComponent],
16 schemas: [NO_ERRORS_SCHEMA]
17})
18export class AppModule { }
اینک خروجی را در مرورگر بررسی میکنیم:
به این ترتیب در این بخش از مقاله آموزش انگولار با مفهوم فرمها و انواع مختلف آن در انگولار آشنا شدیم. در بخش بعدی این مقاله با مفهوم سرویس آشنا خواهیم شد.
سرویس انگولار چیست؟
سرویسهای انگولار به اشیای سینگلتونی گفته میشود که به طور معمول تنها یک بار در طی عمر اپلیکیشن یا ماژول، وهلهسازی میشوند. هر سرویس انگولار شامل چند متد است که همواره دادهها را در سراسر عمر یک اپلیکیشن نگهداری میکنند. این یک مکانیسم برای اشتراک مسئولیتها درون یک یا چند کامپوننت محسوب میشود. در فریمورک انگولار میتوانیم هر اپلیکیشنی را با یک کامپوننت مبتنی بر رابطه تودرتو توسعه دهیم. زمانی که کامپوننتها تودرتو باشند، باید برخی دادهها را درون کامپوننت متفاوت نگهداری کنیم. در این حالت، سرویس بهترین روش برای دریافت دادهها از منابع یا اجرای برخی محاسبات است. به طور مشابه سرویسها میتوانند بسته به نیاز بین چند منبع به اشتراک گذارده شوند.
انگولار مفهوم سرویسها را از نسخه 1.x به بعد تا حدود زیادی ساده کرده است. ما در انگولار 1 با سرویسها، فکتوریها، پرووایدرها، delegate-ها، مقادیر و غیره سروکار داشتیم و روشن نبود که هر کدام باید در کجا استفاده شوند. از این رو در ادامه مفهوم سرویس سادهسازی شده است و اینک برای ساخت سرویس تنها به دو گام زیر نیاز داریم:
- ایجاد یک کلاس با دکوراتور Injectable@
- ثبت کلاس با provider یا تزریق کلاس با استفاده از تزریق وابستگی.
سرویسها زمانی در انگولار استفاده میشوند که لازم باشد یک کارکرد یا منطق تجاری مشترک ارائه شود یا این که نیاز باشد با نام متفاوتی به اشتراک گذاشته شود. در واقع سرویس شیئی است که به طور کامل قابلیت استفاده مجدد دارد. با فرض این که اپلیکیشن انگولار شامل برخی کامپوننتها است که خطاها را به منظور ردگیری لاگ میکند، در نهایت یک متد لاگ خطا در هر یک از این کامپوننتها خواهید داشت. این وضعیت بر اساس رویه استاندارد، یک رویکرد بد محسوب میشود، زیرا از یک متد لاگ خطا در اپلیکیشن چند بار استفاده کردهایم.
اگر بخواهید ساختار لاگ کردن خطا را تغییر دهید، در این صورت باید کد مربوطه را در همه کامپوننتها عوض کنید که روی کل اپلیکیشن تأثیرگذار خواهد بود. بنابراین باید از یک کامپوننت سرویس برای لاگ کردن خطاها بهره بگیرید. به این ترتیب، میتوانیم متد لاگ خطا را برای همه کامپوننتها حذف کنیم و درون کلاس سرویس قرار دهیم. در ادامه کامپوننتها میتوانند از وهلهای از این کلاس سرویس برای اجرای متد بهره بگیرند. در فریمورک انگولار، تزریق سرویس یکی از روشهای تزریق وابستگی محسوب میشود.
مزایای استفاده از سرویس انگولار
سرویسهای انگولار اشیای منفردی هستند که به طور معمول تنها یک بار در طی عمر اپلیکیشن وهلهسازی میشوند. سرویس انگولار دادهها را در سراسر عمر اپلیکیشن نگهداری میکند. هدف اصلی سرویس انگولار استفاده از منطق تجاری مشترک یا دادهها و تابعهای مشترک به صورت چندباره در کامپوننتهای مختلف اپلیکیشن است.
در واقع هدف اصلی از طراحی یک سرویس انگولار، رعایت اصل «جداسازی دغدغهها» (Separation of Concern) است. سرویس انگولار یک شیء بیحالت است و میتوانیم برخی تابعهای مفید را در آن تعریف کنیم. این تابعها میتوانند از هر یک از اجزای اپلیکیشن از قبیل کامپوننتها، دایرکتیوها و غیره فراخوانی شوند. بدین ترتیب میتوانیم کل اپلیکیشن را به واحدهای کوچکتر منطقی و متمایز تقسیم کنیم تا خوانایی آنها افزایش یابد.
شیوه تعریف سرویس انگولار
در انگولار امکان ساخت یک سرویس تعریفشده از سوی کاربر و سفارشی بسته به نیاز وجود دارد. برای ایجاد یک سرویس باید مراحل زیر را طی کنیم:
- ابتدا باید یک فایل تایپ اسکریپت با نامگذاری صحیح ایجاد کنیم.
- سپس یک کلاس تایپ اسکریپت با نام مناسب ایجاد میکنیم که سرویس را پس از متدی نمایش خواهد داد.
- از دکوراتور Injectable@ در ابتدای نام کلاس استفاده میکنیم که از پکیجهای @angular/core ایمپورت شده است. اساساً هدف از Injectable@ این است که سرویس تعریفشدهی کاربر و هر یک از وابستگان آن بتوانند به صورت خودکار از سوی کامپوننتهای دیگر تزریق شوند.
- با این حال، انگولار به دلیلی رعایت اصل خوانایی کد پیشنهاد میکند که همواره دکوراتور Injectable@ را در زمان ایجاد هر نوع سرویسی تعریف کنیم.
- در نهایت از کلیدواژه Export روی اشیای کلاس استفاده میکنیم تا سرویس بتواند در هر کامپوننت دیگر تزریق شده و مورد استفاده مجدد قرار گیرد.
توجه کنید که وقتی یک سرویس سفارشی میسازیم که در کل اپلیکیشن و با استفاده از متادیتای provider در اختیار ما قرار دارد، این متادیتا باید در فایل ماژول اصلی اپلیکیشن یعنی app.module.ts تعریف شود. اگر سرویس در فایل ماژول اصلی ارائه شود، در دید همه اپلیکیشن قرار خواهد گرفت. اگر آن را در هر کامپوننتی عرضه کنید، در این صورت تنها آن کامپوننت میتواند از آن سرویس استفاده کند. با ارائه سرویس در سطح ماژول، انگولار یک وهله از کلاس CustomService ایجاد میکند و میتواند از سوی همه کامپوننتهای اپلیکیشن مورد استفاده قرار گیرد.
1import { Injectable } from '@angular/core';
2
3()njectable@
4export class AlertService
5{
6 constructor()
7 { }
8
9 publish showAlert(message: string)
10 {
11 alert(message);
12 }
13}
مثالی از یک سرویس ابتدایی
در این بخش شیوه استفاده از Injectable Service را در انگولار بررسی میکنیم. به این منظور یک مدخل از اطلاعات دانشجو را ایجاد کرده و در زمان کلیک شدن دکمه تحویل، آن دادهها را به درون سرویس منتقل میکنیم تا در آنجا ذخیره شوند.
- فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2import { StudentService } from './app.service';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls : ['./custom.css']
8})
9export class AppComponent implements OnInit {
10 private _model: any = {};
11 private _source: Array<any>;
12
13 constructor(private _service: StudentService) {
14 this._source = this._service.returnStudentData();
15 }
16
17 ngOnInit(): void {
18 }
19
20 private submit(): void {
21 if (this.validate()) {
22 this._service.addStudentData(this._model);
23 this.reset();
24 }
25 }
26
27 private reset(): void {
28 this._model = {};
29 }
30
31 private validate(): boolean {
32 let status: boolean = true;
33 if (typeof (this._model.name) === "undefined") {
34 alert('Name is Blank');
35 status = false;
36 return;
37 }
38 else if (typeof (this._model.age) === "undefined") {
39 alert('Age is Blank');
40 status = false;
41 return;
42 }
43 else if (typeof (this._model.city) === "undefined") {
44 alert('City is Blank');
45 status = false;
46 return;
47 }
48 else if (typeof (this._model.dob) === "undefined") {
49 alert('dob is Blank');
50 status = false;
51 return;
52 }
53 return status;
54 }
55}
- فایل app.component.html
1<div style="padding-left: 20px">
2 <h2>Student Form</h2>
3 <table style="width:80%;">
4 <tr>
5 <td>Student Name</td>
6 <td><input type="text" [(ngModel)]="_model.name" /></td>
7 </tr>
8 <tr>
9 <td>Age</td>
10 <td><input type="number" [(ngModel)]="_model.age" /></td>
11 </tr>
12 <tr>
13 <td>City</td>
14 <td><input type="text" [(ngModel)]="_model.city" /></td>
15 </tr>
16 <tr>
17 <td>Student DOB</td>
18 <td><input type="date" [(ngModel)]="_model.dob" /></td>
19 </tr>
20 <tr>
21 <td></td>
22 <td>
23 <input type="button" value="Submit" (click)="submit()" />
24 <input type="button" value="Reset" (click)="reset()" />
25 </td>
26 </tr>
27 </table>
28 <h3>Student Details</h3>
29 <div class="ibox-content">
30 <div class="ibox-table">
31 <div class="table-responsive">
32 <table class="responsive-table table-striped table-bordered table-hover">
33 <thead>
34 <tr>
35 <th style="width:40%;">
36 <span>Student's Name</span>
37 </th>
38 <th style="width:15%;">
39 <span>Age</span>
40 </th>
41 <th style="width:25%;">
42 <span>City</span>
43 </th>
44 <th style="width:20%;">
45 <span>Date of Birth</span>
46 </th>
47 </tr>
48 </thead>
49 <tbody>
50 <tr *ngFor="let item of _source; let i=index">
51 <td><span>{{item.name}}</span></td>
52 <td><span>{{item.age}}</span></td>
53 <td><span>{{item.city}}</span></td>
54 <td><span>{{item.dob}}</span></td>
55 </tr>
56 </tbody>
57 </table>
58 </div>
59 </div>
60 </div>
61</div>
- فایل app.service.ts
1import { Injectable } from "@angular/core";
2
3@Injectable()
4export class StudentService {
5 private _studentList: Array<any> = [];
6
7 constructor() {
8 this._studentList = [{name:'Amit Roy', age:20, city:'Kolkata', dob:'01-01-1997'}];
9 }
10
11 returnStudentData(): Array<any> {
12 return this._studentList;
13 }
14
15 addStudentData(item: any): void {
16 this._studentList.push(item);
17 }
18}
- فایل app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { StudentService } from './app.service';
7
8@NgModule({
9 declarations: [
10 AppComponent
11 ],
12 imports: [
13 BrowserModule, FormsModule, ReactiveFormsModule
14 ],
15 providers: [StudentService],
16 bootstrap: [AppComponent],
17 schemas: [NO_ERRORS_SCHEMA]
18})
19export class AppModule { }
اینک خروجی مثال را در مرورگر بررسی کنید:
دکوراتور ()njectable@
Injectable در واقع یک دکوراتور در انگولار محسوب میشود دکوراتورها اکستنشنهای عرضه شده در جاوا اسکریپت هستند. به طور خلاصه، دکوراتور امکان ویرایش متدها، کلاسها، مشخصهها و پارامترها را به برنامهنویسان میدهد. هر کلاس «تزریقپذیر» (Injectable) در انگولار در عمل درست مانند یک کلاس نرمال عمل میکند. به همان جهت است که کلاسهای Injectable هیچ چرخه عمر خاصی در فریمورک انگولار ندارند. بنابراین زمانی که یک شیء از کلاس Injectable ایجاد میکنید، سازنده آن کلاس صرفاً متد ()ngOnInit کلاس کامپوننت را اجرا میکند. اما در مورد کلاس Injectable امکان تعریف destructor وجود ندارد، زیرا در جاوا اسکریپت مفهومی به نام destructor وجود ندارد. بنابراین به بیان ساده کلاس سرویس destructor نمیتواند تخریب شود. اگر بخواهیم وهلهای از کلاس سرویس را حذف کنیم، باید نقطه ارجاع تزریق وابستگی مرتبط با آن کلاس را حذف کنیم.
دکوراتور Injectable@ نشان میدهد که یک کلاس خاص در فریمورک انگولار میتواند همراه با تزریقکننده وابستگی کار کند. در صورتی که کلاس دارای دکوراتورهای دیگر انگولار از قبیل دکوراتور کامپوننت، دکوراتور دایرکتیو و غیره باشد و یا هیچ وابستگی نداشته باشد، نیازی به تعیین صریح دکوراتور Injectable@ نخواهد بود.
نکته مهم این است که هر کلاسی باید با دکوراتور ()njectable@ تعریف شود تا بتواند در اپلیکیشن تزریق شود.
1@Injectable()
2export class SampleService
3{
4 constructor()
5 {
6 console.log('Sample service is created');
7 }
8}
انگولار نیز همانند فریمورک NET MVC. از تزریق وابستگی یا DI پشتیبانی میکند. ما میتوانیم از سازنده کامپوننت برای تزریق وهلههای کلاس سرویس بهره بگریم. انگولار متادیتای provider را در دو سطح ماژول و سطح کامپوننت عرضه کرده است که میتواند عملیات تزریق وابستگی خودکار را برای هر سرویس Injectable در زمان اجرا انجام دهد.
تزریق وابستگی به چه معنا است؟
«تزریق وابستگی» (Dependency Injection) همواره یکی از مزیتهای اصلی فریمورک انگولار بوده است. در واقع انگولار به جهت همین مزیت در میان توسعهدهندگان محبوبیت زیادی کسب کرده است، زیرا امکان انجام این کار در دیگر فریمورکهای سمت کلاینت به این سهولت وجود ندارد. ما به کمک این قابلیت میتوانیم هر نوع وابستگی را مانند سرویس یا ماژول کاربردی بیرونی در ماژول اصلی اپلیکیشن خود تزریق کنیم. به این منظور حتی لازم نیست در مورد آن ماژولهای وابستگی یا سرویسها اطلاعاتی داشته باشیم.
فریمورک انگولار مکانیسم خاص خود را برای سیستم تزریق وابستگی دارد. در این سیستم هر ماژول انگولار مقادیر متادیتای تزریقکننده خاص مرتبط با خود را دارد. بر همین اساس تزریقکننده هر ماژول، مسئول ایجاد نقطه ارجاع شیء است و در ادامه ماژول را به بسته به نیاز تزریق میکند. در عمل هر وابستگی مانند یک جفت کلید- مقدار عمل میکند. کلید عملی است که باید انجام شود و مقدار نیز شیئی است که باید تزریق شود. اما علیرغم این مکانیسم جالب، برخی مشکلات نیز به شرح زیر وجود دارند:
- کش داخلی: هر شیء وابستگی در انگولار یک صورت یک شیء سینگلتون ایجاد میشود. بنابراین زمانی که یک کلاس سرویس را به عنوان شیء وابستگی تزریق میکنیم، آن وهله از آن کلاس سرویس یک بار برای کل چرخه عمر اپلیکیشن ایجاد شده است.
- تصادم فضای نام: در انگولار امکان تزریق دو کلاس سرویس متفاوت از دو ماژول متفاوت با نامهای یکسان وجود ندارد. به عنوان مثال فرض کنید یک سرویس به نام UserService برای ماژول خودمان ایجاد میکنیم و آن سرویس را تزریق میکنیم. اکنون تصور کنید EmployeeModule را ایمپورت کنیم که شامل سرویسی با همان نام UserService است و میخواهیم آن را نیز تزریق کنیم. این کار در انگولار ممکن نیست، زیرا سرویس با نام مشابهی از قبل در اپلیکیشن تزریق شده است.
- داخل فریمورک تعبیه شده است: تزریق وابستگی در انگولار به صورت کامل با ماژولهای انگولار جفت شده است. امکان جداسازی تزریق وابستگی از ماژولهای انگولار و ارائه به صورت یک سیستم مستقل وجود ندارد.
تزریق وابستگی در انگولار شامل سه بخش Injector ،Provider و Dependency است.
- Injector: هدف اصلی استفاده از بخش Injector این است که یک شیء یا API-هایی را عرضه کنیم که به ما کمک کند تا وهلههایی از کلاس یا سرویسهای وابسته را بسازد.
- Provider: یک Provider اساساً به عنوان یک فرمانده عمل میکند. این بخش دستورالعملهای مورد نیاز را در خصوص فرایند ایجاد وهلههای اشیای وابسته در اختیار تزریقکننده (Provider) قرار میدهد. همچنین Provider همواره مقدار توکن را به عنوان ورودی گرفته و آن مقدار توکن را به وهلههای ایجاد شده جدید از اشیای کلاس نگاشت میکند.
- Dependency: بخش Dependency نوع پردازندهای است که ماهیت اشیای ایجاد شده را شناسایی میکند.
بر اساس توضیحات فوق، میتوانیم وظایف زیر را با استفاده از تزریق وابستگی در انگولار اجرا کنیم.
- در انگولار میتوانیم وهلههای کلاسهای سرویس را در سطح constructor با استفاده از متادیتای provider ایجاد کنیم
- همچنین میتوانیم از پرووایدرها برای تعیین وابستگیها بین پرووایدرهای سطح ماژول و پرووایدرهای سطح کامپوننت استفاده کنیم.
- فرایند تزریق وابستگی وهلههایی از کلاس وابسته ایجاد کرده و در موارد نیاز در کد ارجاعهایی برای آن اشیا عرضه میکند.
- همه وهلههای اشیای تزریق شده وابستگی به صورت اشیای سینگلتون ایجاد میشوند.
Provider چیست؟
با مطالعه بخش فوق احتمالاً برایتان سؤال پیش آمده که Provider چیست؟ Provider یک شیء یا کلاس است که انگولار از آن برای ارائه چیزی که قرار است استفاده کنیم بهره میگیرد. این چیز میتواند یکی از گزینههای زیر باشد:
- یک provider کلاس همواره یک وهله از کلاس ایجاد میکند.
- یک provider فکتوری همواره چیزی را که در زمان اجرای یک تابع خاص بازگشت مییابد، تولید یا عرضه میکند.
- یک provider مقدار به هیچ اکشنی برای ارائه نتیجه نیاز ندارد و صرفاً یک مقدار بازگشت میدهد.
مثالی از یک کلاس
1export class testClass {
2 public message: string = "Hello from Service Class";
3 public count: number;
4 constructor() {
5 this.count=1;
6 }
7}
در کد فوق یک کلاس میبینیم که باید از دکوراتور ()Injectable استفاده کنیم تا انگولار بتواند این کلاس را به عنوان یک provider ثبت کند و بتوانیم از وهلهای از آن کلاس درون اپلیکیشن استفاده کنیم. ما یک کامپوننت ایجاد میکنیم که به عنوان کامپوننت root اپلیکیشنمان عمل میکند. اضافه کردن یک provider به نام testClass به این کامپوننت کاری سرراست محسوب میشود:
- testClass را ایمپورت میکنیم.
- آن را به مشخصه دکوراتور ()Component@ اضافه میکنیم.
یک آرگومان از نوع testClass نیز به سازنده اضافه میکنیم.
1import { Component } from "@angular/core";
2import { testClass} from "./service/testClass";
3
4@Component({
5 module : module.id,
6 selector : ‘test-prog’,
7 template : ‘<h1>Hello {{_message}}</h1>’,
8 providers : [testClass]
9})
10
11export class TestComponent
12{
13 private _message:string="";
14 constructor(private _testClass : testClass)
15 {
16 this._message = this._testClass.message;
17 }
18}
در پشت صحنه وقتی انگولار وهلهای از این کامپوننت میسازد، سیستم تزریق وابستگی یک تزریقکننده برای کامپوننت میسازد که testClass provider را ثبت میکند. در ادامه انگولار نوع testClass تعیین شده در لیست آرگومان سازنده را میبیند و به دنبال testClass provider ثبت شده میگردد و از آن برای تولید یک وهله بهره میگیرد که به _testClass انتساب مییابد. فرایند گشتن به دنبال testClass provider و تولید وهله و انتساب آن به _testClass همگی در پشت صحنه از سوی انگولار انجام مییابد.
تزریق یک سرویس در یک ماژول
امکان تزریق یک سرویس انگولار در سطح اپلیکیشن یا سطح ماژول وجود دارد. مشخصه دکوراتور NgModule در provider به ما امکان میدهد که لیستی از سرویسهای انگولار را در سطح ماژول تزریق کنیم این متد یک وهله از سرویس انگولار را ارائه خواهد کرد که در کل اپلیکیشن در اختیار ما قرار دارد و دادههای یکسانی را درون کامپوننتهای مختلف به اشتراک میگذارد.
1@NgModule({
2 imports: [ BrowserModule, FormsModule ],
3 declarations: [ AppComponent, ParentComponent, ChildComponent ],
4 bootstrap: [ AppComponent ],
5 providers: [ SimpleService, EmailService ] ①
6 })
7class AppModule { }
مثالی از تزریق سرویس درون یک ماژول
در این بخش به بررسی روش تزریق سرویسهای انگولار در سطح ماژول میپردازیم. به این منظور، دو کامپوننت را به صورت کامپوننتهای والد-فرزند ایجاد میکنیم. سپس از سلکتور کامپوننت والد در کامپوننت Root استفاده میکنیم. برای نمایش سرویس سطح ماژول از دو وهله از کامپوننت والد درون کامپوننت root استفاده خواهیم کرد. کد به صورت زیر است:
- فایل parent.component.ts
1import { Component, OnInit } from '@angular/core';
2import { DemoService } from './app.service';
3
4@Component({
5 selector: 'parent',
6 templateUrl: './parent.component.html',
7 styleUrls : ['./custom.css']
8})
9export class ParentComponent implements OnInit {
10
11 constructor(private demoService:DemoService){
12
13 }
14
15 ngOnInit(){
16 }
17}
- فایل parent.component.html
1<div class="parent">
2 <p>Parent Component</p>
3 <div class="form-group">
4 <input type="text" class="form-control" name="value" [(ngModel)]="demoService.message">
5 </div>
6 <child></child>
7</div>
- فایل child.component.ts
1import { Component, OnInit } from '@angular/core';
2import { DemoService } from './app.service';
3
4@Component({
5 selector: 'child',
6 templateUrl: './child.component.html',
7 styleUrls : ['./custom.css']
8})
9export class ChildComponent implements OnInit {
10 constructor(private demoService:DemoService){
11 }
12
13 ngOnInit(){
14 }
15}
- فایل child.component.html
1<div class="child">
2 <p>Child Component</p>
3 {{ demoService.message }}
4</div>
- فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent implements OnInit {
9
10 ngOnInit(){
11
12 }
13}
- فایل app.component.html
1<div style="padding-left: 20px;padding-top: 20px; width: 500px;">
2 <div class="row">
3 <div class="col-xs-6">
4 <h2>Parent Componet - 1</h2>
5 <parent></parent>
6 </div>
7 <div class="col-xs-6">
8 <h2>Parent Componet - 2</h2>
9 <parent></parent>
10 </div>
11 </div>
12</div>
- فایل custom.css
1.parent{
2 background-color: bisque;
3 font-family: Arial, Helvetica, sans-serif;
4 font-weight: bolder;
5 font-size: large;
6}
7
8.child{
9 background-color:coral;
10 font-family: Georgia, 'Times New Roman', Times, serif;
11 font-weight: bold;
12 font-size: medium;
13}
- فایل app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4
5import { AppComponent } from './app.component';
6import { ParentComponent } from './parent.component';
7import { ChildComponent } from './child.component';
8import { DemoService } from './app.service';
9
10@NgModule({
11 declarations: [
12 AppComponent,ParentComponent,ChildComponent
13 ],
14 imports: [
15 BrowserModule, FormsModule, ReactiveFormsModule
16 ],
17 providers: [DemoService],
18 bootstrap: [AppComponent],
19 schemas: [NO_ERRORS_SCHEMA]
20})
21export class AppModule { }
اینک میتوانید خروجی را در مرورگر بررسی کنید:
در مثال فوق میبینیم که اگر نوعی متن را در هر یک از کادرهای ورودی کامپوننت والد وارد کنیم، به صورت خودکار در کامپوننت تودرتوی فرزند نیز بهروزرسانی میشود. دلیل این امر آن است که DemoService در سطح ماژول تزریق شده است. بنابراین یک وهله سینگلتون از آن سرویس ایجاد کرده و آن را در سراسر اپلیکیشن در اختیار ما قرار میدهد.
تزریق یک سرویس در یک کامپوننت
امکان تزریق یک سرویس انگولار در سطح کامپوننت نیز وجود دارد. در این مورد نیز همانند NgModule، مشخصه پرووایدر دکوراتور کامپوننت به ما امکان میدهد که لیستی از سرویسهای انگولار را درون کامپوننت خاصی تزریق کنیم. به این ترتیب یک وهله منفرد از آن سرویس انگولار در سراسر کامپوننت و همه کامپوننتهای فرزند آن در اختیار ما قرار خواهد داشت.
1@Component({
2 selector: 'parent',
3 template: `...`,
4 providers: [ EmailService ]
5 })
6 class ParentComponent {
7 constructor(private service: EmailService) { }
8 }
مثالی از تزریق سرویس درون یک کامپوننت
برای ساخت یک سرویس که در سطح کامپوننت تزریق شود، باید تغییرهای زیر را در مثال قبلی و در فایل parent.component.ts اعمال کنیم:
1import { Component, OnInit } from '@angular/core';
2import { DemoService } from './app.service';
3
4@Component({
5 selector: 'parent',
6 templateUrl: './parent.component.html',
7 styleUrls : ['./custom.css'],
8 providers:[DemoService]
9})
10export class ParentComponent implements OnInit {
11 constructor(private demoService:DemoService){
12 }
13
14 ngOnInit(){
15 }
16}
اکنون خروجی را در مرورگر بررسی میکنیم:
در مثال فوق به وضوح میبینیم که با وارد کردن مقداری در کامپوننت parent 1 این مقدار به صورت خودکار در وهلههای دیگر کامپوننت والد به اشتراک گذاشته نمیشود. دلیل این امر آن است که برخلاف مثال قبلی DemoService در سطح کامپوننت تزریق شده است.
به این ترتیب به پایان بحث سرویسهای انگولار میرسیم. در ادامه این مقاله آموزش انگولار در خصوص شیوه اجرای درخواستهای Ajax در فریمورک انگولار صحبت خواهیم کرد.
فراخوانی Ajax در انگولار
امروزه زمانی که یک اپلیکیشن مبتنی بر وب میسازیم، باید API-های HTTP بیرونی را برای انواع مختلفی از عملیات مرتبط دادهها فراخوانی کنیم. به این منظور، انگولار کتابخانه کلاینت خاص HTTP خود را ارائه کرده است که میتواند برای اجرای این نوع از عملیات مورد استفاده قرار گیرد.
درخواست HTTP در محیط جاوا اسکریپت همواره یک عملیات ناهمگام است، یعنی خطوط بعدی کد بدون منتظر ماندن برای دریافت پاسخ فراخوانی قبلی اجرا میشوند. زمانی که API پاسخ را پس از چند میلیثانیه یا ثانیه یا دقیقه بازگشت میدهد، مطلع میشویم که باید بر اساس این پاسخ شروع به پردازش آن بکنیم. در انگولار دو روش مختلف برای مدیریت این نوع از عملیات وجود دارد که یکی Observable و دیگری Promise است. در ادامه این مقاله در خصوص این دو روش مدیریت درخواستهای HTTP بحث خواهیم کرد.
ایده ابتدایی فراخوانی Ajax
Ajax اختصاری برای عبارت «جاوا اسکریپت و XML ناهمگام» (Asynchronous JavaScript and XML) است. در واقع Ajax یک مفهوم اسکریپتنویسی سمت کلاینت است که برای برقراری ارتباط بین سرور و کلاینت و اجرای هر نوع عملیات مورد استفاده قرار میگیرد. در واقع Ajax روشی برای مبادله و واکشی دادهها از سرور است. در عمل ajax همواره به استفاده از اشیای XmlHttpRequest برای تعامل با یک سرور به صورت دینامیک و از طریق کتابخانه جاوا اسکریپت گفته میشود. استفاده از Ajax در هر وباپلیکیشن برخی مزیتهای خاص به شرح زیر دارد:
- Ajax برای اجرای عملیات callback استفاده میشود که یک موجب مبادله سریع دادهها بین سرور و اپلیکیشن میشود و عملیات مبادله دادهها نیازی به رفرش کل صفحه ندارد. به این ترتیب میتوانیم استفاده از شبکه و عملکرد اپلیکیشن را ارتقا ببخشیم.
- Ajax به ما امکان میدهد که فراخوانیهای ناهمگام را به یک وبسرور اجرا کنیم. به این ترتیب مرورگر کلاینت لازم نیست صبر کند تا همه دادهها برسند تا به کاربر امکان تعامل بدهد.
- به کمک Ajax میتوانیم سرعت، عملکرد و کاربردپذیری وباپلیکیشن را افزایش دهیم.
آشنایی با HttpClient
گوگل در نسخه 6 انگولار برای نخستین بار یک روش آسانتر برای کار با درخواستهای http ارائه کرد که به صورت کتابخانه جدیدی به نام HttpClient بود. انگولار نام این کتابخانه جدید را طوری انتخاب کرد که هیچ ناسازگاری با کتابخانه Http موجود نداشته باشد. به کمک این کتابخانه جدید Http میتوانیم از کارکردهای جدیدی از قبیل امکان گوش دادن به رویدادهای پیشرونده و یا رصد گیرندهها برای ویرایش درخواستها یا پاسخ بهره بگیریم. در عمل ماژول Http همواره به صورت داخلی از API مبتنی بر Observable به نام RxJS استفاده میکند. به همین جهت، زمانی که چند فراخوانی Ajax را با استفاده از ماژول HTTP به سرور ارسال میکنیم، همه پاسخها روی یک شیء observable بازگشت مییابد که باید به طریقی در آن مشترک شویم. زمانی که با اشیای observable کار میکنیم، باید برخی نکات کلیدی را به خاطر بسپاریم.
- برای دریافت یک مقدار از یک پاسخ در هر فراخوانی HTTP باید در observable-ها مشترک شویم. اگر این اشتراک را فراموش کنیم، در این صورت هیچ اتفاقی نخواهد افتاد.
- اگر چند بار در observable مشترک شویم، چندین درخواست HTTP تریگر میشوند.
- هر observable بر اساس ماهیت یا نوع خود، استریمهای با مقدار منفرد است، بنابراین اگر درخواست HTTP موفق باشد، observable-ها تنها یک مقدار بازگشت داده و تکمیل میشوند.
- اگر درخواست HTTP ناموفق باشد، در این صورت این observable ها یک خطا صادر میکنند.
برای استفاده از HttpClient در یک اپلیکیشن انگولار باید ماژول HttpClientModule را در ماژول ریشه اپلیکیشن ایمپورت کرده و از سرویس Http استفاده کنیم:
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { HttpClientModule } from '@angular/common/http';
4
5import { AppComponent } from './app.component';
6
7@NgModule({
8 declarations: [
9 AppComponent
10 ],
11 imports: [
12 BrowserModule,HttpClientModule
13 ],
14 bootstrap: [AppComponent],
15 schemas: [NO_ERRORS_SCHEMA]
16})
17export class AppModule { }
Observable چیست؟
Observable یکی از قابلیتهای مورد استفاده در فریمورک انگولار محسوب میشود، اما در اصل اختصاصی به انگولار ندارد و یک روش استاندارد برای مدیریت هر نوع از دادهها یا عملیات همگامسازیشده است که در نسخه ES7 جاوا اسکریپت تدارک شده است. به کمک Observable-ها میتوانیم عملیات پیوسته را برای چند ارتباط اجرا کنیم و از طریق آنها چند مقدار دادهای را ارسال کنیم. از این رو با استفاده از Observable-ها میتوانیم هر نوع عملیاتی روی دادههای شبیه آرایه، اعم از ویرایش، تجزیه و ذخیره دادهها اجرا کنیم. به همین جهت Observable در انگولار استفاده گستردهای دارد.
Observable-ها در واقع یک نوع مقدماتی جدید هستند که در عمل به صورت یک نقشه اولیه برای شیوه ایجاد استریمها، اشتراک در آنها و واکنش به مقادیر جدید و ترکیب استریمها با هم برای ساخت استریمهای جدید استفاده میشوند.
Promise چیست؟
زمانی که در اپلیکیشن خود هر عملیاتی را به صورت همگام اجرا میکنیم، پیش از رفتن به خط کد بعدی منتظر میمانیم تا این عملیات پایان یابد. اما اگر کاری را به شیوه ناهمگام اجرا کنیم، برنامه پیش از آن که آن کار خاتمه یابد به خط بعدی میرود و شاید تکمیل آن وظیفه به چند ثانیه زمان یا بیشتر نیاز داشته باشد. از این رو به بیان ساده میتوانیم فرض کنیم که برنامهنویسی همگام شبیه منتظر ماندن در یک صف و برنامهنویسی ناهمگام مانند عبور از یک خط ویژه و عدم منتظر ماندن در صف است.
از آنجا که ما یک بلیت ویژه داریم، میتوانیم کارهای دیگری را انجام دهیم و زمانی که آن کار خاص کامل شد، این موضوع را به ما اعلام خواهد کرد. یکی از آسانترین روشها برای اجرای ناهمگام برنامهها استفاده از تابعهای callback است. به این ترتیب میتوانیم یک تابع را به صورت یک پارامتر به یک تابع ناهمگام ارسال کنیم که وقتی وظیفه مورد نظر کامل شد، فراخوانی خواهد شد.
1function doAsyncTask(cb) {
2 setTimeout(() => {
3 console.log("Async Task Calling By Callback Function");
4 cb();
5 }, 1000);
6}
7
8doAsyncTask(() => console.log("Callback Function Called"));
در مثال فوق، تابع doAsyncTask یک وظیفه ناهمگام آغاز میکند و بیدرنگ بازگشت مییابد. زمانی که آن وظیفه ناهمگام کامل شد، یک تابع فراخوانی خواهد شد. این یک تابع callback است، زیرا شما را مجدداً صدا خواهد زد.
ES6 یک مکانیسم جایگزین در اختیار ما قرار میدهد که Promise نام دارد. این Promise یک placeholdr برای مقدار آتی است. در واقع پرامیس همان کارکرد callback را اجرا میکند و ساختار زیبا و سازمانیافتهای ارائه میکند که یک روش آسان برای مدیریت خطاها در اختیار ما قرار میدهد. ما میتوانیم یک وهله از Promise را با فراخوانی new روی کلاس Peomise به صورت زیر ایجاد کنیم:
1var promise = new Promise((resolve, reject) => {
2});
به این ترتیب همواره یک تابع درونی را با دو آرگومان resolve و reject ارسال میکنیم. همانطور که در تابع تعریف کردهایم، میتوانیم نام این آرگومان را هر چیزی که دوست داریم بگذاریم، اما رویه استاندارد این است که آنها را به همین صورت یعنی resolve و reject نامگذاری کنیم. درون تابع داخلی در عمل یک پردازش ناهمگام اجرا میکنیم و سپس زمانی که آماده بودیم، میتوانیم ()resolve را به صورت زیر فراخوانی کنیم:
1var promise = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 console.log("Async Work Complete");
4 resolve();
5 }, 1000);
6});
اگر بخواهیم از Promise در مقال تابع clallback قبلی خودمان استفاده کنیم، به صورت زیر خواهد بود:
1let error = true;
2function doAsyncTask() {
3 var promise = new Promise((resolve, reject) => {
4 setTimeout(() => {
5 console.log("Async Work Complete");
6 if (error) {
7 reject();
8 }
9 else
10 {
11 resolve();
12 }
13 }, 1000);
14 });
15 return promise;
16}
تفاوت Observable و Promise
زمانی که در انگولار از HttpClient برای درخواستهای Ajax استفاده میکنیم، میتوانیم از هر دو پیادهسازی Observable و Promise بهره بگیریم که یک روش آسان برای عملیات API برای مدیریت درخواستها و دریافت پاسخها در اختیار ما قرار میدهند. اما همچنان برخی تفاوتهای کلیدی وجود دارند که موجب میشوند Observable-ها در مقایسه با Promise-ها مزیت بیشتری داشته باشند:
- Promise هر بار تنها میتواند یک مقدار بگیرد، مگر این که چند بار آن را اجرا کنیم.
- Promise را در صورت نیاز نمیتوان لغو کرد.
در نسخههای جدید انگولار، مفاهیم برنامهنویسی واکنشی به طور کامل بر مبنای observable-ها نوشته شدهاند و با استفاده از آن دادهها به صورت ناهمگام پردازش میشوند. در نسخههای قبلی انگولار ما به طور معمول از Promise برای مدیریت پردازشهای ناهمگام در ارتباط با فراخوانیهای ایجکس استفاده میکردیم. اما امکان استفاده از Promise در نسخههای جدید انگولار وجود دارد. هدف اصلی اینک، برنامهنویسی واکنشی عنصر است که به طور عمده برای رصد یک مدل یا متغیر مورد استفاده قرار میگیرد. در سطح نرمال هر دو مفهوم promise و observable مشابه همدیگر به نظر میرسند و هر دوی آنها برای اجرای درخواست ناهمگام ایجکس همراه با تابعهای callback برای هر دو پاسخ موفق و ناموفق مورد استفاده قرار میگیرند و زمانی که نتیجه به دست آمد، ما را مطلع میسازند.
شیوه تعریف Observable چگونه است؟
پیش از آن که شروع به بررسی افعال HTTP بکنیم، باید با شیوه تعریف اشیای Observable آشنا شویم. برای ایجاد یک Observable باید از متد create شیء Observable استفاده کنیم به این منظور باید یک شیء تابع به عنوان یک پارامتر ارائه کنیم که باید در پردازش Observable مقداردهی شود. این تابع میتواند یک تابع بازگشت دهد یا Observable را لغو کند.
1var observable = Observable.create((observer) => {
2 setTimeout(() => {
3 observer.next('some event');
4 }, 500);
5});
Observable-ها نیز همانند promise-ها میتوانند به کمک متدهای زیر چند نوتیفیکیشن داشته باشند:
- Next – یک رویداد ایجاد میکند که میتواند چند بار اجرا شود.
- Error – به طور عمده برای صدور یک خطا استفاده میشود. زمانی که این متد اجرا شود، استریم متوقف خواهد شد. این بدان معنی است که callback خطا بیدرنگ فراخوانی شده و هیچ رویداد یا گام دیگری اجرا نخواهد شد.
- Complete - این متد برای نشان دادن این واقعیت استفاده میشود که complete یا تکمیل شده است. پس از این رویداد دیگر هیچ رویدادی اجرا نخواهد شد.
ما به کمک observable-ها میتوانیم callback را برای نوتیفیکیشنهای قبلاً تعریف شده ثبت کنیم. متد subscribe این مشکل را حل میکند. این متد سه تابع callback به صورت پارامتر میپذیرد:
- onNext – این callback زمانی فراخوانی میشود که یک رویداد تریگر شود.
- onError – این callback زمانی فراخوانی میشود که یک خطا ایجاد شود.
- onError – این callback در مواردی فراخوانی خواهد شد که یک observable تکمیل شود.
افعال HTTP
در پروتکل HTTP برخی فعلها وجود دارند که برای توصیف انواع عملیات روی URL مورد استفاده قرار میگیرند. روی هر نوع URL میتوان یک درخواست GET،PUT ،POST ،DELETE ،HEAD و OPTIONS اجرا کرد. در این بخش از مقاله آموزش انگولار به بررسی شیوه اجرای API URL برای افعال فوقالذکر میپردازیم. به این منظور فرض میکنیم که مانند کد زیر، تزریق وابستگی برای HttpClient در سطح سازنده کامپوننت اجرا شده است:
1constructor(private http: HttpClient) {
2}
Get
برای اجرای یک درخواست Get باید تابع get را روی کلاینت http خود فراخوانی کنیم. به این ترتیب یک observable بازگشت مییابد که باید در آن مشترک شویم تا دادهها یا پاسخ از سمت سرور بازیابی شود.
1performGET() {
2 console.log("GET");
3 let url = `http://localhost/get`;
4 this.http.get(url).subscribe(res => console.log(res.text()));
5}
Delete
برای اجرای یک درخواست Delete کافی است تابع delete مربوط به کلاینت http را فراخوانی کنیم. قالببندی تابع کاملاً مشابه تابع get فوق است. اما در این مورد میتوانیم پارامترهای کوئری را درون این تابع ارسال کنیم.
1performDELETE() {
2 console.log("DELETE");
3 let url = `http://localhost/delete`;
4 let search = new URLSearchParams();
5 search.set('foo', 'moo');
6 search.set('limit', 25);
7 this.http.delete(url, {search}).subscribe(res => console.log(res.json()));
8}
POST
برای اجرای یک درخواست POST باید تابع post کلاینت http را فراخوانی کنیم. قالببندی تابع post نیز همانند تابع get و delete قبلی است. اما به طور معمول درخواست POST برای ارسال دادهها به سرور استفاده میشود. بنابراین در پارامتر دوم متد POST باید یک شیء به جای پارامترهای کوئری ارسال کنیم که payload درخواست را میفرستد.
1performPOST() {
2 console.log("POST");
3 let url = `http://localhost/post`;
4 this.http.post(url, {moo:"foo",goo:"loo"}).subscribe(res =>
5 console.log(res.json()));
6}
PUT
برای اجرای یک درخواست PUT باید تابع put را فراخوانی کنیم. عملکرد آن دقیقاً مانند تابع post است:
1performPUT() {
2 console.log("PUT");
3 let url = `http://localhost/put`;
4 let search = new URLSearchParams();
5 search.set('foo', 'moo');
6 search.set('limit', 25);
7 this.http.put(url, {moo:"foo",goo:"loo"}, {search}).subscribe(res =>
8 console.log(res.json()));
9}
مدیریت خطاها
زمانی که یک فراخوانی Ajax را در اپلیکیشن انگولار اجرا میکنیم، ممکن است یک خطا یا استثنا در سمت سرور ایجاد شود. در این حالت، آن پیام خطا یا استثنا را به اپلیکیشن انگولار بازگشت میدهیم. بنابراین باید با شیوه دریافت آن پیام خطا یا استثنا آشنا باشیم تا بتوانیم آنها را به کاربر نمایش دهیم یا در لاگ خطا ذخیره کنیم. بنابراین چه مشغول مدیریت پاسخهای یک Observable و یا یک Promise باشیم، همواره میتوانیم تابع مدیریت خطا را به صورت پارامتر دوم روی متد http اجرا کنیم. در مورد Promise ظاهر آن به صورت زیر است:
1performGETAsPromiseError() {
2 console.log("GET AS PROMISE ERROR");
3 let url = `http://localhost/post`;
4 this.http.get(url)
5 .toPromise()
6 .then(
7 res => console.log(res.json()),
8 msg => console.error(`Error: ${msg.status} ${msg.statusText}`) ①
9 );
10}
در مورد observable نیز ظاهر آن به صورت زیر است:
1performGETAsError() {
2 console.log("GET AS OBSERVABLES ERROR");
3 let url = `http://localhost/post`;
4 this.http.get(url).subscribe(
5 res => console.log(res.json()),
6 msg => console.error(`Error: ${msg.status} ${msg.statusText}`)
7 );
8}
هدرها
یکی از بخشهای مهم دیگر در یک فراخوانی ایجکس، هدرهای HTTP هستند که در آن میتوانیم اطلاعات مختلف مرتبط با پیکربندی را به سمت سرور ارسال کنیم. هدرهای HTTP به طور طبیعی نوعی از متادیتا هستند و عموماً از سوی مرورگر به درخواست HTTP الصاق میشوند. برخی اوقات، باید برخی هدرهای اضافی را نیز به درخواست ایجکس الصاق کنیم. این کار به سادگی به کمک کلاینت http میسر است. به این منظور باید ابتدا و کلاس کمکی را از ماژول http در کامپوننت یا سرویس ایمپورت کنیم.
1import { HttpClient, HttpResponse, HttpHeaders } from '@angular/common/http';
ابتدا باید یک وهله از HttpHeaders را ایجاد کنیم تا بتوانیم هدرهای مورد اشاره را در وهله به صورت زیر تعریف کنیم:
1const httpOptions = {
2 headers: new HttpHeaders({
3 'Content-Type': 'application/json; charset=utf-8'
4 })
5 };
سپس باید httpHeader را به فعل مناسب HTTP ارسال کنیم.
1performPOST() {
2 console.log("POST");
3 let url = `http://localhost/post`;
4 this.http.post(url, {moo:"foo",goo:"loo"},httpOptions).subscribe(res =>
5 console.log(res.json()));
6 }
مثالی از API مرکزی HTTP
در این بخش با شیوه استفاده از یک فراخوانی ایجکس در انگولار آشنا خواهیم شد. به این منظور یک گردش کار کامل در رابطه با یک کارمند توسعه میدهیم تا بتوانیم یک کارمند جدید را اضافه کنیم، رکوردهای موجود را نمایش دهیم، دادهها را ویرایش کنیم و یا دادههای کارمند مورد نظر خودمان را حذف نماییم. در این مثال ساده سه کامپوننت به شرح زیر خواهیم ساخت:
- EmployeeListComponent – برای نمایش لیستی از کارمندان.
- AddEmployeeComponent – برای اضافه کردن جزییات کارمند جدید.
- UpdateEmployeeComponent - برای بهروزرسانی اطلاعات کارمندان موجود.
فایل app.component.employeelist.ts
1import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
2import { HttpClient, HttpResponse, HttpHeaders } from '@angular/common/http';
3
4@Component({
5 selector: 'employee-list',
6 templateUrl: 'app.component.employeelist.html'
7})
8
9export class EmployeeListComponent implements OnInit {
10
11 public data: any = [];
12 public showDetails: boolean = true;
13 public showEmployee: boolean = false;
14 public editEmployee: boolean = false;
15 private _selectedData: any;
16 private _deletedData: any;
17
18 constructor(private http: HttpClient) {
19 }
20
21 ngOnInit(): void {
22
23 }
24
25 ngAfterViewInit(): void {
26 this.loadData();
27 }
28
29 private loadData(): void {
30 let self = this;
31 this.http.get("http://localhost:81/SampleAPI/employee/getemployee")
32 .subscribe((res: Response) => {
33 self.data = res;
34 });
35 }
36
37 private addEmployee(): void {
38 this.showDetails = false;
39 this.showEmployee = true;
40 }
41
42 private onHide(args: boolean): void {
43 this.showDetails = !args;
44 this.showEmployee = args;
45 this.editEmployee = args;
46 this.loadData();
47 }
48
49 private onUpdateData(item: any): void {
50 this._selectedData = item;
51 this._selectedData.DOB = new Date(this._selectedData.DOB);
52 this._selectedData.DOJ = new Date(this._selectedData.DOJ);
53 this.showDetails = false;
54 this.editEmployee = true;
55 }
56
57 private onDeleteData(item: any): void {
58 this._deletedData = item;
59 if (confirm("Do you want to Delete Record Permanently?")) {
60 let self = this;
61 const httpOptions = {
62 headers: new HttpHeaders({
63 'Content-Type': 'application/json; charset=utf-8'
64 })
65 };
66 this.http.post("http://localhost:81/SampleAPI/employee/DeleteEmployee", this._deletedData, httpOptions)
67 .subscribe((res: Response) => {
68 self.loadData();
69 });
70 }
71 }
72}
- فایل app.component.employeelist.html
1<div class="panel">
2 <h3>HTTP Module Sample - Add and Fetch Data</h3>
3 <div class="panel-body container-fluid" *ngIf="showDetails">
4 <div class="row row-lg">
5 <table class="table" style="width:100%;">
6 <thead>
7 <tr>
8 <th class="cell-100">Srl No</th>
9 <th class="cell-300">Alias</th>
10 <th class="cell-300">Employee Name</th>
11 <th class="cell-300">Date of Birth</th>
12 <th class="cell-300">Join Date</th>
13 <th class="cell-300">Department</th>
14 <th class="cell-300">Designation</th>
15 <th class="cell-300">Salary</th>
16 <th class="cell-180"></th>
17 </tr>
18 </thead>
19 <tbody>
20 <tr *ngFor="let item of data">
21 <td class="cell-100">{{item.Id}}</td>
22 <td class="cell-300">{{item.Code}}</td>
23 <td class="cell-300">{{item.Name}}</td>
24 <td class="cell-300">{{item.DOB | date :'shortDate'}}</td>
25 <td class="cell-300">{{item.DOJ | date :'mediumDate'}}</td>
26 <td class="cell-300">{{item.Department}}</td>
27 <td class="cell-300">{{item.Designation}}</td>
28 <td class="cell-300">{{item.Salary |currency:'INR':true}}</td>
29 <td class="cell-180">
30 <a (click)="onUpdateData(item);" style="cursor:pointer;">
31 <span class="badge badge-primary">Edit</span>
32 </a>
33 <a (click)="onDeleteData(item);" style="cursor:pointer;">
34 <span class="badge badge-danger">Delete</span>
35 </a>
36 </td>
37 </tr>
38 </tbody>
39 </table>
40 <p>
41 <button class="btn btn-primary" (click)="addEmployee()">
42 Add Employee
43 </button>
44 </p>
45 </div>
46 </div>
47 <div class="panel-body container-fluid" *ngIf="showEmployee">
48 <employee-add (onHide)="onHide($event);"></employee-add>
49 </div>
50 <div class="panel-body container-fluid" *ngIf="editEmployee">
51 <employee-update [source]="_selectedData" (onHide)="onHide($event);"></employee-update>
52 </div>
53</div>
- فایل app.component.employeeadd.ts
1import { Component, OnInit, EventEmitter, Output } from '@angular/core';
2import { HttpClient, HttpResponse, HttpHeaders } from '@angular/common/http';
3
4@Component({
5 selector: 'employee-add',
6 templateUrl: 'app.component.employeeadd.html'
7})
8
9export class AddEmployeeComponent implements OnInit {
10
11 public _model: any = {};
12 @Output() private onHide: EventEmitter<boolean> = new EventEmitter<boolean>();
13
14 constructor(private http: HttpClient) {
15 }
16
17 ngOnInit(): void {
18
19 }
20
21 public onCancel(): void {
22 this._model = {};
23 this.onHide.emit(false);
24 }
25
26 public submit(): void {
27 if (this.validate()) {
28 let self = this;
29 const httpOptions = {
30 headers: new HttpHeaders({
31 'Content-Type': 'application/json; charset=utf-8'
32 })
33 };
34 this.http.post("http://localhost:81/SampleAPI/employee/AddEmployee", this._model, httpOptions)
35 .subscribe((res: Response) => {
36 self.onCancel();
37 });
38
39 }
40 }
41
42 private reset(): void {
43 this._model = {};
44 }
45
46 private validate(): boolean {
47 let status: boolean = true;
48 if (typeof (this._model.code) === "undefined") {
49 alert('Alias is Blank');
50 status = false;
51 return;
52 }
53 else if (typeof (this._model.name) === "undefined") {
54 alert('Name is Blank');
55 status = false;
56 return;
57 }
58 else if (typeof (this._model.dob) === "undefined") {
59 alert('dob is Blank');
60 status = false;
61 return;
62 }
63 else if (typeof (this._model.doj) === "undefined") {
64 alert('DOJ is Blank');
65 status = false;
66 return;
67 }
68 else if (typeof (this._model.department) === "undefined") {
69 alert('Department is Blank');
70 status = false;
71 return;
72 }
73 else if (typeof (this._model.designation) === "undefined") {
74 alert('Designation is Blank');
75 status = false;
76 return;
77 }
78 else if (typeof (this._model.salary) === "undefined") {
79 alert('Salary is Blank');
80 status = false;
81 return;
82 }
83 return status;
84 }
85}
- فایل app.component.employeeadd.html
1<div class="row row-lg">
2 <h4>Provide Employee Details</h4>
3 <table class="table">
4 <tr>
5 <td>Employee Code</td>
6 <td><input type="text" [(ngModel)]="_model.code" /></td>
7 </tr>
8 <tr>
9 <td>Employee Name</td>
10 <td><input type="text" [(ngModel)]="_model.name" /></td>
11 </tr>
12 <tr>
13 <td>Date of Birth</td>
14 <td><input type="date" [(ngModel)]="_model.dob" /></td>
15 </tr>
16 <tr>
17 <td>Date of Join</td>
18 <td><input type="date" [(ngModel)]="_model.doj" /></td>
19 </tr>
20 <tr>
21 <td>Department</td>
22 <td><input type="text" [(ngModel)]="_model.department" /></td>
23 </tr>
24 <tr>
25 <td>Designation</td>
26 <td><input type="text" [(ngModel)]="_model.designation" /></td>
27 </tr>
28 <tr>
29 <td>Salary</td>
30 <td><input type="number" [(ngModel)]="_model.salary" /></td>
31 </tr>
32 <tr>
33 <td></td>
34 <td>
35 <input type="button" value="Submit" (click)="submit()" />
36
37 <input type="button" value="Cancel" (click)="onCancel()" />
38 </td>
39 </tr>
40 </table>
41</div>
- app.component.employeeupdate.ts
1import { Component, OnInit, EventEmitter, Output, Input } from '@angular/core';
2import { HttpClient, HttpResponse, HttpHeaders } from '@angular/common/http';
3
4@Component({
5 selector: 'employee-update',
6 templateUrl: 'app.component.employeeupdate.html'
7})
8
9export class UpdateEmployeeComponent implements OnInit {
10
11 public _model: any = {};
12
13 @Input()
14 set source(data: any) {
15 this._model = data;
16 }
17 get source() {
18 return this._model;
19 }
20
21 @Output() private onHide: EventEmitter<boolean> = new EventEmitter<boolean>();
22
23 constructor(private http: HttpClient) {
24 }
25
26 ngOnInit(): void {
27 if (this._model == undefined) {
28 this._model = this.source;
29 }
30 }
31
32 public onCancel(): void {
33 this._model = {};
34 this.onHide.emit(false);
35 }
36
37 public onUpdate(): void {
38 if (this.validate()) {
39 let self = this;
40 const httpOptions = {
41 headers: new HttpHeaders({
42 'Content-Type': 'application/json; charset=utf-8'
43 })
44 };
45 this.http.put("http://localhost:81/SampleAPI/employee/UpdateEmployee", this._model, httpOptions)
46 .subscribe((res: Response) => {
47 self.onCancel();
48 });
49
50 }
51 }
52
53 private reset(): void {
54 this._model = {};
55 }
56
57 private validate(): boolean {
58 let status: boolean = true;
59 if (typeof (this._model.Code) === "undefined") {
60 alert('Alias is Blank');
61 status = false;
62 return;
63 }
64 else if (typeof (this._model.Name) === "undefined") {
65 alert('Name is Blank');
66 status = false;
67 return;
68 }
69 else if (typeof (this._model.Department) === "undefined") {
70 alert('Department is Blank');
71 status = false;
72 return;
73 }
74 else if (typeof (this._model.Designation) === "undefined") {
75 alert('Designation is Blank');
76 status = false;
77 return;
78 }
79 else if (typeof (this._model.Salary) === "undefined") {
80 alert('Salary is Blank');
81 status = false;
82 return;
83 }
84 return status;
85 }
86
87 private parseDate(dateString: string): Date {
88 if (dateString) {
89 return new Date(dateString);
90 } else {
91 return null;
92 }
93 }
94}
- فایل app.component.employeeupdate.html
1<div class="row row-lg">
2 <h4>Provide Employee Details</h4>
3 <table class="table">
4 <tr>
5 <td>Employee Code</td>
6 <td><input type="text" [(ngModel)]="_model.Code" /></td>
7 </tr>
8 <tr>
9 <td>Employee Name</td>
10 <td><input type="text" [(ngModel)]="_model.Name" /></td>
11 </tr>
12 <tr>
13 <td>Date of Birth</td>
14 <td><input type="text" [(ngModel)]="_model.DOB" readonly /></td>
15 </tr>
16 <tr>
17 <td>Date of Join</td>
18 <td><input type="text" [(ngModel)]="_model.DOJ" readonly /></td>
19 </tr>
20 <tr>
21 <td>Department</td>
22 <td><input type="text" [(ngModel)]="_model.Department" /></td>
23 </tr>
24 <tr>
25 <td>Designation</td>
26 <td><input type="text" [(ngModel)]="_model.Designation" /></td>
27 </tr>
28 <tr>
29 <td>Salary</td>
30 <td><input type="number" [(ngModel)]="_model.Salary" /></td>
31 </tr>
32 <tr>
33 <td></td>
34 <td>
35 <input type="button" value="Update" (click)="onUpdate()" />
36
37 <input type="button" value="Cancel" (click)="onCancel()" />
38 </td>
39 </tr>
40 </table>
41</div>
- فایل app.component.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls : ['./custom.css']
7})
8export class AppComponent implements OnInit {
9
10 ngOnInit(){
11 }
12}
- فایل app.component.html
1<div style="padding-left: 20px;padding-top: 20px; width: 1000px;">
2 <div class="row">
3 <div class="col-xs-12">
4 <employee-list></employee-list>
5 </div>
6 </div>
7</div>
- فایل app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4import { HttpClientModule } from '@angular/common/http';
5
6import { AppComponent } from './app.component';
7import { EmployeeListComponent } from './app.component.employeelist';
8import { AddEmployeeComponent } from './app.component.employeeadd';
9import { UpdateEmployeeComponent } from './app.component.employeeupdate';
10
11@NgModule({
12 declarations: [
13 AppComponent,EmployeeListComponent,AddEmployeeComponent, UpdateEmployeeComponent
14 ],
15 imports: [
16 BrowserModule,HttpClientModule, FormsModule, ReactiveFormsModule
17 ],
18 bootstrap: [AppComponent],
19 schemas: [NO_ERRORS_SCHEMA]
20})
21export class AppModule { }
با اجرای این دمو خروجی زیر در مرورگر مشاهده میشود:



به این ترتیب در این بخش از مقاله با مبحث فراخوانی Ajax در انگولار آشنا شدیم و همچنین به بررسی مفاهیم HttpClient ،Observable و Promise پرداختیم. در ادامه این مقاله آموزش انگولار به بررسی مبحث «مسیریابی» (Routing) در اپلیکیشنهای انگولار خواهیم پرداخت.
مسیریابی در انگولار
مسیریابی به فرایندی اطلاق میشود که در طی آن بستههای داده از طریق شبکهای از یک میزبان به میزبان دیگر منتقل میشوند. در چارچوب توسعه وباپلیکیشن، از مسیریابی اپلیکیشن به طور عمده برای نگاشت یک URL خاص به یک شیء خاص بهره گرفته میشود. به این ترتیب مسیریابی به طور عمده برای دسترسی به یک بخش خاص از UI اپلیکیشن مورد استفاده قرار میگیرد.
در گذشته، وباپلیکیشنهای ابتدایی در مقایسه با وباپلیکیشنهای مدرن از رویکرد متفاوتی برای مسیریابی استفاده میکردند. در آن رویکرد قدیمی به طور طبیعی از رویکرد مسیریابی سمت سرور استفاده میشد. در این رویکرد سرور markup مرتبط با HTML را ایجاد میکرد و به کلاینت یا مرورگر ارسال میکرد. در ادامه کاربران برخی دادهها را در فرم پر میکردند و دادهها را مجدداً به صورت یک پاسخ به سرور ارسال میکردند. اما امروزه فریمورک جاوا اسکریپت یا فریمورک سمت کلاینت در قیاس با گذشته پیشرفت زیادی کرده است.
در حال حاضر برای تولید دادههای markup مربوط به HTML تنها به سرور وابسته نیستیم، بلکه میتوانیم آن را در سمت کلاینت ایجاد کرده و به مرورگر ارسال کنیم. به این ترتیب تنها کافی است که بخش دادهای را به سرور ارسال کنیم. این نوع از مفهوم مسیریابی به طور معمول مسیریابی سمت کلاینت نامیده میشود که در ادامه آن را به صورت اجمالی بررسی میکنیم.
مقدمهای بر مسیریابی سمت کلاینت
هر وباپلیکیشن همواره شامل یک URL است که به طور معمول در نوار آدرس مرورگر نمایش مییابد. این URL اساساً حالت کنونی اپلیکیشن را تعریف میکند. همچنان که میدانیم منظور از «حالت» (State) همه اطلاعات ذخیره شده در یک زمان خاص است که برنامه یا اپلیکیشن میتواند به آنها دسترسی داشته باشد. بنابراین حالت اپلیکیشن به بیان ساده به مقدار کنونی همه متغیرهای آن اپلیکیشن گفته میشود. آدرس URL نمیتواند حجم بالایی از اطلاعات را در خود نگهداری کند، اما با استفاده از URL میتوانیم حالت یکسانی را برای کاربران مختلف در مرورگر ایجاد کنیم.
چنان که در بخش قبل اشاره کردیم، در اپلیکیشنهای قدیمی معمولاً از مسیریابی سمت سرور استفاده میشد. در این فرایند، مرورگر یک درخواست به سرور ارسال کرده و دادههای markup مربوط به HTML که بازگشت مییافت را نمایش میداد. تصویر زیر این فرایند را به نمایش گذاشته است.
همان طور که گفتیم امروزه از مسیریابی سمت کلاینت در اپلیکیشنهای مدرن استفاده میشود. در این فرایند زمانی که URL در نوار آدرس مرورگر تغییر مییابد، اپلیکیشن لوکال که در مرورگر اجرا میشود (کلاینت) باید تغییرها را مدیریت کند. ما نمیخواهیم درخواست را به سرور ارسال کنیم. زمانی که به وبسایت جدیدی میرویم، سرور فایلهای HTML، جاوا اسکریپت و CSS مورد نیاز برای رندر اپلیکیشن در آن صفحه را بازگشت میدهد. در ادامه همه تغییرهای بعدی در URL به صورت لوکال از سوی اپلیکیشن کلاینت مدیریت میشوند. اپلیکیشن کلاینت برای بازیابی اطلاعات مرتبط با UI بعدی که باید نمایش یابد، یک یا چند درخواست API به سرور میزند و سپس تغییرهای بعدی روی صفحه همگی از سوی کلاینت مدیریت خواهد شد. به همین جهت است که این نوع از اپلیکیشنها به نام «اپلیکیشن تکصفحهای» (Single Page Application) یا به اختصار SPA نامیده میشوند.
مزیت اصلی SPA به شرح زیر است:
- این اپلیکیشنها بسیار سریعتر هستند، چون به جای این که با ایجاد هر تغییر یک درخواست زمانبر به سرور ریموت بزنیم، اپلیکیشن کلاینت خودش صفحه را با سرعت بسیار بالاتری به صورت لوکال بهروزرسانی میکند.
- ترافیک شبکه و پهنای باند کمتری مصرف میشود. در این حالت نیازی به ارسال صفحههای HTML سنگین با هر درخواست تغییر وجود ندارد. به جای آن کافی است یک API کوچکتر را فراخوانی کنیم که صرفاً دادههای مورد نیاز برای رندر تغییر مورد نظر در صفحه را ارسال میکند.
- SPA سهولت بیشتری نیز به همراه دارد، زیرا توسعهدهنده میتواند اغلب کارکردهای مورد نیاز سایت را خودش بنویسد و دیگر نیازی به تقسیم وظایف بین توسعهدهنده فرانتاند و بکاند یا سمت سرور وجود ندارد.
ما با استفاده از ماژولهای مختلف انگولار میتوانیم وباپلیکیشن خود را به صورت SPA پیادهسازی کنیم. به این منظور انگولار همواره از یک مفهوم بنیادین به نام «روتر کامپوننت» پیروی میکند.
اشیای تعریف مسیر
پیش از آن که به بررسی ماژولهای روتر انگولار بپردازیم، ابتدا باید «اشیای تعریف مسیر» (route definition objects) را بررسی کنیم. برای تعریف کردن اشیای تعریف مسیر باید نوع مسیر را تعریف کنیم که اساساً آرایهای از مسیرها است که مسیریابی را برای اپلیکیشن کلاینت تعریف میکند. درون این آرایه میتوانیم تنظیمات مربوط به راههای مورد انتظار و همچنین کامپوننتهای مرتبط که قرار است با استفاده از مسیر باز کرده یا مورد استفاده قرار دهیم را نگهداری کنیم. هر مسیر میتواند با خصوصیتهای مختلف تعریف شود. برخی از خصوصیتهای رایج و پرکاربرد به شرح زیر هستند:
- Path – این خصوصیت برای اشاره به آن URL استفاده میشود که در زمان ریدایرکت روی مسیر خاص در مرورگر نمایش مییابد.
- Component – این خصوصیت برای اشاره به نام کامپوننتی استفاده میشود که وقتی اپلیکیشن روی مسیر خاصی است، رندر خواهد شد.
- redirectTo – این خصوصیت یک خصوصیت اختیاری است و باید زمانی استفاده شود که میخواهیم در صورت مفقود بودن مسیر اصلی به یک مسیر خاص ریدایرکت کنیم. مقدار این خصوصیت یا نام کامپوننت و یا خصوصیت redirect تعریف شده در مسیر خواهد بود.
- pathMatch – این نیز یک خصوصیت اختیاری است که مقدار پیشفرض آن defining است. این خصوصیت تعیین میکند که آیا با URL-های کامل مطابقت داشته باشیم یا صرفاً با ابتدای آنها تطبیق بدهیم. زمانی که یک مسیر با رشته path خالی تعریف میکنیم باید مقدار pathMatch را روی full قرار دهیم، در غیر این صورت با همه path-ها تطبیق پیدا میکند.
- Children – این خصوصیت شامل آرایهای از اشیای تعاریف مسیر است که مسیرهای فرزند این مسیر را نمایش میدهد.
برای استفاده از مسیرها باید یک آرایه از پیکربندیهای مسیر به صورت زیر تعریف کنیم:
1const routes: Routes = [
2 { path: 'component-one', component: ComponentOne },
3 { path: 'component-two', component: ComponentTwo }
4];
مفهوم ماژولهای مسیر
برای پیادهسازی مسیرها در هر اپلیکیشن باید ماژول مسیر (RouterModule) را در به کمک NgModules در اپلیکیشن ایمپورت کنیم. RouterModule.forRoot آرایه Routes را به عنوان یک آرگومان میگیرد و ماژول مسیر پیکربندیشده را بازگشت میدهد. مثال زیر شیوه ایمپورت کردن این ماژول را در فایل app.routes.ts نشان میدهد.
1import { RouterModule, Routes } from '@angular/router';
2
3const routes: Routes = [
4 { path: 'component-one', component: ComponentOne },
5 { path: 'component-two', component: ComponentTwo }
6];
7
8export const routing = RouterModule.forRoot(routes);
پس از تعریف کردن پیکربندی مسیر، باید پیکربندی روتر را در فایلهای ماژول اپلیکیشن به صورت زیر ایمپورت کنیم:
1import { routing } from './app.routes';
2
3@NgModule({
4 imports: [ BrowserModule,routing],
5 declarations: [AppComponent,ComponentOne,ComponentTwo],
6 bootstrap: [ AppComponent ]
7})
8
9export class AppModule {
10}
زمانی که اپلیکیشن شروع به کار میکند، به صورت پیشفرض به مسیر خالی میرود. بنابراین میتوانیم روتر را طوری پیکربندی کنیم که به صورت پیشفرض به مسیر مورد اشاره ریدایرکت شود.
1export const routes: Routes = [
2 { path: '', redirectTo: 'component-one', pathMatch: 'full' },
3 { path: 'component-one', component: ComponentOne },
4 { path: 'component-two', component: ComponentTwo }
5];
مشخصه pathMatch که به طور عمده برای ریدایرکتها مورد نیاز است، به روتر نشان میدهد که چطور با URL ارائه شده تطبیق پیدا کند تا به مسیر معینشده ریدایرکت شود. از آنجا که از مقدار pathMatch: full استفاده شده است، روتر در حالتی که کل URL با '' تطبیق پیدا کند به component-one ریدایرکت میشود.
به این ترتیب زمانی که اپلیکیشن آغاز شود، سیستم روتر به صورت خودکار کامپوننت به نام component-one را در ابتدای مسیر پیشفرض بارگذاری میکند.
لینک روتر
امکان کنترل ناوبری درون اپلیکیشن با استفاده از دایرکتیو «لینک روتر» (routerLink) در قالب HTML به صورت زیر وجود دارد:
1<nav class="navbar navbar-light bg-faded">
2 <a class="navbar-brand" [routerLink]="['component-one']">Component 1</a>
3 <ul class="nav navbar-nav">
4 <li class="nav-item active">
5 <a class="nav-link" [routerLink]="['']">Home</a>
6 </li>
7 <li class="nav-item">
8 <a class="nav-link" [routerLink]="['component-two']">Component 2</a>
9 </li>
10 </ul>
11</nav>
به طور جایگزین میتوانیم به فراخوانی تابع navigate روی روتر به یک مسیر خاص برویم:
1this.router.navigate(['/component-one']);
یکی از مهمترین خصوصیات هر کامپوننت ناوبری این است که بازخوردی در مورد این که هماینک کدام آیتم لینک در حال نمایش است به کاربر بدهد. به عبارت دیگر باید به کاربر نشان دهیم که اکنون کدام مسیر فعال است. انگولار به منظور کمک به حذف و اضافه کردن کلاسها بسته به مسیری که هماینک فعال است، یک دایرکتیو به نام routerLinkActive ارائه کرده است. دایرکتیو routerLinkActive از طریق یک دایرکتیو routerLink با مسیر مرتبط است. این دایرکتیو یک آرایه از کلاسها به عنوان ورودی میگیرد که در صورت فعال بودن مسیر به عنصری که به آن الصاق یافته است، اضافه میکند. به مثال زیر توجه کنید:
1<a class="nav-link"
2 [routerLink]="['home']"
3 [routerLinkActive]="['active']">Home</a>
افزودن کامپوننت مسیر
با این که هر کامپوننت مسیر به صورت مجزا تعریف میشود؛ اما میتوانیم از دایرکتیوهای RouterOutlet استفاده کنیم که اساساً به عنوان یک placeholder کامپوننت در اپلیکیشن عمل میکنند. انگولار به صورت دینامیک کامپوننتها را برای مسیر فعال درون عنصر <router-outlet></router-outlet> تزریق میکند.
1<router-outlet></router-outlet>
در مثال فوق، کامپوننت مرتبط با مسیر مشخصشده، در زمانی که روی لینک روتر کلیک کردیم، پس از عنصر <router-outlet></router-outlet> فعال میشود.
مثالی از روتر
برای نمایش مثالی که بیانگر مفهوم روتر در انگولار باشد، باید ابتدا 3 کامپوننت بنویسیم که مستقل از هم باشند و به عنوان کامپوننت مسیر استفاده شوند. به این منظور ابتدا کامپوننتهای زیر را با اسامی MobileComponent، TVComponent ،ComputerComponent و HomeComponent میسازیم.
- فایل app.component.tv.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-tv',
5 templateUrl: 'app.component.tv.html'
6})
7
8export class TvComponent implements OnInit {
9
10 private data: Array<any> = [];
11
12 constructor() {
13 this.data = [{ name: 'LED TV 20"', company: 'Samsung', quantity: '10', price: '11000.00' },
14 { name: 'LED TV 24"', company: 'Samsung', quantity: '50', price: '15000.00' },
15 { name: 'LED TV 32"', company: 'LG', quantity: '10', price: '32000.00' },
16 { name: 'LED TV 48"', company: 'SONY', quantity: '25', price: '28000.00' }];
17 }
18
19 ngOnInit(): void {
20 }
21}
- فایل app.component.tv.html
1<div class="panel-body">
2 <table class="table table-striped table-bordered">
3 <thead>
4 <tr>
5 <th>Name</th>
6 <th>Company</th>
7 <th class="text-right">Quantity</th>
8 <th class="text-right">Price</th>
9 </tr>
10 </thead>
11 <tbody>
12 <tr *ngFor="let item of data">
13 <td>{{item.name}}</td>
14 <td>{{item.company}}</td>
15 <td class="text-right">{{item.quantity}}</td>
16 <td class="text-right">{{item.price | currency}}</td>
17 </tr>
18 </tbody>
19 </table>
20</div>
- فایل app.component.mobile.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'app-mobile',
5 templateUrl: 'app.component.mobile.html'
6})
7
8export class MobileComponent implements OnInit {
9
10 private data: Array<any> = [];
11
12 constructor() {
13 this.data = [{ name: 'Galaxy Tab 3', company: 'Samsung', quantity: '10', price: '25000.00' },
14 { name: 'Galaxy Tab 5', company: 'Samsung', quantity: '50', price: '55000.00' },
15 { name: 'G4', company: 'LG', quantity: '10', price: '40000.00' },
16 { name: 'Canvas 3', company: 'Micromax', quantity: '25', price: '18000.00' }];
17 }
18
19 ngOnInit(): void {
20 }
21}
- فایل app.component.mobile.html
1<div class="panel-body">
2 <table class="table table-striped table-bordered">
3 <thead>
4 <tr>
5 <th>Name</th>
6 <th>Company</th>
7 <th class="text-right">Quantity</th>
8 <th class="text-right">Price</th>
9 </tr>
10 </thead>
11 <tbody>
12 <tr *ngFor="let item of data">
13 <td>{{item.name}}</td>
14 <td>{{item.company}}</td>
15 <td class="text-right">{{item.quantity}}</td>
16 <td class="text-right">{{item.price |currency:'INR':true}}</td>
17 </tr>
18 </tbody>
19 </table>
20</div>
- فایل app.component.computer.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'computer',
5 templateUrl: 'app.component.computer.html'
6})
7
8export class ComputerComponent implements OnInit {
9
10 private data: Array<any> = [];
11
12 constructor() {
13 this.data = [{ name: 'HP Pavilion 15"', company: 'HP', quantity: '10', price: '42000.00', specification: 'Intel Core i3 2 GB Ram 500 GB HDD with Windows 10' },
14 { name: 'Lenovo Flex 2"', company: 'Lenovo', quantity: '20', price: '32000.00', specification: 'Intel Core i3 2 GB Ram 500 GB HDD with DOS OS' },
15 { name: 'Lenovo Yova 500"', company: 'Lenovo', quantity: '20', price: '70000.00', specification: 'Intel Core i7 8 GB Ram 1TB HDD with Windows 8.1' }]
16 }
17
18 ngOnInit(): void {
19 }
20}
- فایل app.component.computer.html
1<div class="panel-body">
2 <table class="table table-striped table-bordered">
3 <thead>
4 <tr>
5 <th>Name</th>
6 <th>Company</th>
7 <th class="text-right">Quantity</th>
8 <th class="text-right">Price</th>
9 </tr>
10 </thead>
11 <tbody>
12 <tr *ngFor="let item of data">
13 <td>{{item.name}}</td>
14 <td>{{item.company}}</td>
15 <td class="text-right">{{item.quantity}}</td>
16 <td class="text-right">{{item.price | currency}}</td>
17 </tr>
18 </tbody>
19 </table>
20</div>
- فایل app.component.home.ts
1import { Component, OnInit } from '@angular/core';
2
3@Component({
4 selector: 'home',
5 templateUrl: 'app.component.home.html'
6})
7
8export class HomeComponent implements OnInit {
9
10 private message: string = '';
11 constructor() {
12 this.message = 'Click link to move other pages';
13 }
14
15 ngOnInit(): void {
16 }
17}
- فایل app.component.home.html
1<div class="row">
2 <div class="panel-body">
3 Home Page
4 <br />
5 <h3 class="panel-heading"><span>{{message}}</span></h3>
6 </div>
7</div>
اکنون باید لینک روتر را در کامپوننت app-root تعریف کنیم تا بتوانیم بین کامپوننتهای مسیر مختلف ناوبری کنیم.
- فایل app.component.ts
1import { Component } from '@angular/core';
2
3@Component({
4 selector: 'app-root',
5 templateUrl: './app.component.html',
6 styleUrls: ['./app.component.css']
7})
8export class AppComponent {
9 title = 'Ang8RouteDemo';
10}
- فایل app.component.html
1<div class="toolbar" role="banner">
2 <img
3 width="40"
4 alt="Angular Logo"
5 src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ij
6AgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2
7My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJ
8GIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCA
9gZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJI
10MTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
11 />
12 <span>Welcome !! Angular 8 Route Demo</span>
13 <div class="spacer"></div>
14 <a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
15
16 <svg id="twitter-logo" height="24" data-name="Logo — FIXED" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
17 <defs>
18 <style>
19 .cls-1 {
20 fill: none;
21 }
22
23 .cls-2 {
24 fill: #ffffff;
25 }
26 </style>
27 </defs>
28 <rect class="cls-1" width="400" height="400" />
29 <path class="cls-2" d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23"
30 />
31 </svg>
32
33 </a>
34</div>
35
36<div class="content" role="main">
37 <span><h2>{{ title }} app is running!</h2></span>
38 <table style="width:40%;">
39 <tr class="table-bordered">
40 <td><a routerLink="/home" class="btn-block" routerLinkActive="['active']">Home</a></td>
41 <td><a routerLink="/mobile">Mobile</a></td>
42 <td><a routerLink="/tv">TV</a></td>
43 <td><a routerLink="/computer">Computers</a></td>
44 </tr>
45 </table>
46 <br/>
47 <div class="spacer">
48 <router-outlet></router-outlet>
49 </div>
50</div>
- فایل app-routing.module.ts
1import { NgModule } from '@angular/core';
2import { Routes, RouterModule } from '@angular/router';
3import { HomeComponent } from './app.component.home';
4import { MobileComponent } from './app.component.mobile';
5import { TvComponent } from './app.component.tv';
6import { ComputerComponent } from './app.component.computer';
7
8const routes: Routes = [
9 { path: '', redirectTo: 'home', pathMatch: 'full' },
10 { path: 'home', component: HomeComponent },
11 { path: 'tv', component: TvComponent },
12 { path: 'mobile', component: MobileComponent },
13 { path: 'computer', component: ComputerComponent }
14];
15
16@NgModule({
17 imports: [RouterModule.forRoot(routes)],
18 exports: [RouterModule]
19})
20export class AppRoutingModule { }
- فایل app.module.ts
1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3
4import { AppRoutingModule } from './app-routing.module';
5import { AppComponent } from './app.component';
6import { HomeComponent } from './app.component.home';
7import { MobileComponent } from './app.component.mobile';
8import { TvComponent } from './app.component.tv';
9import { ComputerComponent } from './app.component.computer';
10
11@NgModule({
12 declarations: [
13 AppComponent, HomeComponent, MobileComponent, TvComponent, ComputerComponent
14 ],
15 imports: [
16 BrowserModule,
17 AppRoutingModule
18 ],
19 providers: [],
20 bootstrap: [AppComponent]
21})
22export class AppModule { }
اینک با اجرای این مثال، خروجی زیر را در مرورگر مشاهده میکنیم:
به این ترتیب و با معرفی مفهوم مسیریابی و شیوههای مختلف استفاده از روتر در انگولار به انتهای این مقاله آموزش انگولار میرسیم.
سخن پایانی
انگولار یک پلتفرم برای ساخت اپلیکیشنهای تکصفحهای برای موبایل و دسکتاپ است. این فریمورک از تایپ اسکریپت و HTML برای ساخت اپلیکیشنها بهره میگیرد. خود انگولار با استفاده از تایپ اسکریپت نوشته شده است و به همه امکانات مورد نیاز برای ساخت وباپلیکیشنها یا اپلیکیشنهای موبایل پیچیده و بزرگ از جمله کامپوننتها، دایرکتیوها، فرمها، پایپها، سرویسها، تزریق وابستگی و غیره مجهز است. در این مقاله همه این مفاهیم را به زبانی ساده و همراه با طرح مثالهای عملی مورد بررسی قرار دادیم.
سلام جسارتا یک سوال این تمی که کدها توش نوشته شده اسمش چیه
تم دارک
عالی بود و در بخش های به نکات پیشرفته ای اشاره شد که فراتر از انتظار من در این آموزش بود که قابل ستایش است
با تشکر از شما
من میخواستم یه مرور کلی داشته باشم ولی مطلب بسیار کاملتر چیزی بود که انتظار داشتم
خسته نباشید واقعا
بسیار عالی و کاربردی
ممنون بابت پست عالی و مفیدتون.
موفق و پاینده باشین
با سلام و تشکر فراوان بابت این آموزش مفید . در مورد مثالهایی که زده شده بخاطر آدرس backend ما نمیتونیم همین کد رو توی انگولار پیاده سازی کنیم و کارکردش رو ببینیم میشه راهنمایی کنید؟
mamnoon babate poste toon
ama behtare bejaye kalamate farsi az khodet kalamate asli estefade konid mesle:
“اتصال مشخصه”
ke aslan vaazeh nist va kheili tekrar shode