مسیرراه‌ها
WPF
          مطالب
          بازسازی LocalDb
          LocalDb یک نسخه‌ی کوچک شده از SQL Express 2012 است که نیازی به سرویس ندارد که سبب سهولت استفاده از آن را در پروژه‌های Visual Studio  فراهم می‌آورد و یک ویژگی بسیار خوب برای برنامه‌هایی است که نیاز به استفاده از کلیه‌ی امکانات مهیای SQL Server را ندارند. یکی دیگر از ویژگی‌های آن استفاده در محیط هایی است که اطمینانی از نصب بودن SQL Express در آن‌ها نداریم.
          در حین توسعه‌ی نرم افزار ممکن است استفاده از LocalDb امکان پذیر نباشد و خللی در کارکرد آن ایجاد شده باشد و عملا استفاده از آن میسر نشود. اتفاقی که برای من روز گذشته رخ داد و مجبور شدم LocalDb را مجددا نصب نمایم ولی همچنان مشکل وجود داشت. بعد از جستجو به این لینک و این لینک رسیدم که جمع بندی آن بشرح زیر است:
          ابتدا command  prompt را اجرا می‌نماییم (ترجیحا بصورت Administrator اجرا شود) سپس به محلی که Instanceهای LocalDb قرار دارد
           (localappdata\Microsoft\Microsoft SQL Server Local DB\Instances\v11.0) رفته و دستور زیر را صادر می‌نماییم:
          sqllocaldb create "v11.0"
          در صورتیکه خطای زیر نمایش داده شد، مراحل بعد را انجام می‌دهیم.
          Creation of LocalDB instance "v11.0" with version 11.0 failed because of the following error: 
          LocalDB instance is corrupted. See the Windows Application event log for event details.
          هنگامیکه خطای فوق نمایش داده شده، باید ابتدا LocalDb را حذف و مجددا آن را ساخت.
          sqllocaldb delete "v11.0"
          دستور فوق عملیات حذف را انجام می‌دهد.
          و دستور زیر مجددا یک وهله‌ی نسخه 11.0 بنام"v11.0" را از LocalDb ایجاد می‌کند.
          sqllocaldb create "v11.0"
          تا اینجا کلیه‌ی اقدامات لازم جهت راه اندازی مجدد LocalDb انجام شد و هم اکنون می‌توانید از آن استفاده نمایید.
          مطالب دوره‌ها
          بررسی حالت‌های مختلف نصب RavenDB
          چهار روش مختلف برای نصب، استفاده و توزیع RavenDB وجود دارند. ساده‌ترین روش آن‌را که اجرای فایل Raven.Server.exe است، تاکنون بررسی کردیم. این روش صرفا جهت دیباگ و کار برنامه نویسی مناسب است. در ادامه سه روش دیگر را بررسی خواهیم کرد.

          الف) استفاده از RavenDB در حالت مدفون شده یا Embedded

          حالت Embedded به این معنا است که RavenDB درون پروسه برنامه شما اجرا خواهد شد و نه به صورت پروسه‌ای مجزا. این حالت برای ارائه ساده برنامه‌های دسکتاپ بسیار مناسب است؛ یا حتی توزیع برنامه‌های سبک ASP.NET بدون نیاز به نصب بانک اطلاعاتی خاصی بر روی وب سرور.
          برای کار با RavenDB در حالت Embedded ابتدا فایل‌های مورد نیاز آن‌را از طریق نیوگت دریافت کنید:
           PM> Install-Package RavenDB.Embedded -Pre
          در این حالت فایل کلاینت مورد نیاز، اسمبلی Raven.Client.Embedded.dll خواهد بود. سپس در کدهای قبلی خود بجای استفاده از new DocumentStore، اینبار خواهیم داشت new EmbeddableDocumentStore.
           var documentStore = new EmbeddableDocumentStore { DataDirectory = @"~/app_data/ravendb" };
          documentStore.Initialize();
          سایر قسمت‌های برنامه نیازی به تغییر نخواهند داشت.
          امکان تعریف DataDirectory در فایل کانفیگ برنامه نیز وجود دارد. فقط در این حالت باید دقت داشت که نام مسیر، با DataDir شروع می‌شود و نه DataDirectory :
          <connectionStrings>
            <add name="Local" connectionString="DataDir = ~\Data"/>
          سپس همانند قبل، مقدار خاصیت رشته اتصالی EmbeddableDocumentStore به نام مدخل فوق باید تنظیم گردد.


          چند نکته جالب در مورد حالت Embedded
          - امکان اجرای درون حافظه‌ای RavenDB نیز وجود دارد:
           var documentStore = new EmbeddableDocumentStore{RunInMemory = true}.Initialize()
          در اینجا فقط کافی است خاصیت RunInMemory شیء EmbeddableDocumentStore به true تنظیم شود. این مورد بسیار مناسب است برای انجام آزمون‌های واحد بسیار سریع که پس از پایان کار برنامه، اثری از بانک اطلاعاتی آن باقی نخواهد ماند.
          - اجرای حالت Embedded به صورت Embedded HTTP:
          در حالت Embedded دیگر دسترسی به برنامه سیلورلایت Raven studio وجود ندارد. اگر علاقمند به کار با آن بودید، خاصیت UseEmbeddedHttpServer شیء EmbeddableDocumentStore را به true تنظیم کنید. سپس فایل Raven.Studio.xap را در ریشه وب سایت خود قرار دهید. اکنون مانند قبل آدرس localhost:8080/raven/studio.html برقرار خواهد بود.
          همچنین سرور Http این بانک اطلاعاتی را نیز می‌توان دستی راه اندازی کرد. متد NonAdminHttp.EnsureCanListenToWhenInNonAdminContext بررسی می‌کند که آیا برنامه مجوز راه اندازی یک سرور را بر روی پورت مثلا 8080 دارد یا خیر.
           NonAdminHttp.EnsureCanListenToWhenInNonAdminContext(8080);
          
          // Start the HTTP server manually
          var server = new RavenDbHttpServer(documentStore.Configuration, documentStore.DocumentDatabase);
          server.Start();


          ب) نصب RavenDB به صورت سرویس ویندوز NT
          اگر مایل باشیم تا RavenDB را نیز مانند SQL Server به صورت یک سرویس ویندوز NT نصب کنیم تا همواره در پس زمینه سرور در حال اجرا باشد، کنسول پاورشل ویندوز را گشوده و سپس فرمان ذیل را صادر کنید:
           d:\ravendb\server> .\raven.server.exe /install
          اکنون اگر به کنسول مدیریتی سرویس‌های ویندوز یا services.msc مراجعه کنید، ravendb را به صورت یک آیتم جدید در لیست سرویس‌های ویندوز خواهید یافت.
          و اگر خواستید این سرویس را عزل کنید، دستور ذیل را در پاورشل ویندوز صادر کنید:
           d:\ravendb\server> .\raven.server.exe /uninstall


          ج) نصب RavenDB به صورت یک پروسه IIS (یا اجرا شده توسط IIS)

          فایل‌های مورد نیاز حالت اجرای RavenDB را به صورت یک پروسه مجزای IIS از نیوگت دریافت کنید:
           PM> Install-Package RavenDB.AspNetHost -Pre
          در این حالت، پوشه bin، فایل xap و فایل کانفیگ برنامه وب مورد نیاز دریافت خواهند شد. پس از آن، تنها کافی است یک دایرکتوری مجازی را در IIS به این پوشه جدید اختصاص داده و همچنین بهتر است یک Application pool جدید را نیز برای آن تهیه کنید تا واقعا این برنامه در پروسه‌ی مجزای خاص خودش اجرا شود. حتی در این حالت می‌توان شماره پورت دیگری را به این برنامه اختصاص داد. به علاوه در این حالت تنظیمات Recycling مربوط به IIS را هم باید مدنظر داشت (در قسمت تنظیمات Application pool برنامه) و مثلا تنظیم کرد که برنامه پس از چه مدت فعال نبودن از حافظه خارج شود.


          یک نکته
          تمام بسته‌های مورد نیاز را یکجا از آدرس http://ravendb.net/download نیز می‌توان دریافت کرد. در نگارش‌های جدید، بسته نصاب نیز برای این بانک اطلاعاتی تهیه شده است که برای نمونه توزیع آن‌را جهت حالت نصب در IIS ساده‌تر می‌کند.
          مطالب
          شروع کار با Apache Cordova در ویژوال استودیو #4
          در قسمت قبل یک مثال ساده را کار کردیم. در این قسمت با jQuery Mobile آشنا شده و در پروژه‌ی خود استفاده خواهیم کرد.

          توضیح تکمیلی در مورد ساختار فایل‌های پروژه
          همان طور که در قسمتها قبل گفته شد، تگ اسکریپت زیر 
          <script src="cordova.js"></script>
          از استاندارد‌های Cordova است؛ وجود خارجی ندارد و بخشی از فرآیند ساخت برنامه است.
          اگر توجه کنید فایلی با نام platformOverrides.js در فولدر scripts موجود در ریشه، خالی است اما در فولدر merges موجود در ریشه‌ی پروژه مربوط به هر پلتفرم و همنام آن پلتفرم قرار دارد. برای مثال برای android، یک چنین دایرکتوری merges/android/scripts وجود دارد که درون آن فایلی به‌نام  platformOverrides.js دیده می‌شود و اگر دقت کنید، همنام فایل موجود در فولدر scripts موجود در ریشه پروژه است که درون خود فایلی بنام  android2.3-jscompat.js را فراخوانی می‌کند. (برای کمک به سازگاری کتابخانه‌های ثالث)
          در زمان build ، تمام فایل‌های موجود در "merges/"platformname ، در فولدر‌های هم نامی در شاخه‌ی ریشه‌ی پروژه کپی شده و جایگزین فایل‌های قبلی خواهند شد.

           مثال برای اندروید 
          در زمان ساخت (build) فایل scripts/platformOverrides.js با فایل merges/windows/scripts/platformoverrides.js جایگزین خواهد شد. این امکان برای فلدر‌های css, images و بقیه‌ی آنها نیز امکان پذیر است.
          توجه داشته باشید این ادغام در سطح فایل‌ها و نه در سطح محتوای فایل‌ها انجام می‌شود.

          نکته 
          برای محتوای موجود در فولدر res، قضیه فرق می‌کند. زیرا محتوای این resource‌ها برای اپلیکیشن پکیچ ضروریست؛ پیش از آن که کد‌های ما درون WebView یا host رندر شوند. باید توجه کرد که این فولدر به جهت اینکه منابع اصلی را (با توجه به پلتفرم باید از فایل‌های مشخص آن برای تشخیص ساختار فولدر‌های اپلیکیشن پکیچ استفاده کند) در بر دارد و این منابع باید در زمان ساخت پروژه تشخیص داده شوند.


          رویداد‌های بومی
          در زیر تعدادی از رخدادهایی که در Cordova گنجانده شده‌اند تا اپلیکیشن ما از رخداد‌های دستگاه با خبر شوند، نشان داده شده است. برای تست آنها به راحتی بعد از اجرای برنامه توسط شبیه ساز Ripple می‌توانید از قسمت Events، رخداد مورد نظر را شبیه سازی کنید:
          (function () {
              "use strict";
          
              document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false );
          
              function onDeviceReady() {
                  // Handle the Cordova pause and resume events
                  document.addEventListener( 'pause', onPause.bind( this ), false );
                  document.addEventListener('resume', onResume.bind(this), false);
                  document.addEventListener('menubutton', onMenuButton.bind(this), false);
                  document.addEventListener('backbutton', onBackButton.bind(this), false);
                  //document.addEventListener('searchbutton', onResume.bind(this), false);
                  //document.addEventListener('endcallbutton', onResume.bind(this), false);
                  //document.addEventListener('offline', onResume.bind(this), false);
                  //document.addEventListener('online', onResume.bind(this), false);
                  //document.addEventListener('startcallbutton', onResume.bind(this), false);
                  //document.addEventListener('volumedownbutton', onResume.bind(this), false);
                  //document.addEventListener('volumeupbutton', onResume.bind(this), false);
                  
                  // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
              };
          
              function onPause() {
                  // TODO: This application has been suspended. Save application state here.
                  alert("paused");
              };
          
              function onResume() {
                  alert("resume");
              };
              function onMenuButton() {
                  alert("menu");
              };
          
              function onBackButton() {
                  alert("back button");
              };
             
          
          } )();

          .در مقالات آینده از افزونه‌های موجود، برای مدیریت رخداد‌های باتری سیستم استفاده خواهیم کرد



          jQuery Mobile
          جی کوئری موبایل، یک فریمورک (UI Framework) جدید با قابلیت استفاده‌ی آسان برای ساخت اپلیکیشن‌های چند سکویی موبایل است. با استفاده از این فریمورک شما قادر خواهید بود اپلیکیشن‌های موبایل بهینه شده برای اجرا بر روی تمام تلفن‌ها، دسکتاپ و تبلت‌ها را بسازید. علاوه بر این، جی کوئری موبایل می‌تواند یک فریمورک ایده آل برای توسعه دهند گان و طراحان وب که قصد ساخت اپلیکیشن‌های غنی وب برای موبایل را دارند، باشد.

           Supported Devices

            Phones/Tablets 
            Android 1.6+ 
            BlackBerry 5+ 
            iOS 3+ 
            Windows Phone 7 
            WebOS 1.4+ 
            Symbian (Nokia S60) 
            Firefox Mobile Opera Mobile 11+ 
            Opera Mini 5+ 
            Desktop browsers 
            Chrome 11+ 
            Firefox 3.6+ 
            Internet Explorer 7+ 
            Safari   


          برای نصب jQuery Mobile کافی است دستورات  زیر را در package manager console ویژوال استودیو استفاده کنید:

          PM>install-package jquery

          PM>install-package jquery.mobile.rtl

          بعد از دانلود فایل‌های مورد نظر خود، فولدری بنام jquery.mobile.rtl در ریشه پروژه ایجاد خواهد شد. به ترتیب فایل های rtl.jquery.mobile-1.4.0.css و rtl.jquery.mobile-1.4.0.js موجود در زیر شاخه‌های فلدر مذکور را به head و آخر body فایل index.html اضافه کنید.

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8" />
              <title>CordovaApp01</title>
          
              <!-- CordovaApp01 references -->
              <link href="css/index.css" rel="stylesheet" />
              <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
          </head>
          <body>
              <div data-role="page" id="page1">
                  <div data-role="header">
                      <h1>اولین برنامه</h1>
                  </div>
                  <div data-role="content">
                      <p>سلام من محتوای اولین برنامه هستم</p>
                  </div>
                  <div data-role="footer">
                      <h1>من فوتر هستم</h1>
                  </div>
              </div>
          <!-- Cordova reference, this is added to your app when it's built. -->
          <script src="scripts/jquery-2.1.3.min.js"></script>
              <script src="cordova.js"></script>
              <script src="scripts/platformOverrides.js"></script>
              <script src="scripts/index.js"></script>
          
              <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
          </body>
          </html>
          در تکه کد بالا ما یکی از ویجت‌های jQuery Mobile را استفاده کردیم و با استفاده از ویژگی data-role که برای div اصلی با page مقدار دهی شده است، یک  کانتینر (page container) برای ویجت page جی کوئری موبایل تعریف شده‌است.

          نتیجه‌ی نهایی به شکل زیر خواهد بود:

          در مقاله‌ی بعد به استفاده از plugin‌ها خواهیم پرداخت.

          ادامه دارد...

          مطالب
          نوشتن افزونه برای مرورگرها: قسمت دوم : کروم
          در مقاله پیشین ما ظاهر افزونه را طراحی و یک سری از قابلیت‌های افزونه را معرفی کردیم. در این قسمت قصد داریم پردازش پس زمینه افزونه یعنی خواندن RSS و اعلام به روز آوری سایت را مورد بررسی قرار دهیم و یک سری قابلیت هایی که گوگل در اختیار ما قرار داده است.

          خواندن RSS توسط APIهای گوگل
          گوگل در تعدادی از زمینه‌ها و سرویس‌های خودش apiهایی را ارائه کرده است که یکی از آن ها خواندن فید است و ما از آن برای خواندن RSS یا اتم وب سایت کمک می‌گیریم. روند کار بدین صورت است که ابتدا ما بررسی می‌کنیم کاربر چه مقادیری را ثبت کرده است و افزونه قرار است چه بخش هایی از وب سایت را بررسی نماید. در این حین، صفحه پس زمینه شروع به کار کرده و در هر سیکل زمانی مشخص شده بررسی می‌کند که آخرین بار چه زمانی RSS به روز شده است. اگر از تاریخ قبلی بزرگتر باشد، پس سایت به روز شده است و تاریخ جدید را برای دفعات آینده جایگزین تاریخ قبلی کرده و یک پیام را به صورت نوتیفیکیشن جهت اعلام به روز رسانی جدید در آن بخش به کاربر نشان می‌دهد.
           اجازه دهید کدها را کمی شکیل‌تر کنیم. من از فایل زیر که یک فایل جاوااسکریپتی است برای نگه داشتن مقادیر بهره می‌برم تا اگر روزی خواستم یکی از آن‌ها را تغییر دهم راحت باشم و در همه جا نیاز به تغییر مجدد نداشته نباشم. نام فایل را (const.js) به خاطر ثابت بودن آن‌ها انتخاب کرده‌ام.
            //برای ذخیره مقادیر از ساختار نام و مقدار استفاده می‌کنیم که نام‌ها را اینجا ثبت کرده ام
          var Variables={
           posts:"posts",
           postsComments:"postsComments",
           shares:"shares",
           sharesComments:"sharesComments",
          }
          
          //برای ذخیره زمان آخرین تغییر سایت برای هر یک از مطالب به صورت جداگانه نیاز به یک ساختار نام و مقدار است که نام‌ها را در اینجا ذخیره کرده ام
          var DateContainer={
           posts:"dtposts",
           postsComments:"dtpostsComments",
           shares:"dtshares",
           sharesComments:"dtsharesComments",
           interval:"interval"
          }
           
          //برای نمایش پیام‌ها به کاربر
          var Messages={
          SettingsSaved:"تنظیمات ذخیره شد",
          SiteUpdated:"سایت به روز شد",
          PostsUpdated:"مطلب ارسالی جدید به سایت اضافه شد",
          CommentsUpdated:"نظری جدیدی در مورد مطالب سایت ارسال شد",
          SharesUpdated:"اشتراک جدید به سایت ارسال شد",
          SharesCommentsUpdated:"نظری برای اشتراک‌های سایت اضافه شد"
          }
          //لینک‌های فید سایت
          var Links={
           postUrl:"https://www.dntips.ir/feeds/posts",
           posts_commentsUrl:"https://www.dntips.ir/feeds/comments",
           sharesUrl:"https://www.dntips.ir/feed/news",
           shares_CommentsUrl:"https://www.dntips.ir/feed/newscomments"
          }
          //لینک صفحات سایت
          var WebLinks={
          Home:"https://www.dntips.ir",
           postUrl:"https://www.dntips.ir/postsarchive",
           posts_commentsUrl:"https://www.dntips.ir/commentsarchive",
           sharesUrl:"https://www.dntips.ir/newsarchive",
           shares_CommentsUrl:"https://www.dntips.ir/newsarchive/comments"
          }
          موقعی که اولین بار افزونه نصب می‌شود، باید مقادیر پیش فرضی وجود داشته باشند که یکی از آن‌ها مربوط به مقدار سیکل زمانی است (هر چند وقت یکبار فید را چک کند) و دیگری ذخیره مقادیر پیش فرض رابط کاربری که قسمت پیشین درست کردیم؛ پروسه پس زمینه برای کار خود به آن‌ها نیاز دارد و بعدی هم تاریخ نصب افزونه است برای اینکه تاریخ آخرین تغییر سایت را با آن مقایسه کند که البته با اولین به روزرسانی تاریخ فید جای آن را می‌گیرد. جهت انجام اینکار یک فایل init.js ایجاد کرده‌ام که قرار است بعد از نصب افزونه، مقادیر پیش فرض بالا را ذخیره کنیم.
          chrome.runtime.onInstalled.addListener(function(details) {
          var now=String(new Date());
          
          var params={};
          params[Variables.posts]=true;
          params[Variables.postsComments]=false;
          params[Variables.shares]=false;
          params[Variables.sharesComments]=false;
          
          params[DateContainer.interval]=1;
          
          params[DateContainer.posts]=now;
          params[DateContainer.postsComments]=now;
          params[DateContainer.shares]=now;
          params[DateContainer.sharesComments]=now;
          
           chrome.storage.local.set(params, function() {
            if(chrome.runtime.lastError)
             {
                 /* error */
                 console.log(chrome.runtime.lastError.message);
                 return;
             }
                  });
          });
          chrome.runtime شامل رویدادهایی چون onInstalled ، onStartup ، onSuspend و ... است که مربوطه به وضعیت اجرایی افزونه میشود. آنچه ما اضافه کردیم یک listener برای زمانی است که افزونه نصب شده است و در آن مقادیر پیش فرض ذخیره می‌شوند. اگر خوب دقت کنید می‌بینید که روش دخیره سازی ما در اینجا کمی متفاوت از مقاله پیشین هست و شاید پیش خودتان بگویید که احتمالا به دلیل زیباتر شدن کد اینگونه نوشته شده است ولی مهمترین دلیل این نوع نوشتار این است که متغیرهای بین {} آنچنان فرقی با خود string نمی‌کنند یعنی کد زیر:
          chrome.storage.local.set('mykey':myvalue,....
          با کد زیر برابر است:
          chrome.storage.local.set(mykey:myvalue,...
          پس اگر مقداری را داخل متغیر بگذاریم آن مقدار حساب نمی‌شود؛ بلکه کلید نام متغیر خواهد شد.
           برای معرفی این دو فایل const.js و init.js به manifest.json می‌توانید به صورت زیر عمل کنید:
          "background": {
              "scripts": ["const.js","init.js"]
          }
          در این حالت خود اکستنشن در زمان نصب یک فایل html درست کرده و این دو فایل js را در آن صدا میزند که البته خود ما هم می‌توانیم اینکار را مستقیما انجام دهیم. مزیت اینکه ما خودمان مسقیما این کار را انجام دهیم این است که در صورتی که فایل‌های js ما زیاد شوند، فایل manifest.jason زیادی شلوغ شده و شکل زشتی پیدا می‌کند و بهتر است این فایل را تا آنجا که می‌توانیم خلاصه نگه داریم. البته روش بالا برای دو یا سه تا فایل js بسیار خوب است ولی اگر به فرض بشود 10 تا یا بیشتر بهتر است یک فایل جداگانه شود و من به همین علت فایل background.htm را درست کرده و به صورت زیر تعریف کرده‌ام:
          نکته:نمی توان در تعریف بک گراند هم فایل اسکریپت معرفی کرد و هم فایل html
          "background": {
              "page": "background.htm"
          }
          background.htm
          <html>
            <head>
            <script type="text/javascript" src="const.js"></script>
              <script type="text/javascript" src="https://www.google.com/jsapi"></script>
              <script type="text/javascript" src="init.js"></script>
          <script type="text/javascript" src="omnibox.js"></script>
          <script type="text/javascript" src="rssreader.js"></script>
          <script type="text/javascript" src="contextmenus.js"></script>
            </head>
            <body>
            </body>
          </html>
          لینک‌های بالا به ترتیب معرفی ثابت‌ها، لینک api گوگل که بعدا بررسی می‌شود، فایل init.js برای ذخیره مقادیر پیش فرض، فایل ominibox که در مقاله پیشین در مورد آن صحبت کردیم و فایل rssreader.js که جهت خواندن rss در پایینتر در موردش بحث می‌کنیم و فایل contextmenus که این را هم در مطلب پیشین توضیح دادیم.
          جهت خواندن فید سایت ما از Google API استفاده می‌کنیم؛ اینکار دو دلیل دارد:
          1. کدنویسی راحت‌تر و خلاصه‌تر برای خواندن RSS
          2. استفاده اجباری از یک پروکسی به خاطر Content Security Policy و حتی CORS
          قبل از اینکه manifst به ورژن 2 برسد ما اجازه داشتیم کدهای جاوااسکریپت به صورت inline در فایل‌های html بنویسیم و یا اینکه از منابع و آدرس‌های خارجی استفاده کنیم برای مثال یک فایل jquery بر روی وب سایت jquery ؛ ولی از ورژن 2 به بعد، گوگل سیاست امنیت محتوا Content Security Policy را که سورس و سند اصلی آن در اینجا قرار دارد، به سیستم Extension خود افزود تا از حملاتی قبیل XSS و یا تغییر منبع راه دور به عنوان یک malware جلوگیری کند. پس ما از این به بعد نه اجازه داشتیم inline بنویسیم و نه اجازه داشتیم فایل jquery را از روی سرورهای سایت سازنده صدا بزنیم. پس برای حل این مشکل، ابتدا مثل همیشه یک فایل js را در فایل html معرفی می‌کردیم و برای حل مشکل دوم باید منابع را به صورت محلی استفاده می‌کردیم؛ یعنی فایل jquery را داخل دایرکتوری extension قرار می‌دادیم.
          برای حل مشکل مشکل صدا زدن فایل‌های راه دور ما از Relaxing the Default Policy  استفاده می‌کنیم که به ما یک لیست سفید ارائه می‌کند و در این لیست سفید دو نکته‌ی مهم به چشم میخورد که یکی از آن این است که استفاده از آدرس هایی با پروتکل Https و آدرس لوکال local host/127.0.0.1 بلا مانع است و از آنجا که api گوگل یک آدرس Https است، می‌توانیم به راحتی از API آن استفاده کنیم. فقط نیاز است تا خط زیر را به manifest.json اضافه کنیم تا این استثناء را برای ما در نظر بگیرد.
          "content_security_policy": "script-src 'self' https://*.google.com; object-src 'self'"
          در اینجا استفاده از هر نوع subdomain در سایت گوگل بلامانع اعلام می‌شود.
          بنابراین آدرس زیر به background.htm اضافه می‌شود:
           <script type="text/javascript" src="https://www.google.com/jsapi"></script>

          استفاده از این Api در rssreader.js
          فایل rssreader.js را به background.htm اضافه می‌کنیم و در آن کد زیر را می‌نویسیم:
          google.load("feeds", "1");
          google.setOnLoadCallback(alarmManager);
          آدرسی که ما از گوگل درخواست کردیم فقط مختص خواندن فید نیست؛ تمامی apiهای جاوااسکریپتی در آن قرار دارند و ما تنها نیاز داریم قسمتی از آن لود شود. پس اولین خط از دستور بالا بارگذاری بخش مورد نیاز ما را به عهده دارد. در مورد این دستور این صفحه را مشاهده کنید.
          در خط دوم ما تابع خودمان را به آن معرفی می‌کنیم تا وقتی که گوگل لودش تمام شد این تابع را اجرا کند تا قبل از لود ما از توابع آن استفاده نکنیم و خطای undefined دریافت نکنیم. تابعی که ما از آن خواستیم اجرا کند alarmManager نام دارد و قرار است یک آلارم و یک سیکل زمانی را ایجاد کرده و در هر دوره، فید را بخواند. کد تابع مدنظر به شرح زیر است:
          function alarmManager()
          {
          chrome.storage.local.get(DateContainer.interval,function ( items) {
          period_time==items[DateContainer.interval];
          chrome.alarms.create('RssInterval', {periodInMinutes: period_time});
          });
          
          
          chrome.alarms.onAlarm.addListener(function (alarm) {
          console.log(alarm);
              if (alarm.name == 'RssInterval') {
          
          var boolposts,boolpostsComments,boolshares,boolsharesComments;
          chrome.storage.local.get([Variables.posts,Variables.postsComments,Variables.shares,Variables.sharesComments],function ( items) {
          boolposts=items[Variables.posts];
          boolpostsComments=items[Variables.postsComments];
          boolshares=items[Variables.shares];
          boolsharesComments=items[Variables.sharesComments];
          
          
          chrome.storage.local.get([DateContainer.posts,DateContainer.postsComments,DateContainer.shares,DateContainer.sharesComments],function ( items) {
          
          var Vposts=new Date(items[DateContainer.posts]);
          var VpostsComments=new Date(items[DateContainer.postsComments]);
          var Vshares=new Date(items[DateContainer.shares]);
          var VsharesComments=new Date(items[DateContainer.sharesComments]);
          
          if(boolposts){var result=RssReader(Links.postUrl,Vposts,DateContainer.posts,Messages.PostsUpdated);}
          if(boolpostsComments){var result=RssReader(Links.posts_commentsUrl,VpostsComments,DateContainer.postsComments,Messages.CommentsUpdated); }
          if(boolshares){var result=RssReader(Links.sharesUrl,Vshares,DateContainer.shares,Messages.SharesUpdated);}
          if(boolsharesComments){var result=RssReader(Links.shares_CommentsUrl,VsharesComments,DateContainer.sharesComments,Messages.SharesCommentsUpdated);}
          
          });
          });
              }
          });
          }
          خطوط اول تابع alarmManager وظیفه‌ی خواندن مقدار interval را که در init.js ذخیره کرده‌ایم، دارند که به طور پیش فرض 10 ذخیره شده است تا تایمر یا آلارم خود را بر اساس آن بسازیم. در خط chrome.alarms.create یک آلارم با نام rssinterval می‌سازد و قرار است هر 10 دقیقه وظایفی که بر دوشش گذاشته می‌شود را اجرا کند (استفاده از api جهت دسترسی به آلارم نیاز به مجوز "alarms" دارد). وظایفش از طریق یک listener که بر روی رویداد chrome.alarms.onAlarm  گذاشته شده است مشخص می‌شود. در خط بعدی مشخص می‌شود که این رویداد به خاطر چه آلارمی صدا زده شده است. البته از آنجا که ما یک آلارم داریم، نیاز چندانی به این کد نیست. ولی اگر پروژه شما حداقل دو آلارم داشته باشد نیاز است مشخص شود که کدام آلارم باعث صدا زدن این رویداد شده است. در مرحله بعد مشخص می‌کنیم که کاربر قصد بررسی چه قسمت‌هایی از سایت را داشته است و در تابع callback آن هم تاریخ آخرین تغییرات هر بخش را می‌خوانیم و در متغیری نگه داری می‌کنیم. هر کدام را جداگانه چک کرده و تابع RssReader را برای هر کدام صدا می‌زنیم. این تابع 4 پارامتر دارد:
          1. آدرس فیدی که قرار است از روی آن بخواند
          2. آخرین به روزسانی که از سایت داشته متعلق به چه تاریخی است.
          3. نام کلید ذخیره سازی تاریخ آخرین تغییر سایت که اگر بررسی شد و مشخص شد سایت به روز شده است، تاریخ جدید را روی آن ذخیره کنیم.
          4. در صورتی که سایت به روز شده باشد نیاز است پیامی را برای کاربر نمایش دهیم که این پیام را در اینجا قرار می‌دهیم.
          کد تابع rssreader
          function RssReader(URL,lastupdate,datecontainer,Message) {
                      var feed = new google.feeds.Feed(URL);
                      feed.setResultFormat(google.feeds.Feed.XML_FORMAT);
                              feed.load(function (result) {
          if(result!=null)
          {
          var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
          var RssUpdate=new Date(strRssUpdate);
          
          if(RssUpdate>lastupdate)
          {
          SaveDateAndShowMessage(datecontainer,strRssUpdate,Message)
          }
          }
          });
          }
          در خط اول فید توسط گوگل خوانده میشود، در خط بعدی ما به گوگل میگوییم که فید خوانده شده را چگونه به ما تحویل دهد که ما قالب xml را خواسته ایم و در خط بعدی اطلاعات را در متغیری به اسم result قرار میدهد که در یک تابع برگشتی آن را در اختیار ما میگذارد. از آن جا که ما قرار است تگ lastBuildDate را بخوانیم که پنجمین تگ اولین گره در اولین گره به حساب می‌آید، خط زیر این دسترسی را برای ما فراهم می‌کند و چون تگ ما در یک مکان ثابت است با همین تکه کد، دسترسی مستقیمی به آن داریم:
          var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
          مرحله بعد تاریخ را که در قالب رشته‌ای است، تبدیل به تاریخ کرده و با lastupdate یعنی آخرین تغییر قبلی مقایسه می‌کنیم و اگر تاریخ برگرفته از فید بزرگتر بود، یعنی سایت به روز شده است و تابع SaveDateAndShowMessage را صدا می‌زنیم که وظیفه ذخیره سازی تاریخ جدید و ایجاد notification را به عهده دارد و سه پارامتر کلید ذخیره سازی و مقدار آن و پیام را به آن پاس می‌کنیم.

          کد تابع SaveDateAndShowMesage
          function SaveDateAndShowMessage(DateField,DateValue,Message)
          {
          var params={
          }
          params[DateField]=DateValue;
          
          chrome.storage.local.set( params,function(){
          
          var options={
            type: "basic",
             title: Messages.SiteUpdated,
             message: Message,
             iconUrl: "icon.png"
          }
          chrome.notifications.create("",options,function(){
          chrome.notifications.onClicked.addListener(function(){
          chrome.tabs.create({'url': WebLinks.Home}, function(tab) {
          });
          });
          });
          });
          }
          خطوط اول مربوط به ذخیره تاریخ است و دومین نکته نحوه‌ی ساخت نوتیفکیشن است. اجرای یک notification  نیاز به مجوز "notifications " دارد که مجوز آن در manifest به شرح زیر است:
          "permissions": [
              "storage",
               "tabs",
           "alarms",
           "notifications"
            ]
          در خطوط بالا سایر مجوزهایی که در طول این دوره به کار اضافه شده است را هم می‌بینید.
          برای ساخت نوتیفکیشن از کد chrome.notifications.create استفاده می‌کنیم که پارامتر اول آن کد یکتا یا همان ID جهت ساخت نوتیفیکیشن هست که میتوان خالی گذاشت و دومی تنظیمات ساخت آن است؛ از قبیل عنوان و آیکن و ... که در بالا به اسم options معرفی کرده ایم و در آگومان دوم آن را معرفی کرده ایم و آرگومان سوم هم یک تابع callback است که نوشتن آن اجباری است. options شامل عنوان، پیام، آیکن و نوع notification می‌باشد که در اینجا basic انتخاب کرده‌ایم. برای دسترسی به دیگر خصوصیت‌های options به اینجا و برای داشتن notification‌های زیباتر به عنوان rich notification به اینجا مراجعه کنید. برای اینکه این امکان باشد که کاربر با کلیک روی notification به سایت هدایت شود باید در تابع callback مربوط به notifications.create این کد اضافه گردد که در صورت کلیک یک تب جدید با آدرس سایت ساخته شود:
          chrome.notifications.create("",options,function(){
          chrome.notifications.onClicked.addListener(function(){
          chrome.tabs.create({'url': WebLinks.Home}, function(tab) {
          });});
          });

          نکته مهم:  پیشتر معرفی آیکن به صورت بالا کفایت میکرد ولی بعد از این باگ  کد زیر هم باید جداگانه به manifest اضافه شود:
          "web_accessible_resources": [
              "icon.png"
            ]


          خوب؛ کار افزونه تمام شده است ولی اجازه دهید این بار امکانات افزونه را بسط دهیم:
          من می‌خواهم برای افزونه نیز قسمت تنظیمات داشته باشم. برای دسترسی به options میتوان از قسمت مدیریت افزونه‌ها در مرورگر یا حتی با راست کلیک روی آیکن browser action عمل کرد. در اصل این قسمت برای تنظیمات افزونه است ولی ما به خاطر آموزش و هم اینکه افزونه ما UI خاصی نداشت تنظیمات را از طریق browser action پیاده سازی کردیم و گرنه در صورتی که افزونه شما شامل UI خاصی مثلا نمایش فید مطالب باشد، بهترین مکان تنظیمات، options است. برای تعریف options در manifest.json به روش زیر اقدام کنید:
          "options_page": "popup.html"
          همان صفحه popup را در این بخش نشان میدهم و اینبار یک کار اضافه‌تر دیگر که نیاز به آموزش ندارد اضافه کردن input  با Type=number است که برای تغییر interval به کار می‌رود و نحوه ذخیره و بازیابی آن را در طول دوره یاد گرفته اید.

          جایزگزینی صفحات یا 
           Override Pages
          بعضی صفحات مانند بوک مارک و تاریخچه فعالیت‌ها History و همینطور newtab را می‌توانید جایگزین کنید. البته یک اکستنشن میتواند فقط یکی از صفحات را جایگزین کند. برای تعیین جایگزین در manifest اینگونه عمل می‌کنیم:
          "chrome_url_overrides": {
              "newtab": "newtab.htm"
            }

          ایجاد یک تب اختصاصی در Developer Tools
          تکه کدی که باید manifest اضافه شود:
          "devtools_page": "devtools.htm"
          شاید فکر کنید کد بالا الان شامل مباحث ui و ... می‌شود و بعد به مرورگر اعمال خواهد شد؛ در صورتی که اینگونه نیست و نیاز دارد چند خط کدی نوشته شود. ولی مسئله اینست که کد بالا تنها صفحات html را پشتیبانی می‌کند و مستقیما نمی‌تواند فایل js را بخواند. پس صفحه بالا را ساخته و کد زیر را داخلش می‌گذاریم:
          <script src="devtools.js"></script>
          فایل devtools.js هم شامل کد زیر می‌شود:
          chrome.devtools.panels.create(
              "Dotnettips Updater Tools", 
              "icon.png", 
              "devtoolsui.htm",
              function(panel) {
              }
          );
          خط chrome.devtools.panels.create یک پنل یا همان تب را ساخته و در پارامترهای بالا به ترتیب عنوان، آیکن و صفحه‌ای که باید در آن رندر شود را دریافت می‌کند و پس از ایجاد یک callback اجرا می‌شود. اطلاعات بیشتر

          APIها
          برای دیدن لیست کاملی از API‌ها می‌توانید به مستندات آن رجوع کنید و این مورد را به یاد داشته باشید که ممکن است بعضی api‌ها در بعضی موارد پاسخ ندهند. به عنوان مثال در content scripts نمی‌توانید به chrome.devtools.panels دسترسی داشته باشید یا اینکه در DeveloperTools  دسترسی به DOM میسر نیست. پس این مورد را به خاطر داشته باشید. همچنین بعضی api‌ها از نسخه‌ی خاصی به بعد اضافه شده‌اند مثلا همین مثال قبلی devtools از نسخه 18 به بعد اضافه شده است و به این معنی است با خیال راحت می‌توانید از آن استفاده کنید. یا آلارم‌ها از نسخه 22 به بعد اضافه شده‌اند. البته خوشبختانه امروزه با دسترسی آسانتر به اینترنت و آپدیت خودکار مرورگرها این مشکلات دیگر آن چنان رخ نمی‌دهند.

          Messaging
          همانطور که در بالا اشاره شد شما نمی‌توانید بعضی از apiها را در بعضی جاها استفاده کنید. برای حل این مشکل می‌توان از messaging استفاده کرد که دو نوع تبادلات پیغامی داریم:
          1. One-Time Requests یا درخواست‌های تک مرتبه‌ای
          2. Long-Lived Connections یا اتصالات بلند مدت یا مصر

          درخواست‌های تک مرتبه ای
            این درخواست‌ها همانطور که از نامش پیداست تنها یک مرتبه رخ می‌دهد؛ درخواست را ارسال کرده و منتظر پاسخ می‌ماند. به عنوان مثال به کد زیر که در content script است دقت کنید:
          window.addEventListener("load", function() {
              chrome.extension.sendMessage({
                  type: "dom-loaded", 
                  data: {
                      myProperty   : "value" 
                  }
              });
          }, true);
          کد بالا یک ارسال کننده پیام است. موقعی که سایتی باز می‌شود، یک کلید با مقدارش را ارسال می‌کند و کد زیر در background گوش می‌ایستد تا اگر درخواستی آمد آن را دریافت کند:
          chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
              switch(request.type) {
                  case "dom-loaded":
                      alert(request.data.myProperty   ); 
                  break;
              }
              return true;
          });

          اتصالات بلند مدت یا مصر
          اگر نیاز به یک کانال ارتباطی مصر و همیشگی دارید کد‌ها را به شکل زیر تغییر دهید
          contentscripts
          var port = chrome.runtime.connect({name: "my-channel"});
          port.postMessage({myProperty: "value"});
          port.onMessage.addListener(function(msg) {
              // do some stuff here
          });

          background
          chrome.runtime.onConnect.addListener(function(port) {
              if(port.name == "my-channel"){
                  port.onMessage.addListener(function(msg) {
                      // do some stuff here
                  });
              }
          });

          نمونه کد
          نمونه کدهایی که در سایت گوگل موجود هست می‌توانند کمک بسیاری خوبی باشند ولی اینگونه که پیداست اکثر مثال‌ها مربوط به نسخه‌ی یک manifest است که دیگر توسط مرورگرها پشتیبانی نمی‌شوند و مشکلاتی چون اسکریپت inline و CSP که در بالا اشاره کردیم را دارند و گوگل کدها را به روز نکرده است.

          دیباگ کردن و پک کردن فایل‌ها برای تبدیل به فایل افزونه Debugging and packing
          برای دیباگ کردن کد‌ها می‌توان از دو نمونه console.log و alert برای گرفتن خروجی استفاده کرد و همچنین ابزار  Chrome Apps & Extensions Developer Tool هم نسبتا امکانات خوبی دارد که البته میتوان از آن برای پک کردن اکستنشن نهایی هم استفاده کرد. برای پک کردن روی گزینه pack کلیک کرده و در کادر باز شده گزینه‌ی pack را بزنید. برای شما دو نوع فایل را در مسیر والد دایرکتوری extension نوشته شده درست خواهد کرد که یکی پسوند crx دارد که می‌شود همان فایل نهایی افزونه و دیگری هم پسوند pem دارد که یک کلید اختصاصی است و باید برای آپدیت‌های آینده افزونه آن را نگاه دارید. در صورتی که افزونه را تغییر دادید و خواستید آن را به روز رسانی کنید موقعی که اولین گزینه pack را می‌زنید و صفحه باز می‌شود قبل از اینکه دومین گزینه pack را بزنید، از شما می‌خواهد اگر دارید عملیات به روز رسانی را انجام می‌دهید، کلید اختصاصی آن را وارد نمایید و بعد از آن گزینه pack را بزنید:


          آپلود نهایی کار در Google web store

          برای آپلود نهایی کار به google web store که در آن تمامی برنامه‌ها و افزونه‌های کروم قرار دارند بروید. سمت راست آیکن تنظیمات را بزنید و گزینه developer dashboard را انتخاب کنید تا صفحه‌ی آپلود کار برای شما باز شود. دایرکتوری محتویات اکستنشن را zip کرده و آپلود نمایید. توجه داشته باشید که محتویات و سورس خود را باید آپلود کنید نه فایل crx را. بعد از آپلود موفقیت آمیز، صفحه‌ای ظاهر می‌شود که از شما آیکن افزونه را در اندازه 128 پیکسل میخواهد بعلاوه توضیحاتی در مورد افزونه، قیمت گذاری که به طور پیش فرض به صورت رایگان تنظیم شده است، لینک وب سایت مرتبط، لینک محل پرسش و پاسخ برای افزونه، اگر لینک یوتیوبی در مورد افزونه دارید، یک شات تصویری از افزونه و همینطور چند تصویر برای اسلایدشو سازی که در همان صفحه استاندارد آن‌ها را توضیح می‌دهد و در نهایت گزینه‌ی جالب‌تر هم اینکه اکستنشن شما برای چه مناطقی تهیه شده است که متاسفانه ایران را ندیدم که می‌توان همه موارد را انتخاب کرد. به خصوص در مورد ایران که آی پی‌ها هم صحیح نیست، انتخاب ایران چنان تاثیری ندارد و در نهایت گزینه‌ی publish را می‌زنید که متاسفانه بعد از این صفحه درخواست می‌کند برای اولین بار باید 5 دلار آمریکا پرداخت شود که برای بسیاری از ما این گزینه ممکن نیست.

          سورس پروژه را می‌توانید از اینجا ببینید و خود افزونه را از اینجا دریافت کنید.

           
          نظرات مطالب
          شروع کار با webpack - قسمت اول
          در پوشه‌ای که می‌خواهید این دستور را اجرا کنید، ابتدا دستور npm init را اجرا کنید تا فایل package.json ساخته شود.
          نظرات مطالب
          شروع به کار با EF Core 1.0 - قسمت 3 - انتقال مهاجرت‌ها به یک اسمبلی دیگر
          نکته تکمیلی‌تر جهت ایجاد sln در این حالت استفاده از دستور زیر برای ساخت یک solution جدید با نام پوشه والد است:
          dotnet new sln
          سپس برای هر یکی از فایل‌های پروژه یک دایرکتوری ایجاد کرده و پروژه مربوط به هر کدام را داخل آن ایجاد میکنیم.

           سپس از طریق دستور زیر آن‌ها را در sln ثبت می‌نماییم: 
          >dotnet sln add mymvc/mymvc.csproj
          Project `mymvc\mymvc.csproj` added to the solution.
          >dotnet sln add models/models.csproj
          Project `models\models.csproj` added to the solution.
          مطالب
          بیلد سیستم گریدل Gradle Build System
          امروزه در بسیاری از محیط‌های برنامه نویسی جاوا و اندروید، استفاده از این سیستم رایج است. ولی هنوز دیده می‌شود عده‌ای نسبت به آن دید روشنی ندارند و برای آن‌ها ناشناخته است و در حد یک سیستم کانفیگ آن را می‌شناسند. در این مقاله قصد داریم که مفهوم روشن‌تری از این سیستم را داشته باشم و بدانیم هدف آن چیست و چگونه کار می‌کند تا از این به بعد دیگر آن را به چشم یک کانفیگ کننده‌ی ساده نگاه نکنیم.  قبل از هر چیزی بهتر است که با تعدادی از اصطلاحات آن آشنا شویم تا در متن مقاله به مشکل برنخوریم:

          DSL یا Domain Specific languages به معنی زبان‌هایی با دامنه محدود است که برای اهداف خاصی نوشته می‌شوند و تنها بر روی یک جنبه از هدف تمرکز دارند. این زبان‌ها به شما اجازه نمی‌دهند که یک برنامه را به طور کامل با آن بنویسید. بلکه به شما اجازه می‌دهند به هدفی که برای آن نوشته شده‌اند، برسید. یکی از این زبان‌ها همان  css هست که با آن کار میکنید. این زبان به صورت محدود تنها بر روی یک جنبه و آن، تزئین سازی المان‌های وب، تمرکز دارد. در وقع مثل زبان سی شارپ همه منظوره نیست و محدوده‌ای مشخص برای خود دارد. به این نوع از زبان‌های DSL، نوع اکسترنال هم می‌گویند. چون زبانی مستقل برای خود است و به زبان دیگری وابستگی ندارد. ولی در یک زبان اینترنال، وابستگی به زبان دیگری وجود دارد. مثل Fluent Interface‌ها که به ما شیوه آسانی از دسترسی به جنبه‌های یک شیء را می‌دهد. برای آشنایی هر چه بیشتر با این زبان‌ها و ساختار آن، کتاب Domain Specific languages نوشته آقای مارتین فاولر توصیه می‌شود.

          Groovy یک زبان شیء گرای DSL هست که برای پلتفرم جاوا ساخته شده است. برای اطلاعات بیشتر در مورد این زبان، صفحه ویکی ، میتواند مفید واقع شود.

          از دیرباز سیستم‌های Ant و Maven وجود داشتند و کار آن‌ها اتوماسیون بعضی اعمال بود. ولی بعد از مدتی سیستم Gradle یا جمع کردن نقاط قوت آن‌ها و افزودن ویژگی‌های قدرتمندتری به خود، پا به میدان گذاشت تا راحتی بیشتری را برای برنامه نویس فراهم کند. از ویژگی‌های گریدل می‌توان داشتن زبان گرووی اشاره کرده که قدرت بیشتری را نسبت به سایر سیستم‌ها داشت و مزیت مهم دیگر این بود که انعطاف بالایی را جهت افزودن پلاگین‌ها داشت و گوگل با استفاده از این قابلیت،  پشتیبانی از گریدل را در اندروید استادیو نیز گنجاند تا راحتی بیشتری را در اتوماسیون وظایف سیستمی ایجاد کند. در واقع آنچه شما در سیستم گریدل کار میکنید و اطلاعات خود را با آن کانفیگ میکنید، پلاگینی است که از سمت گوگل در اختیار شما قرار گرفته است و در مواقع خاص این وظایف توسط پلاگین‌ها اجرا می‌شوند.
          گریدل به راحتی از سایت رسمی آن قابل دریافت است و می‌توان آن را در پروژه‌های جاوایی که مدنظر شماست، دریافت کنید و با استفاده از خط فرمان، با آن تعامل کنید. هر چند امروزه اکثر ویراستارهای جاوا از آن پشتیبانی می‌کنند.

          گریدل یک ماهیت توصیفی دارد که شما تنها لازم است اعمالی را برای آن توصیف کنید تا بقیه کارها را انجام دهد. گریدل در پشت صحنه از یک "گراف جهت دار بدون دور" Directed Acycllic Graph یا به اختصار DAG  استفاده می‌کند و طبق آن ترتیب وظایف یا task‌ها را دانسته و آن‌ها را اجرا می‌کند. گریدل با این DAG، سه فاز آماده سازی، پیکربندی و اجرا را انجام می‌دهد.
          • در مرحله آماده سازی ما به گریدل می‌گوییم چه پروژه یا پروژه‌هایی نیاز به بیلد شدن دارند. در اندروید استادیو،  این مرحله در فایل settings.gradle انجام می‌شود؛ شما در این فایل مشخص می‌کنید چه پروژه‌های نیاز به بیلد شدن توسط گریدل دارند. ساختار این فایل به این شکل است:
          include ':ActiveAndroid-master', ':app', ':dbutilities'
          در این فایل سه پروژه برای گریدل مشخص شده‌اند. البته از نگاه Intellij سه ماژول معرفی شده‌اند و این فایل برای یک پروژه اختیاری است. گریدل برای پیدا کردن این فایل، از الگوریتم‌های متفاوتی استفاده می‌کند.
          1. در اولین مرحله انتظار دارد که فایل settings در دایرکتوری جاری باشد و اگر آن را پیدا کرد آن را مورد استفاده قرار می‌دهد؛ در غیر اینصورت مرحله بعدی را آغاز می‌کند.
          2. در مرحله دوم،  در این دایرکتوری به دنبال دایرکتوری به نام master میگردد و اگر در آن هم یافت نکرد مرحله سوم را آغاز می‌کند.
          3. در مرحله سوم، جست و جو در دایرکتوری والد انجام می‌شود
          4. چنانچه این فایل را در هیچ یک از احتمالات بالا نیابد، همین پروژه جاری را تشخیص خواهد داد.
          اسامی پروژه‌های بالا با این تفکر هستند که در دایرکتوری یا فضای کاری جاری قرار گرفته‌اند که به آن IncludeFlat می‌گویند. ولی چنانچه آدرس پروژه‌ای در وضعیت خاص قرار بگیرد، می‌توان اینگونه مسیر آن را تغییر داد:
          include ':ActiveAndroid-master', ':app', ':dbutilities'
          project('dbutilities').projectDir=new File(settingsDir,'../dir1/dir2');
          الان پروژه dbutiilities در سطح بالاتری از دایرکتوری جاری قرار گرفته است.

          • در مرحله پیکربندی، وظایف یا task‌ها را معرفی می‌کنیم. این عمل پیکربندی توسط فایل build.gradle که برای پروژه اصلی و هر زیر پروژه‌ای که مشخص شده‌اند، صورت می‌گیرد. در این فایل شما می‌توانید خواص و متدهایی را تعریف و و ظایفی را مشخص کنید.
            در پروژه اصلی، فایل BuildGradle شامل خطوط زیر است:
          buildscript {
              repositories {
                  jcenter()
              }
              dependencies {
                  classpath 'com.android.tools.build:gradle:1.5.0'
              }
          }
          
          allprojects {
              repositories {
                  jcenter()
          
              }
          }
          در کلوژر buildscript مخازنی را که کتابخانه‌های نامبرده (وابستگی ها) در این کلوژ قرار میگیرند، معرفی میکنیم. در کلوژر بعدی تنظمیاتی را که برای همه پروژه‌ها اعمال می‌شوند، انجام می‌دهیم که در آن به معرفی مخزن jcenter پرداختیم. کتابخانه‌ای که در کلوژر buildscript صدا زدیم،  همان پلاگینی است که گوگل برای گریدل منتشر کرده که ما به آن ابزار بیلد می‌گوییم. حال به سراغ دیگر فایل‌های منحصر به فرد هر پروژه بروید. در این فایل‌ها،  شما تنظیمات پیش‌فرضی را می‌بینید که یکی از آن‌ها نسخه بندی پروژه است. پروژه‌های وابسته‌ای را که از مخازن باید دریافت شوند، شامل می‌شود و بسیاری از گزینه‌های دیگری که برای شما مهیا شده است تا در فاز پیکربندی، وظایف را بسازید.

          در مرحله اجرا هم این وظایف را اجرا میکنیم.  تمامی این سه عملیات توسط فایل و دستوری به نام gradlew که برگرفته از gradleWrapper می‌باشد انجام می‌شود. اگر در ترمینال اندروید استادیو این عبارت را تایپ کنید، می‌توانید در ادامه دستور پیام‌های مربوط به این عملیات را ببینید و ترتیب اجرای فازها را مشاهده کنید.
          بیایید یک task را تعریف کنیم
          task mytask <<{
              println ".net tips task in config phase"
          }
          در ابتدا عبارت task را به عنوان معرفی task می‌آوریم و سپس نام آن را وارد می‌کنیم. بعد از آن ما از عبارت‌های شیفت چپ>> استفاده کردیم. این عبارت شیفت چپ به این معناست که تسک مربوطه را آخر از همه وظایف اجرا کن که این عمل از طریق اجرای متدی به نام doLast صورت میگیرد. اگر در ترمینال اندروید عبارت زیر را تایپ کنید، متن مورد نظر باید نمایش یابد:
          gradlew mytask
          برای نمایش اطلاعات بیشتر می‌توانید از فلگ info هم استفاده کنید:
          gradlew --info mytask
          حال شاید بگویید من در بعضی از سایت‌ها یا مستندات و یا پروژه‌های دیگر دیده‌ام که عبارت >> را قرار نمی‌دهند. در این مورد باید گفت که آن‌ها در واقع تسک‌های اجرایی نیستند و برای پیکربندی به کار می‌روند و در فاز پیکربندی هم اجرا می‌شوند که در ادامه نمونه آن را خواهیم دید.
          اگر بخواهید خودتان دستی یک تسک پیکربندی را به یک تسک اجرایی تبدیل کنید، می‌توانید متد doLast را صدا بزنید. کد زیر را توسط gradlew اجرا کنید؛ به همراه اطلاعات verbose تا ببینید که هر کدام از پیام‌ها در کدام بخش چاپ می‌شوند. پیام اول در فاز پیکربندی و پیام دوم در فاز اجرایی چاپ می‌شوند.
          task mytask {
              println ".net tips task in config phase"
          
              doLast{
                  println ".net tips task in exe phase"
              }
          }

          یکی از کارهایی که در یک تسک میتوانید انجام دهید این است که آن را به یک تسک دیگر وابسته کنید. به عنوان مثال ما قصد داریم بعد از تسک mytask1،  تسک my task2 اجرا شود و زمان پایان تسک mytask1 را در خروجی نمایش دهیم. برای اینکار باید بین تسک‌ها  یک وابستگی ایجاد شود و سپس با متد doLast کد خودمان را اجرایی نماییم. البته توجه داشته باشید که این وابستگی‌ها تنها به تسک‌های داخل فایل گریدل انجام می‌شود و نه تسک‌های پلاگین‌ها یا وابستگی هایی که تعریف می‌کنیم.
          task mytask1 << {
              println ".net tips is the best"
          }
          
          task mytask2() {
              dependsOn mytask1
          
              doLast{
                  Date time=Calendar.getInstance().getTime();
                  SimpleDateFormat formatter=new SimpleDateFormat("HH:mm:ss , YYYY/MM/dd");
                  println "mytask1 is done at " + formatter.format(time);
          
              }
          }
          سپس تسک شماره دو را صدا می‌زنیم. در اینجا جون تسک شماره دو به تسک شماره یک وابسته است، ابتدا تسک شماره یک اجرا شده و سپس نوبت تسک شماره دو می‌شود.
          gradlew --info mytask2
          خروجی کار:
          Executing task ':app:mytask1' (up-to-date check took 0.003 secs) due to:
            Task has not declared any outputs.
          خروجی تسک شماره یک
          .net tips is the best       
          :app:mytask1 (Thread[main,5,main]) completed. Took 0.046 secs.
          :app:mytask2 (Thread[main,5,main]) started.
          :app:mytask2                 
          Executing task ':app:mytask2' (up-to-date check took 0.0 secs) due to:
            Task has not declared any outputs.
          خروجی تسک شماره دو
          mytask1 is done at 04:03:09 , 2016/07/07
          :app:mytask2 (Thread[main,5,main]) completed. Took 0.075 secs.
                         
          BUILD SUCCESSFUL

          در گریدل مخالف doLast یعنی doFirst را نیز داریم ولی عملگر جایگزینی برای آن وجود ندارد و مستقیما باید آن را پیاده سازی کنید. خود گریدل به طور پیش فرض نیز تسک‌های آماده ای نیز دارد که می‌توانید در مستندات آن بیابید. به عنوان مثال یکی از تسک‌های مفید و کاربردی آن تسک کپی کردن هست که از طریق آن می‌توانید فایلی یا فایل‌هایی را از یک مسیر به مسیر دیگر کپی کنید. برای استفاده از چنین تسکهایی، باید تسک‌های خود را به شکل زیر به شیوه اکشن بنویسید:
          task mytask(type:Copy) {
              dependsOn mytask1
          
              doLast{
                  from('build/apk')
                          {
                              include '**/*.apk'
                          }
                  into '.'
              }
          
          }
          در تسک بالا بعد از اجرای تسک شماره یک، آخرین کاری که انجام می‌شود این است که فایل‌های apk موجود در زیر دایرکتوری‌های مسیر from به ریشه اصلی کپی خواهند شد. پس همانطور که می‌بینید گریدل به راحتی عملیات اتوماسیون را انجام می‌دهد.
          برای نمایش تسک‌های موجود می‌توانید از گریدل درخواست کنید که لیست تمامی تسک‌های موجود را به شما نشان دهد. برای اینکار می‌توانید دستور زیر را صدا کنید:
          gradlew --info tasks
          بعد از مدتی تمامی تسک‌های موجود به صورت گروه بندی نمایش داده شده و تسک‌هایی که شما جدیدا اضافه کردید را با عنوان  other tasks نمایش خواهد داد:
          Other tasks           
          -----------           
          clean                 
          jarDebugClasses       
          jarReleaseClasses     
          mytask                
          mytask2               
          transformResourcesWithMergeJavaResForDebugUnitTest
          transformResourcesWithMergeJavaResForReleaseUnitTest
          اگر به تسک‌های خود گریدل نگاه کنید برای هر کدام توضیحی هم وجود دارد؛ اگر شما هم قصد دارید توضیحی اضافه کنید از خصوصیت description استفاده کنید:
          task mytask(type:Copy) {
          
              description "copy apk files to root directory"
          
              dependsOn mytask1
          
              doLast{
                  from('build/apk')
                          {
                              include '**/*.apk'
                          }
                  into '.'
              }
          
          }
          یکبار دیگر دستور نمایش تسک‌ها را صدا بزنید تا اینبار توضیح اضافه شده نمایش داده شود.
          یکی دیگر از نکات جالب در مورد گریدل این است که می‌تواند برای شما callback ارسال کند. بدین صورت که اگر اتفاقی خاصی افتاد، تسک خاصی را اجرا کند. به عنوان مثال ما در کد پایین تسکی را ایجاد کرده‌ایم که به ما این اجازه را می‌دهد، هر موقع تسکی در مرحله پیکربندی به بیلد اضافه می‌شود، تسک ما هم اجرا شود و نام تسک اضافه شده به بیلد را چاپ می‌کند.
          tasks.whenTaskAdded{
              task-> println "task is added $task.name"
          }

          گریدل امکانات دیگری چون بررسی استثناءها و ایجاد استثناءها را هم پوشش می‌دهد که می‌توانید در این صفحه آن را پیگیری کنید.

          Gradle Wrapper
          گریدل در حال حاضر مرتبا در حال تغییر و به روز رسانی است و اگر بخواهیم مستقیما با گریدل کار کنیم ممکن است که به مشکلاتی که در نسخه بندی است برخورد کنیم. از آنجا که هر پروژه‌ای که روی سیستم شما قرار بگیرد از نسخه‌ای متفاوتی از گریدل استفاده کند، باعث می‌شود که نتوانید نسخه مناسبی از گریدل را برای سیستم خود دانلود کنید. بدین جهت wrapper ایجاد شد تا دیگر نیازی به نصب گریدل پیدا نکنید. wrapper در هر پروژه میداند که که به چه نسخه‌ای از گریدل نیاز است. پس موقعی که شما دستور gradlew را صدا می‌زنید در ویندوز فایل gradlew.bat صدا زده شده و یا در لینوکس و مک فایل شِل اسکریپت gradlew صدا زده می‌شود و wrapper به خوبی میداند که به چه نسخه‌ای از گریدل برای اجرا نیاز دارد و آن را از طریق دانلود فراهم می‌کند. اگر همینک دایرکتوری والد پروژه اندرویدی خود را نگاه کنید می‌توانید این دو فایل را ببینید.

          از آنجا که خود اندروید استادیو به ساخت wrapper اقدام میکند، شما راحت هستید. ولی اگر دوست دارید خودتان برای پروژه‌ای wrapper تولید کنید، مراحل زیر را دنبال کنید:
          برای ایجاد wrapper توسط خودتان باید گریدل را دانلود و روی سیستم نصب کنید و سپس دستور زیر را صادر کنید:
          gradle wrapper --gradle-version 2.4
          دستور بالا یک wrapper برای نسخه 2.4 ایجاد می‌کند.
          اگر میخواهید ببینید wrapper که اندروید استادیو شما دارد چه نسخه از گردیل را صدا میزند مسیر را از دایرکتوری پروژه دنبال کنید و فایل زیر را بگشایید:
          \gradle\wrapper\gradle-wrapper.properties
          هنگامی که گوگل قصد آپدیت نسخه گریدل شما را بکند این فایل را باز کرده و نسخه داخل آن را ویرایش میکند.

          این‌ها فقط مختصراتی از آشنایی با نحوه عملکر گریدل برای داشتن دیدی روشن‌تر نسبت به آن بود. برای آشنایی بیشتر با گریدل، باید مستندات رسمی آن را دنبال کنید.
          مطالب
          بارگذاری پویای کامپوننت‌های Angular به همراه امکان Lazy loading پویای ماژول‌ها

          در نسخه‌های قبل از Angular CLI 6.0، صرفا امکان Bundle کردن جداگانه‌ی ماژول‌هایی که در قسمت  loadChildren مرتبط با تنظیمات مسیریابی  ذکر شده بودند، وجود داشت. بنابراین در برخی از شرایط اگر نیاز به امکان بارگذاری ماژولی به صورت Lazy load بود، باید از سیستم مسیریابی استفاده می‌شد یا اینکه با یکسری ترفند، CLI و Webpack را مجبور به ساخت فایل chunk جداگانه برای ماژول مورد نظر می‌کردید. از زمان انتشار Angular CLI 6.0 امکان Lazy loading پویا نیز مهیا می‌باشد؛ به این ترتیب بدون وابستگی به سیستم مسیریابی، باز هم می‌توان از مزایای Lazy loading بهره برد. در این مطلب روش استفاده از این قابلیت و همچنین نحوه‌ی بارگذاری پویای یک کامپوننت مرتبط با یک ماژول Lazy load شده را بررسی خواهیم کرد. برای این منظور در ادامه با ایجاد یک TabLayout با استفاده از Angular Material Tabs با یکی از موارد پر استفاده‌ی قابلیت مذکور آشنا خواهیم شد.

          پیش نیازها

          کار را با طراحی و پیاده سازی TabService شروع می‌کنیم. برای این منظور یک سرویس را در فولدر services موجود در کنار CoreModule ایجاد خواهیم کرد؛ به این جهت ابتدا مدل‌های زیر را خواهیم داشت:

          import { Type, ValueProvider } from '@angular/core';
          
          export interface OpenNewTabModel {
            label: string;
            componentType: Type<any>;
            iconName: string;
            modulePath?: string;
            data?: ValueProvider[];
          }
          واسط تعریف شده‌ی در بالا به عنوان قرارداد مدل ورودی متد open مرتبط با سرویس TabService استفاده می‌شود. در اینجا componentType، نوع کامپوننت را مشخص می‌کند که قرار است داخل برگه‌ی جدید نمایش داده شود. modulePath هم به مسیر ماژولی که باید به صورت پویا بارگذاری شود، اشاره می‌کند. دلیل وجود خصوصیت data را نیز در ادامه خواهیم دید.
          import { TabItemComponent } from './tab-item-component';
          
          export interface TabItem {
            label: string;
            iconName: string;
            component: TabItemComponent;
          }

          OpenNewTabModel برای ارسال داده توسط مصرف کننده از این سرویس در نظر گرفته شده است. ولی واسط TabItem دارای خصوصیاتی می‌باشد که ما برای نمایش یک برگه‌ی جدید نیازمندیم. TabItemComponent نیز دارای خصوصیاتی است که مورد نیاز دایرکتیو« NgComponentOutlet» است. 

          import { Injector, NgModuleFactory, Type } from '@angular/core';
          
          export interface TabItemComponent {
            componentType: Type<any>;
            moduleFactory?: NgModuleFactory<any>;
            injector: Injector;
          }

          همانطور که اشاره شد، برای بارگذاری پویای یک کامپوننت از NgComponentOutlet استفاده خواهیم کرد؛ لذا اگر modulePath ای توسط مصرف کننده از TabService، مهیا شده باشد، لازم است ابتدا ماژول مورد نظر به صورت پویا بارگذاری شود و moduleFactory بدست آمده را به عنوان ورودی دایرکتیو مذکور ارسال کنیم. TabService، پیاده سازی به شکل زیر خواهد داشت:
          import { BehaviorSubject, Observable } from 'rxjs';
          import {
            Injectable,
            Injector,
            NgModuleFactory,
            NgModuleFactoryLoader
          } from '@angular/core';
          
          import { OpenNewTabModel } from '../models/open-new-tab-model';
          import { TabItem } from '../models/tab-item';
          
          @Injectable({
            providedIn: 'root'
          })
          export class TabService {
            private tabItemSubject: BehaviorSubject<TabItem[]> = new BehaviorSubject<
              TabItem[]
            >([]);
          
            constructor(
              private loader: NgModuleFactoryLoader,
              private injector: Injector
            ) {}
          
            get tabItems$(): Observable<TabItem[]> {
              return this.tabItemSubject.asObservable();
            }
          
            open(newTab: OpenNewTabModel) {
              if (newTab.modulePath) {
                this.loader
                  .load(newTab.modulePath)
                  .then((moduleFactory: NgModuleFactory<any>) => {
                    this.openInternal(newTab, moduleFactory);
                  });
              } else {
                this.openInternal(newTab);
              }
            }
          
            private openInternal(newTab: OpenNewTabModel, moduleFactory?: NgModuleFactory<any>) {
              const newTabItem: TabItem = {
                label: newTab.label,
                iconName: newTab.iconName,
                component: {
                  componentType: newTab.componentType,
                  moduleFactory: moduleFactory,
                  injector: newTab.data
                    ? Injector.create(newTab.data, this.injector)
                    : this.injector
                }
              };
          
              this.tabItemSubject.getValue().push(newTabItem);
              this.tabItemSubject.next(this.tabItemSubject.getValue());
            }
          
            close(index: number) {
              this.tabItemSubject.getValue().splice(index, 1);
              this.tabItemSubject.next(this.tabItemSubject.getValue());
            }
          }
          روش کار به این شکل می‌باشد که یک مخزن، برای لیست برگه‌های درخواستی برای نمایش، تحت عنوان tabItemSubject و از نوع BehaviorSubject در نظر گرفته شده تا مصرف کننده از این سرویس که قصد نمایش برگه‌ها را دارد، از تغییرات لیست موجود آگاه شود. عموما TabsComponent، مشترک پراپرتی فقط خواندنی ‎‎‎tabItems‎$ خواهد شد و بقیه بخش‌ها صرفا دستور گشودن برگه‌ی جدید را با متد open صادر خواهند کرد.
          یکی از وابستگی‌های این سرویس، وهله‌ای می‌باشد از کلاس  NgModuleFactoryLoader  که در سیستم مسیریابی نیز از همین کلاس برای بارگذاری ماژول‌ها استفاده می‌شود. البته نیاز است که یکی از پیاده سازی‌های این کلاس انتزاعی را به سیستم تزریق وابستگی‌ها نیز معرفی کنید:
          { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }
          در بدنه متد open، ابتدا بررسی می‌شود که اگر modulePath مشخص شده‌است، ماژول مورد نظر ابتدا توسط متد load مرتبط با وهله NgModuleFactoryLoader به صورت پویا بارگذاری شود و سپس با استفاده از moduleFactory بدست آمده، متد openInternal فراخوانی خواهد شد.
           در بدنه متد openInternal، تنهای نکته‌ای که ذکر آن اهمیت دارد، مرتبط است به مقداردهی خصوصیت injector شیء ایجاد شده. باتوجه به اینکه تا زمان نگارش مطلب جاری امکان کار با Input‌ها و Output‌های کامپوننت مورد نظر که قرار است با استفاده از NgComponentOutlet بارگذاری شود، وجود ندارد، لذا راه حل فعلی، استفاده از سیستم تزریق وابستگی‌ها می‌باشد. برای این منظور، با استفاده از متد استاتیک create کلاس Injector یک child injector ایجاد شده و ValueProvider‌های مشخص شده توسط خصوصیت data، به صورت خودکار رجیستر خواهند شد. در نهایت آگاه سازی مشترکین خصوصیت ‎‎‎tabItems‎با استفاده از فراخوانی متد next مرتبط با tabItemSubject انجام می‌گیرد.

          پیاده سازی TabsComponent
          import { Component, OnInit } from '@angular/core';
          
          import { TabService } from './../../../services/tab.service';
          
          @Component({
            selector: 'app-tabs',
            templateUrl: './tabs.component.html',
            styleUrls: ['./tabs.component.scss']
          })
          export class TabsComponent implements OnInit {
            constructor(public service: TabService) {}
          
            ngOnInit() {}
          }

          همانطور که عنوان شد، مشترک اصلی خصوصیت tabItems سرویس TabService، کامپوننت تعریف شده‌ی بالا می‌باشد. قالب مرتبط با آن به شکل زیر است:
          <mat-tab-group>
            <mat-tab
              *ngFor="let tabItem of (service.tabItems$ | async); let index = index"
            >
              <ng-template mat-tab-label>
                <mat-icon
                  class="icon"
                  aria-label="icon for tab"
                >{{tabItem.iconName}}</mat-icon>
                <span class="full">{{ tabItem.label }}</span>
              
                <mat-icon
                  class="close"
                  (click)="service.close(index)"
                  aria-label="close tab button"
                  >close</mat-icon
                >
                <!-- </button> -->
              </ng-template>
          
              <ng-container *ngIf="tabItem.component.moduleFactory">
                <ng-container
                  *ngComponentOutlet="
                    tabItem.component.componentType;
                    ngModuleFactory: tabItem.component.moduleFactory;
                    injector: tabItem.component.injector
                  "
                >
                </ng-container>
              </ng-container>
              <ng-container *ngIf="!tabItem.component.moduleFactory">
                <ng-container
                  *ngComponentOutlet="
                    tabItem.component.componentType;
                    injector: tabItem.component.injector
                  "
                >
                </ng-container>
              </ng-container>
            </mat-tab>
          </mat-tab-group>

          در تکه کد بالا، ابتدا با استفاده از وهله تزریق شده TabService در کامپوننت مذکور، به شکل زیر، مشترک تغییرات لیست برگه‌ها شده‌ایم و با استفاده از دایرکتیو ‎*ngFor به ازای تک تک tabItem‌های درخواست شده برای گشوده شدن، به شکل زیر کار وهله سازی پویا از کامپوننت مشخص شده انجام می‌شود:

          <ng-container *ngComponentOutlet="tabItem.component.componentType; ngModuleFactory: tabItem.component.moduleFactory; injector: tabItem.component.injector">
          </ng-container>

          خوب، با استفاده از آنچه تا اینجای مطلب بررسی شد، می‌توان یک سیستم راهبری مبتنی بر Tab را نیز برپا کرد که مطلب جدایی را می‌طلبد. برای تکمیل مکانیزم بارگذاری پویای ماژول‌ها، نیاز است تا مسیر ماژول مورد نظر را در فایل angular.json و بخش lazyModules به شکل زیر معرفی کنید:

          "build": {
                    "builder": "@angular-devkit/build-angular:browser",
                    "options": {
                      "outputPath": "dist/MaterialAngularTabLayout",
                      "index": "src/index.html",
                      "main": "src/main.ts",
                      "polyfills": "src/polyfills.ts",
                      "tsConfig": "src/tsconfig.app.json",
                      "assets": [
                        "src/favicon.ico",
                        "src/assets"
                      ],
                      "styles": [
                        "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
                        "src/styles.scss"
                      ],
                      "lazyModules": [
                        "src/app/lazy/lazy.module"
                      ],
                      "scripts": []
                    },

          به عنوان مثال قصد داریم ماژول LazyModule را به صورت پویا بارگذاری کرده و LazyComponent موجود در این ماژول را به صورت پویا در برگه‌ی جدیدی نمایش دهیم. برای این منظور کدهای فایل AppComponent.ts را به شکل زیر تغییر خواهیم داد:

          import { Component } from '@angular/core';
          import { IdModel } from './core/models/id-model';
          import { LazyComponent } from './lazy/lazy.component';
          import { OpenNewTabModel } from './core/models/open-new-tab-model';
          import { TabService } from './core/services/tab.service';
          
          @Component({
            selector: 'app-root',
            templateUrl: './app.component.html',
            styleUrls: ['./app.component.scss']
          })
          export class AppComponent {
            title = 'MaterialAngularTabLayout';
            constructor(private tabService: TabService) {}
            loadLazyComponent() {
              this.tabService.open(<OpenNewTabModel>{
                label: 'Loaded Lazy Component',
                iconName: 'thumb_up',
                componentType: LazyComponent,
                modulePath: 'src/app/lazy/lazy.module#LazyModule',
                data: [{ provide: IdModel, useValue: <IdModel>{ id: 1 } }]
              });
            }
          }

          در تکه کد بالا با تزریق TabService به سازنده‌ی آن، قصد گشودن برگه‌ی جدیدی را توسط متد open آن، داریم. در بدنه‌ی متد loadLazyComponent یک شیء با قرارداد OpenNewTabModel ایجاد شده و به عنوان آرگومان به متد open ارسال شده است. توجه داشته باشید که modulePath اینجا نیز به مانند خصوصیت loadChildren مرتبط با اشیاء مسیریابی، باید مقدار دهی شود. همچنین قصد داشتیم اطلاعاتی را نیز به کامپوننت مورد نظر ارسال کنیم؛ همانند مکانیزم مسیریابی که با پارامترها این چنین کارهایی صورت می‌پذیرد. در اینجا از یک کلاس به شکل زیر استفاده شده‌است:

          export class IdModel {
            constructor(public id: number) {}
          }

          در این صورت پیاده سازی LazyComponent نیز به شکل زیر خواهد بود:

          import { Component, OnInit } from '@angular/core';
          
          import { IdModel } from './../core/models/id-model';
          
          @Component({
            selector: 'app-lazy',
            templateUrl: './lazy.component.html',
            styleUrls: ['./lazy.component.scss']
          })
          export class LazyComponent implements OnInit {
            constructor(private model: IdModel) {}
          
            ngOnInit() {
              console.log(this.model);
            }
          }

          البته فراموش نکنید کامپوننتی را که نیاز است به صورت پویا بارگذاری شود، در قسمت entryComponents مرتبط با NgModule متناظر به شکل نیز معرفی کنید:

          import { CommonModule } from '@angular/common';
          import { LazyComponent } from './lazy.component';
          import { NgModule } from '@angular/core';
          
          @NgModule({
            imports: [CommonModule],
            declarations: [LazyComponent],
            entryComponents: [LazyComponent]
          })
          export class LazyModule {}

          با خروجی زیر:

          و chunk تولید شده برای ماژول مورد نظر:


          در صورتیکه در حالت production پروژه را بیلد کنید، هش مرتبط برای chunk تولید شده نیز ایجاد خواهد شد.


          کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید.