مطالب
OpenCVSharp #10
محاسبه و ترسیم Histogram تصاویر

هیستوگرام یک تصویر، توزیع میزان روشنایی آن تصویر را نمایش می‌دهد و در آن تعداد نقاط قسمت‌های روشن تصویر، ترسیم می‌شوند. محاسبه‌ی هیستوگرام تصاویر در حین دیباگ الگوریتم‌های پردازش تصویر، کاربرد زیادی دارند.
OpenCV به همراه متد توکاری است به نام cv::calcHist که قادر است هیستوگرام تعدادی آرایه را محاسبه کند و در C++ API آن قرار دارد. البته هدف اصلی این متد، انجام محاسبات مرتبط است و در اینجا قصد داریم این محاسبات را نمایش دهیم.


تغییر میزان روشنایی و وضوح تصاویر در OpenCV

همانطور که عنوان شد، کار هیستوگرام تصاویر، نمایش توزیع میزان روشنایی نقاط و اجزای آن‌ها است. بنابراین می‌توان جهت مشاهده‌ی تغییر هیستوگرام محاسبه شده با تغییر میزان روشنایی و وضوح تصویر، از متد ذیل کمک گرفت:
private static void updateBrightnessContrast(Mat src, Mat modifiedSrc, int brightness, int contrast)
{
    brightness = brightness - 100;
    contrast = contrast - 100;
 
    double alpha, beta;
    if (contrast > 0)
    {
        double delta = 127f * contrast / 100f;
        alpha = 255f / (255f - delta * 2);
        beta = alpha * (brightness - delta);
    }
    else
    {
        double delta = -128f * contrast / 100;
        alpha = (256f - delta * 2) / 255f;
        beta = alpha * brightness + delta;
    }
    src.ConvertTo(modifiedSrc, MatType.CV_8UC3, alpha, beta);
}
در اینجا src تصویر اصلی است. brightness و contrast، مقادیر میزان روشنایی و وضوح دریافتی از کاربر هستند. این مقادیر را می‌توان به متد ConvertTo ارسال کرد تا src را تبدیل به modifiedSrc نماید و وضوح و روشنایی آن‌را تغییر دهد.

پس از اینکه متد تغییر وضوح تصویر اصلی را تهیه کردیم، می‌توان به پنجره‌ی نمایش تصویر اصلی، دو tracker جهت دریافت brightness و contrast اضافه کرد و به این ترتیب امکان نمایش پویای تغییرات را مهیا نمود:
using (var src = new Mat(@"..\..\Images\Penguin.Png", LoadMode.AnyDepth | LoadMode.AnyColor))
{
    using (var sourceWindow = new Window("Source", image: src,
           flags: WindowMode.AutoSize | WindowMode.FreeRatio))
    {
        using (var histogramWindow = new Window("Histogram",
               flags: WindowMode.AutoSize | WindowMode.FreeRatio))
        {
            var brightness = 100;
            var contrast = 100;
 
            var brightnessTrackbar = sourceWindow.CreateTrackbar(
                    name: "Brightness", value: brightness, max: 200,
                    callback: pos =>
                    {
                        brightness = pos;
                        updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                    });
 
            var contrastTrackbar = sourceWindow.CreateTrackbar(
                name: "Contrast", value: contrast, max: 200,
                callback: pos =>
                {
                    contrast = pos;
                    updateImageCalculateHistogram(sourceWindow, histogramWindow, src, brightness, contrast);
                });
 
 
            brightnessTrackbar.Callback.DynamicInvoke(brightness);
            contrastTrackbar.Callback.DynamicInvoke(contrast);
 
            Cv2.WaitKey();
        }
    }
}
در اینجا src تصویر اصلی است. پنجره‌ی Source کار نمایش تصویر اصلی را به عهده دارد. همچنین به این پنجره، دو tracker اضافه شده‌اند تا کار دریافت مقادیر روشنایی و وضوح را از کاربر، مدیریت کنند.
پنجره‌ی دومی نیز به نام هیستوگرام در اینجا تعریف شده‌است. در این پنجره قصد داریم هیستوگرام تغییرات پویای تصویر اصلی را نمایش دهیم.



روش محاسبه‌ی هیستوگرام تصاویر و نمایش آن‌ها در OpenCVSharp

کدهای کامل محاسبه‌ی هیستوگرام تصویر اصلی تغییر یافته (modifiedSrc) و سپس نمایش آن‌را در پنجره‌ی histogramWindow، در ادامه ملاحظه می‌کنید:
private static void calculateHistogram1(Window histogramWindow, Mat src, Mat modifiedSrc)
{
    const int histogramSize = 64;
    int[] dimensions = { histogramSize }; // Histogram size for each dimension
    Rangef[] ranges = { new Rangef(0, histogramSize) }; // min/max
 
    using (var histogram = new Mat())
    {
        Cv2.CalcHist(
            images: new[] { modifiedSrc },
            channels: new[] { 0 },
            mask: null,
            hist: histogram,
            dims: 1,
            histSize: dimensions,
            ranges: ranges);
 
        using (var histogramImage = (Mat)(Mat.Ones(rows: src.Rows, cols: src.Cols, type: MatType.CV_8U) * 255))
        {
            // Scales and draws histogram
 
            Cv2.Normalize(histogram, histogram, 0, histogramImage.Rows, NormType.MinMax);
            var binW = Cv.Round((double)histogramImage.Cols / histogramSize);
 
            var color = Scalar.All(100);
 
            for (var i = 0; i < histogramSize; i++)
            {
                Cv2.Rectangle(histogramImage,
                    new Point(i * binW, histogramImage.Rows),
                    new Point((i + 1) * binW, histogramImage.Rows - Cv.Round(histogram.Get<float>(i))),
                    color,
                    -1);
            }
 
            histogramWindow.Image = histogramImage;
        }
    }
}
معادل متد cv::calcHist، متد Cv2.CalcHist در OpenCVSharp است. این متد آرایه‌ای از تصاویر را قبول می‌کند که در اینجا تنها قصد داریم با یک تصویر کار کنیم. به همین جهت آرایه‌های images، اندازه‌های آن‌ها و بازه‌های min/max این تصاویر تنها یک عضو دارند. خروجی این متد پارامتر hist آن است که توسط یک new Mat تامین شده‌است. مقدار dims به یک تنظیم شده‌است؛ زیرا در اینجا تنها قصد داریم شدت نقاط را اندازه گیری کنیم. پارامتر ranges مشخص می‌کند که مقادیر اندازه گیری شده باید در چه بازه‌ایی جمع آوری شوند.
پس از محاسبه‌ی هیستوگرام، یک تصویر خالی پر شده‌ی با عدد یک را توسط متد Mat.Ones ایجاد می‌کنیم. این تصویر به عنوان منبع تصویر هیستوگرام نمایش داده شده، مورد استفاده قرار می‌گیرد. سپس نیاز است اطلاعات محاسبه شده، در مقیاسی قرار گیرند که قابل نمایش باشد. به همین جهت با استفاده از متد Normalize، آن‌ها را در مقیاس و بازه‌ی ارتفاع تصویر، تغییر اندازه خواهیم داد. سپس به کمک متد مستطیل، خروجی آرایه هیستوگرام را در صفحه، با رنگ خاکستری مشخص شده توسط متد Scalar.All ترسیم خواهیم کرد.


همانطور که در این تصویر ملاحظه می‌کنید، با کدرتر شدن تصویر اصلی، هیستوگرام آن، توزیع روشنایی کمتری را نمایش می‌دهد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
پیاده سازی PWA در Angular
امروزه طراحی اپلیکیشن‌های موبایل بخش زیادی از جامعه را در برگرفته است و روز به روز در حال توسعه میباشند. موازی با رشد روز افزون و نیاز بیشتر به این اپلیکیشن‌ها فریمورک‌های زیادی نیز  ابداع شده اند. از جمله این فریم ورک‌ها می‌توان به موارد زیر اشاره کرد:

Ionic  , react native , flutter , xamarin ….

دیگر لازم نیست برای طراحی اپلیکیشن خود حتما از زبان‌های native  استفاده کنید. بیشتر فریم ورک‌های معرفی شده جاوا اسکریپتی میباشند.

 
مزایای نوشتن یک اپلیکیشن با  فریم ورک‌ها:

1-  نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورک‌ها، فریم ورک‌های جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر می‌شود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم می‌توان همزمان برای پلتفرم‌های دیگر خروجی گرفت.


معایب:

۱- برنامه نویس را محدود  میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده می‌کند.

اگر شما هم میخواهید از فریم ورک‌ها برای طراحی اپلیکیشن استفاده کنید در اینجا  می‌توانید اطلاعات بیشتری را درباره هر کدام از فریم ورک‌ها ببینید. هر کدام از این فریم ورک‌ها دارای مزایا معایب و همچنین رزومه کاری خوب می‌باشند. بیشتر فریم ورک‌های بالا در رندر آخر، همان کد native را تولید می‌کنند؛ مثلا اگر برنامه‌ای را با react native بنویسید، میتوانید همان برنامه را بر روی اندروید استودیو و کد native بالا بیارید. توضیحات  بالا مقایسه فریم ورک‌های Cross Platform با زبان‌های native بود. اما در این مقاله قصد آشنایی با تکنولوژی جدیدی را داریم که شما را قادر می‌سازد یک وب اپلیکیشن را با آن طراحی کنید.


 pwa چیست؟

pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشن‌های تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرم‌های مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحه‌ی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفاده‌ی از یک صفحه‌ی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحه‌ی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن داده‌ها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline  کار کند. شما حتی با pwa می‌توانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.


مزیت pwa

۱- یکی از مزیت‌های مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحه‌ی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان به‌روز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده می‌کند، دارای امنیت بالایی میباشد.


معایب

۱- محدود به مرورگر می‌باشد.
۲- هرچند امروزه اکثر مرورگر‌ها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگر‌ها و مرورگر‌های با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامه‌ی مبتنی بر os را نوشت و محدود به مرورگر میباشد


چند نکته درباره pwa

1- برای pwa  لزومی ندارد حتما از فریم ورک‌های spa استفاده کنید. شما از هر فریم ورک Client Side ای می‌توانید استفاده کنید.
۲- چون صفحات شما در پلتفرم‌های مختلف و با صفحه نمایش‌های مختلفی اجرا می‌شود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.

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

قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمت‌های مهم  Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازش‌ها در پس زمینه را بر عهده دارد.


با توجه به شکل بالا، Service Worker مانند یک لایه‌ی نرم افزاری مابین کلاینت و سرور قرار دارد که درخواست‌های داده شده از کلاینت را در صورت اتصال به اینترنت، ارسال کرده و مجددا response را برگشت میدهد‌. اگر به هر دلیلی اینترنت قطع باشد، درخواست به صورت آفلاین به کش مرورگر که توسط proxy ساخته شده است، فرستاده میشود و response را برگشت میدهد.


چند نکته  در رابطه با Service Worker

- نباید برای  نگهداری داده global از Service Worker استفاد کرد. برای استفاده از داده‌های Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker  به dom دسترسی ندارند.


 سناریو

 قبل از شروع، سناریوی پروژه را تشریح خواهیم کرد. رکن اصلی یک برنامه‌ی وب، UI آن میباشد و قصد داریم کاربران را متوجه کار با یک صفحه‌ی وبی نکنیم. قالبی که ما در این مثال در نظر گرفتیم خیلی شبیه به یک اپلیکیشن پلتفرم اندرویدی میباشد. یک کاربر با کشیدن منوی کشویی میتواند گزینه‌های خود را انتخاب نمایند. اولین گزینه‌ای که قصد پیاده سازی آن را داریم، ثبت کاربران میباشد. بعد از ثبت کاربران در یک Component جدا، کاربران را در یک جدول نمایش خواهیم داد.


 قبل از شروع لازم است به چند نکته زیر توجه کرد

1-  سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصله‌ی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
2 - هر اپلیکیشنی قطعا نیاز به یک وب سرویس دارد که واسط بین دیتابیس و اپلیکیشن باشد. ولی ما چون در این مثال به عنوان front end کار قصد طراحی  اپلیکیشن را داریم، برای حذف back end از firebase استفاده خواهیم کرد و مستقیما از انگولار به دیتابیس firebase کوئری خواهیم زد.


 شروع به کار

پیش نیاز‌های یک پروژه‌ی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیج‌های پروژه را نصب کنید:
 npm install
پس از نصب پکیج‌ها، با دستور ng serve، پروژه را اجرا کنید.

پس از اجرا باید خروجی بالا را داشته باشید. می‌خواهیم یکی از قابلیت‌های pwa را بررسی کنیم. بر روی صفحه کلیک راست کرده و وارد inspect شوید. وارد تب Application  شوید و Service Worker را انتخاب کنید.
 


قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید.
این بار صفحه No internet به معنی قطع بودن اینترنت نمایش داده میشود و شما دیگر نمی‌توانید بر روی اپلیکیشن خود فعالیتی داشته باشید! 


نصب pwa  بر روی پروژه

برای اضافه کردن pwa به پروژه وارد ریشه‌ی پروژه شوید و دستور زیر را وارد کنید:
 ng add @angular/PWA
پس از  اجرای دستور بالا، تغییراتی در پروژه لحاظ میشود از جمله در فایل‌هایindex.html,app-module,angular.json، همچنین یک پوشه‌ی جدید به نام icons در assets و دو فایل جیسونی زیر به روت پروژه اضافه شده‌اند:
 Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن می‌باشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
 Nsgw-config.json : این فایل نسبت به فایل manifest فنی‌تر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.

بعد از نصب pwa  بر روی پروژه، همه تنظیمات به صورت خودکار انجام می‌شود. البته می‌توانید تنظیمات مربوط به کش کردن داده‌ها را به صورت پیشرفته‌تر کانفیگ کنید.


  اجرا کردن وب اپلیکیشن

 برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
 ng build -- prod
سپس  با دستور زیر وارد پوشه‌ای که فایل‌های Build  شده در آن قرار دارند، شوید:
 cd dist/Web Application  Pwa
بعد با دستور زیر برنامه را اجرا کنید:
 Http-server -o
اگر چنانچه با دستور بالا به خطا برخوردید، با دستور زیر پکیج npm آن را نصب کنید:
 npm i http-server
اگر تا به اینجای کار درست پیش رفته باشید، پروژه را بدون هیچ تغییری مشاهده خواهیم کرد. inspect گرفته و وارد تب Application شوید. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید! اینبار برنامه بدون هیچ مشکلی کار میکند. حتی شما میتوانید در کنسول، برنامه را به صورت کامل stop کنید.


برسی فایل Nsgw-config.json 

وارد فایل Nsgw-config.json شوید: 

"$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }

    }
  ],
  "dataGroups": [
    {
      "name": "api-performance",
      "urls": [
        "https://api/**"
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 100,
        "maxAge": "3d"
      }
    }
  ]
}
 این فایل مربوط به کش کردن داده‌ها میباشند و میتواند شامل چند object زیر باشد. مهمترین آنها را بررسی خواهیم کرد:
  ۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
  ۲- Index : کش کردن  فایل مربوط به index.html
  ۳-  assetGroups : کش کردن فایل‌های مربوط به asset، شامل فایل‌های js، css  و  غیره
  ۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان داده‌های در حال اجرای اپلیکیشن را کش کرد. داده‌ی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعه‌ی بعد در حالت offline  اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن داده‌ها را از کش فراخوانی میکند. این عمل درباره post کردن داده‌ها هم صدق میکند.

خود dataGroups شامل چند object زیر میباشد:
  ۱- name :  یک نام انتخابی برای Groups  میباشد.
 ۲- urls  : شامل آرایه‌ای از آدرس‌ها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۳- cacheConfig  : که شامل
  ۱- maxSize  : حداکثر تعداد کش‌های مربوط به Groups .
  ۲- maxAge  : حداکثر  lifetime مربوط به کش.
  ۳- strategy  : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance  به معنی Cache-First باشد.


 پیاده سازی پیغام نمایش به‌روزرسانی جدید وب اپلیکیشن

در اپلیکیشن‌های native  وقتی به‌روزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر به‌روزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را به‌روزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات به‌روزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت به‌روزرسانی نسخه یا هر  پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled)
    {
      this.swUpdate.available.subscribe(()=> {

        if(confirm("New Version available.Load New Version?")){
          window.location.reload();
        }
      })
    }


وصل شدن به دیتابیس

برای اینکه بتوانیم یک وب اپلیکیشن کامل را طراحی کنیم، قطعا نیاز به دیتابیس داریم. برای دیتابیس از firebase استفاده میکنیم. قبل از شروع، وارد سایت firebase  شوید. پس از لاگین، بر روی Add Project کلیک کنید. بعد از انتخاب نام مناسب، create Project را انتخاب کنید. ساختار دیتابیس‌های firebase  شبیه nosqlها می‌باشد و بر پایه‌ی json کار میکنند. پس از ساخت پروژه و دریافت کد جاواسکریپتی زیر شروع به طراحی فیلد‌های دیتابیس خواهیم کرد.


در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode  ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلد‌های زیر را به آن اضافه کنید:
Age :number
Fullname :string
Mobile : string
مرحله‌ی بعد، config پروژه‌ی انگیولاری میباشد. وارد  vscode شوید و با دستور زیر پکیج firebase را اضافه کنید:
  npm install --save firebase @angular/fire
سپس وارد appmodule شوید و آن‌را به صورت زیر کانفیگ کنید:

@NgModule({
  declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environmentFirebase.firebase),
    AngularFireDatabaseModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
  ],
  providers: [FirebaseService],
  bootstrap: [AppComponent]
})
export class AppModule {}
خط ۱۰ مربوط به pwa است که هنگام نصب pwa اضافه شده‌است و خط ۸ و مربوط به apiKey فایربیس میباشد. می‌شود گفت environmentFirebase.firebase شبیه  connection string می‌باشد که به صورت زیر کانفیگ شده است:
export const environment ={
   production: true,
   firebase: {
     apiKey: "AIz×××××××××××××××××××××××××××××××××8",
     authDomain: "pwaangular-6c041.firebaseapp.com",
     databaseURL: "https://pwaangular-6c041.firebaseio.com",
     projectId: "pwaangular-6c041",
     storageBucket: "pwaangular-6c041.appspot.com",
     messagingSenderId: "545522081966"
   }

}

FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireDatabase } from '@angular/fire/database';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ThrowStmt } from '@angular/compiler';
@Injectable({
  providedIn: 'root'
})
export class FirebaseService {

  ref = firebase.firestore().collection('users');
  constructor(public db: AngularFireDatabase,public _http:HttpClient) { 
 
  }

  getUsers(): Observable<any> {
    return new Observable((observer) => {
      this.ref.onSnapshot((querySnapshot) => {
        let User = [];
        querySnapshot.forEach((doc) => {
          let data = doc.data();
          User.push({
            key: doc.id,
            fullname: data.fullname,
            age: data.age,
            mobile: data.mobile
          });
        });
        observer.next(User);
      });
    });
  }

  getUser(id: string): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).get().then((doc) => {
        let data = doc.data();
        observer.next({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
    });
  }

  postUser(user): Observable<any> {
    return new Observable((observer) => {
      
      this.ref.add(user).then((doc) => {
        observer.next({
          key: doc.id,
        });
      });
    });
  }

  updateUser(id: string, data): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).set(data).then(() => {
        observer.next();
      });
    });
  }

  deleteUser(id: string): Observable<{}> {
    return new Observable((observer) => {
      this.ref.doc(id).delete().then(() => {
        observer.next();
      });
    });
  }

getDataOnApi(){
  return this._http.get('https://site.com/api/General/Getprovince')
  .pipe(
      map((res: Response) => {
return res;

      })
  );
}

getOnApi(){
  return  this._http.get("https://site.com/api/General/Getprovince",).pipe(
    map((response:any) => {
       
return  response
    } )
    );
}
}
اگر دقت کرده باشید، خیلی شبیه به کد نویسی سمت سرور میباشد.

با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod
cd dist/Pwa-WepApp
Http-server -o


تست وب اپلیکیشن بر روی پلتفرم‌های مختلف

برای اینکه بتوانیم خروجی وب اپلیکیشن را بر روی پلتفرم‌های مختلفی تست کنیم، میتوانیم آن را آپلود کرده و مثل یک سایت اینترنتی، با وارد کردن دومین، وارده پروژه شد. ولی کم هزینه‌ترین راه، استفاده از ابزار ngrok میباشد. میتوانید توسط این مقاله پروژه خودتان در لوکال بر روی https سوار کنید.

نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.


کد‌های مربوط به این قسمت را میتوانید از این repository دریافت کنید .
مطالب
اصول و قراردادهای نام‌گذاری در دات‌نت
نامگذاری (Naming) اشیا یک برنامه شاید در نگاه اول دارای اهمیت بالایی نباشه، اما تجربه نشون داده که در پروژه‌های بزرگ که با کمک چندین مجموعه به انجام میرسه نامگذاری صحیح و اصولی که از یکسری قواعد کلی و مناسب پیروی میکنه میتونه به پیشبرد اهداف و مدیریت راحتتر برنامه کمک بسیاری بکنه.
بیشتر موارد اشاره شده در این مطلب از کتاب جامع و مفید Framework Design Guidelines اقتباس شده که خوندن این کتاب مفید رو به خوانندگان توصیه میکنم.
برای کمک به نوشتن اصولی و راحتتر سورسهای برنامه‌ها در ویژوال استودیو نرم افزارهای متعددی وجود داره که با توجه به تجربه شخصی خودم نرم افزار Resharper  محصول شرکت Jetbrains یکی از بهترین هاست که در مورد خاص مورد بحث در این مطلب نیز بسیار خوب عمل میکنه.
برخی از موارد موجود در مطلب جاری نیز از قراردادهای پیشفرض موجود در نرم افزار Resharper نسخه 6.0 برگرفته شده است و قسمتی نیز از تجربه شخصی خودم و سایر دوستان و همکاران بوده است.
اصل این مطلب حدود یکسال پیش تهیه شده و اگر نقایصی وجود داره لطفا اشاره کنین.

اصول و قراردادهای نام‌گذاری در دات‌نت
انواع نام‌گذاری
نام‌گذاری اشیا در حالت کلی را می‌توان به سه روش زیر انجام داد:
1. Pascal Casing: در این روش حرف اول هر کلمه در نام شی به صورت بزرگ نوشته می‌شود.
FirstName
2. camel Casing: حرف اول در اولین کلمه نام هر شی به صورت کوچک و حرف اول بقیه کلمات به صورت بزرگ نوشته می‌شود.
firstName
3. Hungarian: در این روش برای هر نوع شی موجود یک پیشوند درنظر گرفته می‌شود تا از روی نام شی بتوان به نوع آن پی برد. در ادامه و پس از این پیشوندها سایر کلمات بر اساس روش Pascal Casing نوشته می‌شوند.
strFirstName
lblFirstName
نکته: استفاده از این روش به جز در نام‌گذاری کنترل‌های UI منسوخ شده است.

قراردادهای کلی
1. نباید نام اشیا تنها در بزرگ یا کوچک بودن حروف با هم فرق داشته باشند. به عنوان مثال نباید دو کلاس با نام‌های MyClass و myClass داشته باشیم. هرچند برخی از زبان‌ها case-sensitive هستند اما برخی دیگر نیز چنین قابلیتی ندارند (مثل VB.NET). بنابراین اگر بخواهیم کلاس‌های تولیدی ما در تمام محیط‌ها و زبان‌های برنامه نویسی قابل اجرا باشند باید از این قرارداد پیروی کنیم.
2. تا آنجا که امکان دارد باید از به‌کار بردن مخفف کلمات در نام‌گذاری اشیا دوری کنیم. مثلا به جای استفاده از GetChr باید از GetCharacter استفاده کرد.
البته در برخی موارد که مخفف واژه موردنظر کاربرد گسترده ای دارد می‌توان از عبارت مخفف نیز استفاده کرد. مثل UI به جای UserInterface و یا IO به جای InputOutput.
 
آ. اصول نام‌گذاری فضای نام (namespace)
1. اساس نام‌گذاری فضای نام باید از قاعده زیر پیروی کند:
<Company>.<Technology|Produt|Project>[.<Feature>][.<SubNamespace>]
( < > : اجباری        [ ] : اختیاری )
2. برای نام‌گذاری فضای نام باید از روش Pascal Casing استفاده شود.
3. در هنگام تعریف فضاهای نام به وابستگی آنها توجه داشته باشید. به عنوان مثال اشیای درون یک فضای نام پدر نباید به اشیای درون فضای نام یکی از فرزندانش وابسته باشد. مثلا در فضای نام System نباید اشیایی وجود داشته باشند که به اشیای درون فضای نام System.UI وابسته باشند.
4. سعی کنید از نام‌ها به صورت جمع برای عناوین فضای نام استفاده کنید. مثلا به جای استفاده از Kara.CSS.HQ.Manager.Entity از Kara.CSS.HQ.Manager.Entities استفاده کنید. البته برای مورادی که از عناوین مخفف و یا برندهای خاص استفاده کرده‌اید از این قرارداد پیروی نکنید. مثلا نباید از عنوانی شبیه به Kara.CSS.Manager.IOs استفاده کنید.
5. از عنوانی پایدار و مستقل از نسخه محصول برای بخش دوم عنوان فضای نام استفاده کنید. بدین معنی که این عناوین با گذر زمان و تغییر و تحولات در محتوای محصول و یا تولیدکننده نباید تغییر کنند. مثال:
Microsoft.Reporting.WebForms
Kara.Support.Manager.Enums
Kara.CSS.HQ.WebUI.Configuration
6. از عناوین یکسان برای فضای نام و اشیای درون آن استفاده نکنید. مثلا نباید کلاسی با عنوان Manager در فضای نام Kara.CSS.Manager وجود داشته باشد.
7. از عناوین یکسان برای اشیای درون فضاهای نام یک برنامه استفاده نکنید. مثلا نباید دو کلاس با نام Package در فضاهای نام Kara.CSS.Manger و Kara.CSS.Manger.Entities داشته باشید.

ب. اصول نام‌گذاری کلاس‌ها و Structها
1. عنوان کلاس باید اسم یا موصوف باشد.
2. در نام‌گذاری کلاس‌ها باید از روش Pascal Casing استفاده شود.
3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
4. از پیشوندهای زائد مثل C یا Cls نباید استفاده شود.
5. نباید از کاراکترهایی به غیر از حروف (و یا در برخی موارد خیلی خاص، شماره نسخه) در نام‌گذاری کلاس‌ها استفاده شود.
مثال:
درست:
PackageManager , PacakgeConfigGenerator
Circle , Utility , Package
نادرست:
CreateConfig , classdata
CManager , ClsPackage , Config_Creator , Config1389
6. در نام‌گذاری اشیای Generic از استفاده از عناوینی چون Element, Node, Log و یا Message پرهیز کنید. این کار موجب به‌وجودآمدن کانفلیکت در عناوین اشیا می‌شود. در این موارد بهتر است از عناوینی چون FormElement, XmlNode, EventLog و SoapMessage استفاده شود. درواقع بهتر است نام اشیای جنریک، برای موارد موردنیاز، کاملا اختصاصی و درعین حال مشخص‌کننده محتوا و کاربرد باشند.
7. از عناوینی مشابه عناوین کتابخانه‌های پایه دات‌نت برای اشیای خود استفاده نکنید. مثلا نباید کلاسی با عنوان Console, Parameter, Action و یا Data را توسعه دهید. هم‌چنین از کابرد عناوینی مشابه اشیای موجود در فضاهای نام غیرپایه دات‌نت و یا غیردات‌نتی اما مورداستفاده در پروژه خود که محتوا و کاربردی مختص همان فضای نام دارند پرهیز کنید. مثلا نباید کلاسی با عنوان Page در صورت استفاده از فضای نام System.Web.UI تولید کنید.
8. سعی کنید در مواردی که مناسب به‌نظر می‌رسد از عنوان کلاس پایه در انتهای نام کلاس‌های مشتق‌شده استفاده کنید. مثل FileStream که از کلاس Stream مشتق شده است. البته این قاعده در تمام موارد نتیجه مطلوب ندارد. مثلا کلاس Button که از کلاس Control مشتق شده است. بنابراین در به‌کاربردن این مورد بهتر است تمام جوانب و قواعد را درنظر بگیرید.

پ. اصول نام‌گذاری مجموعه‌ها (Collections)
یک مجموعه در واقع یک نوع کلاس خاص است که حاوی مجموعه‌ای از داده‌هاست و از همان قوانین کلاس‌ها برای نام‌گذاری آن‌ها استفاده می‌شود.
1. بهتر است در انتهای عنوان مجموعه از کلمه Collection استفاده شود. مثال:
CenterCollection , PackageCollection
2. البته درصورتی‌که کلاس مورد نظر ما رابط IDictionary را پیاده‌سازی کرده باشد بهتر است از پسوند Dictionary استفاده شود.

ت. اصول نام‌گذاری Delegateها
1. عنوان یک delegate باید اسم یا موصوف باشد.
2. در نام‌گذاری delegateها باید از روش Pascal Casing استفاده شود.
3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
4. از پیشوندهای زائد مثل D یا del نباید استفاده شود.
5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری delegateها استفاده شود.
6. نباید در انتهای نام یک delegate از عبارت Delegate استفاده شود.
7. بهتر است که درصورت امکان در انتهای نام یک delegate که برای Event Handler استفاده نمی‌شود از عبارت Callback استفاده شود. البته تنها درصورتی‌که معنی و مفهوم مناسب را داشته باشد.
مثال:
نحوه تعریف یک delegate
public delegate void Logger (string log);
public delegate void LoggingCallback (object sender, string reason);

ث. اصول نام‌گذاری رویدادها (Events)
1. عنوان یک رویداد باید فعل یا مصدر باشد.
2. در نام‌گذاری کلاس‌ها باید از روش Pascal Casing استفاده شود.
3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
4. از پیشوندهای زائد نباید استفاده شود.
5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری رویدادها استفاده شود.
6. بهتر است از پسوند EventHandler در عنوان هندلر رویداد استفاده شود.
7. از به کاربردن عباراتی چون AfterXXX و یا BeforeXXX برای نمایش رویدادهای قبل و یا بعد از رخداد خاصی خودداری شود. به جای آن باید از اسم مصدر (شکل ingدار فعل) برای نام‌گذاری رویداد قبل از رخداد و هم‌چنین شکل گذشته فعل برای نام‌گذاری رویداد بعد از رخداد خاص استفاده کرد.
مثلا اگر کلاسی دارای رویداد Open باشد باید از عنوان Openning برای رویداد قبل از Open و از عنوان Opened برای رویداد بعد از Open استفاده کرد.
8. نباید از پیشوند On برای نام‌گذاری رویداد استفاده شود.
9. همیشه پارامتر e و sender را برای آرگومانهای رویداد پیاده سازی کنید. sender که از نوع object است نمایش دهنده شیی است که رویداد مربوطه را به وجود آورده است و e درواقع آرگومانهای رویداد مربوطه است.
10. عنوان کلاس‌آرگومان‌های رویداد باید دارای پسوند EventArgs باشد.
مثال:
عنوان کلاس‌آرگومان:
AddEventArgs , EditEventArgs , DeleteEventArgs
عنوان رویداد:
Adding , Add , Added
تعریف یک EventHandler:
public delegate void <EventName>EventHandler  (object sender, <EventName>EventArgs e);
نکته: نیاز به تولید یک EventHandler مختص توسعه یک برنامه به‌ندرت ایجاد می‌شود. در اکثر موارد می‌توان با استفاده از کلاس جنریک <EventHandler<TEventArgs تمام احتیاجات خود را برطرف کرد.
تعریف یک رویداد:
public event EventHandler <AddEventArgs> Adding;

ج. اصول نام‌گذاری Attributeها
1. عنوان یک attribute باید اسم یا موصوف باشد.
2. در نام‌گذاری attributeها باید از روش Pascal Casing استفاده شود.
3. نباید از عناوین مخففی که رایج نیستند استفاده کرد.
4. از پیشوندهای زائد مثل A یا atr نباید استفاده شود.
5. نباید از کاراکترهایی به غیر از حروف در نام‌گذاری attributeها استفاده شود.
6. بهتر است در انتهای نام یک attribute از عبارت Attribute استفاده شود.
مثال:
DisplayNameAttribute , MessageTypeAttribute

چ. اصول نام‌گذاری Interfaceها
1. در ابتدای عنوان interface باید از حرف I استفاده شود.
2. نام باید اسم، موصوف یا صفتی باشد که interface را توصیف می‌کند.
به عنوان مثال:
IComponent  (اسم)
IConnectionProvider (موصوف)
ICloneable (صفت)
3. از روش Pascal Casing استفاده شود.
4. خودداری از بکاربردن عبارات مخفف غیررایج.
5. باید تنها از کاراکترهای حرفی در نام interface استفاده شود.
 
ح. اصول نام‌گذاری Enumerationها
1. استفاده از روش Pascal Casing
2. خودداری از کاربرد عبارات مخفف غیررایج
3. تنها از کاراکترهای حرفی در نام Enumretionها استفاده شود.
4. نباید از پسوند یا پیشوند Enum یا Flag استفاده شود.
5. اعضای یک Enum نیز باید با روش Pascal Casing نام‌گذاری شوند.
مثال:
public enum FileMode {
    Append,
    Read, …
}
6. درصورتی‌که enum موردنظر از نوع flag نیست باید عنوان آن مفرد باشد. 
7. درصورتی‌که enum موردنظر برای کاربرد flag طراحی شده باشد نام آن باید جمع باشد. مثال:
[Flag]
public enum KeyModifiers {
    Alt = 1, 
    Control = 2,
    Shift = 4
}
8. از به‌کاربردن پسوند و یا پیشوندهای اضافه در نام‌گذاری اعضای یک enum نیز پرهیز کنید. مثلا نام‌گذاری زیر نادرست است:
public enum OperationState {
    DoneState, 
    FaultState,
    RollbackState
}

خ. اصول نام‌گذاری متدها
1. نام متد باید فعل یا ترکیبی از فعل و اسم یا موصوف باشد.
2. باید از روش Pascal Casing استفاده شود.
3. خودداری از بکاربردن عبارات مخفف غیررایج و یا استفاده زیاد از اختصار
4. تنها از کاراکترهای حرفی برای نام متد استفاده شود.
مثال:
AddDays , Save , DeleteRow , BindData , Close , Open

د. اصول نام‌گذاری Propertyها
1. نام باید اسم، صفت یا موصوف باشد.
2. باید از روش Pascal Casing استفاده شود.
3. خودداری از بکاربردن عبارات مخفف غیررایج.
4. تنها از کاراکترهای حرفی برای نام‌گذاری پراپرتی استفاده شود.
مثال:
Radius , ReportType , DataSource , Mode , CurrentCenterId
5. از عبارت Get در ابتدای هیچ Propertyای استفاده نکنید.
6. نام خاصیت‌هایی که یک مجموعه برمی‌گرداند باید به صورت جمع باشد. عنوان این Propertyها نباید به‌صورت مفرد به همراه پسوند Collection یا List باشد. مثال:
public CenterCollection Centers { get; set; }
7. خواص Boolean را با عناوینی مثبت پیاده‌سازی کنید. مثلا به‌جای استفاده از CantRead از CanRead استفاده کنید. بهتر است این Propertyها پیشوندهایی چون Is, Can یا Has داشته باشند، البته تنها درصورتی‌که استفاده از چنین پیشوندهایی ارزش افزوده داشته و مفهوم آن را بهتر برساند. مثلا عنوان CanSeek مفهوم روشن‌تری نسبت به Seekable دارد. اما استفاده از Created خیلی بهتر از IsCreated است یا Enabled کاربرد به مراتب راحت‌تری از IsEnabled دارد.
برای تشخیص بهتر این موارد بهتر است از روش ifسنجی استفاده شود. به عنوان مثال
if (list.Contains(item))
if (regularExpression.Matches(text))
if (stream.CanSeek)
if (context.Created)
if (form.Enabled)
مفهوم درست‌تری نسبت به موارد زیر دارند:
if (list.IsContains(item))
if (regularExpression.Match(text))
if (stream.Seekable)
if (context.IsCreated)
if (form.IsEnabled)
8. بهتر است در موارد مناسب عنوان Property با نام نوعش برابر باشد. مثلا
public Color Color { get; set; }

ذ. اصول نام‌گذاری پارامترها
پارامتر درحالت کلی به آرگومان وروی تعریف شده برای یک متد گفته می‌شود.
1. حتما از یک نام توصیفی استفاده شود. از نام‌گذاری پارامترها براساس نوعشان به‌شدت پرهیز کنید.
2. از روش camel Casing استفاده شود.
3. تنها از کاراکترهای حرفی برای نام‌گذاری پارامترها استفاده شود.
مثال:
firstName , e , id , packageId , centerName , name
4. نکاتی برای نام‌گذاری پارامترهای Operator Oveloading:
- برای operatorهای دو پارامتری (binary operators) از عناوین left و right برای پارامترهای آن استفاده کنید:
public static MyType operator +(MyType left, MyType right)
public static bool operator ==(MyType left, MyType right)
- برای operatorهای تک‌پارامتری (unary operators) اگر برای پارامتر مورد استفاده هیچ عنوان توصیفی مناسبی پیدا نکردید حتما از عبارت value استفاده کنید:
public static MyType operator ++(MyType value)
- درصورتی‌که استفاده از عناوین توصیفی دارای ارزش افزوده بوده و خوانایی کد را بهتر می‌کند حتما از این نوع عناوین استفاده کنید:
public static MyType operator /(MyType dividend, MyType divisor)
- نباید از عبارات مخفف یا عناوینی با اندیس‌های عددی استفاده کنید:
public static MyType operator -(MyType d1, MyType d2) // incorrect!

ر. اصول نام‌گذاری متغیر (Variable)ها
- نام متغیر باید اسم، صفت یا موصوف باشد.
نام‌گذاری متغیرها باید با توجه به نوع آن انجام شود.
1. متغیرهای عمومی (public) و protected و Constantها
- استفاده از روش Pascal Casing
- تنها از کاراکترهای حرفی برای نام متغیر عمومی استفاده شود.
مثال:
Area , DataBinder , PublicCacheName
2. متغیرهای private (در سطح کلاس یا همان field)
- نام این نوع متغیر باید با یک "_" شروع شود.
- از روش camel Casing استفاده شود.
مثال:
_centersList
_firstName
_currentCenter
3. متغیرهای محلی در سطح متد
- باید از روش camel Casing استفاده شود.
- تنها از کاراکترهای حرفی استفاده شود.
مثال:
parameterType , packageOperationTypeId

ز. اصول نام‌گذاری کنترل‌های UI
1. نام باید اسم یا موصوف باشد.
2. استفاده از روش Hungarian !
3. عبارت مخفف معرفی‌کننده کنترل، باید به اندازه کافی برای تشخیص نوع آن مناسب باشد.
مثال:
lblName (Label)
txtHeader (TextBox)
btnSave (Button)

ژ. اصول نام‌گذاری Exceptionها
تمام موارد مربوط به نام‌گذاری کلاس‌ها باید در این مورد رعایت شود. 
1. باید از پسوند Exception در انتهای عنوان استفاده شود.
مثال:
ArgumentNullException , InvalidOperaionException

س. نام‌گذاری اسمبلی‌ها و DLLها
1. عناوینی که برای نام‌گذاری اسمبلی‌ها استفاده می‌شوند، باید نمایش‌دهنده محتوای کلی آن باشند. مثل:
System.Data
2. از روش زیر برای نام‌گذاری اسمبلی‌ها استفاده شود:
<Company>.<Component>.dll
<Company>.<Project|Product|Technology>.<Component>.dll
مثل:
Microsoft.CSharp.dll , Kara.CSS.Manager.dll

ش. نام‌گذاری پارامترهای نوع (Generic (type parameter
1. از حرف T برای پارامترهای تک‌حرفی استفاده کنید. مثل:
public int IComparer<T> {…}
public delegate bool Predicate<T> (T item)
2. تمامی پارامترهای جنریک را با عناوینی توصیفی و مناسب که مفهوم و کاربرد آنرا برساند نام‌گذاری کنید، مگر آنکه یافتن چنین عباراتی ارزش افزوده‌ای در روشن‌تر کردن کد نداشته باشد. مثال:
public int ISessionChannel<TSession> {…}
public delegate TOutput Converter<TInput, TOutput> (TInput from)
public class Nullable<T> {…}
public class List<T> {…}
3. در ابتدای عناوین توصیفی حتما از حرف T استفاده کنید.
4. بهتر است تا به‌صورتی روشن نوع قید قرار داده شده بر روی پارامتری خاص را در نام آن پارامتر نمایش دهید. مثلا اگر قید ISession را برای پارامتری قرار دادید بهتر است نام آن پارامتر را TSession درنظر بگیرید.
 
ص. نام‌گذاری کلید Resourceها
به دلیل شباهت ساختاری که میان کلیدهای resource و propertyها وجود دارد قواعد نام‌گذاری propertyها در اینجا نیز معتبر هستند.
1. از روش نام‌گذاری Pascal Casing برای کلیدهای resource استفاده کنید.
2. از عناوین توصیفی برای این کلیدها استفاده کنید. سعی کنید تا حد امکان به هیچ وجه از عناوین کوتاه و یا مخففی که مفهوم را به‌صورت ناکامل می‌رساند استفاده نکنید. درواقع سعی کنید که خوانایی بیشتر کد را فدای فضای بیشتر نکنید.
3. از کلیدواژه‌های CLR و یا زبان مورداستفاده برای برنامه‌نویسی در نام‌گذاری این کلیدها استفاده نکنید.
4. تنها از حروف و اعداد و _ در نام‌گذاری این کلیدها استفاده کنید.
5. سعی کنید از عناوین توصیفی همانند زیر برای پیام‌های مناسب خطاها جهت نمایش به کاربر برای کلیدهای مربوطه استفاده کنید. درواقع نام کلید باید ترکیبی از نام نوع خطا و یک آی‌دی مشخص‌کننده پیغام مربوطه باشد:
ArgumentExceptionIllegalCharacters
ArgumentExceptionInvalidName
ArgumentExceptionFileNotFound
 
نکاتی درمورد کلمات مرکب
کلمات مرکب به کلماتی گفته می‌شود که در آن از بیش از یک کلمه با مفهوم مستقل استفاده شده باشد. مثل Callback یا FileName. 
باید توجه داشت که با تمام کلمات موجود در یک کلمه مرکب نباید همانند یک کلمه مستقل رفتار کرد و حرف اول آن را در روش‌های نام‌گذاری موجود به‌صورت بزرگ نوشت. کلمات مرکبی وجود دارند که به آن‌ها closed-form گفته می‌شود. این کلمات مرکب با اینکه از 2 یا چند کلمه دارای مفهوم مستقل تشکیل شده‌اند اما به‌خودی‌خود دارای مفهوم جداگانه و مستقلی هستند. برای تشخیص این کلمات می‌توان به فرهنگ لغت مراجعه کرد (و یا به‌سادگی از نرم‌افزار Microsoft Word استفاده کرد) و دریافت که آیا کلمه مرکب موردنظر آیا مفهوم مستقلی برای خود دارد، یعنی درواقع آیا عبارتی closed-form است. با کلمات مرکب از نوع closed-form همانند یک کلمه ساده برخورد می‌شود! 
در جدول زیر مثال‌هایی از عبارات رایج و نحوه درست و نادرست استفاده از هریک نشان داده شده است.

Pascal Casing

camel Casing

Wrong

Callback

callback

CallBack

BitFlag

bitFlag

Bitflag / bitflag

Canceled

canceled

Cancelled

DoNot

doNot

Donot / Don’t

Email

email

EMail

Endpoint

endpoint

EndPoint / endPoint

FileName

fileName

Filename / filename

Gridline

gridline

GridLine / gridLine

Hashtable

hashtable

HashTable / hashTable

Id

id

ID

Indexes

indexes

Indices

LogOff

logOff

Logoff / LogOut !

LogOn

logOn

Logon / LogIn !

SignOut

signOut

Signout / SignOff

SignIn

signIn

Signin / SignOn

Metadata

metadata

MetaData / metaData

Multipanel

multipanel

MultiPanel / multiPanel

Multiview

multiview

MultiView / multiView

Namespace

namespace

NameSpace / nameSpace

Ok

ok

OK

Pi

pi

PI

Placeholder

placeholder

PlaceHolder / placeHolder

UserName

username

Username / username

WhiteSpace

whiteSpace

Whitespace / whitespace

Writable

writable

Writeable / writeable

همان‌طور که در جدول بالا مشاهد می‌شود در استفاده از قوانین عبارات مخفف دو مورد استثنا وجود دارد که عبارتند از Id و Ok. این کلمات باید همان‌طور که در اینجا نشان داده شده‌اند استفاده شوند.
 
نکاتی درباره عبارات مخفف
1. عبارات مخفف (Acronym) با خلاصه‌سازی کلمات (Abbreviation) فرق دارند. یک عبارت مخفف شامل حروف اول یک عبارت طولانی یا معروف است، درصورتی‌که خلاصه‌سازی یک عبارت یا کلمه از حذف بخشی از آن به‌دست می‌آید. تا آنجاکه امکان دارد از خلاصه‌سازی عبارات نباید استفاده شود. هم‌چنین استفاده از عبارات مخفف غیررایج توصیه نمی‌شود.
مثال: IO و UI
2. براساس تعریف، یک مخفف حداقل باید 2 حرف داشته باشد. نحوه برخورد با مخفف‌های دارای بیشتر از 2 حرف با مخفف‌های دارای 2 حرف باهم متفاوت است. با مخفف‌های دارای 2 حرف همانند کلمه‌ای یک حرفی! برخورد می‌شود، درصورتی‌که با سایر مخفف‌ها همانند یک کلمه کامل چندحرفی برخورد می‌شود. به‌عنوان مثال IOStream برای روش PascalCasing و ioStream برای روش camelCasing استفاده می‌شود. هم‌چنین از HtmlBody و htmlBody به‌ترتیب برای روش‌های Pascal و camel استفاده می‌شود.
3. هیچ‌کدام از حروف یک عبارت مخفف در ابتدای یک واژه نام‌گذاری‌شده به روش camelCasing به‌صورت بزرگ نوشته نمی‌شود!
نکته: موارد زیادی را می‌توان یافت که در ابتدا به‌نظر می‌رسد برای پیاده‌سازی آنها باید قوانین فوق را نقض کرد. این موارد شامل استفاده از کتابخانه‌های سایر پلتفرم‌ها (مثل MFC, HTML و غیره)، جلوگیری از مشکلات جغرافیایی! (مثلا در مورد نام کشورها یا سایر موقعیت‌های جغرافیایی)، احترام به نام افراد درگذشته، و مواردی از این دست می‌شود. اما در بیشتر قریب به اتفاق این موارد هم می‌توان بدون تقض قوانین فوق اقدام به نام‌گذاری اشیا کرد، بدون اینکه با تغییر عبارت اصلی (که موجب تطابق با این قوانین می‌شود) لطمه‌ای به مفهوم آن بزند. البته تنها موردی که به‌نظر می‌رسد می‌تواند قوانین فوق را نقض کند نام‌های تجاری هستند. البته استفاده از نام‌های تجاری توصیه نمی‌شود، چون این نام‌ها سریع‌تر از محتوای کتابخانه‌های برنامه‌نویسان تغییر می‌کنند!
نکته: برای درک بهتر قانون "عدم استفاده از عبارات مخففی که رایج نیستند" مثالی از زبان توسعه دهندگان دات‌نت‌فریمورک ذکر می‌شود. در کلاس Color متد زیر با Overloadهای مختلف در دسترس است:
public class Color {
    …
    public static Color FromArgb(…)
    { … }
}
همان‌طور که مشاهده می‌شود برای رعایت قوانین فوق به‌جای استفاده از ARGB از عبارت Argb استفاده شده است. اما این نحوه استفاده موجب شده تا این سوال به‌ظاهر خنده‌دار اما درست پیش‌آید:
"چطور می‌شود در این کلاس رنگی را از ARGB تبدل کرد؟ هرچه که من میبینم فقط تبدیل از طریق (Argb) آرگومان b است!"
حال در این نقطه به‌نظر می‌رسد که در اینجا باید قوانین فوق را نقض کرد و از عنوان FromARGB که مفهوم درست را می‌رساند استفاده کرد. اما با کمی دقت متوجه می‌شویم که این قوانین در ابتدا نیز با پیاده‌سازی نشان داده شده در قطعه کد بالا نقض شده‌اند! همه می‌دانیم که عبارت RGB مخفف معروفی برای عبارت Red Green Blue است. اما استفاده از ARGB برای افزودن کلمه Alpha به ابتدای عبارت مذکور چندان رایج نیست. پس استفاده از مخفف Argb از همان ابتدا اشتباه به‌نظر می‌رسد. بنابراین راه‌حل بهتر می‌تواند استفاده از عنوان FromAlphaRgb باشد که هم قوانین فوق را نقض نکرده و هم مفهوم را بهتر می‌رساند.
 
دیگر نکات
1. قراردادهای اشاره شده در این سند حاصل کار شبانه روزی تعداد بسیاری از برنامه نویسان در سرتاسر جهان در پروژه‌های بزرگ بوده است. این اصول کلی تنها برای توسعه آسان‌تر و سریع‌تر پروژه‌های بزرگ تعیین شده‌اند و همان‌طور که روشن است تنها ازطریق تجربه دست‌یافتنی هستند. بنابراین چه بهتر است که در این راه از تجارب بزرگان این عرصه بیشترین بهره برده شود.
2. هم‌چنین توجه داشته باشید که بیشتر قراردادهای اشاره شده در این سند از راهنمای نام‌گذاری تیم توسعه BCL دات‌نت‌فریمورک گرفته شده است. این تیم طبق اعتراف خودشان زمان بسیار زیادی را برای نام‌گذاری اشیا صرف کرده‌اند و توصیه کرده‌اند که دیگران نیز برای نام‌گذاری، زمان مناسب و کافی را در توسعه پروژه‌ها درنظر بگیرند.
 
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت اول
یکی از دغدغه‌های جدی امروزه توسعه دهندگان نرم افزار در سمت Front end، توسعه برنامه‌های Cross Platform است. در این سری آموزشی به صورت قدم به قدم و پروژه محور می‌خواهیم برنامه‌ای را برای Android/iOS/Windows توسعه دهیم که روی کامپیوتر، تبلت و موبایل به خوبی کار کند.
انتخاب ابزار درست برای شروع به کار از اهمیت شایانی برخوردار است و بد نیست در ابتدا به بررسی دلایل انتخاب ابزارهایی بپردازیم که قرار است در این دوره از آنها استفاده شود.

۱- زبان برنامه نویسی: CSharp
CSharp با وجود امکاناتی مانند Generics‌، Lambda Expressions، Linq، Async و ... که تا حدودی در سایر زبان‌ها هم هستند، زبانی خوش ساختار و کاربردی است. همچنین اضافه شدن امکانات جدیدی مانند ref returns و ... نشان دهنده این است که این زبان رو به جلو در حرکت و در برخی موارد پیشرو است. اما در توسعه یک برنامه Cross Platform مواردی اهمیت پیدا می‌کنند که شاید توسعه دهنده نرم افزار مستقیما با آنها درگیر نشود، ولی از آن‌ها تاثیر می‌پذیرد. در زبان CSharp مواردی مانند P/Invoke ،Pointers، Extern و ... جزء این دست از موارد هستند که کمک می‌کنند CSharp به یکی از لذت بخش‌ترین زبان هایی تبدیل شود که قابلیت فراخوانی 100% امکانات زبان‌های دیگر را بدون اما و اگرهای فراوان داشته باشد.
در سایر زبان‌های Cross Platform اگر کتابخانه‌های توسعه داده شده و ترکیب زبان‌های برنامه نویسی استفاده شده در آنها را بررسی کنید، می‌بینید که اگر قرار است کتابخانه مربوطه مثلا در JavaScript استفاده شود، توسعه دهنده کد، درصدی از کد را با Java، درصدی را با Swift و درصدی را با JavaScript توسعه داده است! اگر معادل همان کتابخانه را برای CSharp پیدا کنید، می‌بینید که تمامی قسمت‌های مربوط به اندروید، iOS و ویندوز به زبان CSharp است.
برای مثال در ادامه کدهای مربوط به پروژه‌ای را می‌بینید که هدف آن، ارائه متدهایی ساده برای کار با امکانات مختلف دستگاه، به صورت Cross Platform هست. مثلا برای بررسی وضعیت باطری بنویسید:
var state = Battery.State; //  Charging, Full, Discharging, ...
که تماما با CSharp توسعه داده شده است.
اما معادل چنین پروژه‌ای در هیچ زبان دیگری به صورت 100% با خود آن زبان توسعه داده نشده‌است و بیشتر مواقع با چنین چیزی مواجه می‌شوید:

این مسئله وقتی حائز اهمیت می‌شود که در پروژه‌تان به سمت کارهایی حرکت کنید که کمی خاص باشند و نتوانید کتابخانه‌ای را پیدا کنید که نیازهای شما را پوشش دهد و یا از کیفیت خوبی برخوردار نباشد و ... و خلاصه بخواهید کمی بیشتر دست به کد شوید. در چنین مواقعی شما عملا درگیر چندین زبان و محیط توسعه و سیستم عامل و Debugger و ... می‌شوید. به هر میزان که برنامه شما خاص باشد، این هزینه افزایش پیدا می‌کند تا جایی که ممکن است ادامه توسعه نرم افزار را غیر ممکن کند.
در CSharp شما به صد در صد امکانات سیستم عامل‌ها (Android/iOS/Windows/Linux/Mac/Tizen) دسترسی دارید.

۲- اجرا کننده برنامه: NET.
انتخاب NET. و کتابخانه‌های آن مانند Task Parallel Library - Entity Framework(Sqlite) - Noda - JSON.NET که در هر زمینه‌ای بالاترین کیفیت ممکن را به شما ارائه می‌کنند به خودی خود منطقی به نظر می‌رسد. اما تمامی این‌ها در کنار سرعت اجرای NET. به صورت Native و همچنین قابلیت اجرای NET. در تمامی سیستم عامل‌ها و همچنین امکان اجرای آن در مرورگر به کمک استاندارد Web Assembly آن را به انتخابی فوق العاده بدل می‌کند. سرعت گسترش محبوبیت و استفاده از NET. در دنیا نیز دلیل دیگری است برای اطمینان خاطر از انتخاب درست.

۳- Xamarin forms
Xamarin forms همه آن چیزهای پایه‌ای است که برای نوشتن یک برنامه لازم داریم. کنترل هایی مانند ListView، Button و ...به همراه Binding - Navigation و ...
در عمل می‌توانید آن را معادل Angular & Angular Material بدانید. وقتی شما فرمی را با Xamarin Forms توسعه می‌دهید و درون آن دکمه‌ای است که از فرم اول، شما را به فرم دوم می‌برد، می‌توانید آن را در هر جایی که Xamarin forms پشتیبانی می‌کند، استفاده کنید. پشتیبانی Xamarin forms برای Android/iOS/Windows خوب و برای Linux/Mac/Tizen و Web در مراحل اولیه است.
در Xamarin forms شما UI کاملا Native خواهید داشت.

۴- Prism Patterns & practices
Prism همه آن چیزی است که برای نوشتن یک برنامه با کیفیت، با قابلیت نگهداری بالا و تست پذیر احتیاج داریم.

با نقش Bit و کمک‌های آن در طول مسیر آموزش بیشتر آشنا خواهیم شد.

در قسمت‌های بعدی به آموزش نصب و نحوه دیباگ کردن کد و ارائه پابلیش در Android-iOS-Windows خواهیم پرداخت و سپس وارد کدنویسی شده و پروژه اولیه را خواهیم ساخت و در قسمت‌های بعد از آن هم کار با دیتابیس کلاینت ساید، ارتباط با سرور و ... را آموزش می‌بینیم.
اگر قبلا Xamarin Forms را تست کرده‌اید و به علت مسائلی مانند حجم بالای خروجی برنامه و یا کندی در توسعه برنامه یا اجرای آن در دستگاه مشتری آن را کنار گذاشته‌اید، توصیه می‌کنم بار دیگر آن را با ما تست کنید و با رعایت چند نکته ساده از نوشتن برنامه Cross Platform به بهترین شکل لذت ببرید و خروجی خوبی را در نهایت به مشتریان سیستم ارائه کنید.
نظرات مطالب
نمایش پیام هشدار در Blazor با استفاده از کامپوننت Alert بوت استرپ ۵
روش دوم پیاده سازی این مثال: ارسال یک کامپوننت محصور کننده‌ی سراسری، به صورت پارامتر آبشاری، به تمام زیر کامپوننت‌ها

با استفاده از پارامترهای آبشاری می‌توان شیءای را در اختیار تمام کامپوننت‌های قرار گرفته شده‌ی در سلسله مراتب آن‌ها قرار داد. برای مثال اگر در فایل Client\Shared\MainLayout.razor، جائیکه سایر کامپوننت‌ها قرار است رندر شوند را توسط یک کامپوننت سطح بالا محصور کنیم:
<Alert>
  @Body
</Alert>
در این حالت هر کامپوننتی که بجای Body درج شود، می‌تواند به پارامترهای آبشاری Alert دسترسی پیدا کند؛ یعنی تمام کامپوننت‌های نمایشی برنامه. یا حتی می‌توان این Alert را در فایل Client\App.razor نیز در بالاترین سطح قرار داد و کل Router را توسط آن محصور کرد.
بنابراین طراحی ساده‌ی کامپوننت Alert ای (Client\Shared\Alert.razor) که تامین کننده‌ی یک پارامتر آبشاری سراسری است، به صورت زیر می‌تواند باشد:
<CascadingValue Value=this>
    @if(IsVisible)
    {
        <div class="alert @Css" role="alert">
            @Message
            <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="HideAlert">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    }
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool IsVisible;
    private string Message;
    private string Css = "alert-primary";

    public void ShowAlert(string message, AlertType alertType)
    {
        IsVisible = true;
        Message = message;

        Css = alertType switch
        {
            AlertType.Success => "alert-success",
            AlertType.Info => "alert-primary",
            AlertType.Danger => "alert-danger",
            AlertType.Warning => "alert-warning",
            _ => "alert-primary"
        };

        StateHasChanged();
    }

    public void HideAlert()
    {
        IsVisible = false;
    }
}
که البته در همان پوشه به همراه فایل Client\Shared\AlertType.cs نیز هست:
namespace BlazorWasmAlert.Client.Shared
{
    public enum AlertType
    {
        Success,
        Info,
        Danger,
        Warning
    }
}
در کامپوننت ویژه‌ی Alert، دو قابلیت استاندارد Blazor بکار گرفته شده‌اند:
الف) وجود یک CascadingValue که اینبار Value آن به خود کامپوننت اشاره می‌کند (Value=this). یعنی پارامتر آبشاری که در اختیار سایر کامپوننت‌های محصور شده‌ی توسط آن ارسال می‌شود، دقیقا وهله‌ای از کامپوننت Alert است که توسط آن می‌توان برای مثال، متد عمومی ShowAlert آن‌را فراخوانی کرد:
<CascadingValue Value=this>
ب) چون کامپوننت Alert قرار است کامپوننت‌هایی را که بجای body@ درج می‌شوند را نمایش دهد، اینکار را توسط یک RenderFragment انجام داده‌است.


پس از درج این کامپوننت در فایل layout، روش استفاده‌ی از آن برای مثال در کامپوننت Index به صورت زیر است:
@page "/"

<h1>Hello, world!</h1>

<button class="btn btn-primary" @onclick="ShowAlert">Show Alert!</button>

@code
{
    [CascadingParameter]
    public Alert Alert { get; set; }

    private void ShowAlert()
    {
        Alert.ShowAlert("This is a test!", AlertType.Info);
    }
}
در اینجا پارامتر آبشاری که دریافت می‌شود، دقیقا به کلاس و شیء Alert (وهله‌ای از کامپوننت Alert) اشاره می‌کند. به همین جهت می‌توان متد عمومی ShowAlert آن‌را در اینجا فراخوانی کرد.


پ.ن.
در طراحی Blazor، از طراحی React الهام گرفته شده‌است و CascadingValue آن دقیقا معادل Context API جدید React است.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت ششم - کامپوننت‌های تو در تو
گاهی از اوقات جهت refactoring یک template بزرگ، بهتر است آن‌را به چند template کوچک خرد کرد و سپس از جمع آن‌ها به صورت یک template اصلی استفاده نمود. در این حالت نیاز است بین این زیر کامپوننت‌ها و کامپوننت‌های دربرگیرنده‌ی آن‌ها ارتباطات لازم را برقرار کرد.
تا اینجا در قسمت سوم، نحوه‌ی قراردادن یک کامپوننت را در کامپوننتی دیگر، توسط مقدار دهی خاصیت directives مزین کننده‌ی Component بررسی کردیم. همینقدر که یک کامپوننت دارای selector باشد، قابلیت قرارگرفتن در یک کامپوننت دیگر را دارد. اما چگونه باید بین این کامپوننت‌ها ارتباط برقرار کرد؟


تهیه کامپوننت نمایش ستاره‌ای امتیازهای محصولات

مثال نمایش لیست محصولات سری جاری، دارای ستون «5Star Rating» است. در این قسمت می‌خواهیم بجای نمایش عددی این امتیازها، کامپوننتی را طراحی کنیم که نماش ستاره‌ای آن‌ها را سبب شود. این کامپوننت باید بتواند یک مقدار ورودی، یا همان عدد امتیاز محصول را از کامپوننت دربرگیرنده‌ی آن دریافت کند. همچنین می‌خواهیم اگر کاربر بر روی این ستاره‌ها کلیک کرد، کامپوننت در برگیرنده را نیز مطلع سازیم.
در این مثال در فایل product-list.component.html چنین سطری تعریف شده‌است:
 <td>{{ product.starRating }}</td>
البته می‌توان در همینجا کدهای نمایش ستاره‌ای را بجای درج مقدار عددی آن قرار داد، اما ... قالب جاری را بیش از اندازه شلوغ خواهد کرد. به همین دلیل بهتر است نمایش آن‌را تبدیل به یک کامپوننت مجزا کرد. به علاوه در این حالت، قابلیت استفاده‌ی مجدد از آن در سایر کامپوننت‌ها نیز وجود خواهد داشت.
با توجه به اینکه کامپوننت نمایش ستاره‌ای امتیازها، قابلیت استفاده‌ی مجدد را دارد و الزامی ندارد که حتما در لیست محصولات، بکار گرفته شود، بهتر است محل تعریف آن‌را به خارج از پوشه‌ی products فعلی منتقل کنیم. برای مثال می‌توان پوشه‌ی app\shared را برای آن و تمامی کامپوننت‌های با قابلیت استفاده‌ی مجدد ایجاد کرد.


برای شروع، فایل جدید App\shared\star.component.ts را اضافه کنید؛ با کدهای کامل ذیل:
import { Component, OnChanges, Input, Output, EventEmitter } from 'angular2/core';
 
@Component({
    selector: 'ai-star',
    templateUrl: 'app/shared/star.component.html',
    styleUrls: ['app/shared/star.component.css']
})
export class StarComponent implements OnChanges {
    @Input() rating: number;
    starWidth: number;
    @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
 
    ngOnChanges(): void {
        this.starWidth = this.rating * 86 / 5;
    }
 
    onClick() {
        this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
    }
}
روش ساخت این کامپوننت نیز همانند سایر کامپوننت‌ها است و اصول کلی آن تفاوتی نمی‌کند. در اینجا نیز کلاسی وجود دارد که export شده و همچنین به Component مزین است. مقدار selector آن نیز به ai-star تنظیم شده‌است.
سپس مسیر template و مسیر فایل css ویژه‌ی آن، در تزئین کننده‌ی Component مشخص شده‌اند. محتوای کامل این دو فایل را در ذیل مشاهده می‌کنید:
الف) محتوای فایل App\shared\star.component.html
<div class="crop"
     [style.width.px]="starWidth"
     [title]="rating"
     (click)='onClick()'>
    <div style="width: 86px">
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
        <span class="glyphicon glyphicon-star"></span>
    </div>
</div>
ب) محتوای فایل App\shared\star.component.css
.crop {
    overflow: hidden;
}
 
div {
    cursor: pointer;
}
قالب star.component.html کار نمایش پنج ستاره را انجام می‌دهد. عرض کلی آن بر اساس مقدار خاصیت starWidth مشخص می‌شود و بر همین اساس، تعداد نمایان ستاره‌ها، مشخص خواهند شد. خاصیت starWidth به width این div بر حسب px، متصل شده‌است (property binding). همچنین خاصیت title این div نیز به مقدار rating متصل شده‌است و اگر بر روی آن کلیک شود، متد onClick را در کلاس متناظر با کامپوننت خود، فراخوانی خواهد کرد (event binding).

معرفی مقدماتی life cycle hooks در قسمت قبل صورت گرفت. در اینجا چون نیاز است به ازای هر بار رندر شدن این کامپوننت، عرض آن متفاوت باشد، بنابراین نیاز است راهی را پیدا کنیم تا بتوان مقدار خاصیت starWidth را متغیر کرد. به همین منظور از hook مخصوص این تغییرات یا همان OnChanges استفاده می‌شود. بنابراین باید کلاس این کامپوننت، اینترفیس OnChanges را پیاده سازی کند. پس از آن، importهای لازم جهت تعریف OnChanges به ابتدای فایل اضافه شده و همچنین متد ngOnChanges نیز جهت تکمیل کار پیاده سازی اینترفیس OnChanges، به کلاس جاری اضافه می‌شود.
کار متد ngOnChanges، تبدیل عدد امتیاز یک محصول، به عرض div نمایش ستاره‌ها است.


مکانیزم کار رخداد ngOnChanges و دریافت اطلاعات از والد

متد ngOnChanges، تنها به خواص ویژه‌ای به نام «input properties» واکنش نشان می‌دهد. اگر یک کامپوننت تو در توی قرار گرفته‌ی در یک کامپوننت دیگر، بخواهد اطلاعاتی را از والد خود دریافت کند، باید خاصیتی را در معرض دید آن دربرگیرنده قرار دهد. این کار توسط decorator ویژه‌ای به نام ()Input@ انجام می‌شود.
به همین جهت است که پیش از خاصیت rating در کلاس  StarComponent، شاهد درج مزین کننده‌ی ویژه‌ی ()Input@ هستیم:
export class StarComponent implements OnChanges {
      @Input() rating: number;
مزین کننده‌ی ()Input@ را به هر خاصیتی با هر نوعی، می‌توان انتساب داد. در اینجا نیاز است کامپوننت نمایش ستاره‌ای امتیازها، عدد امتیاز را از والد خود دریافت کنند. به همین جهت خاصیت امتیاز را از نوع «خاصیت ورودی» مشخص کرده‌ایم.
پس از آن، کامپوننت دربرگیرنده یا والد، این خاصیت ورودی ویژه را از طریق روش property binding متداول، مقدار دهی می‌کند:
 [rating]='product.starRating'

بدیهی است در اینجا چون خاصیت starWidth از نوع ورودی تعریف نشده‌است، قابلیت property binging فوق را در کامپوننت والد، ندارد.

اکنون به ازای هر بار نمایش این کامپوننت فرزند، خاصیت rating ورودی آن مقدار دهی شده و مقدار آن در رخداد ngOnChanges قابل دسترسی و استفاده خواهد بود. اینجا است که می‌توان از این مقدار تغییر یافته، جهت ترجمه‌ی آن به عرض div نمایش ستاره‌ها، استفاده کرد.


ارسال داده‌ها از کامپوننت فرزند به کامپوننت والد

تا اینجا با استفاده از «خواص ورودی» امکان دسترسی به مقادیر ارسالی از طرف والد را در کامپوننت فرزند، پیدا کردیم. عکس آن نیز امکان پذیر است؛ اما توسط رخدادها.
کامپوننت فرزند، با استفاده از decorator ویژه‌ی دیگری به نام ()Output@ امکان ارسال رخدادها را به کامپوننت والد پیدا می‌کند:
export class StarComponent implements OnChanges {
    @Input() rating: number;
    starWidth: number;
    @Output() ratingClicked: EventEmitter<string> = new EventEmitter<string>();
در اینجا خاصیت ratingClicked را که با ()Output@ مزین شده‌است، مشاهده می‌کنید. نوع این خاصیت باید از نوع رخدادها باشد که در AngularJS 2.0 توسط شیء EventEmitter جنریک، تعریف شده‌است. در این مثال، نوع رخداد و مقداری که توسط آن ارسال می‌شود، رشته‌ای درنظر گرفته شده‌است. اگر نیاز به ارسال چندین مقدار بود، می‌توان یک شیء را در اینجا مشخص کرد.
در مثال جاری اگر کاربر بر روی div ستاره‌های نمایش داده شده کلیک کند، اتصال به آن از طریق event binging متداول انجام می‌شود (متد جدید onClick به رخداد click متصل شده‌است):
<div class="crop"
     [style.width.px]="starWidth"
     [title]="rating"
     (click)='onClick()'>
و سپس این رخداد کلیک، در کلاس StarComponent به نحو ذیل به والد خود منتقل خواهد شد:
onClick() {
     this.ratingClicked.emit(`The rating ${this.rating} was clicked!`);
 }
در اینجا متد emit است که اطلاعات را به کامپوننت دربرگیرنده، ارسال می‌کند. نحوه‌ی تعریف این رشته هم توسط back tick مربوط به ES 6 صورت گرفته‌است.

تا اینجا مرحله‌ی تنظیمات رخدادها در کامپوننت فرزند صورت گرفت. ابتدا خاصیتی از نوع Output تعریف شد. سپس در کدهای قالب این کامپوننت جدید، متد onClick به رخداد click متصل گردید و سپس در کدهای مدیریت کننده‌ی این متد، متد ratingClicked.emit جهت ارسال اطلاعات نهایی به والد، فراخوانی گردید.

اکنون در کامپوننت والد، باید این مراحل برای دریافت اطلاعات از کامپوننت فرزند خود، طی شوند:
الف) ابتدا نام خاصیت مزین شده‌ی با Output، به عنوان مقصد event binding مشخص می‌شود و سپس متدی در کلاس کامپوننت والد، به آن متصل می‌گردد:
 (ratingClicked)='onRatingClicked($event)'
event$ مقدار ارسالی از کامپوننت فرزند را به کامپوننت والد منتقل می‌کند.
ب) در ادامه، تعریف این متد جدید متصل شده را به کلاس ProductListComponent اضافه می‌کنیم:
onRatingClicked(message: string): void {
    this.pageTitle = 'Product List: ' + message;
}
نوع پارامتر ورودی این متد را با توجه به نوع رشته‌ای <EventEmitter<string در کلاس StarComponent  تعریف کردیم.
به این ترتیب با کلیک بر روی div هر کامپوننت نمایش ستاره‌ای امتیازها، خاصیت pageTitle درج شده‌ی در صفحه تغییر می‌کند.


استفاده از کامپوننت نمایش ستاره‌ای امتیازها

نکات کلی افزودن این کامپوننت جدید، تفاوتی با مطالب عنوان شده‌ی در قسمت سوم، در حین بررسی مراحل افزودن دایرکتیو نمایش لیست محصولات، به کامپوننت ریشه‌ی سایت ندارد و یکی هستند.
برای افزودن و استفاده از این کامپوننت جدید، ابتدا قالب product-list.component.html را گشوده و سپس سطر نمایش عددی امتیاز یک محصول را به نحو ذیل تغییر می‌دهیم:
<td>
    <ai-star [rating]='product.starRating'
             (ratingClicked)='onRatingClicked($event)'>
    </ai-star>
</td>
همانطور که ملاحظه می‌کنید، ابتدا selector این کامپوننت جدید، به صورت یک المان جدید و سفارشی HTML، در قالب کامپوننت لیست محصولات درج می‌شود. همچنین خاصیت rating ورودی آن به عدد امتیاز محصول جاری در حال رندر، متصل شده‌است. به علاوه توسط event binding به خاصیت ratingClicked که از نوع ()Output@ تعریف شده‌است، متد onRatingClicked متصل گردیده‌است.

سپس باید به کلاس کامپوننت لیست محصولات (کامپوننت در برگیرنده) اعلام کرد که این کامپوننت جدید را باید از کجا پیدا کند. برای این منظور فایل product-list.component.ts را گشوده و خاصیت directives این کامپوننت را مقدار دهی می‌کنیم:
import { Component, OnInit } from 'angular2/core';
import { IProduct } from './product';
import { ProductFilterPipe } from './product-filter.pipe';
import { StarComponent } from '../shared/star.component';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css'],
    pipes: [ProductFilterPipe],
    directives: [StarComponent]
})
در اینجا دو کار جدید انجام شده‌است. ابتدا خاصیت directives، به نام کلاس این کامپوننت جدید (StarComponent)، تنظیم شده‌است. سپس تعریف import ماژول این کامپوننت، به ابتدای فایل جاری اضافه شده‌است.

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

در اینجا ستون امتیازهای محصولات با کامپوننت نمایش ستاره‌ای این امتیازها جایگزین شده‌است و همچنین با کلیک بر روی یکی از آن‌ها، عنوان panel جاری تغییر کرده‌است.


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



خلاصه‌ی بحث

در اینجا نحوه‌ی طراحی API عمومی یک کامپوننت را بررسی کردیم. تا زمانیکه خواص کلاس یک کامپوننت به نحو متداولی تعریف می‌شوند، میدان دید آن‌ها محدود است به قالب تعریف شده‌ی متناظر با آن‌ها. اگر نیاز است خاصیتی خارج از این قالب و به صورت عمومی در کامپوننت دربرگیرنده‌ی دیگری در دسترس قرار گیرد، آن‌را با مزین کننده‌ی ()Input@ مشخص می‌کنیم و اگر قرار است این کامپوننت فرزند، اطلاعاتی را به کامپوننت والد ارسال کند، این‌کار را توسط رخدادها و با تعریف ویژگی ()Output@ و EventEmitter انجام می‌دهد. نوع آرگومان جنریک EventEmitter، تعیین کننده‌ی نوع اطلاعاتی است که قرار است به کامپوننت دربرگیرنده ارسال شوند.
پس از تعریف کامپوننت فرزند، برای تعریف آن در کامپوننت والد، از نام selector آن به عنوان یک المان جدید HTML استفاده می‌شود و سپس با استفاده از property binding، اطلاعات لازم، به خاصیت از نوع ()Input@ کامپوننت فرزند ارسال می‌گردد. از event binding برای دریافت رخدادها از کامپوننت فرزند استفاده می‌شود. در اینجا هر رخدادی که توسط مزین کننده‌ی ()Output@ تعریف شده باشد، می‌تواند به عنوان مقصد event binding تعریف شود و اگر نیاز است به رخدادهای property binding از والد به فرزند، گوش فرا داد، می‌توان اینترفیس OnChanges را در کلاس کامپوننت فرزند پیاده سازی کرد.
مطالب
Performance در AngularJS قدم پنجم
در این مقاله موضوعی را مطرح خواهم کرد که شاید برای خیلی‌ها این نوع کد نویسی خوشایند نباشد. حتی برای خود من هم خوشایند نیست؛ ولی نهایتا در بهبود Performance تاثیر خیلی زیادی دارد. به کد زیر دقت کنید.
<div ng-repeat="item in items">
     <div ng-if="setting.header">{{item.header}}</div>
     <div>{{item.title}}</div>
     <div ng-if="setting.footer">{{item.footer}}</div>
</div>
توضیح کد: فرض کنید سناریوی پروژه ما به این صورت هست که ما یک لیست داریم، شامل 3 فیلد که header، title و footer را در تنظیمات می‌توانیم مشخص کنیم که header و footer در شرایطی نمایش داده شود و در شرایطی نمایش داده نشود و یا حالت‌های دیگر. 

خوب مشکل چیست و راهکار چیست؟
فرض کنید لیست ما شامل 100 رکورد هست و در تنظیمات مشخص کرده‌ایم که header نمایش داده شود و footer نمایش داده نشود. اما اتفاقی بدی که میفتد این است که وقتی لیست در View ساخته می‌شود، 100 بار ng-if مربوط به header و footer چک میشود؛ در جمع 200 بار می‌شود. چه این مقادیر true باشند چه false فرقی نمی‌کند و 200 بار بررسی می‌شود.
راهکار این مشکل به این صورت است که ما باید از ng-if داخل ng-repeat استفاده نکنیم. اما برای پیاده سازی تنظیمات باید ng-if‌ها را قبل از ng-repeat بررسی کنیم. پس مسلما ng-repeatها باید قالب پیش بینی کرده ما را نسبت به ng-if‌ها درست کند. نتیجه‌ی کار به صورت کد زیر است که شاید برای شما هم خوشایند نباشد:
<div ng-if="setting.header && setting.footer">
     <div ng-repeat="item in items">
          <div>{{item.header}}</div>
          <div>{{item.title}}</div>
          <div>{{item.footer}}</div>
     </div>
</div>
<div ng-if="setting.header && setting.footer==false">
     <div ng-repeat="item in items">
          <div>{{item.header}}</div>
          <div>{{item.title}}</div>      
     </div>
</div>
<div ng-if="setting.header==false && setting.footer">
     <div ng-repeat="item in items">         
          <div>{{item.title}}</div>   
          <div>{{item.footer}}</div>   
     </div>
</div>
<div ng-if="setting.header==false && setting.footer==false">
     <div ng-repeat="item in items">         
          <div>{{item.title}}</div>                
     </div>
</div>
درست است من هم با شما موافق هستم که خوشایند نیست. در این کد ما همه‌ی حالت‌ها را پیش بینی و قالب مناسب هر شرط را درست کرده‌ایم. حجم کد چند برابر شده، ولی از لحاظ Performance در ساخت لیست در View در حد 98% بهبود پیدا کرده‌است. همان مثال قبلی را در نظر بگیرید. ng-if مربوط به header و footer در این کد فقط 4 بار بررسی می‌شود. چه 100 رکورد باشد، چه 1000 تا، چه 10 تا رکورد. 
در مورد ng-repeat‌ها هم نگران نباشید فقط یک بار اجرا میشوند. اگر کارکرد ng-if را در مقاله‌ی قبلی من ، خوانده باشید، متوجه‌ی این موضوع می‌شوید که element‌های داخلی و direction‌های AngularJS داخلی ng-if زمانی پردازش می‌شوند که شرط true باشد. از این روش زمانی استفاده کنید که تعداد داده‌ها و حالت‌های زیادی دارید و Performance اهمیت بیشتری دارد. امیدوارم مقاله‌ی مفیدی باشد.
مطالب
AngularJS #4
در این قسمت قصد دارم تا یک سیستم ارسال دیدگاه را به کمک Angular پیاده سازی کنم. هدف از این مثال؛ آشنایی با چند Directive توکار Angular و همچنین آموختن چگونگی کار با سرویس http$ برای ارتباط با سرور است.
کدهای HTML زیر را در نظر بگیرید:
<div ng-app="myApp">
    <div ng-controller="CommentCtrl">

        <div ng-repeat="comment in comments">
            <div style="float:right;cursor:pointer;" ng-click="remove(comment.Id,$index);">X</div>
            <a href="#">
                <img style="width:32px;" ng-src="/Content/user.gif" alt="{{comment.Name}}">
            </a>
            <div>
                <h4>{{comment.Name}}</h4>
                {{comment.CommentBody}}
            </div>
        </div>

        <div>
            <form action="/Comment/Add" method="post">
                <div>
                    <label for="Name">Name</label>
                    <input id="Name" type="text" name="Name" ng-model="comment.Name" placeholder="Your Name" />
                </div>
                <div>
                    <label for="Email">Email</label>
                    <input id="Email" type="text" name="Email" ng-model="comment.Email" placeholder="Your Email" />
                </div>
                <div>
                    <label for="CommentBody">Comment</label>
                    <textarea id="CommentBody" name="CommentBody" ng-model="comment.CommentBody" placeholder="Your Comment"></textarea>
                </div>
                <button type="button" ng-click="addComment()">Send</button>
            </form>
        </div>

    </div>
</div>
خب از ابتدا ساختار را مورد بررسی قرار می‌دهم و موارد ناآشنای آن را توضیح می‌دهم:

ng-app: خاصیت ng-app جز خواص پیش فرض HTML نیست و یک خاصیت سفارشی است که توسط Angular به صورت پیش فرض تعریف شده است. این خاصیت به Angular می‌گوید که کدام بخش از DOM باید توسط Angular مدیریت و پردازش شود. در اینجا div ای که با خاصیت ng-app مزین شده است به همراه تمامی عناصر فرزند آن توسط موتور پردازش گر DOM توکار مورد پردازش قرار گرفته و اصطلاحا کامپایل می‌شود. بله! اینجا از لفظ کامپایل شدن برای بیان این فرآیند استفاده کردم. هیچ کدام از این Directive‌های سفارشی به خودی خود برای مرورگر قابل تفسیر نیست و اینجاست که Angular وارد عمل شده و این Directive‌ها را به کدهای HTML و جاوا اسکریپت که برای مرورگر قابل فهم است تبدیل می‌کند. به همین جهت با ng-app مشخص می‌کنیم که کدام بخش از DOM باید توسط Angular تفسیر و مدیریت شود.شاید این سوال برای شما مطرح شده باشد که در مثال قبلی ng-app مقداری نداشت و برای تگ html تعریف شده بود. پاسخ این است که در مثال قبلی چون برنامه‌ی ما دارای یک ماژول بیشتر نبود می‌توانستیم از مقدار دهی ng-app صرف نظر کنیم؛ اما در این مثال ما قصد داریم کمی هم مفهوم ماژول را در Angular بررسی کنیم. در نتیجه در این مثال برنامه‌ی ما از ماژولی به نام myApp تشکیل شده است. دلیل اینکه در این مثال ng-app بر روی یک div تعریف شده است این است که همین قسمت از DOM توسط Angular تفسیر شود برای ما کفایت می‌کند. هنگامی ng-app را بر روی html تعریف می‌کنیم که قصد داشته باشیم کل صفحه توسط Angular تفسیر شود.
 
ng-controller: در Angular کنترلر‌ها تابع سازنده‌ی کلاس‌های ساده‌ی جاوا اسکریپتی هستند که به کمک آن‌ها بخشی از صفحه را مدیریت می‌کنیم. این که کدام بخش از صفحه توسط کدام کلاس کنترل و مدیریت شود، توسط ng-controller مشخص می‌شود. در اینجا هم عنصری که با ng-controller مشخص شده به همراه تمامی فرزندانش، توسط کلاس جاوا اسکریپتی به نام CommentCtrl مدیریت می‌شود. در حقیقت ما به کمک ng-controller مشخص می‌کنیم که کدام قسمت از View توسط کدام Controller مدیریت می‌شود. مرسوم است که در Angular نام کنترلرها با Ctrl خاتمه یابد.
   
ng-repeat: همه‌ی نظرات دارای یک قالب html یکسان هستند که به ازای داده‌های متفاوت تکرار شده اند. اگر می‌خواستیم نظرات را استفاده از موتور نمایشی Razor نشان دهیم از یک حلقه‌ی foreach استفاده می‌کردیم. خبر خوب این است که ng-repeat هم دقیقا به مانند حلقه‌ی foreach عمل می‌کند.در اینجا عبارت comment in comments دقیقا برابر با آن چیزی است که در یک حلقه‌ی foreach می‌نوشتیم. Comments در اینجا یک لیست به مانند آرایه ای از comment هست که در کنترلر مقدار دهی شده است. پس اگر با حلقه‌ی foreach مشکلی نداشته باشید با مفهوم ng-repeat هم مشکلی نخواهید داشت و دقیقا به همان شکل عمل می‌نماید.
     
ng-click: همان طور که گفتیم Directive‌های تعریف شده می‌توانند یک event سفارشی نیز باشند. ng-click هم یک Directive تو کار است که توسط Angular به صورت پیش فرض تعریف شده است. کاملا مشخص است که یک تابع به نام remove تعریف شده است که به هنگام کلیک شدن، فراخوانی می‌شود. دو پارامتر هم به آن ارسال شده است. اولین پارامتر Id دیدگاه مورد نظر است تا به سرور ارسال شود و از پایگاه داده حذف شود. دومین پارامتر index$ است که یک متغیر ویژه است که توسط Angular در هر بار اجرای حلقه‌ی ng-repeat مقدارش یک واحد افزایش می‌یابد. index$ هم به تابع remove ارسال می‌شود تا بتوان فهمید در سمت کلاینت کدام نظر باید حذف شود.
 
ng-src: از این Directive برای مشخص کردن src عکس‌ها استفاده می‌شود. البته در این مثال چندان تفاوتی بین ng-src و src معمولی وجود ندارد. ولی اگر آدرس عکس به صورت Content/{{comment.Name}}.gif می‌بود دیگر وضع فرق می‌کرد. چرا که مرورگر با دیدن آدرس در src سعی به لود کردن آن عکس می‌کند و در این حالت در لود کردن آن عکس با شکست روبرو می‌شود. ng-src سبب می‌شود تا در ابتدا آدرس عکس توسط Angular تفسیر شود و سپس آن عکس توسط مرورگر لود شود.
   
{{comment.Name}}: آکلودهای دوتایی برای انقیاد داده (Data Binding) با view-model استفاده می‌شود. این نوع اقیاد داده در مثال‌های قبلی مورد بررسی قرار گرفته است و نکته‌ی بیشتری در اینجا مطرح نیست.
   
ng-model: به کمک ng-model می‌توان بین متن داخل textbox و خاصیت شی مورد نظر انقیاد داده بر قرار کرد و هر دو طرف از تغییرات یکدیگر آگاه شوند. به این عمل انقیاد داده دوطرفه (Two-Way Data-Binding) می‌گویند.برای مثال textbox مربوط به نام را به comment.Name و textbox مربوط به email را به comment.Email مقید(bind) شده است. هر تغییری که در محتوای هر کدام از طرفین صورت گیرد دیگری نیز از آن تغییر با خبر شده و آن را نمایش می‌دهد.
   
تا به اینجای کار قالب مربوط به HTML را بررسی کردیم. حال به سراغ کدهای جاوا اسکریپت می‌رویم:
var app = angular.module('myApp', []);

app.controller('CommentCtrl', function ($scope, $http) {

    $scope.comment = {};

    $http.get('/Comment/GetAll').success(function (data) {

        $scope.comments = data;

    })

    $scope.addComment = function () {

        $http.post("/Comment/Add", $scope.comment).success(function () {

            $scope.comments.push({ Name: $scope.comment.Name, CommentBody: $scope.comment.CommentBody });

            $scope.comment = {};

        });
    };

    $scope.remove = function (id, index) {

        $http.post("/Comment/Remove", { id: id }).success(function () {

            $scope.comments.splice(index, 1);

        });
    };

});
در تعریف ng-app اگر به یاد داشته باشید برای آن مقدار myApp در نظر گرفته شده بود. در اینجا هم ما به کمک متغیر سراسری angular که توسط خود کتابخانه تعریف شده است، ماژولی به نام myApp را تعریف کرده ایم. پارامتر دوم را فعلا توضیح نمی‌دهم، ولی در این حد بدانید که برای تعریف وابستگی‌های این ماژول استفاده می‌شود که من آن را برابر یک آرایه خالی قرار داده ام.
در سطر بعد برای ماژول تعریف شده یک controller تعریف کرده ام. شاید دفعه‌ی اول است که تعریف کنترلر به این شکل را مشاهده می‌کنید. اما چرا به این شکل کنترلر تعریف شده و به مانند قبل به شکل تابع سازنده‌ی کلاس تعریف نشده است؟
پاسخ این است که اکثر برنامه نویسان از جمله خودم دل خوشی از متغیر سراسری ندارند. در شکل قبلی تعریف کنترلر، کنترلر به شکل یک متغیر سراسری تعریف می‌شد. اما استفاده از ماژول برای تعریف کنترلر سبب می‌شود تا کنترلرهای ما روی هوا تعریف نشده باشند و هر یک در جای مناسب خود باشند. به این شکل مدیریت کدهای برنامه نیز ساده‌تر بود. مثلا اگر کسی از شما بپرسد که فلان کنترلر کجا تعریف شده است؛ به راحتی می‌گویید که در فلان ماژول برنامه تعریف و مدیریت شده است.
در تابعی که به عنوان کنترلر تعریف شده است، دو پارامتر به عنوان وابستگی درخواست شده است. scope که برای ارتباط با view-model و انقیاد داده به کار می‌رود و http$  که برای ارتباط با سرور به کار می‌رود. نمونه‌ی مناسب هر دوی این پارامترها توسط سیستم تزریق وابستگی تو کار angular در اختیار کنترلر قرار می‌گیرد.
قبلا چگونگی استفاده از scope$ برای اعمال انقیاد داده توضیح داده شده است. نکته‌ی جدیدی که مطرح است چگونگی استفاده از سرویس http$ برای ارتباط با سرور است. سرویس http $   دارای 4 متد put ، post ، get و delete است.
واقعا استفاده از این سرویس کاملا واضح و روشن است. در متد addComment وقتی که دیدگاه مورد نظر اضافه شد، به آرایه‌ی کامنت‌ها یک کامنت جدید می‌افزاییم و چون انقیاد داده دو طرفه است، بالافاصله دیدگاه جدید نیز در view به نمایش در می‌آید.کار تابع remove هم بسیار ساده است. با استفاده از index ارسالی، دیدگاه مورد نظر را از آرایه‌ی کامنت‌ها حذف می‌کنیم و ادامه‌ی کار توسط انقیاد داده دو طرفه انجام می‌شود.
همان طور که مشاهده می‌شود مفاهیم انقیاد داده دو طرفه و تزریق وابستگی خودکار سرویس‌های مورد نیاز، کار با angularjs را بسیار ساده و راحت کرده است. اصولا در بسیاری از موارد احتیاجی به باز اختراع چرخ نیست و کتابخانه‌ی angular آن را برای ما از قبل تدارک دیده است.
   
کدهای این مثال ضمیمه شده است. این کدها در Visual Studio 2013 و به کمک ASP.NET MVC 5 و Entity Framework 6 نوشته شده است. سعی شده تا مثال نوشته شده به واقعیت نزدیک باشد. اگر دقت کنید مدل کامنت در مثالی که نوشتم به گونه ای است که دیدگاه‌های چند سطحی به همراه پاسخ هایش مد نظر بوده است. به عنوان تمرین نمایش درختی این گونه دیدگاه‌ها را به کمک Angular انجام دهید. کافیست Treeview in Angular را جست و جو کنید؛ مطمئنا به نتایج زیادی می‌رسید. گرچه در مثال ضمیمه شده اگر جست و جو کنید من پیاده سازیش را انجام دادم. هدف از جست و جو در اینترنت مشاهده این است که بیشتر مسائل در Angular از پیش توسط دیگران حل شده است و احتیاجی نیست که شما با چالش‌های جدیدی دست و پنجه نرم کنید.
پس به عنوان تمرین، دیدگاه‌های چند سطحی به همراه پاسخ که نمونه اش را در همین سایتی که درحال مشاهده آن هستید می‌بینید را به کمک AngularJS پیاده سازی کنید.
  
در مقاله‌ی بعدی چگونگی انتقال منطق تجاری برنامه از کنترلر به لایه سرویس و چگونگی تعریف سرویس جدید را مورد بررسی قرار می‌دهم.
   

مطالب
Angular Animation – بخش اول
متحرک سازی (Motion)، یکی از مفاهیم مهم در طراحی وب‌اپلیکیشن‌های مدرن محسوب می‌شود. امروزه استفاده از انیمیشن در طراحی رابط کاربری به عنوان جزء جدا ناپذیر UX محسوب می‌شود. برای درک اهمیت انیمیشن در طراحی، « نه فقط به برای زیبایی و چیدمان، بلکه به عنوان جزء جدایی ناپذیر UX » را ببینید. در Angular طراحی انیمیشن برای ساخت رابط کاربری نه تنها کاری سرگرم کننده، بلکه بسیار آسان است.

نصب Angular Animations  
برای شروع کار با Animation در Angular، ابتدا باید کتابخانه Angular Animation را توسط دستور زیر نصب کرد و سپس BrowserAnimationsModule را به ماژول اصلی برنامه اضافه کنید. 
> npm install @angular/animations@latest --save
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
  imports: [
    //…
    BrowserAnimationsModule
  ],
})

در اینجا مثال ساده‌ای را مشاهده خواهید کرد که انتقال (transition) المنت li را از حالت active به inactive، با یک انمیشن ساده همراه خواهد کرد. محل تعریف انیمیشن‌ها در Angular، داخل متادیتای component@ و توسط خصوصیت animations می‌باشد. در ابتدای کار باید توابع مختص انیمیشن را در فایل component مورد نظر خود توسط دستور import به شکل زیر وارد کنیم: 
import { trigger, state, style, transition, animate } from '@angular/animations';
برای پیاده سازی انیمیشن در component، وارد کردن تمامی توابع بالا اجباری هستند. در قدم بعدی با افزودن خصوصیت animations به متادیتای component@، کدهای مربوط به کامپوننت را به شکل زیر تغییر می‌دهیم: 
import { Component } from '@angular/core';
import { trigger,state,style,transition,animate } from '@angular/animations';

@Component({
  selector: 'app-root',
  template: `
    <div style='width:50%; margin:auto'>
      <ul>
        <li [@myState]="currentState"
          style='width:100px; padding:10px; list-style-type: none' 
            (click)="toggleState()">
          {{currentState}}
        </li>
      </ul>
    </div>
  `,
  styleUrls: ['./app.component.css'],
  animations: [
      trigger('myState', [
        state('inactive', style({
          backgroundColor: '#eee',
          transform: 'scale(1)'
        })),
        state('active',   style({
          backgroundColor: '#cfd8dc',
          transform: 'scale(1.1)'
        })),
        transition('inactive => active', animate('100ms ease-in')),
        transition('active => inactive', animate('100ms ease-out'))
      ])
    ]
})
export class AppComponent {
  currentState: string = 'inactive';
  toggleState():void{
    this.currentState = this.currentState === 'inactive' ? 'active' : 'inactive';
  }
}


توضیحات تکمیلی

trigger: جهت تعریف یک انیمیشن، از تابع trigger استفاده می‌کنیم. این تابع استفاده شده‌ی در خصوصیت animations، در پارامتر اول خود، یک نام یکتا را دریافت خواهد کرد و در پارامتر بعدی، لیستی از توابع وارد شده مختص انیمیشن را دریافت می‌کند.

نکته: خصوصیت animations، قابلیت دریافت چندین تابع trigger را برای داشتن چندین انیمیشن مجزا، دارا است.

state: با استفاده از این تابع قادر به تعریف وضعیت‌های مختلف خواهیم بود. انیمشن در Angular بر دو منطق وضعیت (state) و گذار (transition) پیاده سازی می‌شود. به عبارت دیگر انیمیشن در Angular بر روی گذار (transition) بین وضعیت‌ها (states) قابل تعریف هستند. این تابع در پارامتر اول خود یک نام و در پارامتر دوم خود تابع style را دریافت می‌کند.

style: با استفاده از این تابع قادر به تعریف استایلی برای وضعیت تعریف شده خواهیم بود.

transition: برای ساخت انیمیشن واقعی مورد استفاده قرار می‌گیرد. این تابع در پارامتر اول خود، مسیر حرکت بین دو حالت (state) را به صورت یک رشته دریافت کرده و در پارامتر دوم خود، تابع animate را دریافت می‌کند. در این مثال مسیر حرکت به صورت 'inactive => active' تعریف شده است. یعنی هنگام گذار از وضعیت inactive به وضعیت active، تابع animate در پارامتر دوم اجرا خواهد شد.

animate: این تابع دو پارامتر timing و styles را دریافت می‌کند. timing مقداری از جنس رشته (string) است که می‌تواند ترکیبی از مدت زمان (duration) با مقدار اختیاری تاخیر(delay) و مقدار easing باشد. به عنوان مثال مقدار کامل زیر را درنظر بگیرید: 

'1s 100ms ease-out'

در این اینجا مدت زمان، برابر ۱ ثانیه، تاخیر، ۱۰۰ میلی ثانیه و easing، مقدار ease-out خواهد بود و به معنی اجرای انیمیشن با افکت ease-out و در مدت زمان ۱ ثانیه و با تاخیر در شروع به مدت ۱۰۰ میلی ثانیه می‌باشد. در صورتیکه به این پارامتر مقداری عددی را ارسال کنیم، عدد به عنوان مدت زمان (duration) و بر مبنای واحد میلی ثانیه در نظر گرفته خواهد شد. تمامی مقادیر زیر برای ارسال به این پارامتر معتبر می‌باشند: 

500            // duration=500ms 
"1s"            // duration=1s
"100ms 0.5s"        // duration=100ms, delay=0.5s
"5s ease"        // duration=5s, easing=ease
"5s 10ms cubic-bezier(.17,.67,.88,.1)"    // duration=5s, delay=10ms, easing=cubic-bezier(.17,067,.88,.1)

پارامتر styles در تابع animate یکی از توابع style یا keyframes می‌باشد. البته این پارامتر اختیاری است و در قسمت نکات تکمیلی توضیح داده خواهد شد. در مثال بالا از این پارامتر صرف نظر شده است.

برای متصل کردن انیمیشن تعریف شده به المنت‌های موجود در صفحه، از خصوصیت [triggerName@] استفاده کنید. trigger تعریف شده در قطعه کد بالا myState نام دارد. بنابراین برای اینکه المنت li در گذار بین حالت‌ها، این انیمیشن را داشته باشد، باید [myState@] را به المنت خود اضافه کنید. همچنین مقدار حالت جاری را باید برای این خصوصیت تامین کرد. این مقدار می‌تواند یک رشته استاتیک یا یک عبارت محاسبه شده توسط یک تابع یا یک متغیر باشد.

همانطور که در مثال بالا مشاهده می‌کنید، با استفاده از متغیر currentState، المنت li در ابتدا در حالت inactive قرار دارد. با کلیک اول بر روی المنت، تابع toggleState باعث تغییر وضعیت المنت از وضعیت inactive به وضعیت active خواهد شد (inactive=>active) بنابراین انیمیشن زیر اجرا خواهد شد.

 transition('inactive => active', animate('100ms ease-in'))

با کلیک دوباره، المنت از وضعیت active به inactive خواهد رفت (active=>inactive)، بنابراین انیمیشن زیر اجرا می‌شود.

 

نکات تکمیلی

 در صورتیکه در تابع transition، پارامتر دوم برای حالتهای مختلف یکسان باشد، برای مثال رفتن از حالت active به حالت inactive و برعکس، می‌توان از روش زیر استفاده کرد. 
transition('inactive => active, active => inactive', animate('100ms ease-out'))

یا به شکل ساده‌تر: 

transition('inactive <=> active', animate('100ms ease-out'))


کاراکتر * جایگزین تمامی حالتهای موجود در برنامه خواهد بود. برای مثال:

'* <= active': بر روی تمامی گذارهای از حالت active به هر حالت دیگر، اعمال خواهد شد.

'active <= *': بر روی تمامی گذارهای از هر حالت به حالت active، اعمال خواهد شد.

 '* <= *': بر روی تمامی گذارها اعمال خواهد شد. 



همانطور که قبلا ذکر شد، تابع animate در پارامتر دوم خود یک تابع style یا keyframes را دریافت می‌کند. در صورتیکه بخواهیم در مدت زمان اجرای انیمیشن بر روی المنت، استایلی را نیز همراه کنیم، می‌توانیم از تابع style استفاده کنیم. اما این استایل بعد از اتمام انیمیشن بر روی المنت باقی نخواهد ماند؛ چون حالت (state) مقصد برای خود استایل تعریف شده‌است. علاوه بر این، در تابع transition می‌توان به شکل زیر یک استایل خطی را نیز تعریف کرد: 

transition('inactive => active', [
  style({
    backgroundColor: '#cfd8dc',
    transform: 'scale(1.3)'
  }),
  animate('80ms ease-in', style({
    backgroundColor: '#eee',
    transform: 'scale(1)'
  }))
]),

این تعریف استایل در تابع transition در شروع گذار بلافاصله بر روی المنت اعمال خواهد شد و در طول انیمیشن استایل را از دست خواهد داد و به استایل مقصد خواهد رسید.

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