Для быстрой разработки приложений (RAD) команды разработчиков (или отдельные разработчики) должны применять стратегии, ускоряющие разработку. Одна из таких стратегий - позволить разрабатывать разные части приложения параллельно. Однако такие хорошие планы часто сталкиваются с ситуацией курицы и яйца, когда (например) части внешнего интерфейса приложения нуждаются в данных от серверной части, и в том же духе части серверной части должны понимать, что интерфейс будет запрашивать. .

Таким образом, в этой статье я научу вас, как решить эту проблему при разработке вашего интерфейса с помощью Angular (2+) Framework (извиняюсь перед пользователями других фреймворков, я хотел бы звучать более искренне).

Цель
Создание поддельной серверной части для ответа на HTTP-вызовы.

Предварительные требования
1. Базовые знания Angular 2+
2. Знакомство с JSON
3. Выполнение HTTP-вызовов в Angular

Демо-проект
Мы собираемся создать приложение TODO List (я знаю, верно? другое приложение TODO list). Почему ты спрашиваешь? Потому что мы собираемся подделывать только операции Create, Read, Update и Delete (CRUD), и на протяжении многих лет приложение TODO отлично зарекомендовало себя в обучении разработчиков концепциям, которые вращаются вокруг CRUD.
Чтобы не беспокоить вас с учетом аспекта дизайна интерфейса этой демонстрации, я создал папку под названием mock-backend-start внутри репозитория github для этого проекта, который является отправной точкой проекта. Вы можете взять это и продолжить урок.
Просто запустите npm install в корне проекта и после того, как все модули узлов будут установлены, запустите ng serve также из корня приложения, теперь вы должны увидеть этот экран когда вы просматриваете свое приложение в браузере:

Приложение TODO

Как было сказано ранее, мы собираемся создать приложение TODO List, которое сможет создавать, отображать, редактировать и удалять задачи. Каждая задача будет иметь идентификационный номер (Id), чтобы сделать ее уникальной, а также описание задачи. Эти два свойства составляют модель наших задач.

Теперь, когда мы знаем структуру нашей модели, давайте напишем службу, которая будет вызывать наш бэкэнд для выполнения следующих действий:
* Получить все задачи
* Создать задачу
* Редактировать задачу
* Удалить задачу

Просто создайте файл с именем app.service.ts внутри вашей src/app папки.

Теперь вставьте код ниже:


import { Injectable } from “@angular/core”;
import { Http } from “@angular/http”;
import “rxjs/add/operator/map”;
@Injectable()
export class AppService {
 base_url: string = “http://mybackend.com/api/";
 tasks_endpoint = “tasks”;
 constructor(private http: Http) {}
//Gets all tasks
 getTasks() {
 return this.http
 .get(this.base_url + this.tasks_endpoint)
 .map(res => res.json());
 } //getTasks
//Creates a task
 createTask(task) {
 return this.http
 .post(this.base_url + this.tasks_endpoint, task)
 .map(res => res.json());
 } //createTask
//Updates a Task
 updateTask(update) {
 return this.http
 .put(this.base_url + this.tasks_endpoint, update)
 .map(res => res.json());
 } //updateTask
//Deletes a Task
 deleteTask(taskId) {
 return this.http
 .delete(`${this.base_url + this.tasks_endpoint}/${taskId}`)
 .map(res => res.json());
 } //deleteTask
}

Теперь давайте пройдемся по только что созданному сервису.
Класс сервиса состоит из base_url, который указывает на корневую конечную точку нашего фактического API. Затем у нас есть tasks_endpoint property, который является конечной точкой, которая сопоставляется с нашей моделью задач на нашем сервере в соответствии со стандартами RESTful.

Метод getTasks
Эта функция возвращает все задачи, существующие в нашем бэкэнде, отправляя запрос GET в нашу tasks endpoint

Метод createTask
Эта функция принимает объект задачи в качестве аргумента и отправляет запрос POST в tasks endpoint, передавая объект задачи в качестве данных он затем возвращает вновь созданную задачу, если операция прошла успешно.

Метод updateTask
Эта функция получает объект задачи, содержащий id в качестве аргумента, и отправляет серверу запрос PUT. Обновленная задача возвращается, если операция прошла успешно

Метод deleteTask
Эта функция принимает id задачи, которую нужно удалить, в качестве аргумента и передает ее в URL-адресе, используемом в отправка на сервер запроса DELETE.

Обратите внимание, что эта служба написана для связи с реальной серверной частью, она не связана с какой-либо логикой, которая связывает HTTP-вызовы с поддельным сервером.

Пора создать фальшивый бэкэнд

Мы собираемся реализовать поддельный бэкэнд в коде с помощью модуля angular-in-memory-web-api.

Ниже приведены шаги, которые мы предпримем для создания фальшивого бэкэнда.

  1. Установите модуль angular-in-memory-web-api
  2. Создайте сервис Angular для хранения нашего поддельного бэкэнда, состоящего из поддельной базы данных и конечных точек.
  3. Настройте наше приложение на использование поддельного бэкэнда

Установка модуля angular-in-memory-web-api
Чтобы установить этот модуль, просто выполните следующую команду в командной строке в корне вашего проекта :

npm install --save angular-in-memory-web-api

Создание нашей поддельной серверной части службы
В папке src/app создайте новый служебный файл Angular с именем fake-backend.service.ts (вы можете дать этому файлу любое имя).

Импортируйте InMemoryDbService из модуля angular-in-memory-web-api

import {InMemoryDbService} from “angular-in-memory-web-api”

Создайте класс обслуживания, этот класс будет реализовывать InMemoryDbService.

export class FakeBackendService implements InMemoryDbService{
 
}

InMemoryDbService implementation требует, чтобы наш сервис реализовал createDb method. Этот метод создает хэш «базы данных», ключами которого являются имена коллекций, а значениями - массивы объектов коллекции, которые необходимо вернуть или обновить.

export class FakeBackendService implements InMemoryDbService{
 createDb(){
 
 }
}

Таким образом, нам нужно создать коллекцию для каждой модели, которую мы подделываем. Думайте о коллекциях, которые мы создаем, как о таблицах в модели реляционной базы данных. Коллекции, которые мы создаем здесь, будут представлять начальное содержимое таблицы, поэтому они могут быть либо пустыми, либо содержать некоторые данные.

Обратите внимание, что angular-in-memory-web-api требует, чтобы каждый объект в коллекции имел id property.

Здесь мы только высмеиваем tasks , поэтому нам нужно создать только одну коллекцию.
Ниже мы создаем tasks collection и инициализируем ее двумя задачами.

export class FakeBackendService implements InMemoryDbService {
 createDb() {
 let tasks = [
 {
 id: 1,
 description: “Buy Groceries”
 },
 {
 id: 2,
 description: “Paint the garage”
 }
 ];
 }
}

Теперь нам нужно вернуть хэш (объект), который будет представлять нашу базу данных. Ключи этого хэша - это имена коллекций (они также представляют конечную точку для вашей модели по стандартам RESTful)

Возвращаясь к файлу our app.service.ts, свойству tasks_endpoint, которое представляет конечную точку для наших задач, присваивается значение tasks.

Таким образом, ключ для нашей коллекции задач в хэше будет tasks.

Достаточно сказано, давайте вернем хэш, который будет представлять нашу базу данных из функции createDb .

 return {
   tasks: tasks
 };

Ниже приведен полный код нашей поддельной серверной службы.

import { InMemoryDbService } from “angular-in-memory-web-api”;
export class FakeBackendService implements InMemoryDbService {
 createDb() {
  let tasks = [
   {
     id: 1,
     description: “Buy Groceries”
   },
   {
     id: 2,
     description: “Paint the garage”
   }
  ];
  return {
   tasks: tasks
  };
 }
}

Настройте приложение для использования фальшивого бэкэнда
Затем мы настраиваем наше приложение так, чтобы наши (подлинные) HTTP-запросы перехватывались и на них отвечал наш фальшивый бэкэнд. .

Для этого просто зайдите в файл app.module.ts, импортируйте InMemoryWebApiModule from angular-in-memory-web-api, а также импортируйте нашу поддельную внутреннюю службу.

import { InMemoryWebApiModule } from “angular-in-memory-web-api”;
import { FakeBackendService } from “./fake-backend.service”;

Конфигурация выполняется в массиве @NgModule import путем включения InMemoryWebApiModule в массив, вызова его forRoot method и передачи нашего FakeBackendService в качестве единственного аргумента.

imports: [
 BrowserModule,
 FormsModule,
 InMemoryWebApiModule.forRoot(FakeBackendService)
]

Большой! Мы закончили настройку нашего поддельного серверного модуля для ответа на наши HTTP-вызовы. Не забудьте указать свою AppService service в основном модуле приложения в app.module.ts.
Ниже приведен полный код для app.module.ts:

import { AppService } from “./app.service”;
import { BrowserModule } from “@angular/platform-browser”;
import { NgModule } from “@angular/core”;
import { FormsModule } from “@angular/forms”;
import { HttpModule } from “@angular/http”;
import { AppComponent } from “./app.component”;
import { InMemoryWebApiModule } from “angular-in-memory-web-api”;
import { FakeBackendService } from “./fake-backend.service”;
@NgModule({
 declarations: [AppComponent],
 imports: [
 BrowserModule,
 FormsModule,
 HttpModule,
 InMemoryWebApiModule.forRoot(FakeBackendService)
 ],
 providers: [AppService],
 bootstrap: [AppComponent]
})
export class AppModule {}

Поскольку в этом руководстве рассказывается о создании поддельного бэкэнда, а не о том, как создать приложение TODO List, ниже приведен код для app.component.ts и app.component.html. .

app.component.ts

import { AppService } from “./app.service”;
import { Component, OnInit } from “@angular/core”;
@Component({
 selector: “app-root”,
 templateUrl: “./app.component.html”,
 styleUrls: [“./app.component.css”]
})
export class AppComponent implements OnInit {
 tasks: any[] = [];
 myTask: string;
 taskEdit: string;
 editMode: boolean = false;
 loading: boolean = false;
 constructor(private appservice: AppService) {}
ngOnInit() {
 this.getAllTasks();
 } //ngOnInit
getAllTasks() {
 this.appservice.getTasks().subscribe(data => {
 this.tasks = data;
 });
 } //getAllTasks
create() {
 this.loading = true;
 const postData = {
 description: this.myTask
 };
this.appservice.createTask(postData).subscribe(data => {
 this.loading = false;
 this.getAllTasks();
 this.myTask = “”;
 });
 } //create
edit(task) {
 this.taskEdit = Object.assign({}, task);
 task.editing = true;
 this.editMode = true;
 } //edit
saveEdit(task) {
 this.appservice.updateTask(this.taskEdit).subscribe(data => {
 //task = data;
 this.getAllTasks();
 task.editing = false;
 this.editMode = false;
 });
 } //saveEdit
delete(task) {
 console.log(“Delete”);
 this.appservice.deleteTask(task.id).subscribe(data => {
 this.getAllTasks();
 });
 } //delete
}

app.component.html

<div class=”row”>
 <header class=”col-md-12">
 <nav class=”navbar navbar-inverse”>
 <div class=”container-fluid”>
 <! — Brand and toggle get grouped for better mobile display →
 <div class=”navbar-header”>
 <button type=”button” class=”navbar-toggle collapsed” data-toggle=”collapse” data-target=”#bs-example-navbar-collapse-1"
 aria-expanded=”false”>
 <span class=”sr-only”>Toggle navigation</span>
 <span class=”icon-bar”></span>
 <span class=”icon-bar”></span>
 <span class=”icon-bar”></span>
 </button>
 <a class=”navbar-brand” href=”#”>Angular Mock Back End</a>
 </div>
<! — Collect the nav links, forms, and other content for toggling →
 <div class=”collapse navbar-collapse” id=”bs-example-navbar-collapse-1">
</div>
 <! — /.navbar-collapse →
 </div>
 <! — /.container-fluid →
 </nav>
 </header>
</div>
<section>
 <div class=”row”>
 <div class=”col-md-6">
 <form #f=”ngForm”>
 <div class=”form-group”>
 <input [(ngModel)]=”myTask” name=”myTask” type=”text” class=”form-control” placeholder=”Enter Task” required>
 </div>
<p *ngIf=”loading”>
 <i class=”fa fa-spinner fa-spin”></i>
 </p>
<div class=”form-group”>
 <button class=”btn btn-primary” [disabled]=”!f.valid” (click)=”create()”>
 Save Task
 </button>
 </div>
 </form>
 </div>
 <div class=”col-md-6">
 <table class=”table table-hover”>
 <tr>
 <th>#</th>
 <th>Task</th>
 <th></th>
 </tr>
 <tr *ngFor=”let task of tasks”>
 <td>{{task.id}}</td>
 <td>
 <span *ngIf=”!task.editing”>{{task.description}}</span>
 <div *ngIf=”task.editing”>
 <div class=”form-group”>
 <input type=”text” [(ngModel)]=”taskEdit.description” />
 </div>
 <div class=”form-group”>
 <button class=”btn btn-warning” (click)=”saveEdit(task)”>
 Save
 </button>
 </div>
 </div>
</td>
 <td>
 <button class=”btn btn-success” [disabled]=”editMode” (click)=”edit(task)”>
 <i class=”fa fa-edit”></i>
 </button>
<button class=”btn btn-danger” [disabled]=”editMode” (click)=”delete(task)”>
 <i class=”fa fa-remove”></i>
 </button>
 </td>
 </tr>
 </table>
 </div>
 </div>
</section>

Теперь запустите приложение (если оно еще не запущено). Вы увидите, как на экране загружаются задачи, которые мы инициализировали в поддельной серверной службе (файл fake-backend.service.ts).
Теперь выполните следующие операции.

1. Создание задачи
2. Редактирование задачи
3. Удаление задачи

Посмотрите, как приложение реагирует на все HTTP-вызовы, фактически не имея серверной части.

В классе AppComponent class метод getAllTasks вызывается после каждого запроса для новой загрузки задач. Это сделано намеренно, поскольку доказывает, что новая задача добавляется в коллекцию после создания задачи. Это также доказывает, что задачи фактически редактируются при операции редактирования и удаляются при операции удаления.

Важно отметить, что все данные и изменения данных хранятся в памяти, поэтому при каждой перезагрузке страницы изменения, внесенные вами в инициализированные данные, не сохраняются.

Итак, у вас есть рабочее приложение Angular, работающее с поддельным бэкэндом.

Полный рабочий код получить здесь:

Переход на настоящий бэкэнд

Когда ваш реальный бэкэнд готов, переключиться на него так же просто, как удалить строку конфигурации в файле app.module.ts.

Затем вы можете удалить файл fake-backend.service.ts, он вам больше не понадобится.

Заворачивать

Вы можете узнать больше о angular-in-memory-web-api здесь

Этот демонстрационный проект следует строгим правилам RESTful при работе с нашим поддельным сервером. Однако в реальных проектах мы часто отклоняемся от этих правил REST в некоторых особых случаях. Таким образом, в следующей статье я объясню, как использовать поддельный бэкэнд для конечных точек, отличных от RESTful.

Удачного кодирования :)