在本文中,我们展示了如何使用Angular 2 MockBackend类开发应用程序,为前端团队提供了一种独立于后端的方法,并提供了一种有用的界面,可以降低结构变更的风险。

每个公司都在寻找使您的前端和后端团队达到最高速度的方法。 但是,团队经常陷入阻碍依赖的陷阱。 在这种情况下,一个团队的即将开展的工作被另一团队拥有的用户故事阻止了。

这些示例之一是前端和后端之间的通信过程。 近年来,REST API登上了所谓通信标准的宝座。 使用JSON(一种简单而有效的数据传输格式)的好处是,前端工作人员不再需要关心实际的后端。 任何跨越网络的都是直接消耗的,并且可以利用它来将数据带入您的应用程序。 因此,毫不奇怪的是,这些基本实体通常根本不在前端建模,而在它们到达时就被消耗掉了。 这给我们带来了根本的问题,即必须等待后端团队提供有用的东西。 如下图所示,我们看到两个团队并行启动,但是在某个时间,一个团队一直在等待另一个团队追赶。

除此之外,没有一种固定的结构会使每个更改成为潜在的危险。 因此,本文的重点是提出一种方法,使前端团队可以独立于后端,同时提供有用的界面,以减少结构更改的风险。

本文已根据Angular 2.1.2版本的最新更新进行了更新。 链接的Plunkr示例应用程序也已更新。

没有真实后端的票务系统

为了实现这种独立性,必须开始对项目进行预先考虑。 您将使用什么实体? 因此,哪些通信端点会产生结果?

这可以通过创建一个小表突出显示必要的REST端点并描述其目的来完成。 请记住,我们之所以这样做是因为双方要就共同的沟通结构达成共识。 这并不意味着它必须被完美地完成,但是它应该可以帮助您开始最重要的步骤。 随着时间的流逝,只需使用所需的新路由相应地更新您的界面即可。

创建后端环境的实际过程是捕获所有HTTP请求,而不是让它们发疯,然后用包含我们想要的信息的假响应进行回复。 本文将通过描述一个简单的票务系统来演示该方法。 它使用下表中显示的端点。

请注意,该示例将POST动词用于更新和创建路由。 另一种选择是利用PUT进行更新 。 但是请记住,PUT应该是幂等的 ,这意味着每个连续的调用都必须产生相同的结果。 随意选择您需要的任何套房。

方法 路线 请求正文 描述
得到 /票 没有 索取所有门票
得到 / ticket /:id 没有 通过提供的:id参数请求一张票证
开机自检 /票 票务实体 创建新票证或更新现有票证
删除 / ticket /:id 没有 删除由:id参数标识的票证

表1:售票系统的消耗端点

Ticket实体是一个简单的TypeScript类,其中包含一些基本的票证信息:

export class Ticket {public _id: string;public title: string;public assignedTo: string;public description: string;public percentageComplete: number;constructor(id: string, title: string, assignedTo: string,description: string, percentageComplete: number) {this._id = id;this.title = title;this.assignedTo = assignedTo;this.description = description;this.percentageComplete = percentageComplete;}
}

票证实体的ticket.entity.ts

您可以在Plunker上找到完整的代码以及此示例的预览:

Angular 2项目设置

理论足够多,让我们开始编写一些代码。 此处显示的项目结构是基于建议的《 Angular 2入门指南》构建的。 这样,我们将不会浪费太多时间来解释其中的每个部分。 如果您正在寻找介绍性文章,请查看使用TypeScript的Angular 2入门 。 对于本文,您可以打开上述的Plunker来遵循下面说明的代码部分。

由于大多数单页应用程序都是以index.html文件开头的,所以让我们先来看一下。 第一部分导入必要的polyfill。 接下来,我们可以看到对system.config.js另一个引用,该引用除其他外还配置了第三方依赖关系和Angular的应用程序文件。 Reactive Extensions(Rx)实际上并不是真正的依赖项,但是可以简化Angular的observable的工作,它们是以前使用的Promises的替代品。 我强烈推荐Cory Rylan撰写的这篇文章,以了解有关此主题的更多信息。

请注意,建议不要使用手动脚本引用来创建可用于生产环境的应用程序。 您应该使用像npm或jspm这样的包管理器。 后一部分与SystemJS结合使用,如第二部分所述。 SystemJS是以前基于ECMAScript 2015草案的模块加载器,现在是WHATWG的加载器规范的一部分 。 因此,它可以使用import x from 'module'语法中的import x from 'module' 。 为了正确使用它,我们需要在前面提到的文件system.config.js对其进行配置,然后导入应用程序的主入口点app ,该app指向文件app/boot.ts

本文不会深入探讨system.config.js细节,因为这些只是作为示例,是基于Angular Quickstart示例的。

最后,我们使用名为my-app的自定义标签创建应用my-app 。 这些称为“组件”,在某种程度上可与Angular.JS 1.x指令相提并论。

<!DOCTYPE html>
<html><head><title>ng2 Ticketing System</title><!-- 1. Load libraries --><!-- Polyfill(s) for older browsers --><script src="https://unpkg.com/core-js/client/shim.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script><script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script><script src="https://unpkg.com/reflect-metadata@0.1.8"></script><script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script><!-- 2. Configure SystemJS --><script src="system.config.js"></script><script>System.import('app').then(null, console.error.bind(console));</script><meta charset="utf-8"/><link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"/><link rel="stylesheet" href="styles.css"/></head><!-- 3. Display the application --><body><my -app>Loading ...</my></body>
</html>

boot.ts文件用于将Angular引导到my-app组件中。 连同所有特定于应用程序的代码,它位于文件夹app 。 在boot.ts内部,我们将执行必要的第一步,以利用boot.ts后端,该后端将替代真实的后端。

我们首先创建一个根模块来容纳我们的应用程序。 它的provider部分用于告诉Angular的DI(依赖项注入)系统我们要使用的类的实际实例以及所需的依赖项。 BaseRequestOptions提供了常规的http帮助器,MockBackend注册了一个模拟实现的实例,我们将使用该实例来创建假回复。 如果查看第三个提供程序配置,创建Http服务的自定义实例,我们可以看到请求的依赖项( deps )传递给useFactory方法。 然后将它们用于创建Http的新实例。

然后,使用imports属性声明其他模块依赖性,然后declarations ,以注册根模块的所有可用组件。 通过模块范围的注册,每个组件都可以知道可用的组件,而不必像以前的Angular 2版本那样显式声明指令请求。最后一个属性bootstrap用于声明哪个组件应该是入口点。

最后,使用bootstrapModule方法启动应用程序。

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { MockBackend } from '@angular/http/testing';
import { Http, BaseRequestOptions } from '@angular/http';
import { FormsModule }   from '@angular/forms';import {AppComponent} from './app.component';
import {TicketComponent} from './ticket.component';@NgModule({providers: [BaseRequestOptions,MockBackend,{provide: Http,deps: [MockBackend, BaseRequestOptions],useFactory: (backend, options) => { return new Http(backend, options); }}],imports: [BrowserModule, FormsModule],declarations: [ AppComponent, TicketComponent ],bootstrap: [AppComponent]
})
export class AppModule { }const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

MockBackend类最初旨在用于单元测试方案,以便模拟实际的服务器调用,从而保持单元测试的快速和隔离。 您可以在官方Http文档中阅读有关此内容的更多信息。

使用组件

现在该看一下完成的应用程序,以标识我们将要使用的组件。 与每个Angular 2应用程序一样,有一个所谓的AppComponent ,它充当该应用程序的主要入口点。 它也可以用作容器,显示常规导航和托管子组件。 说到这些,我们可以看到TicketComponent被重复用于显示多个票证实体。

应用程序组件配置为与选择器my-app ,加载位于templates子文件夹中的模板index.html 。 最后, providers告诉Angular的DI,我们想获取TicketService的实例。

...
@Component({selector: 'my-app',templateUrl: 'app/templates/index.html',providers: [TicketService]
})
export class AppComponent {

接下来,我们定义一个db类属性,该属性将保存一组伪造的Tickets。

// Fake Tickets DB
private db: Ticket[] = [
new Ticket('1', 'Missing Exception', 'John Smith','Method XYZ should throw exception in case ABC', 0),
new Ticket('2', 'Log errors', 'John Smith','Logs need to be persisted to a local file', 24),
new Ticket('3', 'Update AngularJS', 'John Smith','Need to update the App to AngularJS version 1.5', 0),
new Ticket('4', 'Border is missing', 'Jane Doe','The element div.demo has no border defined', 100),
new Ticket('5', 'Introduce responsive grid', 'Jane Doe','Implement reponsive grid for better displays on mobile devices', 17)
];

现在,构造函数将接收注入的TicketService以及伪造的后端。 在这里,我们现在订阅connections流。 对于每个传出的请求,我们现在要检查其request.methodrequest.url ,以找出请求的端点类型。 如果匹配了正确的路由,我们将使用mockRespond方法进行回复,并使用包含Response结果的新Response作为正文,并使用ResponseOptions类进行初始化。

constructor(private service: TicketService, private backend: MockBackend) {
this.backend.connections.subscribe( c => {let singleTicketMatcher = /\/api\/ticket\/([0-9]+)/i;// return all tickets// GET: /ticketif (c.request.url === "http://localhost:8080/api/ticket" && c.request.method === 0) {let res = new Response( new ResponseOptions({body: JSON.stringify(this.db)}));c.mockRespond(res);}

请求单张票证时,我们使用singleTicketMatcher定义的singleTicketMatcher以便对request.url执行正则表达式搜索。 之后,我们搜索给定的ID,并与相应的票证实体进行回复。

// return ticket matching the given id
// GET: /ticket/:id
else if (c.request.url.match(singleTicketMatcher) && c.request.method === 0) {
let matches = this.db.filter( (t) => {return t._id == c.request.url.match(singleTicketMatcher)[1]
});c.mockRespond(new Response( new ResponseOptions({body: JSON.stringify(matches[0])
})));
}

在更新和创建新票证的情况下,我们通过请求正文而不是查询参数或URL模式来获取票证实体。 除此之外,工作非常简单。 我们首先检查故障单是否已经存在并进行更新,否则我们将创建一个新故障单并将其与响应一起发送回去。 我们这样做是为了通知请求者新的票证ID。

  // Add or update a ticket// POST: /ticketelse if (c.request.url === 'http://localhost:8080/api/ticket' && c.request.method === 1) {let newTicket: Ticket = JSON.parse(c.request._body);let existingTicket = this.db.filter( (ticket: Ticket) => { return ticket._id == newTicket._id});if (existingTicket && existingTicket.length === 1) {Object.assign(existingTicket[0], newTicket);c.mockRespond(new Response( new ResponseOptions({body: JSON.stringify(existingTicket[0])})));} else {newTicket._id = parseInt(_.max(this.db, function(t) {return t._id;})._id || 0, 10) + 1 + '';this.db.push(newTicket);c.mockRespond(new Response( new ResponseOptions({body: JSON.stringify(newTicket)})));}}// Delete a ticket// DELETE: /ticket/:idelse if (c.request.url.match(singleTicketMatcher) && c.request.method === 3) {let ticketId = c.request.url.match(singleTicketMatcher)[1];let pos = _.indexOf(_.pluck(this.db, '_id'), ticketId);this.db.splice(pos, 1);c.mockRespond(new Response( new ResponseOptions({body: JSON.stringify({})})));}});
}

最后但并非最不重要的一点是,当组件完全呈现后,页面生命周期挂钩ngOnInit将触发所有票证的加载。

public ngOnInit() {this.service.loadAllTickets();}
}

在实际的生产应用程序中,您将模拟设置分离到单独的服务中,并将其作为依赖项注入到AppComponent中。 甚至更好的是,您将创建一个包含假服务器的全新模块,并将其添加到应用程序的根模块中。 为了使演示更简单,此处将其省略。

查看TicketComponent我们可以看到除了组件装饰器之外,没有什么有趣的事情发生。 我们将ticket定义为选择器,并再次指向一个单独的模板文件。 现在,与AppComponent相反,我们期望AppComponent创建一个票证标签,并带有一个名为title的属性,并获得要呈现的实体。

然后,构造函数最终将注入TicketService并将其分配给类属性service

import {Component,Input
} from '@angular/core';import {Ticket} from './ticket.entity';
import {TicketService} from './ticket.service';@Component({moduleId: module.id,selector: 'ticket',templateUrl: 'templates/ticket.html',//providers: [TicketService] < -- this would override the parent DI instance
})
export class TicketComponent {@Input('ticket') ticket: Ticket;constructor(private service: TicketService) { }
}

票务服务

缺少的最后一件事是TicketService ,用于将Ajax调用从组件中抽象出来。 如我们所见,它期望注入http服务。 现在,记住最初的boot.ts文件,我们知道提供的实例将是具有boot.ts的实例。 通过利用HTTP服务请求方法(例如postget ,映射结果(在这种情况下将是假答复)并继续使用自定义应用程序逻辑,实际请求保持不变。

import {Ticket} from './ticket.entity';
import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import 'rxjs/add/operator/map';@Injectable()
export class TicketService {tickets: Ticket[] = [];constructor(private http: Http) {}addNewTicket() {var headers = new Headers();headers.append('Content-Type', 'application/json');var newTicket = new Ticket("0", 'New Ticket', 'Nobody', 'Enter ticket description here', 0);this.http.post('http://localhost:8080/api/ticket', JSON.stringify(newTicket), headers).map(res => res.json()).subscribe(data => this.tickets.push(data),err => this.logError(err),() => console.log('Updated Ticket'));}saveTicket(ticket: Ticket) {...}deleteTicket(ticket: Ticket) {...}loadAllTickets() {...}loadTicketById(id) {...}logError(err) {console.error('There was an error: ' + err);}
}

结论

总结一下,我们看到了Angular的依赖项注入如何帮助我们用XHRBackend替换HTTP服务的默认XHRBackend 。 然后,在AppComponent内部,我们创建了虚假数据库,拦截了每个传出的请求,并回复了自定义的虚假响应。 现在,我们获得的好处是完全独立于后端团队,同时具有定义的界面。 现在,一旦生产后端到位,我们所需要做的就是删除依赖项注入覆盖和伪造的后端,我们很高兴继续前进。

本文由Dan Prince和Rabi Kiran进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

From: https://www.sitepoint.com/angular-2-mockbackend/

使用MockBackend开发没有后端的Angular应用相关推荐

  1. 大型医院云HIS系统:采用前后端分离架构,前端由Angular语言、JavaScript开发;后端使用Java语言开发 融合B/S版电子病历系统

    一套医院云his系统源码 采用前后端分离架构,前端由Angular语言.JavaScript开发:后端使用Java语言开发.融合B/S版电子病历系统,支持电子病历四级,HIS与电子病历系统均拥有自主知 ...

  2. 视频教程-VUE前端开发/前后端分离-Java

    VUE前端开发/前后端分离 13年软件开发经验,设计开发30多个大型软件,涉及政府.银行.电信.能源等大型软件项目. 精通J2EE体系架构,熟练使用Struts.Spring.hibernate.ib ...

  3. python开发前端后端区别_一文看懂前端和后端开发

    作为一名开发者,你可能会想:2019 年最好的软件开发技术和编程语言会是什么?它们又是如何被应用在软件开发当中的?如果你在思考这个问题,那就来对地方了.这篇文章将对前端和后端开发技术做一个对比,先从基 ...

  4. python前端开发和后端开发工程师_一文看懂前端和后端开发

    作为一名开发者,你可能会想:2019 年最好的软件开发技术和编程语言会是什么?它们又是如何被应用在软件开发当中的?如果你在思考这个问题,那就来对地方了.这篇文章将对前端和后端开发技术做一个对比,先从基 ...

  5. python适合做后端开发吗-用Python开发app后端有优势吗

    app后端开发学Python. Python的优点: 1.简单易学 Python 编程语言最大的优点之一,是其具有伪代码的特质,它可以让我们在开发 Python 程序时,专注于解决问题,而不是搞明白语 ...

  6. web前端技术分享:前端开发与后端开发的区别是什么?

    相信很多人在技术岗都听到过前端和后端这两个职位,但是大部分人对前端开发与后端开发的区别是什么?并不是很清楚,下面小千就为大家详细的介绍一下两者的区别之处. web前端分享:前端开发与后端开发的区别是什 ...

  7. android开发之后端云bmob的使用

    android开发之后端云bmob的使用 由于开发的应用需要搭建服务器和数据库,所以了解了一下网上的后端云服务,初步了解之后选择了国内的bmob,下面就来简单介绍一下它的使用: 1.注册Bmob帐号 ...

  8. vue调用接口修改密码_vue开发前后端分离前端如何调用后端接口?

    对前后端分离如何调用接口这块感觉一直没怎么弄明白,但又不知如何说明,下面我模拟一个项目说明我的问题. 现在我们有个项目,前端用vue开发,后端php开发,后端测试地址为:localhost:8181, ...

  9. web前端开发和后端开发哪个好?

    这几年互联网行业发展很快,很多人都想在这个行业中寻找到自己合适的岗位,特别是近几年手机普遍的情况下,与此同时,程序员这个职业走进了我们视野,那前端开发和后端开发哪个发展前景更好? 对于想要学习计算机的 ...

最新文章

  1. java 短链跳转原理_给你代码:短链接生成原理
  2. matlab r2007课后答案,《MATLAB R2007基础教程》习题答案.doc
  3. ASP.NET Core Web API + Identity Server 4 + Angular 6 实战小项目视频
  4. Zimbra开发接口文档API下载地址
  5. 新开源!实时语义分割算法Light-Weight RefineNet
  6. Laravel源码解析之Eloquent Model
  7. linux c++应用程序内存高或者占用CPU高的解决方案_20161213
  8. SAP License:SAP顾问行业的生活状态实录,新人值得一看!
  9. Android 内核的开发“顽疾”如何解决?
  10. byte数组转字符串_字符串性能优化不容小觑
  11. 一款支持CHM格式的安卓阅读器:ireader
  12. IP报文分片抓包简析
  13. h3c交换机配置nat_H3C-NAT 命令配置
  14. linux日志关键词高亮,【转载】Linux使用tailf高亮显示关键字
  15. goto解密PHP源码解密程序源码下载
  16. 老宇哥带你玩转ESP32,12篇基础教程已经更新完毕,接下来是进阶教程
  17. 【python】二进制与十进制的转换
  18. C/C++常用的文件函数注释格式
  19. cadence 画电路图时出现绿色的倒三角
  20. 数据库的增删改查加遍历

热门文章

  1. 精益|什么是价值流图分析(VSM)?
  2. Blockchain.com CEO:投资者获利颇丰,以太坊「杀手」们水平有限
  3. 谐波电流注入 为解决汽车NvH而开发,旨在消除转矩谐波,降低运行噪声
  4. 教你设置淘宝店铺收藏代码
  5. 大学计算机基础试题第六章,大学计算机基础第六章.doc
  6. Dart语法学习-数据类型
  7. vue3 父组件获取数据传值给子组件,子组件有值,但是不渲染
  8. 图书馆借阅的e-r图
  9. linux flash 制作工具,AM335x Flash Tool -- UniFlash 烧写工具使用简介及问题解决方案汇总(持续更新中…)...
  10. Akka(16): 持久化模式:PersistentFSM-可以自动修复的状态机器