angular 手机端开发_使用MockBackend开发没有后端的Angular应用

angular 手机端开发

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

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

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

Diagram showing how front-end tasks depend on the back-end team finishing the API

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

本文已根据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撰写的这篇文章,以了解有关此主题的更多信息。

请注意,建议不要使用手动脚本引用来创建可用于生产环境的应用程序。 您应该使用像npmjspm这样的包管理器。 后一部分与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示例的。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

最后,我们使用名为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被重复用于显示多个票证实体。

Screenshot of the ticket system, highlighting the separate components

应用程序组件配置为与选择器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: /ticket
  if (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: /ticket
  else 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/:id
  else 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 PrinceRabi Kiran进行同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

翻译自: https://www.sitepoint.com/angular-2-mockbackend/

angular 手机端开发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Angular中,您可以使用Angular Material库中的`MatDatepicker`组件来创建一个带有日历和日期范围的日期选择器。以下是一个使用`MatDatepicker`的例子: 1. 首先,您需要在您的应用中安装和导入`@angular/material`库。 2. 在您的组件的HTML模板中添加一个`input`元素,并使用`matInput`指令将其定义为`MatDatepicker`的输入。 ```html <mat-form-field> <input matInput [matDatepicker]="picker" placeholder="选择日期"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker> </mat-form-field> ``` 3. 在您的组件中,您需要定义一个`MatDatepicker`对象,并将其与`input`元素相关联。您还可以使用`min`和`max`属性来定义可选择的日期范围。 ```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-date-picker', templateUrl: './date-picker.component.html', styleUrls: ['./date-picker.component.css'] }) export class DatePickerComponent { minDate = new Date(2020, 0, 1); maxDate = new Date(2020, 11, 31); constructor() { } } ``` 在这个例子中,我们定义了一个最小日期为2020年1月1日,最大日期为2020年12月31日。 4. 最后,您需要在`MatDatepicker`中使用`mat-datepicker`指令来定义日期选择器的样式。 ```css @import '~@angular/material/prebuilt-themes/indigo-pink.css'; mat-form-field { margin-right: 12px; } mat-datepicker-toggle { margin-left: 12px; } mat-datepicker { background-color: #fff; } ``` 在这个例子中,我们使用Angular Material库中提供的一个预定义的主题,`indigo-pink.css`,并定义了一些自定义样式来调整日期选择器的外观和感觉。 这就是如何使用Angular Material库中的`MatDatepicker`组件创建一个带有日历和日期范围的日期选择器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值