注:這是這個系列的第二部分,主要集中在Angular的使用方面。以前使用過AngularJS(Angular 1.x),混在Django的模板中使用,這些頁面通常徹底是結果展現頁。在有Django表單輸入的頁面中,就很難將二者很好的結合起來。本身在學習新版的Angular時,跟了2遍官方網站的「英雄指南」教程。第1次徹底是照搬,熟悉了一下基本概念;第2次本身作了一些修改,寫了一個圖片分享系統(只有一個雛形,還不是特別完善)。css
推薦IDE:Visual Studio Codehtml
代碼: github地址(若是喜歡,記得star哦~)前端
第一部分:記Angular與Django REST框架的一次合做(1):分離 or 不分離,it's the questionjquery
做爲一個新入坑web開發的人,原本一開始想選擇一個輕量級的前端+Django來開發網站。開始比較了幾個框架(主要是糾結於React和Angular),最後仍是選擇了Angular(當時用的是1.x版)。之因此做出這樣的決定,在Stock Overflow中Josh David Miller對問題「Thinking in AngularJS」 if I have a jQuery background? 的回答對個人影響很大。其中有一句話我很認同。git
Don't design your page, and then change it with DOM manipulationsangularjs
上面的問題是比較Angular和jQuery的,可是一樣適用從某些方面來比較Angualr和React:Angular是以html爲中心的,從某種程度上能夠看作是對html的一種擴展;React是以JS爲中心的,在JS中混入了html。因爲是初學,當時發現若是沒有先用html搭個架子,就徹底搞不清楚網站的結構。github
如今學習新版的Angular,反而以爲與React更像了。不得不說,組件(Component)是一種恰到好處的組織代碼的結構單元,並且感受通過從新設計的Angular學習起來比AngularJS更容易上手,學習曲線也更加平滑(也許是由於以前的經驗)。Angular中有許多重要的基本概念,可是最重要的應該就是組件了。web
1.1 突出的特色數據庫
目前瞭解到的關於Angualr項目中,印象最爲深入的兩個特色:npm
這個項目徹底是仿照着官方的「英雄指南」教程修改並添加了一些元素構成的。修改以下:
圖1:http://localhost:4201/dashboard 視圖
圖2-1:http://localhost:4201/users 視圖
圖2-2:http://localhost:4201/detail/19 視圖
整個項目實現的功能:進入主頁(dashboard視圖,圖1),能夠看到分享圖片最多的4位用戶,點擊每位用戶能夠進入用戶的詳情頁,在主頁能夠按照用戶名搜索用戶並進入詳情頁;點擊導航欄中的user,能夠查看全部用戶的列表(users視圖,圖2-1),點擊每一個user,能夠從下方的View Details進入該user的詳情頁(detail視圖,圖2-2)。在users視圖中還能夠添加、刪除用戶。
2.1 本地運行的方法
github:https://github.com/OnlyBelter/image-sharing-system
首先要安裝Node.js和Angular CLI,將項目clone到本地,而後運行下面的命令
npm install ng serve --host 0.0.0.0 --port 4201
若是運行正常,就能夠在瀏覽器中查看了,http://localhost:4201/dashboard
2.2 整個程序的結構
圖3:圖片分享系統程序的結構,查看pdf
CLI是Angular的命令行接口,使用CLI能夠經過命令行建立項目,建立新的組件,服務,模塊等;並且能夠用來實時編譯,測試,發佈。CLI建立組件時(或服務等)會自動將組件import到app.module.ts文件中,並在NgModule中聲明。建立一個新項目後的文件結構以下圖:
圖4:Angular CLI生成的文件結構(VS Code中打開)
後面全部的修改都在app文件夾中,其餘的文件通常不須要修改。
下面是一些經常使用的命令:
# 安裝cli npm install -g @angular/cli # 建立一個新項目 ng new my-project cd my-project # 啓動項目,能夠實時編譯 ng serve --host 0.0.0.0 --port 4201 # 建立新的component ng g component my-new-component # 建立新的服務 ng g service my-new-service
CLI的文檔:https://github.com/angular/angular-cli/wiki
組件就是「一小塊TypeScript代碼 + 一小塊html代碼 + 一小塊css代碼「,每一個組件相對來講均可以實現一個比較獨立的功能。而前端最重要的功能就是內容的展現和與用戶的交互。所以組件至關於將整個大任務進行了分解:每一個組件都完成一小塊任務,而後將這些組件拼在一塊兒,就能夠獲得整個功能完整的網站。
好比在我本身寫的這個小項目中,一共有5個組件(圖3橙色部分):
上面的5個組件,app用於組織網站的結構,肯定路由的出口;其餘組件要麼負責一個獨立的頁面,或者是一個頁面的一部分。其餘不管是module(例如路由模塊,圖3綠色部分)仍是service(主要用於提供數據,圖3紫色部分),都是爲組件的正常工做提供支持的。所以能夠說,組件是位於Angular框架的中心位置的。
4.1 組件的組成
利用CLI建立一個新的component後,默認會在app文件夾下生成一個文件夾,這個文件夾內包含四個文件(以users組件爲例):
4.2 組件中的構造函數(constructor)
組件的構造函數用來解決依賴注入,初始化服務或路由。其餘變量的初始化不該該放在這裏,而應該放在ngOnInit中。下面是users組件中的構造函數:
// the constructor itself does nothing, the parameter simultaneously deinfes // a private userService property and identifies it as a UserService injection constructor( private userService: UserService, // 組件在構造函數中請求服務 private router: Router // 在構造函數中注入Router ) { }
初始化服務和路由後,就能夠在後面經過this.userService和this.router來調用服務和路由中的方法了。
服務是鏈接服務器端和組件的橋樑,使用單獨的服務能夠保持組件精簡,服務能夠經過http協議中的方法(get, post等)向服務器請求資源或修改、添加資源。
服務也能夠經過CLI直接建立,服務的標誌是在export前有一個@Injectable()修飾符。當 TypeScript 看到@Injectable()裝飾器時,就會記下本服務的元數據。 若是 Angular 須要往這個服務中注入其它依賴,就會使用這些元數據。像上面組件的構造函數中介紹的那樣,服務能夠注入到組件中,從而爲組件提供數據服務。
5.1 承諾
服務老是異步的。Angular的http.get返回一個 RxJS 的Observable對象。Observable(可觀察對象)是一個管理異步數據流的強力方式。能夠利用toPromise操做符把Observable轉換成Promise對象。
一個Observable對象是一個數組,其中的元素隨着時間的流逝異步地到達。 Observable幫助咱們管理異步數據,例如來自後臺服務的數據。 Angular 自身使用了Observable,包括 Angular 的事件系統和它的 http 客戶端服務。爲了使用Observable, Angular 採用了名爲 Reactive Extensions (RxJS) 的第三方包。
這部分應該是官方教程中最複雜的一起了。我打算後面單獨寫一篇博客,介紹這部分的內容。下面看一下我本身改寫的項目中user.service的實現:
5.2 服務的實現
在這部分實現瞭如下操做:
import { Injectable } from '@angular/core'; import { Headers, Http } from "@angular/http"; // 有不少像toPromise這樣的操做符,用於擴展Observable,爲其添加有用的能力 import 'rxjs/add/operator/toPromise'; import { USERS } from './mock-users'; import { User } from "./user"; @Injectable() export class UserService { private usersUrl = 'api/users'; constructor(private http: Http) { } //UserService暴露了getUsers方法,返回跟之前同樣的模擬數據,但它的消費者不須要知道這一點 //服務是一個分離關注點,建議你把代碼放到它本身的文件裏 getUsers(): Promise<User[]> { // return USERS; // 直接返回一個數組 return Promise.resolve(USERS); // 返回一個Promise對象 } // 延遲6s後返回 getUsersSlowly(): Promise<User[]> { return new Promise(resolve => setTimeout(() => resolve(USERS), 6000)); } // 返回全部user的數據再過濾 getUser(id: number): Promise<User> { return this.getUsers() .then(rep => rep.find(user => user.id === id)); } //Angular 的http.get返回一個 RxJS 的Observable對象 getUsersByHttp(): Promise<User[]> { return this.http.get(this.usersUrl) .toPromise() .then(res => res.json().data as User[]) .catch(this.handleError); } // 來發起一個 get-by-id 請求,直接請求單個user的數據 getUserByHttp(id: number): Promise<User> { const url = `${this.usersUrl}/${id}`; return this.http.get(url) .toPromise() .then(res => res.json().data as User) .catch(this.handleError); } private headers = new Headers({'Content-Type': 'application/json'}); // 使用 HTTP 的 put() 方法來把修改持久化到服務端 update(user: User): Promise<User> { const url = `${this.usersUrl}/${user.id}`; return this.http.put(url, JSON.stringify(user), {headers: this.headers}) .toPromise() .then(() => user) // () .catch(this.handleError); } create(name: string): Promise<User> { return this.http .post(this.usersUrl, JSON.stringify({name: name}), {headers: this.headers}) .toPromise() // 下面的.then方法對默認返回的數據進行了加工,獲得了一個完整的User對象 .then(res => res.json().data as User) .catch(this.handleError); } delete(id: number): Promise<void> { const url = `${this.usersUrl}/${id}`; return this.http.delete(url, {headers: this.headers}) .toPromise() .then(() => null) // 什麼也不返回 .catch(this.handleError); } private handleError(error: any): Promise<any> { console.error('An error occurred', error); // for demo purposes only return Promise.reject(error.message || error); } }
5.3 http請求與響應
在上面的代碼中,每次調用http.get(url),或其餘http方法(post, put, delete),就至關於對相應的url發送了一次請求(Request)。發出這個請求後,收到請求的一方(通常是服務器端)總會給出一個響應(Response),這個響應能夠是各類不一樣的形式。上面的getUsersByHttp方法中,就返回了一個User[]數組(由res.json().data獲得),若是咱們作一些修改:
1 //Angular 的http.get返回一個 RxJS 的Observable對象 2 getUsersByHttp(): Promise<User[]> { 3 return this.http.get(this.usersUrl) 4 .toPromise() 5 // .then(res => res.json().data as User[]) 6 .then(res => res) 7 .catch(this.handleError); 8 }
如今返回的是一個原生態的Response,若是在users組件中打印出這個Response:
1 getUsers(): void { 2 // res是UserService返回的User數組,做爲參數傳遞並賦值給組件的users屬性 3 // 使用.then(res => console.log(res))能夠將res打印到終端 4 this.userService.getUsersByHttp() 5 // .then(res => this.users = res); 6 .then(res => console.log(res)); 7 }
咱們能夠看到下面的結果:
圖5:一個標準的Response類
咱們能夠看到status爲200,表示咱們請求成功了。在_body的data中,能夠看到返回的數據。
到目前爲止,咱們並無真正的服務器端,咱們的服務器端是利用"angular-in-memory-web-api"模擬出來的一個內存數據庫。所以數據只是保存到了內存,在不刷新的狀況下,暫時作到了對數據的持久化。下面是"in-memory-data.service.ts"文件中的內容:
import { InMemoryDbService } from 'angular-in-memory-web-api'; export class InMemoryDataService implements InMemoryDbService { // 因爲沒有後端,這裏建立了一個內存數據庫來存放數據 createDb() { let users = [ { id: 11, name: 'Mr. Nice', files: [1, 2] }, { id: 12, name: 'Narco', files: [32] }, { id: 13, name: 'Bombasto', files: [11, 5] }, { id: 14, name: 'Celeritas', files: [4, 12] }, { id: 15, name: 'Magneta', files: [6] }, { id: 16, name: 'RubberMan', files: [21] }, { id: 17, name: 'Dynama', files: [3, 7, 9] }, { id: 18, name: 'Dr IQ', files: [] }, { id: 19, name: 'Magma', files: [10] }, { id: 20, name: 'Tornado', files: [8, 13, 14, 16] } ]; let images = [ { id: 1, userId: 11, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/butterfly1.jpg' }, { id: 2, userId: 11, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/cat1.jpg' }, { id: 3, userId: 17, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/cloud1.jpg' }, { id: 4, userId: 14, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/river1.jpg' }, { id: 5, userId: 13, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/flower1.jpg' }, { id: 6, userId: 15, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/disney1.jpg' }, { id: 7, userId: 17, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/cloud2.jpg' }, { id: 8, userId: 20, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/panda1.jpg' }, { id: 9, userId: 17, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/sunfei2.jpg' }, { id: 10, userId: 19, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/panda2.jpg' }, { id: 11, userId: 13, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/flower2.jpg' }, { id: 12, userId: 14, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/IMG_20161105_100414_A19.jpg' }, { id: 13, userId: 20, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/panda3.jpg' }, { id: 14, userId: 20, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/shanghai4.jpg' }, { id: 16, userId: 20, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/shanghai5.jpg' }, { id: 21, userId: 16, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/grass.jpg' }, { id: 32, userId: 12, des: '', fileUrl: 'https://raw.githubusercontent.com/OnlyBelter/learn_neuralTalk/master/self_pic/img/lamp1.jpg' }, ]; return {users, images}; } }
查看上面的代碼,咱們在內存數據庫中定義了兩個數據庫users和images;所以,咱們能夠在service中利用http協議中的動詞(get, post, put, delete)經過"api/users"和"api/images"這兩個url地址對這兩數據庫進行操做。在service中,對這種內存數據庫的操做和對真正的利用Django REST框架搭建的API的操做是沒有差異的。下個部分,我會嘗試用Django REST Framework搭建一個能夠替代"angular-in-memory-web-api"構建的內存數據庫的,真正意義上的後端。
接下來...
第三部分:後端服務化——Django REST框架
中文版英雄教程:https://angular.cn/tutorial
https://stackoverflow.com/a/15012542/2803344
https://angular.cn/guide/glossary#observable-對象
https://stackoverflow.com/questions/35763730/difference-between-constructor-and-ngoninit/35763811#35763811
https://github.com/OnlyBelter/image-sharing-system