记Angular与Django REST框架的一次合作(2):前端组件化——Angular

注:这是这个系列的第二部分,主要集中在Angular的使用方面。之前使用过AngularJS(Angular 1.x),混在Django的模板中使用,这些页面一般完全是结果展示页。在有Django表单输入的页面中,就很难将两者很好的结合起来。自己在学习新版的Angular时,跟了2遍官方网站的“英雄指南”教程。第1次完全是照搬,熟悉了一下基本概念;第2次自己做了一些修改,写了一个图片分享系统(只有一个雏形,还不是特别完善)。

推荐IDE:Visual Studio Code

代码: github地址(如果喜欢,记得star哦~)

第一部分:记Angular与Django REST框架的一次合作(1):分离 or 不分离,it‘s the question

1. Angular



作为一个新入坑web开发的人,本来一开始想选择一个轻量级的前端+Django来开发网站。开始比较了几个框架(主要是纠结于React和Angular),最后还是选择了Angular(当时用的是1.x版)。之所以作出这样的决定,在Stock Overflow中Josh David Miller对问题“Thinking in AngularJS” if I have a jQuery background?的回答对我的影响很大。其中有一句话我很认同。

Don‘t design your page, and then change it with DOM manipulations

上面的问题是比较Angular和jQuery的,但是同样适用从某些方面来比较Angualr和React:Angular是以html为中心的,从某种程度上可以看做是对html的一种扩展;React是以JS为中心的,在JS中混入了html。由于是初学,当时发现如果没有先用html搭个架子,就完全搞不清楚网站的结构。

现在学习新版的Angular,反而觉得与React更像了。不得不说,组件(Component)是一种恰到好处的组织代码的结构单元,而且感觉经过重新设计的Angular学习起来比AngularJS更容易上手,学习曲线也更加平滑(也许是因为之前的经验)。Angular中有许多重要的基本概念,但是最重要的应该就是组件了。

1.1 突出的特点

目前了解到的关于Angualr项目中,印象最为深刻的两个特点:

  • 在导航栏中,链接到其他页面不会触发页面重载(这也是SPA应用的一大特色);
  • 在不主动刷新的情况下,整个网站可以离线使用。

2. 图片分享系统(image-sharing-system)



这个项目完全是仿照着官方的“英雄指南”教程修改并添加了一些元素构成的。修改如下:

  • 将hero换成了user;
  • 每个user多了一个属性"files",包含该用户上传的图片的编号(一个数组);
  • 添加了图片相关的数据,与user的相关数据一起放在"in-memory-data.service.ts"文件中;
  • 添加了"image.service"用来处理与图片相关的数据;
  • 修改了"user-detail"组件,使得进入详情页后可以看到每个用户分享的图片;
  • 修改了"dashboard"组件,使得可以展示分享图片最多的4位用户;

图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

3. Angular CLI



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

4. 组件(component)



组件就是“一小块TypeScript代码 + 一小块html代码 + 一小块css代码“,每个组件相对来说都可以实现一个比较独立的功能。而前端最重要的功能就是内容的展示和与用户的交互。因此组件相当于将整个大任务进行了分解:每个组件都完成一小块任务,然后将这些组件拼在一起,就可以得到整个功能完整的网站。

比如在我自己写的这个小项目中,一共有5个组件(图3橙色部分):

  • app.component:由Angular CLI创建时自动生成,直接被添加到了index.html中,相当于所有组件的父组件,控制着整个网站的基本结构,也是路由的出口;
  • users.component:/users路由到这个视图,主要用于显示user列表,增加、删除用户;
  • dashboard.component:/dashboard路由到这个视图(根路由也被重定向到这个视图),展示分享图片最多的前4位用户,按用户名搜索;
  • user-search.component:dashboard组件的子组件,负责搜索相关事务;
  • user-detail.component:/detail/user-id,用于展示每个用户的详情;

上面的5个组件,app用于组织网站的结构,确定路由的出口;其他组件要么负责一个独立的页面,或者是一个页面的一部分。其他无论是module(例如路由模块,图3绿色部分)还是service(主要用于提供数据,图3紫色部分),都是为组件的正常工作提供支持的。因此可以说,组件是位于Angular框架的中心位置的。

4.1 组件的组成

利用CLI创建一个新的component后,默认会在app文件夹下生成一个文件夹,这个文件夹内包含四个文件(以users组件为例):

  • users.component.css:负责该组件的样式;
  • users.component.html:该组件的模板;
  • users.component.spec.ts:一般用不到;
  • users.component.ts:负责逻辑的处理,可以定义变量和函数,在模板中展示或调用;此外还可以导入相应的service,通过调用service的方法获取数据;

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来调用服务和路由中的方法了。

5. 服务(service)



服务是连接服务器端和组件的桥梁,使用单独的服务可以保持组件精简,服务可以通过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 服务的实现

在这部分实现了以下操作:

  • 获取所有user的数据(三种实现,直接返回一个数组、返回一个Promise对象、利用http返回一个Observable对象再转换成Promise对象);
  • 获取单个user的数据(两种实现,返回所有user的数据再根据id过滤、直接请求单个user的数据);
  • 修改某个user的信息:利用http的put方法修改服务器端数据,使得数据可以持久化;
  • 添加新的user:利用http的post方法,将数据添加到服务器端;
  • 删除已存在的user:利用http的delete方法,删除数据;
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中,可以看到返回的数据。

6. 一个模拟的服务器端



到目前为止,我们并没有真正的服务器端,我们的服务器端是利用"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框架

Reference



中文版英雄教程: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

时间: 2024-12-25 21:00:24

记Angular与Django REST框架的一次合作(2):前端组件化——Angular的相关文章

2015前端组件化框架之路(转)

https://github.com/xufei/blog/issues/19 1. 为什么组件化这么难做 Web应用的组件化是一个很复杂的话题. 在大型软件中,组件化是一种共识,它一方面提高了开发效率,另一方面降低了维护成本.但是在Web前端这个领域,并没有很通用的组件模式,因为缺少一个大家都能认同的实现方式,所以很多框架/库都实现了自己的组件化方式. 前端圈最热衷于造轮子了,没有哪个别的领域能出现这么混乱而欣欣向荣的景象.这一方面说明前端领域的创造力很旺盛,另一方面却说明了基础设施是不完善的

2015前端组件化框架之路

特别声明:本文转自@民工精髓的<2015前端组件化框架之路>.谢谢@民工精髓的分享!著作权归作者所有. 编辑推荐: 掘金是一个高质量的技术社区,从 CSS 到 Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货. 点击链接查看最新前端内容,或到各大应用市场搜索「 掘金」下载APP,技术干货尽在掌握中著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.原文: http://www.w3cplus.com/components-in-webapp.html ? w

【转】前端组件化框架之路

1. 为什么组件化这么难做 Web应用的组件化是一个很复杂的话题. 在大型软件中,组件化是一种共识,它一方面提高了开发效率,另一方面降低了维护成本.但是在Web前端这个领域,并没有很通用的组件模式,因为缺少一个大家都能认同的实现方式,所以很多框架/库都实现了自己的组件化方式. 前端圈最热衷于造轮子了,没有哪个别的领域能出现这么混乱而欣欣向荣的景象.这一方面说明前端领域的创造力很旺盛,另一方面却说明了基础设施是不完善的. 我曾经有过这么一个类比,说明某种编程技术及其生态发展的几个阶段: 最初的时候

client高性能组件化框架React简单介绍、特点、环境搭建及经常使用语法

[本文源址:http://blog.csdn.net/q1056843325/article/details/54729657 转载请加入该地址] 明天就是除夕了 预祝大家新春快乐 [ ]~( ̄▽ ̄)~* 天天饭局搞得我是身心疲惫= = 所以更新比較慢 今天想跟大家分享的就是这个大名鼎鼎的React框架 简单介绍 React是近两年非常流行的框架 流行到什么程度呢? 我看了一下Github上的数据 React达到了5w8+的star 在JavaScript中star排名第4 受欢迎程度可见一斑

记一次django的诡异报错 Could not parse the remainder: &#39;=&#39; from &#39;=&#39;

如题,一个展示日志的功能,调用该模板文件时报错,Could not parse the remainder: '=' from '='.这行模板语言在上面出现过同样的,仅仅是改了'<='右端的整数大小,而且该有的标签也不少. 百般思考无解,stackoverflow和百度都没答案.后来重新看了下开发文档,发现是Django的版本不对,开发时用的1.8.2,我部署时用的1.11.5.重新安装低版本的django后问题解决.但原因仍待查. 记一次django的诡异报错 Could not parse

python Django Wbe框架教程

python  Django Wbe框架教程 一.简介 Django 中提供了开发网站经常用到的模块,常见的代码都为你写好了,通过减少重复的代码,Django 使你能够专注于 web 应用上有 趣的关键性的东西.为了达到这个目标,Django 提供了通用Web开发模式的高度抽象,提供了频繁进行的编程作业的快速解决方法,以及为“如何解决问题”提供了清晰明了的约定.Django的理念是DRY(Don't Repeat Yourself)来鼓励快速开发! 让我们一览 Django 全貌 urls.py

Django—— 缓存框架

译者注:1.无用的,吹嘘的说辞不翻译:2.意译,很多地方不准确. 动态网站最为重要的一点就是好,网页是动态的.每一次用户请求页面,网站就要进行各种计算——从数据库查询,到render模板,到各种逻辑运算——生成页面所需的.这个过程是异常消耗资源的,远远比从硬盘读取一个文件然后显示出来的代价高昂. 对于大多数中小网站来说,这也许不是问题,因为他们的访问量不大,而对于大型网站而言,必须尽量减少不必要的服务器资源开支. 因此,有了缓存技术. 缓存就是把一些需要消耗很多资源的计算结果保存下来,当下次需要

Django 六大框架之 Models

  先睹为快 from django.db import models class Person(models.Model):     first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)   核心要点 每一个model 是 django.db.models.Model 的子类       --> 数据库表 每一个model属性 是 django.db.models.Fil

Linux(CentOS7)系统中部署Django web框架

1. 概述 部署django和vue架在逻辑上可以分为web层与数据库层:web前端通过实现了WSGI协议的模块对python代码进行解析,而python代码中则通过特定于数据库的操作接口对数据库进行读取与写入. Django自身内置了轻量级的web服务器与sqlite数据库,可以用于简单的代码测试,并支持Apache httpd与Nginx作为web前端,以及PostgreSQL/MySQL/Oracle等数据库作为后端存储,用于实际的生产环境. 本文分别以MySQL + Apache htt