مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش سوم (پایانی)
در بخش پیشین  به بررسی جزئی‌تر ایجاد پایگاه داده و همچنین توسعه Custom Filter Attribute پرداختیم که وظیفه تایید صلاحیت کاربر جاری و بررسی دسترسی وی به API Method مورد نظر را بررسی می‌کرد. در این مقاله به این بحث می‌پردازیم که در Filter Attribute توسعه داده شده، قصد داریم یک سرویس Access Control ایجاد نماییم.
این سرویس وظیفه تمامی اعمال مربوط به نقش‌ها و دسترسی‌های کاربر را بر عهده خواهد داشت. این سرویس به صورت زیر تعریف می‌گردد:
public class AccessControlService
{
        private DbContext db;

        public AccessControlService()
        {
            db = new DbContext();
        }

        public IEnumerable<Permission> GetUserPermissions(string userId)
        {
            var userRoles = this.GetUserRoles(userId);
            var userPermissions = new List<Permission>();
            foreach (var userRole in userRoles)
            {
                foreach (var permission in userRole.Permissions)
                {
                    // prevent duplicates
                    if (!userPermissions.Contains(permission))
                        userPermissions.Add(permission);
                }
            }
            return userPermissions;
        }
        public IEnumerable<Role> GetUserRoles(string userId)
        {
            return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList();
        }

        public bool HasPermission(string userId, string area, string control)
        {
            var found = false;
            var userPermissions = this.GetUserPermissions(userId);
            var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control);
            if (permission != null)
                found = true;
            return found;
        }
{
همانطور که ملاحظه می‌کنید، ما سه متد GetUserPermissions، GetUserRoles و HasPermission را توسعه داده‌ایم. حال اینکه بر حسب نیاز، می‌توانید متدهای بیشتری را نیز به این سرویس اضافه نمایید. متد اول، وظیفه‌ی واکشی تمامی permissionهای کاربر را عهده دار می‌باشد. متد GetUserRoles نیز تمامی نقش‌های کاربر را در سیستم، بازمی‌گرداند و در نهایت متد سوم، همان متدی است که ما در Filterattribute از آن استفاده کرد‌ه‌ایم. این متد با دریافت پارامترها و بازگردانی یک مقدار درست یا نادرست، تعیین می‌کند که کاربر جاری به آن محدوده دسترسی دارد یا خیر.
تمامی حداقل‌هایی که برای نگارش سمت سرور نیاز بود، به پایان رسید. حال به بررسی این موضوع خواهیم پرداخت که چگونه این سطوح دسترسی را با سمت کاربر همگام نماییم. به طوری که به عنوان مثال اگر کاربری حق دسترسی به ویرایش مطالب یک سایت را ندارد، دکمه مورد نظر که او را به آن صفحه هدایت می‌کند نیز نباید به وی نشان داده شود. سناریویی که ما برای این کار در نظر گرفته‌ایم، به این گونه می‌باشد که در هنگام ورود کاربر، لیستی از تمامی دسترسی‌های او به صورت JSON به سمت کلاینت ارسال می‌گردد. حال وظیفه مدیریت نمایش یا عدم نمایش المان‌های صفحه، بر عهده زبان سمت کلاینت، یعنی AngularJs خواهد بود. بنابراین در ابتدایی‌ترین حالت، ما نیاز به یک کنترلر و متد Web API داریم تا لیست دسترسی‌های کاربر را بازگرداند. این کنترلر در حال حاضر شامل یک متد است. اما بر حسب نیاز، می‌توانید متدهای بیشتری را به کنترلر اضافه نمایید.
    [RoutePrefix("َAuth/permissions")]
    public class PermissionsController : ApiController
    {
        private AccessControlService _AccessControlService = null;
        public PermissionsController()
        {
            _AccessControlService = new AccessControlService();
        }

        [Route("GetUserPermissions")]
        public async Task<IHttpActionResult> GetUserPermissions()
        {
            if (!User.Identity.IsAuthenticated)
            {
                return Unauthorized();
            }
            return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId()));
        }        
    }
در متد فوق ما از متد سرویس Access Control که لیست تمامی permissionهای کاربر را باز می‌گرداند، کمک گرفتیم. متد GetUserPermissions پس از ورود کاربر توسط کلاینت فراخوانی می‌گردد و لیست تمامی دسترسی‌ها در سمت کلاینت، در rootScope انگیولار ذخیره سازی می‌گردد. حال نوبت به آن رسیده که به بررسی عملیات سمت کلاینت بپردازیم.

توسعه سرویس‌ها و فرآیندهای سمت وب کلاینت AngularJS

در ابتدا در سمت کلاینت نیاز به سرویسی داریم که دسترسی‌های سمت سرور را دریافت نماید. از این رو ما نام این سرویس را permissionService می‌نامیم.
'use strict';
angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) {

    var _getUserPermissions = function () {
        return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/');
    }

    var _isAuthorize = function (area, control) {
        return _.some($scope.permissions, { 'area': area, 'control': control });
    }

    return {
        getUserPermissions: _getUserPermissions,
        isAuthorize: _isAuthorize
    };
}]);
اگر بخواهیم مختصری درباره‌ی این سرویس صحبت کنیم، متد اول که یک دستور GET ساده است و لیست دسترسی‌ها را از PermissionController دریافت می‌کند. متد بعدی که در آینده بیشتر با آن آشنا می‌شویم، عملیات تایید صلاحیت کاربر را به ناحیه مورد نظر، انجام می‌دهد (همان مثال دسترسی به دکمه ویرایش مطلب در یک صفحه). در این متد برای جستجوی لیست permissions از کتابخانه‌ای با نام Lodash کمک گرفته‌ایم. این کتابخانه کاری شبیه به دستورات Linq را در کالکشن‌ها و آرایه‌های جاوااسکریپتی، انجام می‌دهد. متد some یک مقدار درست یا نادرست را بازمی‌گرداند. بازگردانی مقدار درست، به این معنی است که کاربر به ناحیه‌ی مورد نظر اجازه‌ی دسترسی را خواهد داشت.
حال باید متد‌های این سرویس را در کنترلر لاگین فراخوانی نماییم. در این مرحله ما از rootScope dependency استفاده می‌کنیم. برای نحوه‌ی عملکرد rootScope میتوانید به مقاله‌ای در این زمینه در وب سایت toddomotto مراجعه کنید. در این مقاله ویژگی‌ها و اختلاف‌های scope و rootScope به تفصیل بیان شده است. مقاله‌ای دیگر در همین زمینه نوشته شده است که در انتهای مقاله به بررسی چند نکته در مورد کدهای مشترک پرداخته شده‌است. تکه کد زیر، متد login را نمایش می‌دهد که در loginController قرار گرفته است و ما در آن از نوشتن کل بلاک loginController چشم پوشی کرده‌ایم. متد savePermissions تنها یک کار را انجام می‌دهد و آن هم این است که در ابتدا، به سرویس permissionService متصل شده و تمامی دسترسی‌ها را واکشی می‌نماید و پس از آن، آنها را درون rootScope قرار می‌دهد تا در تمامی کنترلرها قابل دسترسی باشد.
    $scope.login = function () {
        authService.login($scope.loginData).then(function (response) {
            savePermissions();
            $location.path('/userPanel');
        },
         function (err) {
             $scope.message = err.error_description;
         });
    };

    var savePermissions = function () {
        permissionService.getUserPermissions().then(function (response) {
            $rootScope.permissions = response.data;
        },
        function (err) {
        });
    }
}

   حال تمامی اطلاعات دسترسی، در سمت کلاینت نیز قابل دسترسی می‌باشد. تنها کاری که نیاز است، broadCast کردن متد isAuthorize است که آن هم باید در rootScope قرار بگیرد. ما برای این انتساب یک راهکار را ارائه کرده‌ایم. معماری سیستم کلاینت به این صورت است که تمامی کنترلرها درون یک parentController قرار گرفته‌اند. از این رو می‌توان در parentController این انتساب (ایجاد دسترسی عمومی برایisAuthorize) صورت گیرد. برای این کار در parentController تغییرات زیر صورت می‌گیرد:
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) {
        $scope.authentication = authService.authentication;

        // isAuthorize Method
        $scope.isAuthorize = permissionService.isAuthorize();

        // rest of codes
}]);
در کد فوق ما isAuthorize را درون scope قرار داده‌ایم. دلیل آن هم این است که هر چه که در scope قرار بگیرد، تمامی کنترلر‌های child نیز به آن دسترسی خواهند داشت. البته ممکن است که این بهترین نوع پیاده سازی برای به اشتراک گذاری یک منبع نباشد.
 در گام بعدی کافیست المان‌های صفحه را بر اساس همین دسترسی‌ها فعال یا غیر فعال کنیم. برای این کار از دستور ng-if میتوان استفاده کرد. برای این کار به مثال زیر توجه کنید:
<div ng-controller="childController">
    <div ng-if="isAuthorize('articles', 'edit')" >
    <!-- the block that we want to not see unauthorize person -->
    </div>
</div>
همانطور که مشاهده می‌کنید، تمامی المان‌ها را می‌توان با دستور ساده ng-if، از دید کاربران بدون صلاحیت، پنهان نمود. البته توجه داشته باشید که شما نمی‌توانید تنها به پنهان کردن این اطلاعات اکتفا کنید. بلکه باید تمامی متدهای کنترلرهای سمت سرور را هم با همین روش (فیلتر کردن با Filter Attribute) بررسی نمایید. به ازای هر درخواست کاربر باید بررسی شود که او به منبع مورد نظر دسترسی دارد یا خیر.
تنها نکته‌ای که باقی می‌ماند این است که طول عمر scope و rootScope چقدر است؟! برای پاسخ به این سوال باید بگوییم هر بار که صفحه refresh می‌شود، تمامی مقادیر scope و rootScope خالی می‌شوند. برای این کار هم یک راهکار خیلی ساده (و شاید کمی ناشیانه) در نظر گرفته شده‌است. میتوان بلاک مربوط به پر کردن rootScope.permissions را که در loginController نوشته شده بود، به درون parentController انتقال داد و آن را با استفاده از emit اجرا کرد و در حالت عادی، در هنگام refresh شدن صفحه نیز چون parentController در اولین لحظه اجرا می‌شود، میتوان تمامی مقادیر rootScope.permissions را دوباره از سمت سرور دریافت کرد.
اشتراک‌ها
مفهوم زبان مشترک در DDD

Account

A bank account allows us to send and receive money and has its unique number. Anytime we tell about an account in a bank, an account is always a bank account. In the other hand, an account in an information system is used to authorize a user. We have the term "account" meaning something absolutely different in two different domains. Domain has an impact on what we imagine when someone says a concrete term. So we have to learn and specify domain terms first.

Price

Let's speak about e-shop domain. What is a price? For us, as customers, it is how much we pay. A manager can think about price as an amount that his company pays to the supplier. For an accountant, a price is just a number. And e-shop programmer is now confused.

Language is crucial because customers and experts are telling their stories in their language. But it is also natural language, inaccurate, ambiguous, context-aware. And as we can see, language can be tricky even within one domain. 


مفهوم زبان مشترک در DDD
مطالب
Dependency Injection

در ادامه مباحث بهتر کد بنویسیم و الگوهایی که در این رابطه معرفی شدند، اخیرا کتابی از انتشارات manning منتشر شده تحت عنوان Dependency Injection . هر چند به ظاهر این کتاب برای جاوا کارها تهیه شده اما قسمت عمده‌ای از آن برای سایر زبان‌های برنامه نویسی دیگر نیز قابل استفاده است.




DESCRIPTION
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.

WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks


راستی، این کتاب تر و تازه رو می‌تونید از همین کتاب فروشی‌های دور و اطراف نیز تهیه کنید! در سایت booktraining دات ارگ در قسمت graphics-and-design به تاریخ 4 آگوست.



اشتراک‌ها
18 روش خلاقانه استفاده از Docker در کارهای روزانه
18 Innovative Ways to Use Docker Daily

- Running GUI Applications in Docker Containers
- How to Run a Web Browser Inside Docker
- Using Obsidian Note-Taking App via Docker
- LibreOffice in Docker: Open-Source Office Suite Containerized
- Contribute to Science: Folding@Home with Docker
- Effortless Docker Management with Docker Desktop and Portainer
- Secure Document Handling Using Dangerzone and Docker
- Containerizing CLI Tools for a Clean System
- Enhance Docker Security with Docker Scout
- Set Up an Isolated Hacking Lab with Docker and Kali Linux
- Master Docker Networks for Container Isolation
- Automate Your Docker Workflows with Docker Compose
- Explore New Operating Systems Using Docker Containers
- Running macOS in a Docker Container (Educational Purposes)
- How to Run Raspberry Pi OS via Docker
- Access a Suite of IT Tools Inside a Docker Container
- Using Docker to Containerize AI Tools like Fabric
- Safely Test Applications with Docker Containers
- Building Custom Docker Images from Scratch
- Unconventional Docker Use Cases You Need to Know
- Daily Docker Tips and Tricks for Enhanced Productivity
- Beginner's Guide to Advanced Docker Techniques
- Boost Your IT Workflow with These Docker Hacks
- Docker for Everyday Tasks: Beyond Development
18 روش خلاقانه استفاده از Docker در کارهای روزانه
بازخوردهای پروژه‌ها
Fill and FillByObject
متود زیر برای تولید عبارت مبتنی بر الگو زمانی که الگو پیچیده‌تر از این است که بشود با string.Format به راحتی پیاده سازی شود ولی چندان هم پیچیده نیست که ابزارهای حرفه ای‌تری مانند T4 یا Razor به کار بیایند، میتواند مورد استفاده قرار گیرد:
        /// <summary>
        /// Insert values from an object into a string pattern. To specify the object's property you have to use the '{propertyName}'.
        /// </summary>
        /// <remarks>
        /// This method is a replacement to String.Format, but it has two differences:
        /// <list type="simple">
        /// <item>It reverese the order you call the functionality, instead of writing String.Format(pattern, args) you write pattern.FillByObject(args). This makes the code look cleaner.</item>
        /// <item>You supply the pattern with an object and specify insertion points by property names.</item>
        /// </list>
        /// <example>
        /// <![CDATA[
        /// "First name:{firstName}, Sur name:{Surname}".FillByObject(new {firstName = "Sam", lastName="Naseri"});
        /// ]]>
        /// </example>
        /// </remarks>
        /// <seealso cref="Fill"/>
        /// <typeparam name="T">Type of bindingValue.</typeparam>
        /// <param name="bindingPattern">The pattern to fill.</param>
        /// <param name="bindingValue">The object providing values to fill in the pattern.</param>
        /// <returns>The pattern filled with values.</returns>
        public static string FillByObject<T>(this string bindingPattern, T bindingValue)
        {
            var properties = GetProperties(typeof(T)).ToList();
            var values = properties.Select(property => property.GetValue(bindingValue, new object[] { })).ToList();
            var result = bindingPattern;
            for (int index = 0; index < properties.Count; index++)
            {
                var property = properties[index];
                var propPattern = "{" + property.Name + "}";
                var old = result;
                result = result.Replace(propPattern, values[index] != null ? values[index].ToString() : "");
            }
            return result;
        }

پیاده سازی فوق یک پیاده سازی بسیار خام و بسیار کند است و جای بهبود زیادی دارد. من فقط برای سناریوهای ساده که کارایی مطرح نیست استفاده از متود فوق را توصیه میکنم.
متودهای جانبی مورد نیاز:
private static IEnumerable<PropertyInfo> GetProperties(Type t)
{
    return t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
}

همچنین متود زیر هم میتواند نوشتن string.Format را یک خرده ساده‌تر کند:
/// <summary>
/// A simple replacement for String.Format which only makes the codes look nicer.
/// </summary>
/// <param name="pattern">The source string that you want to replace insertion points on it.</param>
/// <param name="args">Values to be replaced in the pattern.</param>
/// <returns></returns>
public static string Fill(this string pattern, params object[] args)
{
    return string.Format(pattern, args);
}

اشتراک‌ها
Visual Studio 2019 version 16.11.4 منتشر شد
حجم تقریبی آپدیت از نسخه قبلی (16.11.3) حدود 1.11G میشه.
  • Windows 11 SDK support.
  • Adds Xcode 13.0 support.
  • Add AMD64 math functions to ARM64X CRT.
  • Updates to the ARM64 and ARM64EC interfaces between the binary and the POGO instrumentation runtime.
  • Fixed several problems with IntelliSense responsiveness and correctness affecting C++20 concepts, ranges, and abbreviated function templates.
  • Fixed a false positive in local lifetime checks.
  • Corrected an issue where arrays allocated with a constant of size > 32bits could allocate less memory than requested.
  • Ensures that ATL string initialization occurs during static variable initialization, in the default AppDomain.
  • Fixed a bug in C++ Concurrency::parallel_for_each that was crashing the calling process due to integer overflow.
  • Fixed a bug in the STL's iterator debugging machinery that could cause crashes in multithreaded programs using STL containers.
  • We have fixed a fatal internal compiler error caused by unnamed structs whose fields are referenced from SAL annotations.
  • Fixes a rare crash when analyzing templated code that uses __uuidof.
  • Fixed an issue that caused C++ static analysis results to sometimes not display correctly in the FixIt action.
  • Fixed opening .uitest extension files in Coded UI project
  • Fire component change events for non-component objects also in WinForms .NET designer
  • Fix for crash on deleting ContextMenuStrip control in Windows Forms .NET designer.
  • Guard against crashes when the Windows Forms designer reloads when dragging.
  • Fix for intermittent VS crash while interacting with WinForms .NET designer during solution or project rebuild.
  • Fixed a bug causing .NET 5 projects to be reported as out of date when they should have been up to date, causing slower builds.
  • Automatically disable asset-indexing for large scale Unity projects.
  • This release fixes an issue with deploying certain Windows Application Packaging projects where deployment is unnecessarily copying unmodified files. 
Visual Studio 2019 version 16.11.4 منتشر شد
اشتراک‌ها
آشنایی با قابلیت های SQL Server 2016

همانطور که اطلاع دارید نسخه آزمایشی SQL Server 2016 قرار از تابستان امسال (2015) در دسترس باشد. قابلیت‌های جدیدی به این محصول اضافه شده است. تعدادی از آنها عبارتند از 

1- امکان استفاده از Clustered Column Store Index در جداول Memory Optmized

2- َAlways Encrypted

3- پشتیبانی از JSON

4- پشتیبانی از زبان R در SQL Server

و ...

آشنایی با قابلیت های SQL Server 2016
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت دوم - ایجاد اولین فرم
در قسمت قبل، مروری داشتیم بر تفاوت‌های دو نوع مختلف فناوری‌های ایجاد و مدیریت فرم‌ها در Angular و هچنین ساختار ابتدایی برنامه‌ی این سری را ایجاد کردیم. در ادامه، اولین فرم مبتنی بر قالب‌ها را ایجاد خواهیم کرد.


ایجاد اولین فرم مبتنی بر قالب‌ها

پس از ایجاد کامپوننت employee-register، فایل قالب آن یا src\app\employee\employee-register\employee-register.component.html را گشوده و به نحو ذیل تکمیل می‌کنیم:
<h3>Angular Forms</h3>
<form #form="ngForm">
    <input type="text" placeholder="Name">
    <button type="submit">Ok</button>
</form>

form.pristine: {{ form.pristine }}
زمانیکه المان فرم به صفحه اضافه می‌شود، Angular به صورت خودکار دایرکتیو مرتبطی را به فرم اضافه می‌کند. برای دسترسی به این دایرکتیو نیاز است یک template reference variable را تعریف کرد. برای مثال "form="ngForm#  به معنای تعریف متغیر form است که به دایرکتیو توکار ngForm متصل شده‌است و اکنون حاوی وهله‌ای از این دایرکتیو می‌باشد. به همین جهت است که امکان دسترسی به اطلاعات این وهله توسط درج form.pristine در همان قالب وجود دارد.
خاصیت pristine مشخص می‌کند که آیا فرم توسط کاربر تغییر یافته‌است یا خیر؟


مقدار خاصیت pristine در ابتدای کار true است؛ به این معنا که هنوز تغییری در آن اعمال نشده‌است.

یک نکته: ممکن است در حین توسعه‌ی برنامه، خطای ذیل را در کنسول developer tools مرورگرها مشاهده کنید:
 There is no directive with "exportAs" set to "ngForm"
این دایرکتیو تنها زمانی قابل دسترسی است که در قسمت imports ماژول جاری که با آن کار می‌کنید، تعریف FormsModule را به همان نحوی که در انتهای قسمت قبل بررسی کردیم (قسمت «افزودن ماژول فرم‌ها به برنامه»)، افزوده باشید.

در ادامه، در همین فرمی که تعاریف آن‌را در بالا مشاهده می‌کنید، اطلاعاتی را وارد نمائید. هنوز هم مقدار خاصیت pristine مساوی true است. علت اینجا است که هنوز به Angular اعلام نکرده‌ایم که کدام فیلد یا فیلدهای فرم را باید تحت نظر قرار دهد. برای این منظور ابتدا به المان تعریف شده نامی را انتساب داده و سپس دایرکتیو ngModel را نیز به انتهای تعاریف آن اضافه می‌کنیم:
<h3>Angular Forms</h3>
<form #form="ngForm">
    <input type="text" placeholder="Name" name="name" ngModel>
    <button type="submit">Ok</button>
</form>

form.pristine: {{ form.pristine }}

اکنون اگر مقدار فرم را تغییر دهیم، مشاهده خواهیم کرد که مقدار خاصیتpristine  به false تغییر می‌کند:


یک نکته: زمانیکه دایرکتیو ngModel ذکر می‌شود، تعریف name المان متناظر با آن، الزامی است؛ در غیراینصورت خطای ذیل را در کنسول developer tools مرورگرها مشاهده خواهید کرد:
 Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.


خاموش کردن اعتبارسنجی توکار مرورگرها

یکی از کارهایی را که نیاز است در حین کار با فرم‌ها انجام داد، خاموش کردن اعتبارسنجی توکار مرورگرها است. فرض کنید ویژگی معتبر و استاندارد required را به یکی از المان‌های ورودی اضافه کرده‌اید:
<input type="text" required placeholder="Name" name="name" ngModel>
در این حالت اگر برنامه را اجرا کنید و بدون تکمیل این فیلد بر روی دکمه‌ی ارسال فرم کلیک نمائید، به ازای مرورگرهای مختلف، پیام انگلیسی «لطفا این فیلد را تکمیل کنید» ظاهر خواهد شد و هر کدام شکل متفاوتی را دارند که جزئیات آن‌ها را نمی‌توان تغییر داد و یا سفارشی سازی کرد. به این مورد، browser validation می‌گویند. به همین جهت برای خاموش کردن این اعتبارسنجی توکار مرورگرها و ارائه‌ی تجربه‌ی کاربری یکنواخت و یکدستی در تمام مرورگرها، نیاز است ویژگی novalidate را به تگ فرم اضافه کرد:
<form #form="ngForm" novalidate>
هر دوی ویژگی‌های novalidate و یاrequired ، جزو استاندارد HTML هستند و ارتباطی به Angular ندارند.


بهبود ظاهر فرم توسط اعمال شیوه‌نامه‌های بوت استرپ

در قسمت قبل، در ابتدای کار تدارک ساختار مثال این سری، بوت استرپ را نیز نصب و تنظیم کردیم. در ادامه می‌خواهیم اندکی ظاهر این فرم را بر اساس شیوه‌نامه‌های بوت استرپ بهبود ببخشیم:
<div class="container">
    <h3>Angular Forms</h3>
    <form #form="ngForm" novalidate>
        <div class="form-group">
            <label>First Name</label>
            <input type="text" class="form-control" required name="firstName" ngModel>
        </div>
        <div class="form-group">
            <label>Last Name</label>
            <input type="text" class="form-control" required name="lastName" ngModel>
        </div>
        <button class="btn btn-primary" type="submit">Ok</button>
    </form>
</div>

form.pristine: {{ form.pristine }}


- برای افزودن بوت استرپ نیازی نیست تا شیوه‌نامه‌ی آن‌را به صورت دستی به Index.html برنامه اضافه کرد. همینقدر که ارجاعی از آن در فایل angular-cli.json. در قسمت شیوه‌نامه‌های آن وجود داشته باشد، به صورت خودکار در bundle نهایی تولید شده‌ی توسط سیستم ساخت برنامه‌ی Angular CLI ظاهر خواهد شد.
- در اینجا ابتدا فرم خود را در داخل یک container قرار داده‌ایم. این مورد سبب می‌شود تا محتوای آن به میانه‌ی صفحه منتقل شود.
- سپس شیوه‌نامه‌ی btn به دکمه‌ی ارسال فرم اضافه شده‌است تا شکل دکمه‌های بوت استرپ را پیدا کند.
- سپس هر فیلد ورودی داخل یک div با کلاس form-group محصور می‌شود و هر کنترل، کلاس form-control را خواهد یافت.


افزودن سایر المان‌های ورودی به فرم

تا اینجا دو text box را به فرم اضافه کرده‌ایم. در ادامه می‌خواهیم المان‌های دیگری را نیز تعریف کنیم:

افزودن Check boxes

        <div class="checkbox">
            <label>
                <input type="checkbox" name="is-full-time" ngModel> Full Time Employee
            </label>
        </div>
چون با بوت استرپ کار می‌کنیم، نیاز است المان ورودی از نوع checkbox را داخل یک div با کلاس checkbox محصور کنیم. سپس یک label را تعریف کرده و Input را داخل آن قرار دهیم. در اینجا نیز همانند سایر المان‌ها نیاز است نامی را به آن انتساب داده و سپس دایرکتیو ngModel را قید نمود تا Angular این کنترل را تحت نظر قرار دهد.


افزودن Radio buttons

        <label>Payment Type</label>
        <div class="radio">
            <label>
                <input type="radio" name="pay-type" value="FullTime" checked>
                Full Time
            </label>
        </div>
        <div class="radio">
            <label>
                <input type="radio" name="pay-type" value="PartTime">
                Part Time
            </label>
        </div>
Radio buttons نیز شبیه به Check boxها تعریف می‌شوند. در اینجا نیز یک div با کلاس radio و سپس label ایی که المان ورودی از نوع radio داخل آن قرار می‌گیرد، افزوده خواهد شد. فقط در اینجا باید دقت داشت که گروه بندی این المان‌ها بر اساس نام آن‌ها انجام می‌شود. به همین جهت است که نام این دو المان یکی وارد شده‌است. همچنین باید value آن‌را نیز تنظیم کرد. این مقداری است که در نهایت به سرور ارسال خواهد شد.


افزودن Drop downs

        <div class="form-group">
            <label>Primary Language</label>
            <select class="form-control">
                <option *ngFor="let lang of languages">
                    {{ lang }}
                </option>
            </select>
        </div>
در اینجا از المان select برای تشکیل یک drop down استفاده می‌کنیم و نحوه‌ی تعریف آن بسیار شبیه است به تعریف text boxهایی که داخل form-group محصور شده و همچنین کلاس form-control را پیدا می‌کنند.
اما قسمت مهم آن، اطلاعاتی است که قرار است در این drop down نمایش داده شوند. این اطلاعات را می‌توان از آرایه‌ی languages گرفت و سپس توسط یک ngFor به المان select اضافه کرد. بنابراین باید به فایل employee-register.component.ts مراجعه کرده و آرایه‌ی languages را به آن افزود:
export class EmployeeRegisterComponent implements OnInit {
  languages = ["Persian", "English", "Spanish", "Other"];
کاری که در اینجا انجام می‌شود، تکرار المان option توسط ngFor است. برای مثال در اینجا 4 بار المان option توسط عناصر آرایه‌ی زبان‌ها در داخل المان select تکرار خواهد شد. به عبارتی select نهایی رندر شده‌ی در صفحه، چنین شکلی را پیدا می‌کند:
<select class="form-control">
  <option>Persian</option>
  <option>English</option>
  <option>Spanish</option>
  <option>Other</option>
</select>

تا اینجا فرم تشکیل شده‌ی ما چنین نمایی را پیدا می‌کند:


در قسمت بعد این فرم را توسط مباحث data binding و بررسی نحوه‌ی دسترسی به اطلاعات آن در کامپوننت مرتبط، تکمیل خواهیم کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-02.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.