[CLI] Build-time performance improvements.
[CLI] Global tools; replaces .NET CLI Tools (DotNetCliToolReference).
[CoreCLR] Minor-version roll-forward.
[CoreCLR] No-copy array slicing with Span<T>.
[CoreFX] HttpClient performance improvements.
[CoreFX] Windows Compatibility Pack.
[ASP.NET] SignalR is available for .NET Core.
[ASP.NET] HTTPS is on by default for ASP.NET.
[EF] Basic lazy loading support.
[EF] Support for Azure Cosmos DB.
اشتراکها
سلام. ممنون. در کنار این آموزش ها، که این سری سمت Xamarin اش هست، و سری سمت ASP.NET Core و Angular اش هم آموزش هاشون تو راهه. یاد که بگیرید، یه CLI داریم، باهاش میشه راحت پروژهها رو ساخت. کلا ما این مدت مشغول ساختن امکانات بودیم و تا آخر امسال هم پرونده Docs رو از هر جهت میبندیم. شما فعلا پیش بیایید و از روی همین پروژه XamApp هم با امکانات آشنا بشید (مثلا تو همین قسمت چهاردهم که به تازگی نوشته شده، مدیریت خطا تو برنامههای Bit enabled رو میتونید ببینید)
Insight #1: React is here to stay
Insight #2: Angular is shifting to a new role --> enterprise apps
Insight #3: You can’t ignore Vue.js anymore
Insight #4: Knowledge of some libraries will help you earn more (but not for the reasons you might think)
Insight #5: 2018 will be the year of GraphQL
Insight #6: JavaScript != Front-end
Insight #7: Microsoft is striking back
Insight #8: JavaScript is different around the world
Insight #9: Typed JavaScript is on the rise
Insight #10: JavaScript is whatever you want it to be
در این سری قصد داریم یک برنامهی سادهی دفترچه تلفن را توسط Angular 6x و کامپوننتهای متریال آن ایجاد کنیم؛ اما Grid جزئی از بستهی Angular Material نیست. بنابراین برای طرحبندی برنامه و قرار دادن المانهای مختلف در مکانهای تعیین شدهی صفحه، از Angular FlexBox Module استفاده خواهیم کرد که محصور کنندهی CSS 3 FlexBox است.
آشنایی با Flex Layout Box Model
برای طراحی ظاهر یک برنامهی وب نیاز است عناصر آنرا در مکانهای مختلفی از صفحه قرار داد که به آن Layout گفته میشود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS
امروزه دیگر آنچنان روشهای 1 و 2 به صورت مستقیم مورد استفاده قرار نمیگیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی میکنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی میشود.
FlexBox CSS، سیلان المانهای قرار گرفتهی در داخل آنرا سبب میشود. در اینجا یک container اصلی وجود دارد که در برگیرندهی المانها است. در تصویر فوق دو محور را مشاهده میکنید. محور افقی از چپ به راست ادامه پیدا میکند. محور عمودی نحوهی ارتباط عناصر را مشخص میکند.
اکنون این سؤال مطرح میشود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلولها نگاه کرد. برای مثال عناصر قرار گرفتهی درون Container یا به صورت افقی درون آن گسترده شده و قرار میگیرند و یا به صورت عمودی.
نحوهی تفکر و کارکرد با FlexBox چگونه است؟
در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که میتواند افقی و یا عمودی باشد.
ب) اندازهی المانها که میتواند ثابت و یا نسبی باشد.
یک Container، جهت سیلان عناصر درون آنرا مشخص میکند. المانهای آن، اندازه، فاصلهی از یکدیگر و ترتیب قرارگیری را ارائه میدهند. یک flex container میتواند شامل چندین flex container تو در تو نیز باشد.
نحوهی سیلان عناصر در FlexBox چگونه است؟
برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:
نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:
سپس نحوهی سیلان عناصر درون Containerهای تعریف شده را مشخص میکنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازههای مشخص دو عنصر قبلی، به سطر دوم منتقل شدهاست.
در ادامه به قسمت میانی میرسیم که آن نیز دارای سیلان افقی از چپ به راست است:
در اینجا نیز میتوان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازهی آنها در یک سطر جا نشدهاند، به سطر بعدی منتقل خواهند شد:
و تمام این سیلانها و انتقال به سطرهای بعدی بر اساس اندازهی المانها صورت میگیرد:
البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیهی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار میگیرد، دارای سیلان خاص خودش است و اندازههای آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیهی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، اینها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.
معرفی کتابخانهی Angular Flex Layout
برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگهای مرورگرها و تفاوتهای پیاده سازیهای آنها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشدهاست.
جهت رفع این مشکلات و محدودیتها، تیم Angular کتابخانهای را به نام Angular Flex Layout مخصوص نگارشهای جدید Angular طراحی کردهاست. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده میشود.
مزایای کار با کتابخانهی Angular flex layout
- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شدهاست.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق میکند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شدهاست.
نصب و تنظیم کتابخانهی Angular Flex layout
برای نصب این کتابخانه، در ریشهی پروژه دستور زیر را صادر کنید:
سپس ماژول آنرا باید به shared.module.ts اضافه کرد:
کار با API استاتیک Angular Flex layout
API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شدهاست که به HTML قالب کامپوننتها اضافه میشود.
- از data binding پشتیبانی میکند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق میکند. Inline CSS تزریق شده به ویژگیهای styles هر المان تزریق میشوند و موارد مشابه را در صورت وجود بازنویسی میکنند.
- از تشخیص تغییرات پشتیبانی میکند.
- به همراه ویژگیهای fxHide و fxShow است.
- کارآیی مطلوبی دارد.
در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده میشود:
- fxLayout جهتهای flex را مشخص میکند.
- fxLayout میتواند دارای مقداری مانند row، column و row-reverse و column-reverse باشد. برای مثال مقدار row-reverse، نمایش از راست به چپ را سبب میشود.
- fxLayoutWrap مشخص میکند که آیا المانها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
- fxLayoutGap فاصلهی بین المانها را مشخص میکند.
- fxLayoutAlign نحوهی چیدمان المان را تعیین میکند.
چند مثال:
و یا حالت راست به چپ آن به صورت زیر است:
و برای تعریف آیتمهای قرار گرفتهی درون containers میتوان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المانها
در اینجا سه مقداری که ذکر میشوند (و یا تنها یک مقدار) چنین معنایی را به همراه دارند:
و یا
- grow به این معنا است که آیتم جاری در صورت وجود فضا (طراحی واکنشگرا و واکنش نشان دادن به اندازهی صفحه)، نسبت به سایر المانها تا چه اندازهای میتواند بزرگ شود.
- shrink به این معنا است که اگر به اندازهی کافی فضا وجود نداشت، این المان نسبت به سایر المانهای دیگر تا چه اندازهای میتواند کوچک شود.
- basis به معنای اندازهی پیشفرض المان است.
در اینجا اندازهها برحسب پیکسل، درصد و یا calcs, em, cw, vh میتوانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.
- fxflexorder برای تعیین ترتیب قرارگیری یک المان
- fxflexoffset برای تعیین فاصله یک المان از container آن
- fxflexAlign برای تعیین محل قرارگیری المان
- fxflexfill برای تعیین اینکه این المان کل ردیف یا ستون را پر خواهد کرد
چند مثال:
در اینجا سه نمایشی را که در ذیل تعریف divها مشاهده میکنید بر اساس تغییر اندازهی صفحه حاصل شدهاند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازهی صفحه و دسترسی به مقدار فضای بیشتر، بزرگتر شدهاست.
یک مثال کامل
اگر علاقمند باشید تا توانمندیهای angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینههای مختلف، کد معادل angular flex layout آن نیز تولید میشود.
همچنین wiki خود پروژه نیز به همراه مثالهای بیشتری است:
https://github.com/angular/flex-layout/wiki
کار با API واکنشگرای Angular Flex layout
در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازهی صفحه و یا اندازهی وسیلهی نمایشی، تغییر اندازه و همچنین موقعیت میدهند و این تغییرات بر اساس انطباق با viewport وسیلهی نمایشی صورت میگیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار میدهد.
اگر علاقمند هستید تا اندازههای واکنشگرای استاندارد متریال را ملاحظه کنید، میتوانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازهای کمتر از 600px در گروه extra small قرار میگیرد (با مخفف xs). از 600px تا 1024px در بازهی small (با مخفف sm)، از 1024px تا 1440px در بازهی medium (با مخفف md) و از 1440px تا 1920px در بازهی large (با مخفف lg) و بیشتر از آن در بازهی xlrage قرار میگیرند (با مخفف xl). این اعداد و بازهها، پایهی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازهها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شدهاند و مورد استفاده قرار میگیرند. همچنین اگر اندازههای مدنظر از این بازهها کمتر باشند، میتوان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازههای مورد نیاز بیش از این اندازهها باشند میتوان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخففها «media query alias» هم گفته میشود و اکنون که لیست آنها مشخص است، تنها کافی است آنها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
برای نمونه فرض کنید یک چنین طرحبندی دسکتاپی را داریم:
معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:
اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آنرا تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟
برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه میکنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازهی xs، محل قرارگیری المانهای این ستون را هم مشخص میکنیم:
همانطور که ملاحظه میکنید کار کردن با این API بسیار سادهاست و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهدهی اثر آنها بهتر است اندازهی مرورگر را کوچک و بزرگ کنید.
مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازهی آن
علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمتهای مختلف صفحه بر اساس اندازهی صفحهی نمایشی نیز هست:
در اینجا fxShow سبب نمایش این div در حالت عادی میشود (پیشفرض آن xl، md و sm است). اما اگر اندازهی صفحه lg باشد، fxHide.lg تنظیم شدهی به true سبب مخفی سازی آن خواهد شد و در اندازهی xs مجددا نمایش داده میشود.
تغییر اندازهی قسمتی از صفحه بر اساس اندازهی آن
در مثال زیر اگر اندازهی صفحه gt-sm باشد (بیشتر از small)، اندازهی این div به 100 درصد بجای 50 درصد حالتهای دیگر، تنظیم میشود:
حالتهای ویژهی طراحی واکنشگرا در Angular Flex Layout
در API واکنشگرای آن حالتهای ویژهی fxshow, fxhide, ngclass و ngstyle نیز درنظر گرفته شدهاند که امکان فعالسازی آنها در اندازههای مختلف صفحه مسیر است:
امکان کار با API واکنشگرا از طریق برنامه نویسی
برای این منظور میتوان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:
در اینجا به فعالسازی یک بازهی خاص گوش فرا خواهیم داد. برای مثال اگر اندازهی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.
برای مطالعهی بیشتر
آشنایی با Flex Layout Box Model
برای طراحی ظاهر یک برنامهی وب نیاز است عناصر آنرا در مکانهای مختلفی از صفحه قرار داد که به آن Layout گفته میشود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS
امروزه دیگر آنچنان روشهای 1 و 2 به صورت مستقیم مورد استفاده قرار نمیگیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی میکنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی میشود.
FlexBox CSS، سیلان المانهای قرار گرفتهی در داخل آنرا سبب میشود. در اینجا یک container اصلی وجود دارد که در برگیرندهی المانها است. در تصویر فوق دو محور را مشاهده میکنید. محور افقی از چپ به راست ادامه پیدا میکند. محور عمودی نحوهی ارتباط عناصر را مشخص میکند.
اکنون این سؤال مطرح میشود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلولها نگاه کرد. برای مثال عناصر قرار گرفتهی درون Container یا به صورت افقی درون آن گسترده شده و قرار میگیرند و یا به صورت عمودی.
نحوهی تفکر و کارکرد با FlexBox چگونه است؟
در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که میتواند افقی و یا عمودی باشد.
ب) اندازهی المانها که میتواند ثابت و یا نسبی باشد.
یک Container، جهت سیلان عناصر درون آنرا مشخص میکند. المانهای آن، اندازه، فاصلهی از یکدیگر و ترتیب قرارگیری را ارائه میدهند. یک flex container میتواند شامل چندین flex container تو در تو نیز باشد.
نحوهی سیلان عناصر در FlexBox چگونه است؟
برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:
نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:
سپس نحوهی سیلان عناصر درون Containerهای تعریف شده را مشخص میکنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازههای مشخص دو عنصر قبلی، به سطر دوم منتقل شدهاست.
در ادامه به قسمت میانی میرسیم که آن نیز دارای سیلان افقی از چپ به راست است:
در اینجا نیز میتوان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازهی آنها در یک سطر جا نشدهاند، به سطر بعدی منتقل خواهند شد:
و تمام این سیلانها و انتقال به سطرهای بعدی بر اساس اندازهی المانها صورت میگیرد:
البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیهی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار میگیرد، دارای سیلان خاص خودش است و اندازههای آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیهی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، اینها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.
معرفی کتابخانهی Angular Flex Layout
برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگهای مرورگرها و تفاوتهای پیاده سازیهای آنها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشدهاست.
جهت رفع این مشکلات و محدودیتها، تیم Angular کتابخانهای را به نام Angular Flex Layout مخصوص نگارشهای جدید Angular طراحی کردهاست. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده میشود.
مزایای کار با کتابخانهی Angular flex layout
- یک کتابخانهی متکی به خود و مستقل است و برای کار با آن الزامی به استفادهی از Angular Material نیست.
- به همراه هیچ فایل CSS جانبی ارائه نمیشود.- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شدهاست.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق میکند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شدهاست.
نصب و تنظیم کتابخانهی Angular Flex layout
برای نصب این کتابخانه، در ریشهی پروژه دستور زیر را صادر کنید:
npm install @angular/flex-layout --save
import { FlexLayoutModule } from "@angular/flex-layout"; @NgModule({ imports: [ FlexLayoutModule ], exports: [ FlexLayoutModule ] }) export class SharedModule { }
کار با API استاتیک Angular Flex layout
API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شدهاست که به HTML قالب کامپوننتها اضافه میشود.
- از data binding پشتیبانی میکند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق میکند. Inline CSS تزریق شده به ویژگیهای styles هر المان تزریق میشوند و موارد مشابه را در صورت وجود بازنویسی میکنند.
- از تشخیص تغییرات پشتیبانی میکند.
- به همراه ویژگیهای fxHide و fxShow است.
- کارآیی مطلوبی دارد.
در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده میشود:
- fxLayout جهتهای flex را مشخص میکند.
<div fxLayout="row" fxLayout.xs="column"></div>
- fxLayoutWrap مشخص میکند که آیا المانها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
<div fxLayoutWrap></div>
<div fxLayoutGap="10px"></div>
<div fxLayoutAlign="start stretch"></div>
چند مثال:
و یا حالت راست به چپ آن به صورت زیر است:
و برای تعریف آیتمهای قرار گرفتهی درون containers میتوان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المانها
<div fxFlex="1 2 calc(15em + 20px)"></div>
fxFlex="grow shrink basis"
fxFlex="basis"
- shrink به این معنا است که اگر به اندازهی کافی فضا وجود نداشت، این المان نسبت به سایر المانهای دیگر تا چه اندازهای میتواند کوچک شود.
- basis به معنای اندازهی پیشفرض المان است.
در اینجا اندازهها برحسب پیکسل، درصد و یا calcs, em, cw, vh میتوانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.
- fxflexorder برای تعیین ترتیب قرارگیری یک المان
<div fxFlexOrder="2"></div>
<div fxFlexOffset="20px"></div>
<div fxFlexAlign="center"></div>
<div fxFlexFill></div>
چند مثال:
در اینجا سه نمایشی را که در ذیل تعریف divها مشاهده میکنید بر اساس تغییر اندازهی صفحه حاصل شدهاند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازهی صفحه و دسترسی به مقدار فضای بیشتر، بزرگتر شدهاست.
یک مثال کامل
اگر علاقمند باشید تا توانمندیهای angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینههای مختلف، کد معادل angular flex layout آن نیز تولید میشود.
همچنین wiki خود پروژه نیز به همراه مثالهای بیشتری است:
https://github.com/angular/flex-layout/wiki
کار با API واکنشگرای Angular Flex layout
در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازهی صفحه و یا اندازهی وسیلهی نمایشی، تغییر اندازه و همچنین موقعیت میدهند و این تغییرات بر اساس انطباق با viewport وسیلهی نمایشی صورت میگیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار میدهد.
اگر علاقمند هستید تا اندازههای واکنشگرای استاندارد متریال را ملاحظه کنید، میتوانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازهای کمتر از 600px در گروه extra small قرار میگیرد (با مخفف xs). از 600px تا 1024px در بازهی small (با مخفف sm)، از 1024px تا 1440px در بازهی medium (با مخفف md) و از 1440px تا 1920px در بازهی large (با مخفف lg) و بیشتر از آن در بازهی xlrage قرار میگیرند (با مخفف xl). این اعداد و بازهها، پایهی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازهها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شدهاند و مورد استفاده قرار میگیرند. همچنین اگر اندازههای مدنظر از این بازهها کمتر باشند، میتوان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازههای مورد نیاز بیش از این اندازهها باشند میتوان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخففها «media query alias» هم گفته میشود و اکنون که لیست آنها مشخص است، تنها کافی است آنها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
fxLayout.sm = "..." fxLayoutAlign.md = "..." fxHide.gt-sm = "..."
معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:
که در اینجا دو container را ملاحظه میکنید. ابتدا Container بیرونی جهت ارائهی ستونی از سه المان اضافه شدهاست. سپس یک Container میانی برای تعریف ردیفی از سه المان تعریف شدهاست. توسط روش "fxFlex="grow shrink basis نیز اندازههای آنها مشخص شدهاند.
اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آنرا تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟
برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه میکنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازهی xs، محل قرارگیری المانهای این ستون را هم مشخص میکنیم:
همانطور که ملاحظه میکنید کار کردن با این API بسیار سادهاست و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهدهی اثر آنها بهتر است اندازهی مرورگر را کوچک و بزرگ کنید.
مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازهی آن
علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمتهای مختلف صفحه بر اساس اندازهی صفحهی نمایشی نیز هست:
<div fxShow fxHide.xs="false" fxHide.lg="true"></div>
تغییر اندازهی قسمتی از صفحه بر اساس اندازهی آن
در مثال زیر اگر اندازهی صفحه gt-sm باشد (بیشتر از small)، اندازهی این div به 100 درصد بجای 50 درصد حالتهای دیگر، تنظیم میشود:
<div fxFlex="50%" fxFlex.gt-sm="100%"></div>
حالتهای ویژهی طراحی واکنشگرا در Angular Flex Layout
در API واکنشگرای آن حالتهای ویژهی fxshow, fxhide, ngclass و ngstyle نیز درنظر گرفته شدهاند که امکان فعالسازی آنها در اندازههای مختلف صفحه مسیر است:
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div> <div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div> <div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div> <div [ngStyle.xs]="{color: 'blue'}"></div>
امکان کار با API واکنشگرا از طریق برنامه نویسی
برای این منظور میتوان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:
در اینجا به فعالسازی یک بازهی خاص گوش فرا خواهیم داد. برای مثال اگر اندازهی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.
برای مطالعهی بیشتر
قسمتهای عمدهای از مطلب جاری، از ویدیوی زیر که توسط نویسندهی اصلی angular flex layout تهیه شدهاست، گردآوری شدند.
نظرات مطالب
WF:Windows Workflow #۵
آقا محسن حتما مثالی که چگونگی کاربرد Workflow در برنامههای وب و دسکتاب به چه نحو است حتما گفته میشود .
در قسمت قبل، فرمهای template driven را بررسی کردیم. همانطور که مشاهده کردید، این نوع فرمها، قابلیتهای اعتبارسنجی پیشرفتهای را به همراه ندارند. برای فرمهایی که نیاز به اعتبارسنجیهای سفارشی دارند، فرمهای model driven پیشنهاد میشوند که در این قسمت بررسی خواهند شد.
طراحی فرم ثبت نام کاربران در سایت با روش model driven
در این قسمت قصد داریم فرم ثبت نام کاربران را به همراه اعتبارسنجیهای پیشرفتهای پیاده سازی کنیم. به همین منظور، ابتدا پوشهی جدید App\users را به مثال سری جاری اضافه کنید و سپس سه فایل user.ts، signup-form.component.ts و signup-form.component.html را به آن اضافه نمائید.
فایل user.ts بیانگر مدل کاربران سایت است؛ با این محتوا:
قالب فرم یا signup-form.component.html، در حالت ابتدایی آن چنین شکل استانداردی را خواهد داشت و فاقد اعتبارسنجی خاصی است:
اکنون میخواهیم این فرم را به یک فرم AngularJS 2.0 ارتقاء دهیم. بنابراین نیاز است اشیاء Control و ControlGroup را ایجاد کنیم و اینبار نمیخواهیم AngularJS 2.0 مانند قسمت قبلی، به صورت خودکار (و ضمنی)، این اشیاء را برای ما ایجاد کند. میخواهیم آنها را با کدنویسی (به صورت صریح) ایجاد کنیم تا بتوانیم بر روی آنها کنترل بیشتری داشته باشیم.
بنابراین ابتدا کلاس کامپوننت این فرم را در فایل signup-form.component.ts به نحو ذیل تکمیل کنید:
و همچنین پیامهای اعتبارسنجی اولیه را نیز به نحو زیر به فایل signup-form.component.html اضافه میکنیم:
توضیحات:
تفاوت مهم این فرم و اعتبارسنجیهایش با قسمت قبل، در ایجاد اشیاء Control و ControlGroup به صورت صریح است:
کلاسهای Control، ControlGroup و Validators در ماژول angular/common@ تعریف شدهاند. بنابراین import متناظری نیز به ابتدای فایل اضافه شدهاست:
یک نکته
اگر محل قرارگیری کلاسی را فراموش کردید، آنرا در مستندات AngularJS 2.0 ذیل قسمت API Review جستجو کنید. نتیجهی جستجو، به همراه نام ماژول کلاسها نیز میباشد.
خاصیت عمومی form که با new ControlGroup تعریف شدهاست، حاوی تعاریف صریح کنترلهای موجود در فرم خواهد بود. در اینجا سازندهی ControlGroup، یک شیء را میپذیرد که کلیدهای آن، همان نام کنترلهای تعریف شدهی در قالب فرم و مقدار هر کدام، یک Control جدید است که پارامتر اول آن یک مقدار پیش فرض و پارامتر دوم، اعتبارسنجی مرتبطی را تعریف میکند (این اعتبارسنجی معرفی شده، یک متد استاتیک در کلاس توکار Validators است).
بنابراین چون سه المان ورودی، در فرم جاری تعریف شدهاند، سه کلید جدید هم نام نیز در پارامتر ورودی ControlGroup ذکر گردیدهاند.
اکنون که خاصیت عمومی form، در کلاس کامپوننت فوق تعریف شد، آنرا در قالب فرم، به ngFormModel بایند میکنیم:
به این ترتیب به AngularJS 2.0 اعلام میکنیم که ControlGroup و Controlهای آنرا به صورت صریح ایجاد کردهایم و بجای وهلههای پیش فرض خود، از خاصیت عمومی form کلاس کامپوننت، این مقادیر را تامین کن.
مراحل بعد آن، با مراحلی که در قسمت قبل بررسی کردیم، تفاوتی ندارند:
الف) در اینجا به هر المان موجود، یک ngControl نسبت داده شدهاست تا هر المان را تبدیل به یک کنترل AngularJS2 2.0 کند.
ب) به هر المان، یک متغیر محلی شروع شده با # نسبت داده شدهاست تا با اتصال آن به ngForm بتوان به ngControl تعریف شده دسترسی پیدا کرد.
البته اکنون میتوان از خاصیت form متصل به ngFormModel نیز بجای تعریف این متغیر محلی، به نحو ذیل استفاده کرد:
ج) از این متغیر محلی جهت نمایش یا عدم نمایش پیامهای خطای اعتبارسنجی، در ngIfهای تعریف شده، استفاده شدهاست.
د) و در آخر متد onSumbit موجود در کلاس کامپوننت را به رخداد ngSubmit متصل کردهایم. همانطور که ملاحظه میکنید اینبار دیگر پارامتری را به آن ارسال نکردهایم. از این جهت که خاصیت form موجود در سطح کلاس، اطلاعات کاملی را از اشیاء موجود در آن دارد و در متد onSubmit کلاس، به آن دسترسی داریم.
this.form.value حاوی یک شیء است که تمام مقادیر پر شدهی فرم را به همراه دارد.
بنابراین تا اینجا تنها تفاوت فرم جدید تعریف شده با قسمت قبل، تعریف صریح ControlGroup و کنترلهای آن در کلاس کامپوننت و اتصال آن به ngFormModel است. به این نوع فرمها، فرمهای model driven هم میگویند.
نمایش فرم افزودن کاربران توسط سیستم Routing
با نحوهی تعریف مسیریابیها در قسمت نهم آشنا شدیم. برای نمایش فرم افزودن کاربران، میتوان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن کاربران، اضافه شدهاست. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شدهاست.
معرفی کلاس FormBuilder
روش دیگری نیز برای ساخت ControlGroup و کنترلهای آن با استفاده از کلاس و سرویس فرم ساز توکار AngularJS 2.0 وجود دارد:
کلاس و سرویس FormBuilder نیز در ماژول angular/common@ قرار دارد. برای استفادهی از آن، آنرا در سازندهی کلاس تزریق کرده و سپس از متد group آن استفاده میکنیم. نحوهی تعریف کلی اعضای آن با اعضای ControlGroup یکی است؛ با این تفاوت که اینبار بجای ذکر new Control، یک آرایه تعریف میشود که دقیقا اعضای آن، همان پارامترهای شیء کنترل هستند. این روش در کل خلاصهتر است و در آن تعریف چندین گروه مختلف، سادهتر میباشد. همچنین با روش تزریق وابستگیهای بکار رفتهی در این فریم ورک نیز سازگاری بیشتری دارد.
پیاده سازی اعتبارسنجی سفارشی
فرض کنید میخواهیم ورود نام کاربرهای دارای فاصله را غیر معتبر اعلام کنیم. برای این منظور فایل جدید usernameValidators.ts را به پوشهی app\users اضافه کنید؛ با این محتوا:
کلاس UsernameValidators میتواند شامل تمام اعتبارسنجیهای سفارشی خاصیت نام کاربری باشد. به همین جهت نام آن جمع است و به s ختم شدهاست.
هر متد پیاده سازی کنندهی یک اعتبار سنجی سفارشی در این کلاس، استاتیک تعریف میشود؛ با نام دلخواهی که مدنظر است.
پارامتر ورودی این متدهای استاتیک، یک وهله از شیء کنترل است که توسط آن میتوان برای مثال به خاصیت value آن دسترسی یافت و بر این اساس منطق اعتبارسنجی خود را پیاده سازی نمود. به همین جهت import آن نیز به ابتدای فایل جاری اضافه شدهاست.
خروجی این متد دو حالت دارد:
الف) اگر null باشد، یعنی اعتبارسنجی موفقیت آمیز بودهاست.
ب) اگر اعتبارسنجی با شکست مواجه شود، خروجی این متد یک شیء خواهد بود که کلید آن، نام اعتبارسنجی مدنظر است و مقدار این کلید، هر چیزی میتواند باشد؛ یک true و یا یک شیء دیگر که اطلاعات بیشتری را در مورد این شکست ارائه دهد.
برای مثال اگر اعتبارسنج توکار required با شکست مواجه شود، یک چنین شیءایی را بازگشت میدهد:
و یا اگر اعتبارسنج minlength باشکست مواجه شود، اطلاعات بیشتری را در قسمت مقدار این کلید بازگشتی، ارائه میدهد:
در کل اینکه چه چیزی را بازگشت دهید، بستگی به طراحی مدنظر شما دارد.
پس از پیاده سازی یک اعتبارسنجی سفارشی، برای استفادهی از آن، ابتدا ماژول آنرا به ابتدای ماژول signup-form.component.ts اضافه میکنیم:
پس از آن، شبیه به افزودن متد استاتیک توکار Validators.required، این متد جدید را به لیست اعتبارسنجیهای خاصیت name اضافه میکنیم. از آنجائیکه پیشتر المان دوم این آرایه مقدار دهی شدهاست، برای ترکیب چندین اعتبارسنجی با هم، از متد Validators.compose که آرایهای از متدهای اعتبارسنجی را قبول میکند، کمک خواهیم گرفت:
و مرحلهی آخر، نمایش یک پیام اعتبارسنجی مناسب و متناظر با متد cannotContainSpace است. برای این منظور فایل signup-form.component.html را گشوده و تغییرات ذیل را اعمال کنید:
همانطور که در قسمت قبل نیز عنوان شد، چون اکنون به یک المان، بیش از یک اعتبارسنجی اعمال شدهاست، استفاده از خاصیت valid، بیش از اندازه عمومی بوده و باید از خاصیت errors استفاده کرد. به همین جهت این دو اعتبارسنجی را در یک div محصور کننده قرار میدهیم و در صورت وجود خطایی، خاصیت name.errors، دیگر نال نبوده و دو برچسب قرار گرفتهی در آن بر اساس شرطهای ngIf آن، پردازش خواهند شد.
نام خاصیت بازگشت داده شدهی در اعتبارسنجی سفارشی، به عنوان یک خاصیت جدید شیء errors قابل استفاده است؛ مانند name.errors.cannotContainSpace.
به عنوان تمرین ماژول جدید emailValidators.ts را افزوده و سپس اعتبارسنجی سفارشی بررسی معتبر بودن ایمیل وارد شده را تعریف کنید:
در ادامه آنرا به لیست formBuilder.group افزوده و همچنین پیام اعتبارسنجی ویژهای را نیز به قالب فرم اضافه کنید (کدهای کامل آن، در فایل zip انتهای بحث موجود است).
یک نکته
اگر نیاز است از regular expressions مانند مثال فوق استفاده شود، میتوان از متد توکار Validators.pattern نیز استفاده کرد و نیازی به تعریف یک متد جداگانه برای آن وجود ندارد؛ مگر اینکه نیاز به بازگشت شیء خطای کاملتری با خواص بیشتری وجود داشته باشد.
اعتبارسنجی async یا اعتبارسنجی از راه دور (remote validation)
یک سری از اعتبارسنجیها را در سمت کلاینت میتوان تکمیل کرد؛ مانند بررسی معتبر بودن فرمت ایمیل وارده. اما تعدادی دیگر، نیاز به اطلاعاتی از سمت سرور دارند. برای مثال آیا نام کاربری در حال ثبت، تکراری است یا خیر؟ این نوع اعتبارسنجیها در ردهی async validation قرار میگیرند.
سازندهی شیء Control در AngularJS 2.0 که در مثالهای بالا نیز مورد استفاده قرار گرفت، پارامتر اختیاری سومی را نیز دارد که یک AsyncValidatorFn را قبول میکند:
پیاده سازی async validators، بسیار شبیه به سایر اعتبارسنجها هستند. اما از آنجائیکه نیاز به کار با سرور را دارند، استاتیک تعریف کردن آنها، سبب قطع شدن دسترسی آنها از context کلاس جاری شده و امکان تزریق وابستگیها را از دست خواهیم داد. برای مثال دیگر نمیتوان به سادگی، سرویس دریافت اطلاعات کاربران را در اینجا تزریق کرد. یک راه حل رفع این مشکل، تعریف همان متد اعتبارسنج در کلاس کامپوننت فرم است:
و سپس فراخوانی آن به صورت ذیل، به عنوان سومین عنصر آرایهی تعریف شده:
در اینجا با استفاده از arrow functions، امکان دسترسی به این متد تعریف شدهی در سطح کلاس، که استاتیک هم نیست، وجود خواهد داشت. به این ترتیب دیگر context کلاس را از دست ندادهایم و اینبار میتوان به this._userService، که آنرا در ادامه تکمیل خواهیم کرد، بدون مشکلی دسترسی یافت.
امضای متد nameShouldBeUnique تفاوتی با سایر متدهای اعتبارسنج نداشته و پارامتر ورودی آن، همان کنترل است که توسط آن میتوان به مقدار وارد شدهی توسط کاربر دسترسی یافت. اما تفاوت اصلی آن در اینجا است که این متد باید یک شیء Promise را بازگشت دهد. یک Promise، بیانگر نتیجهی یک عملیات async است. در اینجا دو حالت resolve و error را باید پیاده سازی کرد. در حالت error، یعنی عملیات async صورت گرفته با شکست مواجه شدهاست و در حالت resolve، یعنی عملیات تکمیل شده و اکنون میخواهیم نتیجهی نهایی را بازگشت دهیم. نتیجه نهایی بازگشت داده شدهی در اینجا، همانند سایر validators است و اگر نال باشد، یعنی اعتبارسنجی موفقیت آمیز بوده و اگر یک شیء را بازگشت دهیم، یعنی اعتبارسنجی با شکست مواجه شدهاست.
این Promise، از یک سرویس تعریف شدهی در فایل جدید user.service.ts استفاده میکند:
با نحوهی تعریف سرویسها و همچنین کار با سرور و دریافت اطلاعات، در قسمتهای قبلی بیشتر آشنا شدیم. در اینجا یک درخواست get، به آدرس home/checkuser سرور، ارسال میشود. سپس نتیجهی آن در قالب اینترفیس IResult بازگشت داده خواهد شد. این اینترفیس را در فایل result.ts به صورت ذیل تعریف کردهایم:
کدهای سمت سرور برنامه که کار بررسی یکتا بودن نام کاربری را انجام میدهند، به صورت ذیل در فایل Controllers\HomeController.cs تعریف شدهاند:
در اینجا اگر نام کاربری وارد شده مساوی Vahid بود، یک شیء anonymous، مطابق امضای اینترفیس IResult سمت کاربر (همان فایل result.ts عنوان شده) بازگشت داده میشود.
بنابراین تا اینجا مسیر سمت سرور home/checkuser تکمیل شدهاست. این مسیر توسط سرویس کاربر صدا زده شده و اگر نام کاربری وارد شده موجود باشد، نتیجهای را مطابق امضای قرارداد IResult سفارشی ما بازگشت میدهد.
پس از آن مجددا به فایل signup-form.component.ts مراجعه کرده و سرویس جدید UserService را به سازندهی آن تزریق کردهایم. همچنین قسمت providers این کامپوننت را هم جهت تکمیل اطلاعات تزریق کنندهی توکار AngularJS 2.0 مقدار دهی کردهایم. البته همانطور که در مبحث تزریق وابستگیها نیز عنوان شد، اگر این سرویس قرار نیست در کلاس دیگری استفاده شود، نیازی نیست تا آنرا در بالاترین سطح ممکن و در فایل app.component.ts ثبت و معرفی کرد:
پس از ترزیق وابستگی private _userService: UserService، اکنون این سرویس به سادگی و به حالت متداولی در متد nameShouldBeUnique(control: Control) قابل دسترسی خواهد بود و از آن میتوان جهت اعتبارسنجیهای غیرهمزمان استفاده کرد.
اکنون که کدهای فعال سازی اعتبارسنجی از راه دور ما تکمیل شدهاست، به فایل signup-form.component.html مراجعه کرده و پیام مناسبی را نمایش خواهیم داد:
در ادامه اگر برنامه را اجرا کنید، با ورود نام کاربری Vahid، یک چنین پیام خطایی، مشاهده خواهد شد:
نمایش پیام loading در حین انجام اعتبارسنجی از راه دور
شاید بد نباشد که در حین انجام عملیات اعتبارسنجی از راه دور و ارسال درخواستی به سرور و بازگشت نتیجهی آن، یک پیام loading را نیز نمایش داد. برای انجام اینکار نیاز است تغییرات ذیل را به فایل signup-form.component.html اضافه کنیم:
در اینجا یک div جدید را ذیل المان ورود نام کاربری اضافه کردهایم. همچنین نحوهی نمایش آنرا با دسترسی به متغیر name# و کنترل منتسب، به آن مدیریت میکنیم. اگر عملیات async ایی بر روی این کنترل در حال اجرا باشد، Promise تعریف شده، وضعیت pending را بازگشت میدهد. به همین جهت میتوان از این خاصیت، جهت نمایش دادن یا مخفی کردن عبارت و یا تصویری استفاده کرد.
اعتبارسنجی ترکیبی در حین submit یک فرم
فرض کنید میخواهید منطقی را که حاصل اعتبارسنجی تمام فیلدهای فرم است (و نه هر کدام به تنهایی)، در حین submit آن اعمال کنید. برای مثال آیا ترکیب نام کاربری و کلمهی عبور شخصی در حین login معتبر است یا خیر؟ در این حالت پس از بررسیهای لازم در متد onSubmit، میتوان با استفاده از متد find شیء form، به یکی از کنترلهای فرم دسترسی یافت و سپس با استفاده از متد setErrors، خطای اعتبارسنجی سفارشی را به آن اضافه کرد:
سپس در سمت قالب این کامپوننت، نحوهی نمایش این اعتبارسنجی سفارشی، همانند قبل است:
اتصال المانهای فرم به مدلی جهت ارسال به سرور
اکنون که دسترسی به خاصیت this.form را داریم و این خاصیت توسط [ngFormModel] به تمام اشیاء تعریف شدهی در فرم و تغییرات آنها دسترسی دارد، میتوان از آن برای دسترسی به شیءایی که حاوی مدل فرم است، استفاده کرد. برای نمونه در مثال فوق، خاصیت value آن، چنین خروجی را دارد:
بنابراین برای ارسال اطلاعات این فرم به سرور، تنها کافی است این شیء را ارسال کنیم. به همین جهت در فایل user.service.ts، به کلاس سرویس کاربران، متد addUser را اضافه میکنیم:
کدهای سمت سرور آن در فایل Controllers\HomeController.cs نیز چنین شکلی را میتوانند داشته باشند:
و پس از آن کدهای متد onSubmit فایل signup-form.component.ts برای ارسال این شیء به صورت ذیل خواهند بود:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: (این کدها مطابق نگارش RC 1 هستند)
MVC5Angular2.part11.zip
خلاصهی بحث
برای اینکه بتوان کنترل بیشتری را بر روی المانهای فرم داشت، ابتدا سرویس FormBuilder را در سازندهی کلاس کامپوننت فرم تزریق میکنیم. سپس با استفاده از متد group آن، المانهای فرم را به صورت کلیدهای شیء پارامتر آن تعریف میکنیم. در اینجا میتوان اعتبارسنجیهای توکار AngularJS 2.0 را که در کلاس پایهی Validators مانند Validators.required وجود دارند، تعریف کرد. با استفاده از متد compose آنها را ترکیب نمود و یا پارامتر سومی را جهت اعتبارسنجیهای async اضافه نمود. در این حالت شیء form تعریف شده به صورت [ngFormModel] به قالب فرم متصل میشود و از تغییرات آن آگاه خواهد شد.
طراحی فرم ثبت نام کاربران در سایت با روش model driven
در این قسمت قصد داریم فرم ثبت نام کاربران را به همراه اعتبارسنجیهای پیشرفتهای پیاده سازی کنیم. به همین منظور، ابتدا پوشهی جدید App\users را به مثال سری جاری اضافه کنید و سپس سه فایل user.ts، signup-form.component.ts و signup-form.component.html را به آن اضافه نمائید.
فایل user.ts بیانگر مدل کاربران سایت است؛ با این محتوا:
export interface IUser { id: number; name: string; email: string; password: string; }
قالب فرم یا signup-form.component.html، در حالت ابتدایی آن چنین شکل استانداردی را خواهد داشت و فاقد اعتبارسنجی خاصی است:
<form> <div class="form-group"> <label form="name">Username</label> <input id="name" type="text" class="form-control" /> </div> <div class="form-group"> <label form="email">Email</label> <input id="email" type="text" class="form-control" /> </div> <div class="form-group"> <label form="password">Password</label> <input id="password" type="password" class="form-control" /> </div> <button class="btn btn-primary" type="submit">Submit</button> </form>
بنابراین ابتدا کلاس کامپوننت این فرم را در فایل signup-form.component.ts به نحو ذیل تکمیل کنید:
import { Component } from '@angular/core'; import { Control, ControlGroup, Validators } from '@angular/common'; @Component({ selector: 'signup-form', templateUrl: 'app/users/signup-form.component.html' }) export class SignupFormComponent { form = new ControlGroup({ name: new Control('', Validators.required), email: new Control('', Validators.required), password: new Control('', Validators.required) }); onSubmit(): void { console.log(this.form.value); } }
<form [ngFormModel]="form" (ngSubmit)="onSubmit()"> <div class="form-group"> <label form="name">Username</label> <input id="name" type="text" class="form-control" ngControl="name"/> <label class="text-danger" *ngIf="!form.controls['name'].valid"> Username is required. </label> </div> <div class="form-group"> <label form="email">Email</label> <input id="email" type="text" class="form-control" ngControl="email" #email="ngForm"/> <label class="text-danger" *ngIf="email.touched && !email.valid"> Email is required. </label> </div> <div class="form-group"> <label form="password">Password</label> <input id="password" type="password" class="form-control" ngControl="password" #password="ngForm"/> <label class="text-danger" *ngIf="password.touched && !password.valid"> Password is required. </label> </div> <button class="btn btn-primary" type="submit">Submit</button> </form>
تفاوت مهم این فرم و اعتبارسنجیهایش با قسمت قبل، در ایجاد اشیاء Control و ControlGroup به صورت صریح است:
form = new ControlGroup({ name: new Control('', Validators.required), email: new Control('', Validators.required), password: new Control('', Validators.required) });
import { Control, ControlGroup, Validators } from '@angular/common';
یک نکته
اگر محل قرارگیری کلاسی را فراموش کردید، آنرا در مستندات AngularJS 2.0 ذیل قسمت API Review جستجو کنید. نتیجهی جستجو، به همراه نام ماژول کلاسها نیز میباشد.
خاصیت عمومی form که با new ControlGroup تعریف شدهاست، حاوی تعاریف صریح کنترلهای موجود در فرم خواهد بود. در اینجا سازندهی ControlGroup، یک شیء را میپذیرد که کلیدهای آن، همان نام کنترلهای تعریف شدهی در قالب فرم و مقدار هر کدام، یک Control جدید است که پارامتر اول آن یک مقدار پیش فرض و پارامتر دوم، اعتبارسنجی مرتبطی را تعریف میکند (این اعتبارسنجی معرفی شده، یک متد استاتیک در کلاس توکار Validators است).
بنابراین چون سه المان ورودی، در فرم جاری تعریف شدهاند، سه کلید جدید هم نام نیز در پارامتر ورودی ControlGroup ذکر گردیدهاند.
اکنون که خاصیت عمومی form، در کلاس کامپوننت فوق تعریف شد، آنرا در قالب فرم، به ngFormModel بایند میکنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
مراحل بعد آن، با مراحلی که در قسمت قبل بررسی کردیم، تفاوتی ندارند:
الف) در اینجا به هر المان موجود، یک ngControl نسبت داده شدهاست تا هر المان را تبدیل به یک کنترل AngularJS2 2.0 کند.
ب) به هر المان، یک متغیر محلی شروع شده با # نسبت داده شدهاست تا با اتصال آن به ngForm بتوان به ngControl تعریف شده دسترسی پیدا کرد.
البته اکنون میتوان از خاصیت form متصل به ngFormModel نیز بجای تعریف این متغیر محلی، به نحو ذیل استفاده کرد:
<label class="text-danger" *ngIf="!form.controls['name'].valid">
د) و در آخر متد onSumbit موجود در کلاس کامپوننت را به رخداد ngSubmit متصل کردهایم. همانطور که ملاحظه میکنید اینبار دیگر پارامتری را به آن ارسال نکردهایم. از این جهت که خاصیت form موجود در سطح کلاس، اطلاعات کاملی را از اشیاء موجود در آن دارد و در متد onSubmit کلاس، به آن دسترسی داریم.
onSubmit(): void { console.log(this.form.value); }
بنابراین تا اینجا تنها تفاوت فرم جدید تعریف شده با قسمت قبل، تعریف صریح ControlGroup و کنترلهای آن در کلاس کامپوننت و اتصال آن به ngFormModel است. به این نوع فرمها، فرمهای model driven هم میگویند.
نمایش فرم افزودن کاربران توسط سیستم Routing
با نحوهی تعریف مسیریابیها در قسمت نهم آشنا شدیم. برای نمایش فرم افزودن کاربران، میتوان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before... import { SignupFormComponent } from './users/signup-form.component'; @Component({ //same as before… template: ` //same as before… <li><a [routerLink]="['AddUser']">Add User</a></li> //same as before… `, //same as before… }) @RouteConfig([ //same as before… { path: '/adduser', name: 'AddUser', component: SignupFormComponent } ]) //same as before...
معرفی کلاس FormBuilder
روش دیگری نیز برای ساخت ControlGroup و کنترلهای آن با استفاده از کلاس و سرویس فرم ساز توکار AngularJS 2.0 وجود دارد:
import { Control, ControlGroup, Validators, FormBuilder } from '@angular/common'; form: ControlGroup; constructor(formBuilder: FormBuilder) { this.form = formBuilder.group({ name: ['', Validators.required], email: ['', Validators.required], password: ['', Validators.required] }); }
پیاده سازی اعتبارسنجی سفارشی
فرض کنید میخواهیم ورود نام کاربرهای دارای فاصله را غیر معتبر اعلام کنیم. برای این منظور فایل جدید usernameValidators.ts را به پوشهی app\users اضافه کنید؛ با این محتوا:
import { Control } from '@angular/common'; export class UsernameValidators { static cannotContainSpace(control: Control) { if (control.value.indexOf(' ') >= 0) { return { cannotContainSpace: true }; } return null; } }
هر متد پیاده سازی کنندهی یک اعتبار سنجی سفارشی در این کلاس، استاتیک تعریف میشود؛ با نام دلخواهی که مدنظر است.
پارامتر ورودی این متدهای استاتیک، یک وهله از شیء کنترل است که توسط آن میتوان برای مثال به خاصیت value آن دسترسی یافت و بر این اساس منطق اعتبارسنجی خود را پیاده سازی نمود. به همین جهت import آن نیز به ابتدای فایل جاری اضافه شدهاست.
خروجی این متد دو حالت دارد:
الف) اگر null باشد، یعنی اعتبارسنجی موفقیت آمیز بودهاست.
ب) اگر اعتبارسنجی با شکست مواجه شود، خروجی این متد یک شیء خواهد بود که کلید آن، نام اعتبارسنجی مدنظر است و مقدار این کلید، هر چیزی میتواند باشد؛ یک true و یا یک شیء دیگر که اطلاعات بیشتری را در مورد این شکست ارائه دهد.
برای مثال اگر اعتبارسنج توکار required با شکست مواجه شود، یک چنین شیءایی را بازگشت میدهد:
{ required:true }
{ minlength : { requiredLength : 3, actualLength : 1 } }
پس از پیاده سازی یک اعتبارسنجی سفارشی، برای استفادهی از آن، ابتدا ماژول آنرا به ابتدای ماژول signup-form.component.ts اضافه میکنیم:
import { UsernameValidators } from './usernameValidators';
name: ['', Validators.compose([Validators.required, UsernameValidators.cannotContainSpace])],
و مرحلهی آخر، نمایش یک پیام اعتبارسنجی مناسب و متناظر با متد cannotContainSpace است. برای این منظور فایل signup-form.component.html را گشوده و تغییرات ذیل را اعمال کنید:
<div class="form-group"> <label form="name">Username</label> <input id="name" type="text" class="form-control" ngControl="name" #name="ngForm" /> <div *ngIf="name.touched && name.errors"> <label class="text-danger" *ngIf="name.errors.required"> Username is required. </label> <label class="text-danger" *ngIf="name.errors.cannotContainSpace"> Username can't contain space. </label> </div> </div>
نام خاصیت بازگشت داده شدهی در اعتبارسنجی سفارشی، به عنوان یک خاصیت جدید شیء errors قابل استفاده است؛ مانند name.errors.cannotContainSpace.
به عنوان تمرین ماژول جدید emailValidators.ts را افزوده و سپس اعتبارسنجی سفارشی بررسی معتبر بودن ایمیل وارد شده را تعریف کنید:
import {Control} from '@angular/common'; export class EmailValidators { static email(control: Control) { var regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; var valid = regEx.test(control.value); return valid ? null : { email: true }; } }
یک نکته
اگر نیاز است از regular expressions مانند مثال فوق استفاده شود، میتوان از متد توکار Validators.pattern نیز استفاده کرد و نیازی به تعریف یک متد جداگانه برای آن وجود ندارد؛ مگر اینکه نیاز به بازگشت شیء خطای کاملتری با خواص بیشتری وجود داشته باشد.
اعتبارسنجی async یا اعتبارسنجی از راه دور (remote validation)
یک سری از اعتبارسنجیها را در سمت کلاینت میتوان تکمیل کرد؛ مانند بررسی معتبر بودن فرمت ایمیل وارده. اما تعدادی دیگر، نیاز به اطلاعاتی از سمت سرور دارند. برای مثال آیا نام کاربری در حال ثبت، تکراری است یا خیر؟ این نوع اعتبارسنجیها در ردهی async validation قرار میگیرند.
سازندهی شیء Control در AngularJS 2.0 که در مثالهای بالا نیز مورد استفاده قرار گرفت، پارامتر اختیاری سومی را نیز دارد که یک AsyncValidatorFn را قبول میکند:
control(value: Object, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn) : Control
nameShouldBeUnique(control: Control) { let name: string = control.value; return new Promise((resolve) => { this._userService.isUserNameUnique(<IUser>{ "name": name }).subscribe( (result: IResult) => { resolve( result.result ? null : { 'nameShouldBeUnique': true } ); }, error => { resolve(null); } ); }); }
this.form = _formBuilder.group({ name: ['', Validators.compose([ Validators.required, UsernameValidators.cannotContainSpace ]), (control: Control) => this.nameShouldBeUnique(control)],
امضای متد nameShouldBeUnique تفاوتی با سایر متدهای اعتبارسنج نداشته و پارامتر ورودی آن، همان کنترل است که توسط آن میتوان به مقدار وارد شدهی توسط کاربر دسترسی یافت. اما تفاوت اصلی آن در اینجا است که این متد باید یک شیء Promise را بازگشت دهد. یک Promise، بیانگر نتیجهی یک عملیات async است. در اینجا دو حالت resolve و error را باید پیاده سازی کرد. در حالت error، یعنی عملیات async صورت گرفته با شکست مواجه شدهاست و در حالت resolve، یعنی عملیات تکمیل شده و اکنون میخواهیم نتیجهی نهایی را بازگشت دهیم. نتیجه نهایی بازگشت داده شدهی در اینجا، همانند سایر validators است و اگر نال باشد، یعنی اعتبارسنجی موفقیت آمیز بوده و اگر یک شیء را بازگشت دهیم، یعنی اعتبارسنجی با شکست مواجه شدهاست.
این Promise، از یک سرویس تعریف شدهی در فایل جدید user.service.ts استفاده میکند:
import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Headers, RequestOptions } from '@angular/http'; import { IUser } from './user'; import { IResult } from './result'; @Injectable() export class UserService { private _checkUserUrl = '/home/checkUser'; constructor(private _http: Http) { } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } isUserNameUnique(user: IUser): Observable<IResult> { let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC let options = new RequestOptions({ headers: headers }); return this._http.post(this._checkUserUrl, JSON.stringify(user), options) .map((response: Response) => <IResult>response.json()) .do(data => console.log("User: " + JSON.stringify(data))) .catch(this.handleError); } }
export interface IResult { result: boolean; }
کدهای سمت سرور برنامه که کار بررسی یکتا بودن نام کاربری را انجام میدهند، به صورت ذیل در فایل Controllers\HomeController.cs تعریف شدهاند:
namespace MVC5Angular2.Controllers { public class HomeController : Controller { [HttpPost] public ActionResult CheckUser(User user) { var isUnique = new { result = true }; if (user.Name?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false) { isUnique = new { result = false }; } return new ContentResult { Content = JsonConvert.SerializeObject(isUnique, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; } } }
بنابراین تا اینجا مسیر سمت سرور home/checkuser تکمیل شدهاست. این مسیر توسط سرویس کاربر صدا زده شده و اگر نام کاربری وارد شده موجود باشد، نتیجهای را مطابق امضای قرارداد IResult سفارشی ما بازگشت میدهد.
پس از آن مجددا به فایل signup-form.component.ts مراجعه کرده و سرویس جدید UserService را به سازندهی آن تزریق کردهایم. همچنین قسمت providers این کامپوننت را هم جهت تکمیل اطلاعات تزریق کنندهی توکار AngularJS 2.0 مقدار دهی کردهایم. البته همانطور که در مبحث تزریق وابستگیها نیز عنوان شد، اگر این سرویس قرار نیست در کلاس دیگری استفاده شود، نیازی نیست تا آنرا در بالاترین سطح ممکن و در فایل app.component.ts ثبت و معرفی کرد:
@Component({ selector: 'signup-form', templateUrl: 'app/users/signup-form.component.html', providers: [ UserService ] }) export class SignupFormComponent { constructor(private _formBuilder: FormBuilder, private _userService: UserService) {
اکنون که کدهای فعال سازی اعتبارسنجی از راه دور ما تکمیل شدهاست، به فایل signup-form.component.html مراجعه کرده و پیام مناسبی را نمایش خواهیم داد:
<div *ngIf="name.touched && name.errors"> <label class="text-danger" *ngIf="name.errors.required"> Username is required. </label> <label class="text-danger" *ngIf="name.errors.cannotContainSpace"> Username can't contain space. </label> <label class="text-danger" *ngIf="name.errors.nameShouldBeUnique"> This username is already taken. </label> </div>
نمایش پیام loading در حین انجام اعتبارسنجی از راه دور
شاید بد نباشد که در حین انجام عملیات اعتبارسنجی از راه دور و ارسال درخواستی به سرور و بازگشت نتیجهی آن، یک پیام loading را نیز نمایش داد. برای انجام اینکار نیاز است تغییرات ذیل را به فایل signup-form.component.html اضافه کنیم:
<input id="name" type="text" class="form-control" ngControl="name" #name="ngForm" /> <div *ngIf="name.control.pending"> Checking server, Please wait ... </div>
اعتبارسنجی ترکیبی در حین submit یک فرم
فرض کنید میخواهید منطقی را که حاصل اعتبارسنجی تمام فیلدهای فرم است (و نه هر کدام به تنهایی)، در حین submit آن اعمال کنید. برای مثال آیا ترکیب نام کاربری و کلمهی عبور شخصی در حین login معتبر است یا خیر؟ در این حالت پس از بررسیهای لازم در متد onSubmit، میتوان با استفاده از متد find شیء form، به یکی از کنترلهای فرم دسترسی یافت و سپس با استفاده از متد setErrors، خطای اعتبارسنجی سفارشی را به آن اضافه کرد:
onSubmit(): void { console.log(this.form.value); this.form.find('name').setErrors({ invalidData : true }); }
<div *ngIf="name.touched && name.errors"> <label class="text-danger" *ngIf="name.errors.invalidData"> Check the inputs.... </label> </div>
اتصال المانهای فرم به مدلی جهت ارسال به سرور
اکنون که دسترسی به خاصیت this.form را داریم و این خاصیت توسط [ngFormModel] به تمام اشیاء تعریف شدهی در فرم و تغییرات آنها دسترسی دارد، میتوان از آن برای دسترسی به شیءایی که حاوی مدل فرم است، استفاده کرد. برای نمونه در مثال فوق، خاصیت value آن، چنین خروجی را دارد:
{ name="VahidN", email="email@site.com", password="123"}
import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Headers, RequestOptions } from '@angular/http'; import { IUser } from './user'; import { IResult } from './result'; @Injectable() export class UserService { private _addUserUrl = '/home/addUser'; constructor(private _http: Http) { } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || 'Server error'); } addUser(user: IUser): Observable<IUser> { let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC let options = new RequestOptions({ headers: headers }); return this._http.post(this._addUserUrl, JSON.stringify(user), options) .map((response: Response) => <IUser>response.json()) .do(data => console.log("User: " + JSON.stringify(data))) .catch(this.handleError); } }
[HttpPost] public ActionResult AddUser(User user) { user.Id = 1; //todo: save user and get id from db return new ContentResult { Content = JsonConvert.SerializeObject(user, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }), ContentType = "application/json", ContentEncoding = Encoding.UTF8 }; }
onSubmit(): void { console.log(this.form.value); /*this.form.find('name').setErrors({ invalidData : true });*/ this._userService.addUser(<IUser>this.form.value) .subscribe((user: IUser) => { console.log(`ID: ${user.id}`); }); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: (این کدها مطابق نگارش RC 1 هستند)
MVC5Angular2.part11.zip
خلاصهی بحث
برای اینکه بتوان کنترل بیشتری را بر روی المانهای فرم داشت، ابتدا سرویس FormBuilder را در سازندهی کلاس کامپوننت فرم تزریق میکنیم. سپس با استفاده از متد group آن، المانهای فرم را به صورت کلیدهای شیء پارامتر آن تعریف میکنیم. در اینجا میتوان اعتبارسنجیهای توکار AngularJS 2.0 را که در کلاس پایهی Validators مانند Validators.required وجود دارند، تعریف کرد. با استفاده از متد compose آنها را ترکیب نمود و یا پارامتر سومی را جهت اعتبارسنجیهای async اضافه نمود. در این حالت شیء form تعریف شده به صورت [ngFormModel] به قالب فرم متصل میشود و از تغییرات آن آگاه خواهد شد.
یک نکته
مثالی از نحوهی استفاده از متد جدید HostingEnvironment.QueueBackgroundWorkItem
+ یک مثال رسمی
مثالی از نحوهی استفاده از متد جدید HostingEnvironment.QueueBackgroundWorkItem
+ یک مثال رسمی
نظرات مطالب
ASP.NET MVC #10
ممنون.
1-تو بعضی مثال ها بجای استفاده از [ActionName] از FormCollection بعنوان یکی از پارامترهای اکشن متد استفاده میشه(اگه اشتباه نکنم Scaffold هم از این روش استفاده میکنه).برای داشتن متد هم نام کدوم روش پیشنهاد میشه؟
2-همونطور که گفتید هنگام استفاده از ORM کار با ModelBinder لذت بخش میشه.با توجه به اینکه بیشتر برنامه های وب هم به این شکل هستن.ممنون میشم از روش های اول و دوم هم مثالی از دنیای واقعی بزنید.
1-تو بعضی مثال ها بجای استفاده از [ActionName] از FormCollection بعنوان یکی از پارامترهای اکشن متد استفاده میشه(اگه اشتباه نکنم Scaffold هم از این روش استفاده میکنه).برای داشتن متد هم نام کدوم روش پیشنهاد میشه؟
2-همونطور که گفتید هنگام استفاده از ORM کار با ModelBinder لذت بخش میشه.با توجه به اینکه بیشتر برنامه های وب هم به این شکل هستن.ممنون میشم از روش های اول و دوم هم مثالی از دنیای واقعی بزنید.