Для быстрой разработки приложений (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.
Ниже приведены шаги, которые мы предпримем для создания фальшивого бэкэнда.
- Установите модуль angular-in-memory-web-api
- Создайте сервис Angular для хранения нашего поддельного бэкэнда, состоящего из поддельной базы данных и конечных точек.
- Настройте наше приложение на использование поддельного бэкэнда
Установка модуля 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.
Удачного кодирования :)