16 July 2019

Angular: когда надо пилить приложение, а backend еще не готов

JavaScriptProgrammingAngularTypeScript
Если вы занимаетесь frontend разработкой, то наверняка вам знакома следующая мизансцена: сроки по проекту стремительно сжимаются, ваше руководство, или заказчик, а-то и оба вместе желают увидеть глазами работающее приложение прямо сейчас, пусть и с ненастоящими данными. При этом back, возможно, и есть, но именно api слой взаимодействия с front'ом отсутствует от слова совсем.

Итак, недавно я столкнулся с такой ситуацией, и я разрабатываю frontend под angular (сидящие вокруг на стульях люди вяло похлопали, кто-то понимающе кивнул).

Теперь попробую серьезно. С одной стороны ситуация нередкая, и решений может быть выбрано много.

В голову приходило несколько вариантов решения:

  1. Захардкодить данные на уровне компонент
  2. Захардкодить данные на уровне resolver сервисов, приколотить их к нужным роутам
  3. Захардкодить данные на уровне сервисов поставщиков данных
  4. Запилить api, и, согласно оговоренным контрактам, возвращать захардкоженные данные

Но любой из данных вариантов казался неизящным костылем, для каждого из которых находились весомые недостатки.

  1. Первый вариант отпал сразу — абсолютно неудобное непереиспользуемое решение, по мере развития проекта все придется переписывать.
  2. Данное решение могло иметь место, но опять таки структура проекта и логика работы компонент будет переписываться.
  3. Возможный вариант, можно даже возвращать заглушечные данные асинхронно, имитируя обращение к серверу, однако, как и в предыдущих вариантах решения проблемы, наши interceptor'ы (если они есть, а они есть) оставались бы не у дел, и выходит, что имитация работы с бэком становится неполной.
  4. Последний вариант казался весьма приемлемым, лишен проблем, которые есть у предыдущих вариантов, но писать хардкод в backend проект не хотелось просто из гигиенических соображений.

В итоге был выбран иной вариант: поднять отдельный web сервер, который возвращал бы данные согласно роутам и контрактам, и настроить отдельную конфигурацию сборки и исполнения angular. Благо и то и другое оказалось сделать нетрудно.

Для реализации mock сервера был выбран express.

Давайте с него и начнем.

Выбираем место где мы хотим писать код для moсk сервера, например в директория mock-server рядом с проектом ng.

Далее нужно инициализировать проект и добавить пакет с express.

npm init

npm install --save express

Далее добавим код, который нам будет возвращать данные. Создаем файл index.js, берем код из первого попавшегося tutorial.

const express = require("express");
const app = express();

app.get("/url", (req, res, next) => {
  res.json(["Tony", "Lisa", "Michael", "Ginger", "Food"]);
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Запустим сервер

node index.js

Проверим с помощью postman:



Все супер, сервер работает. Теперь давайте настроим один из роутов, так как будто мы запрашиваем данные из настоящего api. Допустим, нам нужен список всех книг, наполним книгами файл books.json

[
  {
    "rn": 0,
    "id": "0",
    "name": "Jungle",
    "type": 0,
    "wells": 10042,
    "default": false,
    "hidden": false,
    "author": "Admin"
  },
  {
    "rn": 1,
    "id": "1",
    "name": "Main",
    "type": 1,
    "wells": 156,
    "default": true,
    "hidden": false,
    "author": "User"
  }
]

И обновим файл приложения:

const express = require("express");
const app = express();

app.get("/api/book/", (req, res, next) => {
  const books = require('./books');
  res.json(books);
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

И проверим:



Отлично.
Теперь приступим к приложению angular.
Добавим в файлы environments/environment*.ts конфигурацию, которая хранит адрес до бэка.
environment.ts:

export const environment = {
  production: false,
  backend: 'http://localhost:5000/'
}

environment.prod.ts

export const environment = {
  production: true,
  backend: 'http://localhost:5000/'
}

В нормальном режиме, и в проде и в режиме разработки мы будем искать .net core api на 5000 порту, что и описано выше. Далее опишем конфигурацию для временного бэка
environment.mock.ts

export const environment = {
  production: false,
  backend: 'http://localhost:3000/'
}

Здесь, как видно мы ищем api на 3000 порту, где мы будем запускать express.

Теперь нам нужен interceptor, который будет направлять обращения к бэку на правильный сервер, в зависимости от конфигурации.

@Injectable()
export class RequestInterceptor implements HttpInterceptor {
  baseUrl: string;

  constructor() {
    this.baseUrl = environment.backend;
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(this.modifyRequest(req));
  }

  private modifyRequest = (req: HttpRequest<any>): HttpRequest<any> => {
    if (req.url.startsWith('api/')) {
      const url = this.baseUrl;
      req = req.clone({
        url: url + req.url
      });
    }
    return req;
  }
}

Осталось настроить новую конфигурацию сборки и запуска приложения для работы с mock сервером.

Для этого нам надо немного подправить angular.json.

В секции вашего проекта, architect/build/configurations добавим новую конфигурацию сборки mock, и опишем замену файлов environment для этой конфигурации. Также для для режима serve создадим конфигурацию mock и укажем нужный вариант сборки

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "your-project": {
      /*****/
      "architect": {
        "build": {
          /*****/
          "configurations": {
            "production": {
              /*****/
            },
            "mock": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.mock.ts"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "your-project:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "your-project:build:production"
            },
            "mock": {
              "browserTarget": "your-project:build:mock"
            }
          }
        }
      },
      /*****/
    }
  }
}

Вот и все, теперь осталось запустить проект в нужной конфигурации


ng serve --configuration=mock

и проверить куда улетают обращения к бэку:



Все отлично.

На самом деле данная конструкция нам еще очень поможет, когда мы будем прикручивать к проекту интеграционные и e2e тесты. Об этом постараюсь написать в ближайшее время.
Tags:программированиеfrontendjavascripttypescriptangular
Hubs: JavaScript Programming Angular TypeScript
+4
6.1k 51
Comments 18
Top of the last 24 hours