注:这是这个系列的第二部分,主要集中在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