這兩天看了看angular4的文檔,發現他和angular1.X的差別真的是太大了,官方給出的那個管理英雄的Demo是一個非常好的入門項目,這裡給出一個管理個人計劃的小項目,從頭至尾一步一步講解如何去實現他,希望對你有所幫助。這篇文章不會講解如何去用angular,這部分東西你可以參考官網,本文講解... ...
這兩天看了看angular4的文檔,發現他和angular1.X的差別真的是太大了,官方給出的那個管理英雄的Demo是一個非常好的入門項目,這裡給出一個管理個人計劃的小項目,從頭至尾一步一步講解如何去實現他,希望對你有所幫助。這篇文章不會講解如何去用angular4,這部分東西你可以參考官網,本文講解的是用angular做一個小項目的全過程。寫的比較倉促,疏漏難免,請各位多多指點。
本項目是angular4.0的一個Demo,可以實現對個人計劃的管理。目的是分享一下個人做一個angular項目的一般做法,希望能為一些朋友提供參考。項目參考了vue-tutorial,在此對原作者表示感謝。
git地址:https://github.com/yibingxiong/ng-plan
博客地址:http://www.cnblogs.com/floor/
項目架構
開發過程詳解
本開發過程不對angular4.0的使用進行詳細講解,一些基本概念和一些工具的安裝使用請參考官網
1. 初始化項目
- 使用angular-cli搭建項目
ng new ng-plan
- 啟動項目
cd ng-plan
ng serve
- 訪問localhost:4200/查看效果
如果能夠看到這個效果證明你完成了天才第一步(不是雀式紙尿褲哦)
2. 做一個首頁
- 引入bootstrap樣式庫
為了簡化樣式編寫,直接用個第三方庫bootstrap,在/src/index.html的heade引入,如下
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
- 修改/src/app/app.component.html
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">
<i class="glyphicon glyphicon-time"></i>
計劃板
</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">首頁</a></li>
<li><a href="#">計劃列表</a></li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="col-sm-3">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="text-center">已有時長</h1>
</div>
<div class="panel-body">
<h1 class="text-center">1 小時</h1>
</div>
</div>
</div>
<div class="col-sm-9">
<div class="jumbotron">
<h1>任務追蹤</h1>
<p>
<strong>
<a href="#">創建一個任務</a>
</strong>
</p>
</div>
</div>
</div>
效果如下
3. 自己動手做一個組件
- 使用angular-cli創建組件 ng g component navigation
- 將/src/app/app.component.html中的導航部分移到/src/app/navigation/navigation.component.html
- 使用我們的navigation組件
從上圖可以看見使用命令行創建組件,組件會被自動註冊到根模塊中,所以直接使用就可以了
直接在/src/app/app.component.html最上邊加上
<app-navigation></app-navigation>
如果不出現意外的話,你看到的效果應該和2中效果一樣,那為什麼要這樣做呢?方便維護,便於復用。在這個小項目你可能並不能看出他的好處,而且本項目將導航做成獨立組件沒有太大意義。
4. 做個路由
- 在app.module.ts中導入路由模塊
import { RouterModule } from '@angular/router';
- 配置我們的路由
現在我們的/src/app/module.ts應該是下邊這個樣子
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { NavigationComponent } from './navigation/navigation.component';
@NgModule({
declarations: [
AppComponent,
NavigationComponent,
],
imports: [
BrowserModule,
RouterModule.forRoot([
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
可以看到imports中做了路由配置,此時運行會出錯,因為還沒有Home組件
- 指定路由出口
將/src/app/app.component.html改為如下
<app-navigation></app-navigation>
<div class="container">
<div class="col-sm-3">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="text-center">已有時長</h1>
</div>
<div class="panel-body">
<h1 class="text-center">1 小時</h1>
</div>
</div>
</div>
<div class="col-sm-9">
<router-outlet></router-outlet>
</div>
</div>
可以發現一部分內容被替換為<router-outlet></router-outlet>
這樣,路由匹配的內容將會在這裡顯示。
- 創建Home組件
上邊發現我們home找不到,現在創建這個組件,與上邊創建navigation是類似的
直接運行
ng g component home
angular-cli會幫我們做好一切
運行效果如下
下麵修改我們的Home組件,更改他的html文件如下
<div class="jumbotron">
<h1>任務追蹤</h1>
<p>
<strong>
<a>創建一個任務</a>
</strong>
</p>
</div>
效果如下
發現和前面的效果基本一樣了,不過不要著急,我們慢慢來!
5. 創建計劃列表頁
這其實還是創建一個組件,所以我們還是用命令行來搞!
- 第一步
ng g component plan-list
- 第二步 加到路由(修改app.module.ts)
{ path: 'planlist', component: PlanListComponent },
- 第三步 測試一下(訪問:http://localhost:4200/planlist)
沒毛病!
- 第四步 完善計劃列表
- 修改/src/app/plan-list/plan-list.component.html
<div class="time-entries">
<div class="list-group">
<a class="list-group-item">
<div class="row">
<div class="col-sm-2 user-details">
<img src="http://pic.dfhon.com/pictures/20100401/141048.jpg" class="avatar img-circle img-responsive" />
<p class="text-center">
<strong>
大熊
</strong>
</p>
</div>
<div class="col-sm-2 text-center time-block">
<h3 class="list-group-item-text total-time">
<i class="glyphicon glyphicon-time"></i>
1
</h3>
<p class="label label-primary text-center">
<i class="glyphicon glyphicon-calendar"></i>
2017-08-04
</p>
</div>
<div class="col-sm-7 comment-section">
一定要完成
</div>
<div class="col-sm-1">
<button class="btn btn-xs btn-danger delete-button">
X
</button>
</div>
</div>
</a>
<a class="list-group-item">
<div class="row">
<div class="col-sm-2 user-details">
<img src="http://pic.dfhon.com/pictures/20100401/141048.jpg" class="avatar img-circle img-responsive" />
<p class="text-center">
<strong>
大熊
</strong>
</p>
</div>
<div class="col-sm-2 text-center time-block">
<h3 class="list-group-item-text total-time">
<i class="glyphicon glyphicon-time"></i>
1
</h3>
<p class="label label-primary text-center">
<i class="glyphicon glyphicon-calendar"></i>
2017-08-04
</p>
</div>
<div class="col-sm-7 comment-section">
一定要完成
</div>
<div class="col-sm-1">
<button class="btn btn-xs btn-danger delete-button">
X
</button>
</div>
</div>
</a>
</div>
</div>
- 修改/src/app/plan-list/plan-list.component.css
.avatar {
height: 75px;
margin: 0 auto;
margin-top: 10px;
margin-bottom: 10px;
}
.user-details {
background-color: #f5f5f5;
border-right: 1px solid #ddd;
margin: -10px 0;
}
.time-block {
padding: 10px;
}
.comment-section {
padding: 20px;
}
- 查看效果
5. 讓導航可點
目前我們的導航還不能點擊,下麵解決這個問題,修改/src/app/navigation/navigation.component.html
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" routerLink="/" routerLinkActive="active">
<i class="glyphicon glyphicon-time"></i>
計劃板
</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li routerLinkActive="active"><a routerLink="/home">首頁</a></li>
<li routerLinkActive="active"><a routerLink="/planlist" >計劃列表</a></li>
</ul>
</div>
</div>
</nav>
這樣你就可以在首頁和計劃列表點來點去了。
6. 用假數據渲染planlist
- 修改/src/app/plan-list/plan-list.component.ts提供數據
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-plan-list',
templateUrl: './plan-list.component.html',
styleUrls: ['./plan-list.component.css']
})
export class PlanListComponent implements OnInit {
plans = [
{
name: '大熊',
date: '2017-8-8',
totalTime: 1,
comment: '學習到天亮'
},
{
name: '大熊',
date: '2017-8-8',
totalTime: 1,
comment: '學習到天亮'
},
{
name: '大熊',
date: '2017-8-8',
totalTime: 1,
comment: '學習到天亮'
}
];
constructor() { }
ngOnInit() {
}
}
- 使用ngFor渲染列表,修改/src/app/plan-list/plan-list.component.html
<div class="time-entries">
<div class="list-group">
<a class="list-group-item" *ngFor="let plan of plans">
<div class="row">
<div class="col-sm-2 user-details">
<img src="http://pic.dfhon.com/pictures/20100401/141048.jpg" class="avatar img-circle img-responsive" />
<p class="text-center">
<strong>
{{plan.name}}
</strong>
</p>
</div>
<div class="col-sm-2 text-center time-block">
<h3 class="list-group-item-text total-time">
<i class="glyphicon glyphicon-time"></i>
{{plan.totalTime}}
</h3>
<p class="label label-primary text-center">
<i class="glyphicon glyphicon-calendar"></i>
{{plan.date}}
</p>
</div>
<div class="col-sm-7 comment-section">
{{plan.comment}}
</div>
<div class="col-sm-1">
<button class="btn btn-xs btn-danger delete-button">
X
</button>
</div>
</div>
</a>
</div>
</div>
7. 用服務管理我們的數據
- 建立計劃類
- 在app下建立bean目錄,作為我們存放基本數據類的地方,然後建立plan.js,如下所示
- 在app下新建service目錄,用於存放我們的服務,然後進入該目錄運行ng g service plan創建服務,如下所示:
- 修改剛剛創建的服務的ts文件如下:
import { Injectable } from '@angular/core';
import { Plan } from '../bean/plan';
@Injectable()
export class PlanService {
plans: Plan[];
constructor() {
this.plans = [
{
id: 11,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
},
{
id: 22,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
},
{
id: 33,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
}];
}
getPlans() {
return this.plans;
}
addPlan(newplan: Plan) {
this.plans.push(newplan);
}
deletePlan(id: number) {
let index = 0;
for (let i = 0; i < this.plans.length; i++) {
if (this.plans[i].id === id) {
index = i;
break;
}
}
this.plans.splice(index, 1);
}
getPlan(id: number) {
for (let i = 0; i < this.plans.length; i++) {
if (this.plans[i].id === id) {
return this.plans[i];
}
}
}
}
- 在app.module.ts中加入服務依賴
import進創建的服務,並加入到providers中,如下:
import { PlanService } from './service/plan.service';
providers: [PlanService],
- 在plan-list中註入服務並使用
import { Component, OnInit } from '@angular/core';
import { PlanService } from '../service/plan.service';
import { Plan } from '../bean/plan';
@Component({
selector: 'app-plan-list',
templateUrl: './plan-list.component.html',
styleUrls: ['./plan-list.component.css']
})
export class PlanListComponent implements OnInit {
plans: Plan[];
constructor(private planService: PlanService) { }
ngOnInit() {
this.plans = this.planService.getPlans();
}
}
可以看到,我將原來寫死在這個組件的plans去掉了,而是去調用了一個服務獲取我所需要的數據。至此,你所看到的效果與原來的無異,但是我們的項目又向前買進了一大步。
8. 刪除計劃
下麵看一下如何刪除一個計劃,這裡主要用到的是事件的綁定。
- 首先定義刪除事件處理函數。在/src/app/plan-list/plan-list.component.ts添加如下事件處理函數
// 刪除計劃
deletePlan (plan) {
this.planService.deletePlan(plan.id);
this.plans = this.planService.getPlans();
}
- 在模板中綁定這個事件處理函數
- 這樣你可以點擊叉叉刪除計划了
9. 創建一個管道來格式化我們的日期
同樣的數據在前端的顯示可能要求不同,這時需要我們對數據進行一些格式化,管道這種東西是你的首選。
- 按如下操作創建初始化管道
觀察文件變化和app.module的變化
- 修改我們的/home/yibingxiong/ng-plan/src/app/pipe/data-format.pipe.ts如下
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'dataFormat'
})
export class DataFormatPipe implements PipeTransform {
transform(value: number): String {
const d = new Date();
d.setTime(value);
const year = d.getFullYear();
const month = d.getMonth() + 1;
const date = d.getDate();
const h = d.getHours();
const m = d.getMinutes();
const s = d.getSeconds();
return (year + '-' + (month < 10 ? ('0' + month) : month) + '-' +
(date < 10 ? ('0' + date) : date) + ' ' + (h < 10 ? ('0' + h) : h) +
':' + (m < 10 ? ('0' + m) : m) + ':' +
(s < 10 ? ('0' + s) : s));
}
}
- 使用我們的管道,如圖所示
10. 為我們的planservice添加一個計算總是長的方法
在app.component中使用
但是會法現一個問題,當刪除計劃時總時長沒有響應變化,下麵設法解決這個問題。
11. 用service做一個共用的數據狀態
經過一番艱苦卓絕的尋找,我終於在https://scotch.io/tutorials/get-angular-1-features-in-angular-2#toc-global-communication-with-services 找到了一個比較簡單的解決方法,雖然我也不是太理解這個方法,不過能用。下麵將我做的修改的文件列出來供你參考。此問題的解決還可以用一個些數據狀態庫。
// /home/yibingxiong/ng-plan/src/app/service/plan.service.ts
import { Injectable } from '@angular/core';
import { Plan } from '../bean/plan';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class PlanService {
plans: Plan[];
totalTime = 0;
totalTime$ = new BehaviorSubject<number>(this.totalTime);
constructor() {
this.plans = [
{
id: 11,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
},
{
id: 22,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
},
{
id: 33,
name: '大熊',
date: 100000,
totalTime: 1,
comment: '學習到天亮'
}];
this.totalTime = this.getTotalTime2();
}
getPlans() {
return this.plans;
}
// 添加計劃
addPlan(newplan: Plan) {
this.totalTime = this.getTotalTime2();
this.updateTotalTimeSbj(this.totalTime);
this.plans.push(newplan);
}
// 刪除計劃
deletePlan(id: number) {
let index = 0;
for (let i = 0; i < this.plans.length; i++) {
if (this.plans[i].id === id) {
index = i;
break;
}
}
this.plans.splice(index, 1);
this.totalTime = this.getTotalTime2();
this.updateTotalTimeSbj(this.totalTime);
}
// 獲得一個計劃
getPlan(id: number) {
for (let i = 0; i < this.plans.length; i++) {
if (this.plans[i].id === id) {
return this.plans[i];
}
}
}
// 計算計劃總時間
getTotalTime2() {
let t = 0;
for (const p of this.plans) {
t += p.totalTime;
}
return t;
}
get getTotalTime(): number {
return this.totalTime;
}
private updateTotalTimeSbj(value) {
this.totalTime$.next(value);
}
}
// /home/yibingxiong/ng-plan/src/app/app.component.ts
import { Component } from '@angular/core';
import { PlanService } from './service/plan.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private planService: PlanService) {
}
title = 'app';
}
// /home/yibingxiong/ng-plan/src/app/app.component.html
<app-navigation></app-navigation>
<div class="container">
<div class="col-sm-3">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="text-center">已有時長</h1>
</div>
<div class="panel-body">
<h1 class="text-center">{{planService.getTotalTime}} 小時</h1>
</div>
</div>
</div>
<div class="col-sm-9">
<router-outlet></router-outlet>
</div>
</div>
12. 添加計劃
- 老規矩,先創建頁面組件
- 添加到路由
- 在/home/yibingxiong/ng-plan/src/app/app.module.ts的路由添加
{ path: 'create', component: CreateComponent },
- 修改/home/yibingxiong/ng-plan/src/app/home/home.component.html
<a routerLink="/create">創建一個任務</a>
- 完善添加計劃頁面
修改/home/yibingxiong/ng-plan/src/app/create/create.component.html
<div class="form-horizontal">
<div class="form-group">
<div class="col-sm-6">
<label>日期</label>
<input
type="date"
class="form-control"
placeholder="Date"
/>
</div>
<div class="col-sm-6">
<label>時間</label>
<input
type="number"
class="form-control"
placeholder="Hours"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label>備註</label>
<input
type="text"
class="form-control"
placeholder="Comment"
/>
</div>
</div>
<button class="btn btn-primary">保存</button>
<a class="btn btn-danger">取消</a>
<hr>
</div>
- 做添加計劃事件處理
- 創建事件處理函數
<div class="form-horizontal">
<div class="form-group">
<div class="col-sm-6">
<label>日期</label>
<input
type="date"
class="form-control"
placeholder="Date"
[(ngModel)]="newplan.date"
/>
</div>
<div class="col-sm-6">
<label>時間</label>
<input
type="number"
class="form-control"
placeholder="Hours"
[(ngModel)]="newplan.totalTime"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label>備註</label>
<input
type="text"
class="form-control"
placeholder="Comment"
[(ngModel)]="newplan.comment"
/>
</div>
</div>
<button class="btn btn-primary" (click)="addPlan(newplan)">保存</button>
<a class="btn btn-danger">取消</a>
<hr>
</div>
- 更新模板
<div class="form-horizontal">
<div class="form-group">
<div class="col-sm-6">
<label>日期</label>
<input
type="date"
class="form-control"
placeholder="Date"
[(ngModel)]="newplan.date"
/>
</div>
<div class="col-sm-6">
<label>時間</label>
<input
type="number"
class="form-control"
placeholder="Hours"
[(ngModel)]="newplan.totalTime"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label>備註</label>
<input
type="text"
class="form-control"
placeholder="Comment"
[(ngModel)]="newplan.comment"
/>
</div>
</div>
<button class="btn btn-primary" (click)="addPlan(newplan)">保存</button>
<a class="btn btn-danger">取消</a>
<hr>
</div>
- 取消返回處理
添加location依賴
在create組件註入並使用
在模板添加事件
<a class="btn btn-danger" (click)="cancel()">取消</a>
13. 去掉假數據,並添加無數據支持
修改planService,移除假數據
在planlist中加入無數據判斷
總結一下
本文是我學習angular4兩個晚上做的一個小Demo,我是一邊做項目一邊寫的這個文檔,所以按照我的文檔操作一般可以順利完成項目,但是難免有一些疏漏,請自行查找問題所在。
本項目雖小,但是他將angular的一些和性要點都用到了。
- 數據單向綁定
- 數據雙向綁定
- 事件
- 依賴註入
- 組件
- 服務
- 管道
- 路由
- 模板指令
- 命令行用法
本項目的結構僅供參考,你可以搭建更加清晰的項目結構。例如將頁面組件都放到page目錄,將一些碎片化的組件放到components目錄,這都是可以的,你可以靈活運用。非常感謝你能如此耐性的看到這裡,希望本文能對你有所幫助。
最後,要是你覺得這篇文章還不錯,請在我的博客http://www.cnblogs.com/floor/ 點個關註,或者點個推薦。或者幫忙點一下star,github地址:https://github.com/yibingxiong/ng-plan