در بخش ۱-۴ پس از بررسی سیستم انگولار ۲ و چگونگی کارکرد آن، نوشتن نرم افزار تحت وب نظرسنجی مشابه سایت reddit را آغاز کردیم. در نهایت یک کامپوننت و قالب مشخص برای ثبت نام و لینک مطلب موردنظر ایجاد و آن را به تفصیل شرح دادیم. حال در این بخش به ادامهی مسیر طراحی و برنامهنویسی سایت نظرسنجی میپردازیم. با ما همراه باشید.
هماکنون ما فرمی برای ثبت نام و لینک مطلب موردنظر داریم، اما جدیدترین مطالب قابل مشاهده نیستند. برای اینکار یک کامپوننت جدید با نام article ایجاد میکنیم:
ng generate component article
سه بخش اصلی را برای این کامپوننت درنظر میگیریم:
تمام قسمتهای فوق را به تفصیل شرح میدهیم:
قالب اصلی خود را در فایل article.component.html اضافه میکنیم، مجددا یادآور میشویم که ما در این پروژه از فریمورک CSS بوت استراپ استفاده کردهایم:
<div class="row"> <div class="col-md-3 text-center"> <a href (click)="voteUp()"><i class="fa fa-arrow-up"></i></a> <a href (click)="voteDown()"><i class="fa fa-arrow-down"></i></a> </div> <div class="col-md-3 text-center"><a href="{{ article.link }}"> {{ article.title }} </a></div> <div class="col-md-3 text-center"><a href="{{ article.link }}"> {{ article.link }} </a></div> <div class="col-md-3 text-center"><span class="badge bg-red">{{ article.votes }}</span></div> </div>
با مطالعهی کد بالا با یک سری دستور روبهرو خواهید شد. برای زیباتر شدن فرم نظردهی مطالب، از یک جدول با چهار ستون استفاده کردهایم که ستونهای آن شامل امتیازدهی، عنوان مطلب، لینک مطلب و امتیاز میباشد. درون ستون امتیاز دهی از دو دکمه فلش بالا و پایین برای افزایش و کاهش امتیاز استفاده کردهایم که این دو فلش دو تابع voteup() و votedown() را فراخوانی میکنند. همچنین در ستون امتیاز نیز مقدار امتیاز را با متغییر votes نمایش میدهیم. از طرفی عناوین و لینکهای مطالب را در متغییرهای link و title قرار دادهایم.
ایجاد کلاس ArticleComponent
به مسیر article.component.ts رفته و کلاس ArticleComponent را تعریف میکنیم:
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-article', templateUrl: './article.component.html', styleUrls: ['./article.component.css'] }) export class ArticleComponent implements OnInit { votes: number; title: string; link: string; constructor() { this.title = "Roxo Online Tutorials"; this.link = "http://www.roxo.ir"; this.votes = 10; } voteUp(){ this.votes += 1; } voteDown(){ this.votes -= 1; } ngOnInit() { } }
در اینجا سه ویژگی اصلی ایجاد شده است:
سازندهی پیشفرض این کلاس نیز دارای صفاتی است:
و در انتها دو تابع به نامهای voteUp و voteDown جهت افزایش و کاهش مقادیر امتیاز مورد استفاده قرار میگیرد.
جهت استفاده از این کامپوننت باید تگ <app-article></app-article> را در فایل اصلی AppComponent فراخوانی کنید. برای اینکار فایل app.component.html را باز کرده و تگ فوق را به انتهای فرم خود اضافه کنید. بنابراین داریم:
<div class="row"> <!-- Horizontal Form --> <div class="col-md-8 col-center-block"> <div class="box box-info"> <div class="box-header with-border"> <h3 class="box-title">ثبت لینک</h3> </div> <!-- /.box-header --> <!-- form start --> <form class="form-horizontal"> <div class="box-body"> <div class="form-group"> <label for="title" class="col-sm-2 control-label">عنوان مطلب</label> <div class="col-sm-10"> <input type="text" class="form-control" name="title" placeholder="عنوان مطلب خود را وارد کنید" #newtitle> </div> </div> <div class="form-group"> <label for="link" class="col-sm-2 control-label">لینک</label> <div class="col-sm-10"> <input type="text" class="form-control" name="link" placeholder="لینک مطلب را وارد کنید" #newlink> </div> </div> </div> <!-- /.box-body --> <div class="box-footer"> <button type="submit" (click)="addArticle(newtitle, newlink)" class="btn btn-info pull-right">ثبت</button> </div> <!-- /.box-footer --> </form> </div> <div class="box"> <div class="box-header"> <h3 class="box-title">نظرسنجی مطالب</h3> </div> <!-- /.box-header --> <div class="box-body no-padding"> <div class="row"> <div class="col-md-3 text-center">امتیازدهی</div> <div class="col-md-3 text-center">عنوان مطلب</div> <div class="col-md-3 text-center">لینک مطلب</div> <div class="col-md-3 text-center">مجموع امتیازها</div> </div> <app-article></app-article> </div> <!-- /.box-body --> </div> <!-- /.box --> </div> </div> <!-- /.box -->
چنانچه شما صفحه اصلی خود را بروزرسانی کنید باید تصویر زیر را مشاهده کنید:
علیرغم اینکه شما توابع را مورد استفاده قرار دادهاید با کلیک کردن روی فلشهای بالا و پایین علاوه بر اضافه یا کم شدن امتیاز، صفحه بروزرسانی یا به اصطلاح refresh میشود. جاوا اسکریپت به صورت پیشفرض، رویداد click را برای تمامی کامپوننتهای والد فعال نگه میدارد. زیرا رویداد click یک انتشار برای والد میباشد. بنابراین مرورگر ما تلاش میکند تا یک لینک خالی را دنبال کند و در نهایت صفحه مرورگر مجددا بروزرسانی یا refresh میشود.
برای حل این مشکل باید رویداد click را طوری طراحی کنیم که مقدار false را باز گرداند. این مقدار به مرورگر میگوید که صفحه را بروزرسانی نکن! بنابراین کدهای موجود در توابع voteUp و voteDown را بروزرسانی میکنیم و علاوه بر انجام فرآیند افزایش یا کاهش امتیاز یک نوع boolean با مقدار false را بر میگردانیم. بنابراین داریم:
voteUp(){ this.votes += 1; return false; } voteDown(){ this.votes -= 1; return false; }
هماکنون ما تنها یک ردیف با مقدار پیشفرض در جدول خود داریم و هیچ راهی برای افزودن سایر مطالب وجود ندارد. و اگر بخواهیم چندین ردیف داشته باشیم باید دهها بار تگ <app-article> را کپی کنیم که در این شرایط یک مقدار ۱۰ بار برای هر سطر کپی میشود و اصلا جنبه خوبی ندارد. بنابراین باید یک کلاس تحت عنوان Article ایجاد کنیم.
یکی از راههای عملی و زیبا برای کدنویسی در انگولار ۲، ایزوله کردن ساختار دادهها با استفاده از ایجاد کلاس در کامپوننتهاست. برای انجام این کار یک ساختار داده ایجاد کرده که یک مطلب تکی را نمایش میدهد. بنابراین فایل article.model.ts را به پوشه article اضافه کرده و یک کلاس تحت عنوان article در آن تعریف کنید. سپس کدهای زیر را اعمال کنید:
export class Article{ title: string; link: string; votes: number; constructor(title: string, link: string, votes?: number){ this.title = title; this.link = link; this.votes = votes || 0; } }
در اینجا ما یک کلاس جدید تحت عنوان Article ایجاد کردیم. توجه داشته باشید که این یک کلاس خام است و کامپوننت انگولار نیست! در ساختار و الگوی معماری سهلایه، Model-View-Controller این کلاس یک Model میباشد.
هر مطلب یک title، یک link و یک مجموعه امتیاز دارد. هنگامیکه ما مطلب جدیدی را تولید میکنیم به title و link نیاز داریم. پارامتر votes یک پارامتر دلخواه است که مقدار پیشفرض آن روی ۰ (صفر) تنظیم شده است (از علامت سوال انگلیسی ? برای دلخواه کردن یک فیلد استفاده میشود)
حال باید ArticleComponent را جهت استفاده از کلاس Article بروزرسانی کنیم. به جای ذخیرهسازی مستقیم ویژگیها در ArticleComponent، آنها را در یک نمونه از کلاس Article ذخیره میکنیم. بنابراین فایل article.component.ts را به صورت زیر ویرایش میکنیم:
import { Component, OnInit } from '@angular/core'; import {Article} from "./article.model"; @Component({ selector: 'app-article', templateUrl: './article.component.html', styleUrls: ['./article.component.css'] }) export class ArticleComponent implements OnInit { article: Article; constructor() { this.article = new Article( 'Roxo Online Tutorials', 'http://www.roxo.ir', 10 ); } voteUp(){ this.article.votes += 1; return false; } voteDown(){ this.article.votes -= 1; return false; } ngOnInit() { } }
همانگونه که مشاهده کردید تمام ویژگیها را به کلاس Article اضافه و سپس یک نوع article از کلاس Article تعریف کردیم. همچنین درون توابع voteUp و voteDown نیز تغییراتی را جهت ارتباط با کلاس article ایجاد کردیم. با این تغییر عناوینی که صورت گرفت طبیعتا باید view قالب ما نیز تغییراتی را داشته باشد. چون دیگر متغییری به نام votes وجود ندارد. بلکه تمام متغییرها باید به صورت article.votes تعریف شوند. بنابراین داریم:
<div class="row"> <div class="col-md-3 text-center"> <a href (click)="voteUp()"><i class="fa fa-arrow-up"></i></a> <a href (click)="voteDown()"><i class="fa fa-arrow-down"></i></a> </div> <div class="col-md-3 text-center"><a href="{{ article.link }}"> {{ article.title }} </a></div> <div class="col-md-3 text-center"><a href="{{ article.link }}"> {{ article.link }} </a></div> <div class="col-md-3 text-center"><span class="badge bg-red">{{ article.votes }}</span></div> </div>
اگر صفحه مرورگر خود را بروز رسانی کنید متوجه خواهید شد که همه چیز به درستی کار میکند.
موضوعی که اینجا مطرح میشود عدم رعایت کردن بحث کپسوله کردن یا ایزوله کردن کدها برای متدهای voteUp و voteDown است زیرا ویژگیهای این دو تابع به صورت مستقیم تغییر میکنند. برای حل این مشکل در ArticleComponent متدهای voteUp و voteDown را به کلاس Article انتقال میدهیم. بنابراین برای کلاس Article داریم:
export class Article{ title: string; link: string; votes: number; constructor(title: string, link: string, votes?: number){ this.title = title; this.link = link; this.votes = votes || 0; } voteUp(){ this.votes += 1; } voteDown(){ this.votes -= 1; } domain(){ try{ const link: string = this.link.split('//')[1]; return link.split('/')[0]; } catch (err){ return null; } } }
همچنین تغییرات زیر برای فراخوانی متدهای افزایش و کاهش امتیاز در کامپوننت ArticleComponent به صورت زیر میباشد:
import {Component, OnInit, Input} from '@angular/core'; import {Article} from "./article.model"; @Component({ selector: 'app-article', templateUrl: './article.component.html', styleUrls: ['./article.component.css'] }) export class ArticleComponent implements OnInit { article: Article; voteUp(): boolean{ this.article.voteUp(); return false; } voteDown(): boolean{ this.article.voteDown(); return false; } ngOnInit() { } }
یک سوال: چرا متدهای voteUp و voteDown را هم در کامپوننت و هم در مدل تعریف کردیم؟
پاسخ: در هر دو کلاس ArticleComponent و Article متدهای voteUp() و voteDown() وجود دارد زیرا هر تابع مقدار کمی متفاوت عمل میکند. بنابراین تابع voteUp() در کامپوننت به component view ارتباط دارد، درحالیکه تایع voteUp() در مدل تغییرات اتفاق افتاده در model را تعریف میکند. به عبارت دیگر توابع موجود در model یا کلاس Article تغییرات امتیازها را بررسی میکنند و اکثر درگیریها در این بخش رخ میدهد و شما نمیخواهید برای هر درگیری مدام کامپوننت خود را فعال و غیرفعال کنید درحالیکه توابع موجود در کامپوننتها تنها مقدار false را برای جلوگیری از refresh شدن صفحه استفاده میشوند و بهگونه با view در ارتباط هستند.
اگر صفحه مرورگر خود را بروزرسانی کنید متوجه خواهید شد که همه چیز مرتب میباشد و تغییری در صفحه اصلی صورت نگرفته است.
حال نوبت آن رسیده است تا کدی برای در اختیار داشتن مجموعهای از مطالب بنویسیم. کامپوننت AppComopnent را باز کرده و آن را با کدهای زیر بروزرسانی کنید:
import { Component } from '@angular/core'; import {Article} from "./article/article.model"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], host: { class: "row" } }) export class AppComponent { articles: Article[]; constructor(){ this.articles = [ new Article('Roxo Online Tutorials', 'http://www.roxo.ir', 3), new Article('Roxo Shop', 'http://shop.roxo.ir', 2), new Article('Roxo Forum', 'http://ask.roxo.ir', 2), ]; } addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean { console.log(`Adding article title: ${title.value} and link: ${link.value}`); this.articles.push(new Article(title.value, link.value, 0)); title.value= ''; link.value = ''; return false; } }
همینطور که در جریان هستید برای استفاده از کلاس Article باید همواره آن را ایمپورت کرد. بنابراین در خط دوم مجموعه کد بالا اینکار را انجام دادهایم.
شما با دستور جدید articles: Article[] برخورد کردهاید. عبارت Article0[] یک مقدار ناآشنا به نظر میرسد. در واقع با این دستور اطلاع میدهید که articles به عنوان مجموعهای از اشیاء Article میباشد. معمولا این دستور را به صورت Array<Article> نیز مینویسند. بنابراین، آرایهها با دستور this.articles برای شما قابل دسترس خواهند بود.
با این تفاسیر هم اکنون ما مجموعهای از Article مدلها را داریم. اما سوال اینجاست که چگونه این مجموعه را به ArticleComponent ارسال کنیم؟
قبلا ما از Inputها استفاده کردهایم. زمانی که دستور @Input فراخوانی میشود ما به ورودیهای زیادی اشاره میکنیم. به عبارت دیگر به ازای هر ورودی یک سری عملیات را انجام میدهیم. همانگونه که قبلا مطرح کردیم با استفاده از [ ] میتوان یک متغییر درون المان را به کامپوننت ارسال کنیم. بنابراین برای تگ <app-article> خواهیم داشت:
<app-article [article]="myArticle"></app-article>
بسیار عالی، و در نهایت فایل کامپوننت ما به صورت زیر خواهد بود:
import {Component, OnInit, Input} from '@angular/core'; import {Article} from "./article.model"; @Component({ selector: 'app-article', templateUrl: './article.component.html', styleUrls: ['./article.component.css'] }) export class ArticleComponent implements OnInit { @Input article: Article; voteUp(): boolean{ this.article.voteUp(); return false; } voteDown(): boolean{ this.article.voteDown(); return false; } ngOnInit() { } }
همانگونه که ملاحظه کردید تنظیمات مربوط به AppComponent را جهت ذخیره کردن آرایهها اعمال کردیم. هم اکنون این تنظیمات را برای نمایش و رندر کردن تمام مقالات گسترش میدهیم. برای انجام اینکار به جای استفاده از تگ <app-article> این تگ را به همراه دستور NgFor جهت تکرار نمایش مطالب استفاده میکنیم. بنابراین در فایل app.component.html بعد از تگ Form تغییرات زیر را لحاظ خواهیم کرد:
<app-article *ngFor="let article of articles" [article]="article"> </app-article>
حال اگر صفحه مرورگر خود را بروزرسانی کنید با تصویر زیر مواجه میشوید:
در مراحل گذشته ما توانستیم یک مطلب را ذخیره کرده و در لیست خود به نمایش بگذاریم. حال برای بهتر نمایش دادن آن میخواهیم یک تابع مرتبسازی بر اساس امتیاز به پروژه خود اضافه کنیم. بنابراین فایل app.component.ts را باز کرده و کد زیر را به آن اضافه میکنیم:
sortArticles(): Article[] { return this.articles.sort((a: Article, b: Article) => b.votes - a.votes); }
و سپس فایل مربوط به قالب app.component.html را نیز به صورت زیر ویرایش کنید:
<div class="row"> <!-- Horizontal Form --> <div class="col-md-8 col-center-block"> <div class="box box-info"> <div class="box-header with-border"> <h3 class="box-title">ثبت لینک</h3> </div> <!-- /.box-header --> <!-- form start --> <form class="form-horizontal"> <div class="box-body"> <div class="form-group"> <label for="title" class="col-sm-2 control-label">عنوان مطلب</label> <div class="col-sm-10"> <input type="text" class="form-control" name="title" placeholder="عنوان مطلب خود را وارد کنید" #newtitle> </div> </div> <div class="form-group"> <label for="link" class="col-sm-2 control-label">لینک</label> <div class="col-sm-10"> <input type="text" class="form-control" name="link" placeholder="لینک مطلب را وارد کنید" #newlink> </div> </div> </div> <!-- /.box-body --> <div class="box-footer"> <button type="submit" (click)="addArticle(newtitle, newlink)" class="btn btn-info pull-right">ثبت</button> </div> <!-- /.box-footer --> </form> </div> <div class="box"> <div class="box-header"> <h3 class="box-title">نظرسنجی مطالب</h3> </div> <!-- /.box-header --> <div class="box-body no-padding"> <div class="row"> <div class="col-md-3 text-center">امتیازدهی</div> <div class="col-md-3 text-center">عنوان مطلب</div> <div class="col-md-3 text-center">لینک مطلب</div> <div class="col-md-3 text-center">مجموع امتیازها</div> </div> <app-article *ngFor="let article of sortArticles()" [article]="article"> </app-article> </div> <!-- /.box-body --> </div> <!-- /.box --> </div> </div> <!-- /.box -->
به شما مجددا تبریک میگوییم. فصل ۱ از سری دوره آموزشی انگولار ۲ به زبان فارسی به اتمام رسید. هم اکنون شما نرمافزاری برای نظرسنجی کاربران نسبت به مطالب، ایجاد و کدنویسی کردید. سختترین مرحلهی هر کار شروع آن است. در فصلهای بعدی به توضیحات تخصصیتری نسبت به انگولار ۲ میپردازیم.
برای افزایش تمرکز کاربران عزیز و جلوگیری از طولانی شدن مطالب، هر بخش را به قسمتهای کوچکتری تبدیل کردهایم. در لیست زیر تمام بخشها و زیربخشهای آموزشی مجموعهی انگولار ۲ در اختیار شما قرار گرفته است.
توجه: دوستان عزیز آموزش ویدیویی انگولار ۵ از مقدماتی تا پیشرفته به زبان فارسی را میتوانید با کلیک روی اینجا یاد بگیرید. (این دوره در حال برگزاری است)
فصل ۱
فصل ۲
فصل ۳
فصل ۴
فصل ۵
فصل ۶
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.