ساخت داشبورد سازمانی مقیاس پذیر با انگولار — بخش اول

۱۰۹ بازدید
آخرین به‌روزرسانی: ۱۱ شهریور ۱۴۰۲
زمان مطالعه: ۴ دقیقه
ساخت داشبورد سازمانی مقیاس پذیر با انگولار — بخش اول

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

اگر در زمینه انگولار تاره کار محسوب می‌شوید، پیشنهاد می‌کنیم با مراجعه به آموزش زیر با این فریمورک محبوب جاوا اسکریپت بیشتر آشنا شوید:

در اغلب موارد ما می‌خواهیم داشبوردهایی بسازیم که خود داشبورد از کامپوننت‌هایش کاملاً مستقل باشد. در این حالت هر یک از کارت‌های داشبورد می‌توانند کامپوننت‌های دینامیکی داشته باشند که بدون ارجاع مستقیم در قالب داشبورد به وجود آمده باشند. در ابتدا پیشنهاد می‌کنیم این راهنمای وب‌سایت رسمی انگولار (+) را مطالعه کنید تا در این زمینه اطلاعات بیشتری کسب کنید. با این که آن راهنما نشان نمی‌دهد که چگونه می‌توان چند کامپوننت را بارگذاری کرد. همچنین لازم است که داشبورد داده‌ها را به کامپوننت‌های دینامیک وصل کند که موجب پیچیده شدن اپلیکیشن در عمل می‌شود. در این مقاله این اشکالات راهبرد مقاله فوق را اصلاح می‌کنیم.

شروع

نخستین کاری که باید انجام دهید ایجاد یک ماژول feature برای میزبانی همه موارد مرتبط با داشبورد است. همچنین باید کامپوننت داشبورد را ایجاد کنید. این همان جایی است که همه اکشن‌ها اتفاق می‌افتند.

در داشبورد یک متغیر به نام tracks اعلان می‌کنیم. این متغیر اطلاعات مرتبط نگه‌داری شده و همچنین آیتم‌های متعلق به هر ترک را مورد ردگیری قرار می‌دهد.

فایل dashboard.component.ts

1import {Component,OnInit,} from '@angular/core';
2import { Track } from '../models/track';
3
4@Component({
5   selector: 'app-dashboard',
6   templateUrl: './dashboard.component.html',
7   styleUrls: ['./dashboard.component.scss'],
8})
9export class DashboardComponent implements OnInit {
10   tracks: Array<Track> = [
11      {
12         items: [],
13      },
14      {
15         items: [],
16      },
17   ];
18
19   constructor() {}
20
21   ngOnInit() {}
22
23}

فایل item.ts

1export interface Item {
2   component: string;
3   id: string;
4}

فایل track.ts

1import { Item } from './item';
2
3export interface Track {
4   items: Array<Item>;
5}

به رشته بودن مشخصه روی Item توجه کنید. این کار عامدانه بوده است و در بخش بعدی در مورد آن بیشتر توضیح می‌دهیم. همچنین آیتم‌ها را در قالب داشبورد نمایش می‌دهیم.

فایل dashboard.component.html

1<div class="board">
2   <div *ngFor="let track of tracks">
3      <div *ngFor="let item of track.items">
4         {{ item }}
5      </div>
6    </div>
7</div>

بارگذاری محتوا به صورت دینامیک

در این بخش به بررسی بارگذاری کامپوننت‌ها روی داشبورد بدون ارجاع مستقیم به کامپوننت‌ها می‌پردازیم.

برای بارگذاری کامپوننت‌ها به صورت دینامیک باید یک ارجاع به ViewContainerRef برای هر قالب آیتم داشته باشیم. بری دسترسی به آن یک دایرکتیو ایجاد می‌کنیم که در این مورد آن را dashboardOutlet نامیده‌ایم و ViewContainerRef را به سازنده اضافه می‌کنیم و یک ()Input@ نیز برای آیتم قرار می‌دهیم.

فایل dashboard-outlet.directive.ts

1import { Directive, ViewContainerRef, Input } from '@angular/core';
2
3@Directive({
4   selector: '[appDashboardOutlet]',
5})
6
7export class DashboardOutletDirective {
8   @Input() item;
9
10   constructor(public viewContainerRef: ViewContainerRef) {}
11}

سپس عبارت item.id را از فایل dashboard.component.html به همراه تگ ng-template حذف می‌کنیم. سپس دایرکتیو dashboard-outlet را به ng-template اضافه کرده و داده‌های آیتم را به دایرکتیو آیتم متصل می‌کنیم.

فایل dashboard.component.html

1<div class="board">
2   <div *ngFor="let track of tracks">
3      <div *ngFor="let item of track.items">
4         <ng-template appDashboardOutlet [item]="item"></ng-template>
5      </div>
6   </div>
7</div>

اکنون پیش از آن که کامپوننت‌های محتوا را در داشبورد رندر کنیم باید چند کار دیگر را انجام دهیم.

ابتدا یک کامپوننت dashboardCard و یک کامپوننت dashboardCardContainer ایجاد می‌کنیم که هر کارت داشبورد و کانتینر والد آن بسط می‌یابند و به ما امکان داشتن یک مجموعه مشترک از متغیرها و رویدادهای متصل را می‌دهند. DashboardCardContainer باید یک آیتم ()Input@ داشته باشد که ارجاعی به خود آیتم را نگهداری می‌کند. دلیل این کار آن است که کانتینر همه فراخوانی‌های سرویس و بازیابی داده‌ها را برای کامپوننت ارائه انجام می‌دهد. فعلاً این موارد صرفاً کامپوننت‌های خالی هستند.

فایل dashboard-card.container.ts

1@Component({
2   template: ``,
3})
4
5export class DashboardCardContainer {
6   @Input() item;
7}

میزبانی از کامپوننت‌ها

سپس باید محلی برای میزبانی از کامپوننت‌هایی داشته باشیم که روی داشبورد حضور ندارند. به این منظور یک فایل ایجاد کرده و یک شیء به نام dashboardCards را اکسپورت می‌کنیم. dashboardCards شیئی است که یک رشته را به یک کامپوننت داشبورد نگاشت می‌کند. بدین ترتیب می‌توانیم یک رشته به آن بدهیم و یک کامپوننت جهت رندر به ما بازمی‌گرداند. همچنین باید یک enum برای نگهداری ارجاعی به هر رشته ایجاد کنیم تا بدین ترتیب بتوانیم از امکان تکمیل خودکار در زمان ساخت آیتم‌ها روی شیء track بهره بگیریم.

فایل dashboard-cards.ts

1import { HelloWorldContainer } from '../hello-world/hello-world.container';
2
3export const dashboardCards = {
4   HELLO_WORLD: HelloWorldContainer,
5};

فایل dashboard-cards.enum.ts

1export enum DashboardCards {
2   HELLO_WORLD = 'HELLO_WORLD',
3}

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

اگر به کامپوننت داشبورد بازگردیم، اینک باید ارجاعی به همه قالب‌هایی که از حلقه‌های ngFor* ایجاد می‌شوند داشته باشیم. این وضعیت با استفاده از کوئری کردن ViewChildren@ برای دایرکتیو DashboardOutlet میسر می‌شود.

فایل dashboard.component.ts

1@Component({
2...
3})
4export class DashboardComponent implements OnInit{
5   @ViewChildren(DashboardOutletDirective) dashboardOutlet: QueryList<DashboardOutletDirective>;
6...
7}

با استفاده از این ارجاع می‌توانیم اینک کامپوننت‌ها را در داشبورد بارگذاری کنیم. این وضعیت با بهره‌گیری از متدی به نام loadContent ممکن می‌شود. این متد دو پارامتر می‌گیرد که یکی قالبی از نوع DashboardOutletDirective و دیگری آیتمی با نام آیتم نوع است. سپس از ComponentFactoryResolver در متد loadContent استفاده می‌کنیم تا محتوا را در قالب ایجاد و رندر کنیم. همچنین متد دیگری به نام loadContents ایجاد می‌کنیم که روی همه قالب‌های ارائه‌شده از سوی ViewChildren@ می‌چرخد و آن را به loadContent ارسال می‌کند.

  • فایل dashboard.component.ts
1export class DashboardComponent implements OnInit {
2
3   loadContents = () => {
4      if (!this.dashboardOutlet ||!this.dashboardOutlet.length) {
5         return;
6      }
7
8   this.dashboardOutlet.forEach(template => {
9      this.cd.detectChanges();
10      this.loadContent(template, template.item);
11   });
12   this.cd.detectChanges();
13   };
14
15   loadContent = (template: DashboardOutletDirective, item: Item) => {
16      if (!item.component) {
17         return;
18      }
19
20      const viewContainerRef = template.viewContainerRef;
21      viewContainerRef.clear();
22      const componentFactory = this.cfr.resolveComponentFactory(dashboardCards[item.component]);
23      const componentRef = viewContainerRef.createComponent(componentFactory);
24      const instance = componentRef.instance as DashboardCardContainer;
25      instance.item = item;
26   };
27}

افزودن آیتم‌ها به track

لازم به اشاره است که ما به changeDetectorRef اعلام کرده‌ایم تغییرات را تشخیص دهد (detectChanges) زیرا DOM را به‌روزرسانی کرده‌ایم. بدون این فراخوانی با خطا مواجه می‌شویم و یا با نتایج ناخواسته‌ای مواجه خواهیم شد.

در نهایت تنها کاری که باید انجام دهیم این است که به داشبورد برویم و آیتم‌ها را به track-ها اضافه کنیم و loadContents را در ()ngAfterViewInit فراخوانی نماییم.

فایل dashboard.component.ts

1@Component({
2...
3export class DashboardComponent implements OnInit, AfterViewInit {
4   tracks: Array<Track> = [
5      {
6         items: [
7            {
8               component: DashboardCards.HELLO_WORLD,
9               id: 'hello-world',
10            },
11         ],
12      },
13      {
14         items: [
15            {
16               component: DashboardCards.HELLO_WORLD,
17               id: 'hello-world-2',
18            },
19         ],
20      },
21   ];
22   
23    ngAfterViewInit() {
24       this.loadContents();
25    }
26}

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

داشبورد مقیاس‌پذیر سازمانی با انگولار

در بخش دوم این راهنما به بررسی شیوه پیاده‌سازی قابلیت «کشیدن و رها کردن» (drag and drop) در عین حفظ حالت (State)، چگونگی اجرای فراخوانی‌های سرویس برای کارت‌های منفرد و شیوه اضافه/حذف کردن آیتم‌ها از track-ها در زمان اجرا می‌پردازیم. برای مطالعه بخش بعدی روی لینک زیر کلیک کنید:

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
itnext
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *