استفاده از وب کامپوننت در انگولار — به زبان ساده
اگر توسعهدهنده جاوا اسکریپت باشید، احتمالاً در مورد کامپوننتهای وب چیزهایی شنیدهاید. در صورتی که با آنها آشنا نیستید، باید بگوییم که وب کامپوننت در انگولار امکان تعریف کامپوننتهای HTML سفارشی با استفاده از جاوا اسکریپت را فراهم میسازند. آیا تاکنون خواستهاید یک تگ <my-cat /> ایجاد کنید که محتوایش درون «گربههای در حال حرکت» قرار بگیرد؟ با استفاده از کامپوننتهای وب این کار امکانپذیر است و روی هر مرورگری که از این فریمورک استفاده کند یا نکند، در دسترس خواهد بود.
ایده توسعه وب بر مبنای کامپوننت چیز جدیدی نیست. در واقع قدمت آن به اندازه خود وب است. این گزاره که به جای ایجاد کدهای تکراری HTML برای هر سایت، کامپوننتهایی داشته باشیم که به عنوان بلوکهای با قابلیت استفاده مجدد باشند، همواره مورد استقبال قرار گرفته است.
تاریخچه مختصر کامپوننتها در توسعه وب
نخستین استفاده گسترده از کامپوننتها برای وباپلیکیشنها در نرمافزار شرکت NeXT به نام WebObjects بود که در سال 1996 معرفی شد. چند سال بعد جاوا اسکریپت و به همراه آن فرمهای وب ASP.NET بازار را تصاحب کردند. با این که همه این فریمورکها کدهای HTML سمت سرور میساختند و به مرورگر ارسال میکردند، اما کاربران فریمورکهای مدرن فرانتاند مفاهیم مطرح شده در WebObjects ،JSF و Web Forms را بسیار آشنا میدانند.
در نهایت با تکامل مرورگرها و افزایش قابلیتهای موتورهای جاوا اسکریپت توسعهدهندگان شروع به ساخت اپلیکیشنهای تکصفحهای (SPA) کردند. بدین ترتیب به جای ایجاد کدهای HTML سمت سرور، توسعهدهندگان کدهای سمت کلاینت را در مرورگر مینویسند. یکی از نخستین فریمورکهای سمت کلاینت مبتنی بر کامپوننت که در سال 2006 معرفی شد Google Web Toolkit نام داشت که عموماً به اختصار GWT نامیده میشد. GWT مجموعهای از چند چیز بود که در یک جا گردآوری شده بودند یعنی یک فریمورک مبتنی بر کامپوننت و همچنین کامپایلر جاوا به جاوا اسکریپت بود.
در سال 2010 فریمورکهای با محبوبیت بالاتری معرفی شدند که شامل BackboneJS و KnockoutJS بودند. برخلاف GWT اینها فریمورکهای جاوا اسکریپت خالص بودند. در سال 2012 AngularJS معرفی شد و این زمانی بود که محبوبیت فریمورکهای سمت کلاینت بسیار افزایش یافته بود. در طی چند سال React و Vue نیز ظاهر شدند و برای توسعهدهندگان زیادی SPA مبتنی بر کامپوننت به روش پیشفرض توسعه وباپلیکیشنها تبدیل شد.
البته همه این فریمورکهای سمت کلاینت یک عیب دارند و آن این است که کامپوننتهای ایجاد شده در یک فریمورک نمیتوانند به سادگی در فریمورکهای دیگر مورد استفاده قرار گیرند. در اغلب موارد این مشکل چندان حاد نیست، چون هر شرکتی از یک فریمورک استفاده میکند و معمولاً آن فریمورک برای همه اپلیکیشنهای آن شرکت مورد استفاده قرار میگیرد. اما برای شرکتها، پروژههای اوپنسورس و افرادی که میخواهند کتابخانههای کامپوننت فرانتاند با قابلیت استفاده مجدد بسازند، طیف گسترده فریمورکهای محبوب چالش عمدهای محسوب میشود. چگونه میتوان کامپوننتهایی طراحی کرد که هر کس بدون نیاز به تکرار کد برای هر فریمورکی که میخواهد مورد استفاده قرار دهد؟
کامپوننتهای وب راهحل مشکل فوق هستند. کامپوننتهای وب روش بومی مرورگر برای تعریف عناصر HTML سفارشی هستند که میتوانند در هر فریمورکی مورد استفاده قرار گیرند. حتی میتوان از آنها بدون بهرهگیری از هر فریمورکی نیز استفاده کرد. در این مقاله به بررسی روش استفاده از کامپوننتهای وب در انگولار 8 میپردازیم. در آغاز روش ایجاد چند کامپوننت وب را معرفی خواهیم کرد.
پیشنیازها
در ادامه این مقاله فرض ما بر این است که شما با توسعه انگولار مدرن آشنا هستید. ما از انگولار 8 استفاده میکنیم، اما اگر شما با انگولار نسخههای 4 به بالا آشنا باشید، مشکلی در پیگیری این راهنما نخواهید داشت.
همچنین فرض میکنیم که در مورد طرز کار کامپوننتهای وب اندک مطالعهای داشتهاید. گرچه ما کامپوننتهای وب خودمان را برای استفاده در اپلیکیشنهای انگولار میسازیم، اما فرصت معرفی عمیق کامپوننتهای وب را نخواهیم داشت.
خوشبختانه یک مرجع اختصاصی برای معرفی کامپوننتهای وب (+) ارائه شده است که میتوانید با مبانی ایجاد و استفاده از کامپوننتهای وب آشنا شوید. برخلاف اغلب وبسایتهای توضیح مشخصات نرمافزار خواندن این مرجع آسان است و همه چیز را به زبان ساده توضیح داده و مثالهای زیادی از کدها ارائه کرده است.
ایجاد کامپوننتهای وب
پیش از آن که بتوانیم کامپوننتهای وب را در اپلیکیشنهای انگولار خود قرار دهیم، باید آنها را ایجاد کنیم. با این که میتوانیم آنها را از یک کتابخانه کامپوننت موجود ایمپورت کنیم، اما بهتر است آن را خودمان بسازیم تا آشنایی بیشتری پیدا کنیم.
در ادامه قصد داریم دو کامپوننت ساده شمارنده بسازیم. یکی کامپوننت دستوری (imperative) خواهد بود. این کامپوننت یک API شیءگرا خواهد داشت که برای فراخوانی افزایش یا کاهش مقدار یک شمارنده مورد استفاده قرار میگیرد.
کامپوننت دوم یک کامپوننت شمارنده اعلانی خواهد بود. در این کامپوننت مقدار شمارنده از طریق یک خصوصیت ارسال میشود. این کامپوننت به این مقدار بیرونی نیاز دارد و نمیتواند با ارسال مقدار شمارنده آن را کاهش یا افزایش دهد. با این که این شمارنده اعلانی چندان کارآمد به نظر نمیرسد، اما کامپوننتهای اعلانی در مواردی که کامپوننتها صرفاً مسئول نمایش دادههای ارسالی هستند، بسیار مفید محسوب میشوند. Wijmo Gauge (+) مثال خوبی از این نوع کامپوننتها محسوب میشود.
اکنون به بررسی کد کامپوننت سفارشی خود میپردازیم. برای این که پیگیری این تمرین آسان باشد همه کد را در این صفحه (+) قرار دادهایم که میتواند در مرورگر مشاهده و اجرا کنید. در آغاز به بررسی فایل زیر که در پوشه src/app/web-components قرار دارد میپردازیم.
فایل ImperativeCounter.ts
1class ImperativeCounter extends HTMLElement {
2private readonly shadow: ShadowRoot;
3private currentCount: number = 0;
4constructor() {
5super();
6this.shadow = this.attachShadow({ mode: 'open'});
7this.update();
8}
9update() {
10const template = `
11<style>
12.counter {
13font-size: 25px;
14}
15</style>
16<div class="counter">
17<b>Count:</b> ${this.currentCount}
18</div>
19`;
20this.shadow.innerHTML = template;
21}
22increment(){
23this.currentCount++;
24this.update();
25}
26decrement() {
27this.currentCount--;
28this.update();
29}
30}
31window.customElements.define('i-counter', ImperativeCounter);
این کامپوننت نیز مانند هر کامپوننت وب دیگر با بسط دادن HTMLElement آغاز میشود. ما باید حتماً این کار را بکنیم، چون در غیر این صورت مرورگر امکان ثبت کامپوننت را به ما نمیدهد. سپس دو متغیر وهلهای میسازیم که یکی shadow نام دارد و DOM سایه کامپوننت وب را نگهداری میکند و دیگری currentCount نام دارد که مقدار کنونی شمارنده در آن ذخیره میشود. سپس سازندههای ما DOM سایه را ساخته و در shadow ذخیره میکنند. سازنده کار خود را با فراخوانی متد update به پایان میبرد.
در update یک قالب HTML سفارشی برای عنصری که شامل مقدار currentCount است تعریف میکنیم. سپس رشته قالب را به مشخصه innerHTML مربوط به DOM سایه انتساب دهیم. کلاس شمارنده دستوری ما با تعریف متدهای افزایشی و کاهشی به پایان میرسد. این متدها به سادگی مقدار currentCount را کاهش و افزایش میدهند و سپس update را فرا میخوانند تا مطمئن شوند که مقدار جدید currentCount در HTML کامپوننت نمایش پیدا میکند.
در بیرون از اعلان کلاس، window.customElements.define را برای ثبت کامپوننت جدید خود در مرورگر فرا میخوانیم. بدون این مرحله هیچ کس نمیتواند از کامپوننت شمارنده استفاده کند. اینک به بررسی کامپوننت شمارنده اعلانی میپردازیم. توجه کنید که این کامپوننت شبیه شمارنده دستوری است:
1class DeclarativeCounter extends HTMLElement {
2private readonly shadow: ShadowRoot;
3private currentCount: number = 0;
4constructor() {
5super();
6this.shadow = this.attachShadow({ mode: 'open'});
7}
8static get observedAttributes() {
9return ['count']
10}
11connectedCallback() {
12this.currentCount = parseInt(this.getAttribute('count')) || 0;
13this.update();
14}
15attributeChangedCallback(attrName, oldVal, newVal) {
16this.currentCount = newVal;
17this.update();
18}
19update() {
20const template = `
21<style>
22.counter {
23font-size: 25px;
24}
25</style>
26<div class="counter">
27<b>Count:</b> ${this.currentCount}
28`;
29this.shadow.innerHTML = template;
30}
31}
32window.customElements.define('d-counter', DeclarativeCounter);
همانند شمارنده دستوری کار خود را با تعریف کردن متغیرهای وهلهای برای root مربوط به DOM سایه و مقدار شمارنده کنونی آغاز میکنیم. همان طور که میبینید، سازنده مشابه است، اما دیگر متد update را فراخوانی نمیکنیم. دلیل این امر آن است که میخواهیم ببینیم آیا مقدار از طریق خصوصیت count به کامپوننت ارسال شده است یا نه. این مقدار در زمان فراخوانی سازنده کامپوننت هنوز موجود نیست. به جای آن از یک متد چرخه عمر کامپوننت وب به نام connectedCallback استفاده میکنیم.
این متد پس از این که کامپوننت در DOM قرار گرفت فراخوانی میشود. اگر به متد connectedCallback کامپوننت نگاه کنیم، میبینیم که مقدار خصوصیت count کامپوننت را خوانده و از آن برای تعیین مقدار currentCount استفاده میکند. سپس update را فراخوانی میکند تا کامپوننت را رندر کند.
با نگاه کردن به کامپوننت میبینیم که دو چیز دیگر با شمارنده دستوری متفاوت هستند. یکی مشخصه استاتیک به نام observedAttributes و دیگری متد attributeChangedCallback است. هر دو این موارد بخشی از API کامپوننتهای وب محسوب میشوند.
observedAttributes فهرستی از نامهای خصوصیتها در اختیار مرورگر قرار میدهد که کامپوننت میخواهد در زمان تغییر یافتن نوتیفیکیشنهایی دریافت میکند. این حالت مفید است، زیرا کاربران نهایی کامپوننت میتوانند هر تعداد خصوصیتی که میخواهند اضافه کنند. در صورتی که قرار باشد مرورگر در مورد هر تغییر کامپوننتی که استفاده نمیشود، نوتیفیکیشن ارسال کند، هدر رفت منابع محسوب میشود.
متد attributeChangedCallback هر زمان که یکی از خصوصیتهای فهرست شده در attributeChangedCallback تغییر یابند، از سوی مرورگر ارسال میشود. در این مثال count تنها خصوصیتی است که میخواهیم نوتیفیکیشنهایی در مورد آن دریافت کنیم. از این رو زمانی که متد فراخوانی میشود، آن را با مقدار جدید بهروزرسانی کرده و سپس update را برای رندر مجدد کامپوننت فرا میخوانیم.
استفاده از کامپوننتها در انگولار
اکنون که کامپوننتهایمان را ساختهایم، زمان آن فرا رسیده است که از آنها در انگولار استفاده کنیم. پس از همه کارهایی که تاکنون انجام دادیم، استفاده از کامپوننتهای وب در انگولار باید کار آسانی باشد. در پروژه StackBlitz (+) فایل app.module.ts را باز کنید. باید با کدی مانند زیر مواجه شوید:
1import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2import { BrowserModule } from '@angular/platform-browser';
3import { FormsModule } from '@angular/forms';
4import { AppComponent } from './app.component';
5@NgModule({
6imports: [ BrowserModule, FormsModule ],
7declarations: [ AppComponent ],
8bootstrap: [ AppComponent ],
9schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
10})
11export class AppModule { }
در مجموع بسیار شبیه به ماژول اپلیکیشن مقدماتی است که Angular CLI تولید میکند. اما چند تفاوت مهم وجود دارد که در ادامه بررسی میکنیم. نخستین نکتهای که باید توجه کنیم این است که CUSTOM_ELEMENTS_SCHEMA را از angular/core@ ایمپورت میکنیم. انگولار از شِماها برای تعیین نام عناصری که درون ماژول مجاز هستند استفادهی کند. ایمپورت کردن یک شِمای عناصر سفارشی ضروری است، زیرا بدون وجود آن کامپایلر قالب انگولار زمانی که با نام یک عنصر که نمیشناسد مواجه شود، خطایی ایجاد میکند.
ضمناً توجه کنید که یک مشخصه شِما به دکوراتور NgModule اضافه کردهایم تا به انگولار اعلام کنیم که از شِمای عناصر سفارشی در ماژول اپلیکیشن بهره بگیرد. سپس در فایل main.ts میبینیم که دو ایمپورت اضافه شده است:
1import "./app/web-components/ImperativeCounter.ts";
2import "./app/web-components/DeclarativeCounter.ts";
این خطوط کامپوننتهای شمارنده را که ایجاد کردیم ایمپورت میکنند و آنها را در مرورگر ثبت مینمایند. این گام مهمی است، زیرا بدون وجود آن نمیتوانیم از کامپوننتها در اپلیکیشن انگولار خود استفاده کنیم. در ادامه به بررسی فایل app.component.html میپردازیم تا ببینیم شیوه استفاده از کامپوننتهای وب چگونه است؟
1<h5>Imperative Counter</h5>
2<div class="counter-box">
3<i-counter #iCounter></i-counter>
4</div>
5<h5>Declarative Counter</h5>
6<div class="counter-box">
7<d-counter [count]="count" #dCounter></d-counter>
8</div>
9<button (click)="increment()" class="btn btn-primary">Increment</button>
10<button (click)="decrement()"class="btn btn-primary">Decrement</button>
چنان که میبینید ما کامپوننتهای وب را با استفاده از نامهای تگ که در مرورگر ثبت کردهایم یعنی i-counter و d-counter رندر میکنیم. در مورد شمارنده اعلانی، خصوصیت شماره اولیه آن را نیز به مقدار متغیر count کامپوننت متصل میسازیم.
همچنین دو مورد دیگر به نامهای iCounter# و dCounter# به کامپوننتهای خود اضافه خواهیم کرد. چنان که در ادامه خواهید دید، این تگها به کامپوننتهای انگولار امکان میدهند که ارجاعهای مستقیمی به این عناصر به دست آورند تا بتوانید آنها را مورد استفاده قرار دهید.
در نهایت دکمههای افزایش یا کاهش را داریم که متدهایی را در کامپوننت اپلیکیشن فرا میخوانند تا مقدار شمارندههای کامپوننت وب را تغییر دهند. اینک نگاهی به فایل app.component.ts میاندازیم:
1import { Component, ElementRef, ViewChild } from '@angular/core';
2@Component({
3selector: 'my-app',
4templateUrl: './app.component.html',
5styleUrls: [ './app.component.css' ]
6})
7export class AppComponent {
8private count: number = 0;
9@ViewChild("iCounter") iCounter: ElementRef;
10@ViewChild("dCounter") dCounter: ElementRef;
11increment() {
12this.count++;
13this.iCounter.nativeElement.increment();
14this.dCounter.nativeElement.setAttribute("count", this.count);
15}
16decrement() {
17this.count--;
18this.iCounter.nativeElement.decrement();
19this.dCounter.nativeElement.setAttribute("count", this.count);
20}
21}
کار خود را با ایمپورت کردن کامپوننتها یعنی ElementRef و ViewChild از angular/core@ آغاز میکنیم. ما به ElementRef و ViewChild نیاز داریم، زیرا باید کامپوننتهای وب را مستقیماً دستکاری کنیم تا مقادیرشان تغییر یابد. با اینکه این کار عجیبتر از کار کردن با کامپوننتهای بومی انگولار است، اما اجرای آن آسان است.
درون کلاس یک متغیر وهلهای برای ذخیرهسازی مقدار شمارنده جاری اضافه میکنیم. سپس از دکوراتور ViewChild بهره میگیریم تا به ارجاع ElementRef–ها به دو کامپوننت وب، با استفاده از تگهایی که در قالب دیدید دست پیدا کنیم. وهلههای ElementRef امکان استفاده مستقیم از عناصر بومی بنیادین را فراهم میسازند. در این مورد این عناصر گرههای DOM هستند.
سپس نوبت به متدهای افزایش و کاهش میرسد. این همان جایی است که کار اصلی اجرا میشود. در هر دو متد ما متغیر count کامپوننت را تغییر میدهیم و سپس کامپوننتهای وب را با استفاده از مقدار جدید بهروزرسانی میکنیم. در مورد شمارنده دستوری، متدهای افزایش و کاهش را فرا میخوانیم. در مورد شمارنده اعلانی، از متد setAttribute در DOM برای بهروزرسانی متغیر شمارنده بهره میگیریم.
بدین ترتیب به پایان کار میرسیم. ما اینک یک اپلیکیشن انگولار کاملاً عملیاتی داریم که با بهرهگیری از کامپوننتهای وبی که خودمان ساختهایم کار میکند. نتیجه نهایی را میتوانید در این صفحه (+) مشاهده کنید.
سخن پایانی
بدین ترتیب در این مقاله با روش ساخت کامپوننتهای وب و استفاده از آنها در اپلیکیشنهای انگولار آشنا شدیم. اکنون میتوانید کامپوننتهای وب را به همه اپلیکیشنهای انگولار خود اضافه کنید و دیگر محدود به استفاده از کامپوننتهای انگولار در اپلیکیشنهای خود نیستید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی AngularJS برای ساخت اپلیکیشنهای تکصفحهای
- آموزش انگولار (Angular): ساخت یک اپلیکیشن در ۲۰ دقیقه – به زبان ساده
- کامپوننت کانتینری در انگولار (Angular) — از صفر تا صد
==