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


در بخش قبلی به بررسی بهرهبرداری از مزیت رندرینگ کامپوننت دینامیک برای جداسازی محتوای داشبورد از خود داشبورد پرداختیم. در این بخش به بررسی شیوه حذف/اضافه آیتمها از track-ها، شیوه ایجاد فراخوانیهای سرویس برای کارتهای منفرد روی داشبورد و شیوه پیادهسازی قابلیت drag and drop در عین حفظ حالت در داشبورد سازمانی مقیاس پذیر خواهیم پرداخت. برای مطالعه بخش قبلی روی لینک زیر کلیک کنید:
حذف/اضافه آیتمها
پیش از آغاز این بخش باید اشاره کنیم که بهترین رویه در انگولار این است که «حالت مشترک» (Shared State) را به داخل سرویسهایی که میتوانند آن را مصرف کنند روی کامپوننتهای مختلف اپلیکیشن منتقل کنیم. از آنجا که track-های خود را در dashboard.component.ts اعلان کردهایم به صورت مستقیم در دسترسی هیچ کامپوننت دیگری قرار ندارد و این شامل محتوای خود داشبورد نیز میشود. برای ایجاد امکان حذف و اضافه یک کارت از هر کجا به جز خود کامپوننت داشبورد، باید متغیر tracks را به یک سرویس انتقال دهیم.
پیش از ادامه باید اشاره کنیم که درک مفهوم سرویسهای observable بسیار حائز اهمیت است. سرویسهای observable سرویسهای معمولی هستند که داده و عموماً حالت را به شکل observable بازگشت میدهند. اگر به تازگی با مفهوم سرویسهای observable آشنا شدهاید و مزایا و معایب آنها را به خوبی نمیدانید، پیشنهاد میکنیم به مطالعه این مقاله (+) بپردازید. فعلاً این امکان به ما اجازه میدهد که حالت داشبورد را به اشتراک بگذاریم.
ابتدا یک سرویس داشبورد ایجاد میکنیم. سپس آرایه tracks را از dashboard.component.ts به سرویس کپی میکنیم. همچنین نام متغیر را به defaultState تغییر میدهیم.
فایل dashboard.service.ts
در ادامه یک subject ایجاد میکنیم که میتوانیم دادهها را به آن ارسال کنیم. منظور از subject چیزی مانند یک EventEmitter با امکان داشتن چندین observers است. در این مورد میخواهیم از یک BehaviorSubject استفاده کنیم. یک BehaviorSubject آخرین مقدار مفروض را به شنوندههای جدید ارسال میکند. میتوانیم با فراخوانی ()this.subject.asObservable یک observable را از subject بسازیم که نام متغیر آن را tracks$ گذاشتهایم.
فایل dashboard.service.ts
در نهایت tracks را از سرویس در کامپوننت داشبورد بازیابی میکنیم.
فایل dashboard.component.ts
اینک میتوانیم شروع به حذف و اضافه track-ها از داشبورد بکنیم.
حذف آیتمها
برای حذف آیتمها یک متد روی سرویس داشبورد ایجاد میکنیم و نام آن را removeItem میگذاریم. متد removeItem یک آیتم را به عنوان پارامتر میگیرد و تلاش میکند تا آیتم را از حالت tracks حذف کرده و تغییرات را به subject ارسال کند.
برای دریافت حالت از subject متد ()subject.getValue را فراخوانی میکنیم. بدین ترتیب یک اسنپشات از ()subject.getValue در آن وهله زمانی به دست میآید. اینک که یک اسنپشات داریم میتوانیم شروع به دستکاری آن بکنیم.
فایل dashboard.service.ts
با تعریف یک حلقه روی هر track در آرایه حالت میتوان آیتم را حذف کرد. سپس برای هر track روی آیتمهای موجود در آن حلقهای تعریف میکنیم. اگر آیتم در حلقه با آیتم ارائه شده به عنوان پارامتر مطابقت یابد آن را از ترک برمیداریم. زمانی که کار انجام یافت، حالت جدید را بار دیگر با فراخوانی this.subject.next() به subject ارسال میکنیم.
فایل dashboard.service.ts
افزودن آیتمها
متدی به نام addItem ایجاد میکنیم که یک آیتم را گرفته و آن را به یکی از track-ها اضافه میکند. آیتم جدید را با آخرین مقدار آیتمها به track ارسال میکنیم. همچنین میتوانید به صورت اختیاری پیش از افزودن آیتم بررسی کنید که آیتم روی هیچ کدام از track-ها نباشد تا از بروز موارد تکراری جلوگیری کنید.
فایل dashboard.service.ts
پیش از آن که بتوانیم از متدهای حذف/اضافه آیتمها استفاده کنیم، باید با شیوه ایجاد فراخوانیهای سرویس منفرد از محتواهای داشبورد آشنا شویم. در بخش قبلی این موضوع را توضیح خواهیم داد.
فراخوانی سرویسها و ارسال حالت به درون محتوای داشبورد
جداسازی حالت محتوا از داشبورد بسیار مفید است. بدین ترتیب امکان ایجاد مستقل محتوای داشبورد پدید میآید که به صورت کارتهایی بدون این که منطق محتوایی خاصی روی کامپوننت وجود داشته باشد مشاهده میشود. اگر مشغول کار روی یک محیط سازمانی باشید، جدا کردن داشبورد از حالت محتوا امکان داشتن چندین محیط مختلف برای مدیریت، و نگهداری محتوای داشبورد فراهم میآید که نیازی به دستکاری خود داشبورد هم ندارد. برای نمونه بخش مالی سازمان ممکن است بخواهد یک کارت ملی برای نمایش موارد مالی ایجاد کند. در زمان انجام این کار لازم نیست روی هیچ چیز به جز cards.enum.ts و dashboard-cards.ts کار کنند.
در این مثال سرویس دیگری به نام HelloService نیز میسازیم. این سرویس قرار است لیستی از نامها را نگهداری کند که به وسیله محتوای داشبورد قابل بازیابی است. ضمناً یک نام Input() name هم روی HelloWorldComponent اضافه میکند و آن را در قالب کامپوننت نمایش میدهد.
فایل hello-world.service.ts
فایل hello-world.component.ts
فایل hello-world.component.html
اگر در این راهنما با ما همراه بوده باشید، در بخش قبلی یک کامپوننت HelloWorldContainer ایجاد کردهاید. این کانتینر باید سرویس را فراخوانی کند، اطلاعاتی را که نیاز دارد بازیابی نماید و آن را از طریق مشخصههای ()input کامپوننت فرزند به محتوا ارسال کند.
به دلیل روش خاص نگاشت کانتینرها به آیتمهای track باید کانتینری برای هر آیتم یکتا روی داشبورد وجود داشته باشد. دلیل این امر آن است که کانتینرها باید سرویسهایی را برای بازیابی دادهها داشته باشند و بسته به این که کامپوننتها چگونه سازماندهی شدهاند، این امر میتواند موجب محدودیت قابلیت استفاده مجدد کانتینرهای خاص بشود.
برای بازیابی دادههای مختلف باید یک کانتینر ثانویه به نام hello-world-two.container.ts ایجاد کرده و آن را به entryComponents اضافه کنیم که همان فرایندی است که در مورد hello-world.container.ts انجام دادیم. هر کانتینر قرار است نام را از HelloService بازیابی کرده و آن را به HelloWorldComponent ارسال کند. تنها تفاوت در این است که نام هر کانتینری که بازیابی میشود متفاوت است.
فایلهای hello-world.container.ts و hello-world-two.container.ts
اکنون به dashboard-cards.ts و dashboard-cards.enum.ts میرویم و مدخل HELLO_WORLD_TWO دیگری اضافه میکنیم.
فایل dashboard-cards.ts
فایل dashboards-cards.enum.ts
در فایل dashboard.service.ts کامپوننت آیتم دوم را به صورت HELLO_WORLD_TWO تغییر میدهیم. تغییرات را ذخیره کنید تا ببینید که اینک دو کارت با محتوای متفاوت روی داشبورد قرار دارند:
اکنون یک داشبورد استاتیک با محتوای مستقل ساختهایم. در بخش بعدی کاری میکنیم که محتوا قابلیت کشیدن و رها کردن پیدا کند.
پیادهسازی قابلیت «کشیدن و رها کردن»
قابلیت کشیدن و رها کردن (Drag and Drop) یک وظیفه کاملاً دشوار و خستهکننده است. خوشبختانه کتابخانه ng2-dragula اجرای آن را به شدت آسان کرده است. این کتابخانه را با دستور زیر نصب کنید:
yarn add ng2-dragula
در ریپوی dragula (+) لیستی از دستورالعملهای نصب میبینید که باید پیروی کنید. dragula همچنین باید در ماژول داشبورد ایمپورت شود.
برای پیادهسازی قابلیت کشیدن و رها کردن باید دایرکتیوهای dragula و dragulaModel را همراه با رویداد dragulaModelChanged اضافه کنیم. دایرکتیو dragula گرهی که track مفروض باید به آن تعلق یابد را مشخص میسازد. در این مثال، هر دو مورد به گروه dashboard تعلق دارند. دایرکتیو dragulaModel تعیین میکند که track به نام dragula باید شامل کدام آیتمها باشد. این حالت با دادههای track ما همگامسازی میشود. همچنین مقداری کد CSS وجود دارد که تضمین میکند وقتی track خالی است اندازه آن به صفر کاهش نمییابد و از این رو میتوانیم دوباره روی آن چیزی را بکشیم.
فایل dashboard.component.html
فایل dashboard.component.scss
با افزودن موارد فوق، کارتها اینک قابلیت کشیده شدن یافتهاند. اینک باید به ذخیرهسازی حالت بپردازیم.
ذخیرهسازی حالت
برای حفظ تغییرات در زمان کشیدن و رها کردن باید چند کار انجام دهیم. ابتدا باید حالت تغییر یافته را که از سوی رویداد dragulaModelChanged را بگیریم و آن را در سرویس تنظیم کنیم. به این منظور به یک متد نیاز داریم که کل حالت track-ها را گرفته و آنها را به subject ارسال کند. یک متد به نام setState روی DashboardService اضافه میکنیم. setState نیازمند آرایهای از track-ها است و بر این اساس حالت را تنظیم میکند.
فایل dashboard.service.ts
ما آن را در کامپوننت داشبورد زمانی که track-ها تغییر یافتند فراخوانی میکنیم.
به این ترتیب dragulaModelChanged یک رویداد ارسال میکند که آرایهای بازگشت میدهد و این آرایه به همراه تغییرات مناسب پس از کشیدن و رها کردن به dragulaModel ارسال میشود. نکته کار در این جا است که این متد تنها آیتمهای منفرد یک track خاص را بازگشت میدهد و نه آرایهای از track-ها. این بدان معنی است که باید بدانیم کدام آیتمهای track تغییر یافتهاند. به این منظور میتوانیم یک ارجاع به اندیس روی حلقه ngFor مربوط به track-ها در قالب داشبورد اضافه کنیم. بدین ترتیب ارجاعی به آن که تغییر یافته خواهیم داشت که dragulaModelChanged به آن اشاره میکند.
متدی به نام changed روی کامپوننت داشبورد ایجاد میکنیم. متد changed یک track و یک trackIndex میپذیرد. زمانی که متد changed فراخوانی شود، حالت کنونی را بازیابی کرده و تغییرات ارائه شده از سوی رویداد را برای ما اعمال میکند. زمانی که ارجاع به حالت تغییر یافته به دست ما برسد، آن را به setState ارسال میکنیم تا سرویس را بهروزرسانی کند.
فایل dashboard.component.ts
اینک میتوانیم رویداد dragulaModelChanged را به div خود اضافه کرده و changed را فراخوانی نماییم.
فایل dashboard.component.html
اینک به جایی رسیدیم که همه چیز پیچیده میشود. ما شیئی را که تعیین میکند چه چیزهایی باید رندر شوند بهروزرسانی کردهایم و باید به بخش تشخیص تغییر بگوییم که تغییرات را بررسی کند و تنها آن موقع است که محتوا را بارگذاری میکنیم. اگر این کارها به ترتیب صحیحی انجام نشوند، متد loadContents مجموعه صحیحی از ng-templates برای رندر کردن کامپوننتها بر مبنای آن نخواهد داشت. بهترین مکان برای افزودن فراخوانی در اشتراک tracks است. این اشتراک هر زمان که tracks در سرویس داشبورد یک مقدار جدید گیرد فراخوانی می شود. در مورد مثال ما این زمانی است که کارتها کشیده و رها میشوند.
فایل dashboard.component.ts
چنان که میبینید حالت در زمان کشیده شدن نیز سازگاری خود را حفظ میکند. اینک تنها چیزی که باقی مانده است، ذخیره کردن حالت است.
ذخیرهسازی حالت داشبورد
ذخیرهسازی حالت داشبورد کاملاً ساده است. تنها کاری که باید انجام دهیم ذخیرهسازی track–های داشبورد در storage لوکال (یا هر جای دیگری که دوست داریم) و بارگذاری آن در زمان آغاز به کار است. همچنین باید در موردی که کاربران نخستین بار داشبورد را باز میکنند یک پیکربندی پیشفرض را پیادهسازی کنیم.
یک متد روی سرویس داشبورد به نام loadFromLocalStorage ایجاد میکنیم. این متد حالت داشبورد را میگیرد و سرویس را بهروزرسانی میکند.
فایل dashboard.service.ts
متد دیگری ایجاد به نام saveTracksToStorage میکنیم. این متد چنان که از نامش مشخص است، tracks را به صورت یک رشته در localstorage ذخیره میکند.
فایل dashboard.service.ts
برای ذخیرهسازی بهروزرسانیهای بعدی در tracks باید در tracks$ در سازنده سرویس داشبورد مشترک شویم. البته پیش از آن باید مطمئن شویم که چه چیز را فراخوانی میکنیم تا جدیدترین حالت ذخیره شده را به دست آوریم.
فایل dashboard.service.ts
نتیجه نهایی به صورت زیر است:
اینک یک داشبورد دینامیک با قابلیت کشیدن و رها کردن داریم که حالت خود را به طور کاملا مستقل از محتوا حفظ میکند. برای مشاهده کد منبع به این صفحه (+) مراجعه کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- آموزش آشنایی مقدماتی با AngularJS
- مجموعه آموزشهای برنامهنویسی
- ساخت رابط کاربری Login با انگولار (Angular) و متریال دیزاین – به زبان ساده
- قابلیت کشیدن و رها کردن (ngDragDrop) در انگولار — به زبان ساده
==