مطالب
React 16x - قسمت 11 - طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
در طی چند قسمت قصد داریم مثال قسمت قبل را که کار نمایش لیستی از فیلم‌ها را انجام می‌دهد، تبدیل به یک کامپوننت گرید کنیم که دارای امکانات صفحه بندی، فیلتر کردن و مرتب سازی اطلاعات است.


بررسی ساختار کامپوننت Pagination

شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import Pagination from "./common/pagination";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و پس از بسته شدن tag المان جدول، قصد داریم این کامپوننت صفحه بندی را نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
/>
این کامپوننت نیاز به تعداد کل آیتم‌های فیلم‌ها را دارد؛ به علاوه‌ی اندازه‌ی صفحه، یا همان تعداد ردیفی که قرار است در هر صفحه نمایش داده شود. به این ترتیب کامپوننت صفحه بندی می‌تواند تعداد صفحات مورد نیاز را محاسبه کرده و سپس آن‌ها را رندر کند.
  state = {
    movies: getMovies(),
    pageSize: 4
  };

به همین جهت دو ویژگی itemsCount و pageSize را پیش از هرکاری به تعریف المان صفحه بندی اضافه کرده‌ایم تا داده‌های ورودی آن مشخص شوند. ویژگی itemsCount از تعداد اعضای آرایه‌ی movies حاصل می‌شود و pageSize را به عنوان یک خاصیت جدید شیء منتسب به state تعریف و با عدد 4 مقدار دهی کرده‌ایم.
همچنین در لیست صفحاتی که توسط این کامپوننت رندر می‌شود، باید با کلیک بر روی هر کدام، اطلاعات آن صفحه رندر شود. به همین جهت در اینجا ویژگی onPageChange تعریف شده و به متد جدید handlePageChange در کامپوننت movies، متصل گردیده تا عدد page را دریافت کرده و به آن واکنش نشان دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
  };
تا اینجا اینترفیس کامپوننت صفحه بندی را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی‌ها و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


نمایش شماره صفحات گرید، در کامپوننت صفحه بندی

برای رندر کامپوننت صفحه بندی، از کلاس‌های مخصوص اینکار که در بوت استرپ تعریف شده‌اند، استفاده می‌کنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل می‌شود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحه‌ای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آن‌ها استفاده خواهیم کرد:
import React, { Component } from "react";

class Pagination extends Component {
  render() {
    return (
      <nav>
        <ul className="pagination">
          <li className="page-item">
            <a className="page-link">1</a>
          </li>
        </ul>
      </nav>
    );
  }
}

export default Pagination;

تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلم‌ها مشاهده خواهید کرد:


اکنون باید رندر این liها را بر اساس ورودی‌های این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایه‌ای داریم که بر اساس این ورودی‌ها، شماره‌ی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
class Pagination extends Component {
  // ...
  getPageNumbersArray() {
    const { itemsCount, pageSize } = this.props;
    const pagesCount = Math.ceil(itemsCount / pageSize);
    if (pagesCount === 1) {
      return null;
    }

    const pages = new Array();
    for (let i = 1; i <= pagesCount; i++) {
      pages.push(i);
    }
    return pages;
  }
}
در اینجا بر اساس ورودی‌ها، تعداد صفحات محاسبه شده و سپس بر اساس آن‌ها آرایه‌ای از این شماره صفحه‌ها تشکیل و بازگشت داده می‌شود. همچنین اگر تعداد صفحات 1 بود، می‌خواهیم این کامپوننت چیزی را رندر نکند. به همین جهت در اینجا null بازگشت داده شده‌است. دلیل استفاده‌ی از Math.ceil که کوچکترین عدد صحیح بزرگتر یا مساوی خروجی را بازگشت می‌دهد، نیز همین مورد است. توسط این متد، خروجی float دریافتی به integer تبدیل شده و سپس قابلیت مقایسه‌ی با 1 را پیدا می‌کند. برای مثال اگر تعداد ردیف‌های صفحه را به 10 تنظیم کنیم، خروجی این تقسیم در این مثال، 0.9 خواهد بود که شرط بررسی pagesCount === 1 را برآورده نمی‌کند. به همین جهت توسط متد Math.ceil، این خروجی به عدد 1 تقریب زده شده و سبب بازگشت نال از این متد خواهد شد.
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات می‌کنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کرده‌ایم:
class Pagination extends Component {
  render() {
    const pages = this.getPageNumbersArray();
    if (!pages) {
      return null;
    }

    return (
      <nav>
        <ul className="pagination">
          {pages.map(page => (
            <li key={page} className="page-item">
              <a className="page-link">{page}</a>
            </li>
          ))}
        </ul>
      </nav>
    );
  }

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


با داشتن 9 فیلم در آرایه‌ی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شده‌است. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.


مدیریت انتخاب شماره‌های صفحات

در این قسمت می‌خواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر می‌دهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
<a
   onClick={() => this.props.onPageChange(page)}
   className="page-link"
   style={{ cursor: "pointer" }}
>
   {page}
</a>

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

اکنون می‌خواهیم اگر صفحه‌ای انتخاب شد، شماره‌ی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحه‌ی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه می‌کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
  currentPage={this.state.currentPage}
/>
این ویژگی نیز مقدار خودش را از state به روز شده دریافت می‌کند:
class Movies extends Component {
  state = {
    movies: getMovies(),
    pageSize: 4,
    currentPage: 1
  };
به روز رسانی state نیز در متد handlePageChange که به ویژگی onPageChange متصل است، بر اساس page دریافتی از کامپوننت صفحه بندی، رخ می‌دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
    this.setState({currentPage: page});
  };
بنابراین هرگاه که بر روی یک شماره صفحه در کامپوننت صفحه بندی کلیک می‌شود، رخ‌داد onPageChange متصل به تعریف المان Pagination درج شده‌ی در کامپوننت movies، روی داده و به همراه آن شماره صفحه‌ای، به متد رخ‌دادگران متصل به آن در کامپوننت movies که در اینجا handlePageChange نام دارد، ارسال می‌شود. در این متد state کامپوننت به روز شده و این امر سبب فراخوانی مجدد متد رندر می‌شود که در انتهای آن کامپوننت Pagination درج شده‌است. بنابراین به روز رسانی state، سبب رندر مجدد کامپوننت صفحه بندی با currentPage جدیدی که به آن رسیده‌است، خواهد شد.

پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال می‌کنیم:
<li
  key={page}
  className={
    page === this.props.currentPage
    ? "page-item active"
    : "page-item"
  }
>
پس از اعمال این تغییرات، اکنون برنامه در مرورگر به صورت زیر به نظر می‌رسد:


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


نمایش صفحه بندی شده‌ی اطلاعات

تا اینجا لیستی که نمایش داده می‌شود، حاوی تمام اطلاعات آرایه‌ی this.state.movies است و بر اساس شماره‌ی صفحه‌ی انتخابی، تغییر نمی‌کند. به همین جهت با استفاده از متد slice، تکه‌ای از آرایه‌ی movies را که بر اساس شماره صفحه‌ی انتخابی و تعداد ردیف‌ها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت می‌دهیم:
  paginate() {
    const first = (this.state.currentPage - 1) * this.state.pageSize;
    const last = first + this.state.pageSize;
    return this.state.movies.slice(first, last);
  }
اکنون در متد رندر کامپوننت movies، بجای کار با کل آرایه‌ی this.state.movies، آرایه‌ی جدید slice شده را توسط متد paginate دریافت کرده:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const movies = this.paginate();
و سپس در همین متد رندر، فراخوانی this.state.movies.map را به movies.map تغییر می‌دهیم، تا تنها لیست مرتبط با هر صفحه‌ی انتخابی نمایش داده شود.

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



بررسی صحت نوع پارامترهای ارسالی به کامپوننت‌ها

تا اینجا فرض بر این است که مصرف کننده‌ی کامپوننت صفحه بندی، دقیقا همان ویژگی‌هایی را که ما طراحی کرده‌ایم، با همان نام‌ها و همان نوع‌ها را حتما به آن ارسال می‌کند. همچنین اگر افزونه‌ی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگی‌های زیر در خط فرمان:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha

 به ازای هر خاصیت props استفاده شده‌ی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:



که عنوان می‌کند props validation این خاصیت استفاده شده، فراموش شده‌است.
در نگارش‌های قبلی React، امکانات بررسی نوع‌های ارسالی به کامپوننت‌ها، جزئی از بسته‌ی اصلی آن بود؛ اما از نگارش 15 به بعد، به بسته‌ی مستقلی منتقل شده‌است که باید به صورت جداگانه‌ای در ریشه‌ی پروژه نصب شود:
> npm i prop-types --save

البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بسته‌ی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژول‌های برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل می‌شود:
 C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index

اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه می‌کنیم:
 import PropTypes from "prop-types";
سپس در انتهای این فایل، اعتبارسنجی props آن‌را تعریف خواهیم کرد:
Pagination.propTypes = {
  itemsCount: PropTypes.number.isRequired,
  pageSize: PropTypes.number.isRequired,
  currentPage: PropTypes.number.isRequired,
  onPageChange: PropTypes.func.isRequired
};

export default Pagination;
همانطور که مشاهده می‌کنید، در اینجا خاصیت جدید propTypes (دقیقا با همین نگارش؛ در غیراینصورت بررسی نوع‌ها کار نخواهد کرد)، به تعریف کلاس Pagination اضافه شده‌است (پس از تعریف کلاس کامپوننت به صورت مستقل اضافه می‌شود).
سپس مقدار این خاصیت جدید را به شیءای تنظیم می‌کنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شده‌ی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import می‌شود، کار تعریف نوع این خواص و اجباری بودن آن‌ها را می‌توان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف می‌شوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشته‌ی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند itemsCount یک عدد را می‌پذیرد و نوع ورودی آن اشتباه است:

البته این خطا فقط در حالت development مشاهده می‌شود و در حالت توزیع برنامه، خیر.

بنابراین تعریف propTypes یک best practice ایجاد کامپوننت‌های React است که نه فقط بررسی نوع‌ها را فعال می‌کند، بلکه می‌تواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آن‌ها، اطلاعات کاملی را ارائه کند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-11.zip
اشتراک‌ها
nest فریم ورک قدرتمند nodejs بر پایه Typescript

Features

Compatible with both TypeScript and ES6 (I strongly recommend to use TypeScript)
Based on well-known libraries (Express / socket.io) so you could share your experience
Easy to learn - syntax is similar to Angular / Spring (Java)
Dependency Injection, built-in Inversion of Control container
Exceptions handler layer (helps to focus on logic)
Own modularity system (split your system into reusable modules)
WebSockets module (based on socket.io)
Reactive microservices support with messages patterns (transport via TCP / Redis)
Testing utilities  
nest فریم ورک قدرتمند nodejs بر پایه Typescript
نظرات مطالب
EF Code First #7
د صورتی که یک رابطه many-to - many  داشته باشیم و ابتدا مثل رابطه Role , Customer  ذخیره سازی ارتباط این جداول به چه صورت است ؟
در حالت عادی  برای هر ذخیره سازی هم اطلاعات Role و هم Customer  ذخیره می‌گردد .
آیا باید یه Entity واسط هم در نظر بگیریم و پس از ثبت اطلاعات مثلا Customer  با ID مربوط به Role از طریق Entity  واسط اطلاعات ارتباط این 2 Entity  ذخیره شود ؟
مطالب
پیاده سازی 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 دریافت کنید .
اشتراک‌ها
مدل پیکره بندی جدید در ASP.NET CORE

ASPNET Core has a lot of changes compared with the others versions of ASP.NET. One change I want to highlight is the new way to configure settings. With previous versions, this configuration was made by using the common file web.config, but this file simply doesn’t exist anymore in ASP.NET Core. Instead, a new easy way to declare and access to the settings is provided. Let’s take a look. 

مدل پیکره بندی جدید در ASP.NET CORE
اشتراک‌ها
آموزش 2 ساعته روش کار با Postman

"Learn Postman in One Video: Master the API Testing Tool" is an engaging and comprehensive Udemy course designed to take you from a beginner to a proficient user of Postman. In this concise and focused course, you will learn everything you need to know about Postman, the popular API testing and development tool.
 

آموزش 2 ساعته روش کار با Postman
اشتراک‌ها
Webpack 5 منتشر شد

webpack is arguably the most popular bundler right now and this is the first major release in over two years. We get big improvements to caching (and therefore performance), reduced bundle sizes with better tree shaking, and a variety of breaking changes to prepare for the future (automatic Node polyfills are gone, for one). 

Webpack 5 منتشر شد