Angular + Mock API: работа с сервисами и HTTP Client

← Вернуться к статьям

Введение

Начинаете новый Angular проект, но backend еще не готов? Не хотите ждать неделями, пока API будет разработан? В этом туториале вы создадите полноценное Todo приложение на Angular с API интеграцией за 15 минут — без единой строки backend кода.

✅ Что вы получите в конце:

💡 Для кого этот туториал:

📋 План действий (15 минут)

Что мы создадим 🎯

Мы создадим классическое Todo приложение с полноценным API. Это идеальный проект для изучения, так как он включает все основные операции:

Функция API Endpoint Метод
Получить все задачи GET /todos Read
Создать задачу POST /todos Create
Обновить задачу PUT /todos/:id Update
Удалить задачу DELETE /todos/:id Delete

Технологический стек

Шаг 1: Setup проекта (2 минуты)

1 Установите Angular CLI (если еще не установлен)

# Глобальная установка Angular CLI
npm install -g @angular/cli

# Проверьте версию
ng version

2 Создайте Angular приложение

# Создаем новый проект
ng new todo-app

# Выберите:
# - Yes для routing (если хотите добавить позже)
# - CSS для стилей (или SCSS, если предпочитаете)
# - No для Standalone компонентов (используем модули)

# Переходим в директорию
cd todo-app

# Запускаем dev сервер (опционально, для проверки)
ng serve

3 Структура проекта

todo-app/
├── src/
│   ├── app/
│   │   ├── services/
│   │   │   └── todo.service.ts      # Angular Service для API
│   │   ├── models/
│   │   │   └── todo.model.ts        # TypeScript интерфейсы
│   │   ├── components/
│   │   │   ├── todo-list/
│   │   │   │   └── todo-list.component.ts
│   │   │   ├── todo-item/
│   │   │   │   └── todo-item.component.ts
│   │   │   └── add-todo/
│   │   │       └── add-todo.component.ts
│   │   ├── interceptors/
│   │   │   └── http-error.interceptor.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts            # Главный модуль
│   ├── environments/
│   │   ├── environment.ts           # Production
│   │   └── environment.development.ts
│   ├── main.ts                      # Entry point
│   └── index.html
├── angular.json
└── package.json

Шаг 2: Создание Mock API (3 минуты)

1 Зарегистрируйтесь на LightBox API

Перейдите на lightboxapi.ru и создайте бесплатный аккаунт.

2 Создайте workspace

В dashboard создайте новый workspace с именем todo-app.

3 Настройте endpoints

Endpoint 1: GET /todos
{
  "todos": [
    {
      "id": "1",
      "title": "Изучить Angular Services",
      "completed": false,
      "createdAt": "2025-10-23T10:00:00Z"
    },
    {
      "id": "2",
      "title": "Интегрировать Mock API",
      "completed": true,
      "createdAt": "2025-10-23T09:00:00Z"
    }
  ]
}
Endpoint 2: POST /todos
{
  "id": "{randomUUID}",
  "title": "{body.title}",
  "completed": false,
  "createdAt": "{$timestamp}"
}
Endpoint 3: PUT /todos/:id
{
  "id": "{params.id}",
  "title": "{body.title}",
  "completed": "{body.completed}",
  "createdAt": "{existing.createdAt}"
}
Endpoint 4: DELETE /todos/:id
{
  "success": true,
  "message": "Todo deleted"
}

Скопируйте URL вашего Mock API, например: https://lightboxapi.ru/your-workspace

Шаг 3: Настройка HttpClient (1 минута)

HttpClient — это встроенный Angular сервис для работы с HTTP запросами. Он использует RxJS Observables для асинхронной обработки.

1 Импортируйте HttpClientModule

Файл: src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { TodoListComponent } from './components/todo-list/todo-list.component';
import { TodoItemComponent } from './components/todo-item/todo-item.component';
import { AddTodoComponent } from './components/add-todo/add-todo.component';

@NgModule({
  declarations: [
    AppComponent,
    TodoListComponent,
    TodoItemComponent,
    AddTodoComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule  // Добавляем HttpClient
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

2 Настройте environment variables

Файл: src/environments/environment.ts

export const environment = {
  production: false,
  apiUrl: 'https://lightboxapi.ru/your-workspace'
};

Файл: src/environments/environment.development.ts

export const environment = {
  production: false,
  apiUrl: 'https://lightboxapi.ru/your-workspace'
};

Шаг 4: Создание Angular Service (4 минуты)

Angular Services — это классы, которые содержат бизнес-логику и работу с API. Они инжектируются в компоненты через Dependency Injection.

1 Создайте TypeScript модели

Файл: src/app/models/todo.model.ts

export interface Todo {
  id: string;
  title: string;
  completed: boolean;
  createdAt: string;
}

export interface CreateTodoDto {
  title: string;
}

export interface UpdateTodoDto {
  title?: string;
  completed?: boolean;
}

2 Создайте Angular Service

# Генерируем service через Angular CLI
ng generate service services/todo

Файл: src/app/services/todo.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { Todo, CreateTodoDto, UpdateTodoDto } from '../models/todo.model';

@Injectable({
  providedIn: 'root'  // Сервис доступен во всем приложении
})
export class TodoService {
  private apiUrl = `${environment.apiUrl}/todos`;

  constructor(private http: HttpClient) {}

  // Получить все задачи
  getAll(): Observable {
    return this.http.get<{ todos: Todo[] }>(this.apiUrl).pipe(
      map(response => response.todos),
      catchError(this.handleError)
    );
  }

  // Создать задачу
  create(data: CreateTodoDto): Observable {
    return this.http.post(this.apiUrl, data).pipe(
      catchError(this.handleError)
    );
  }

  // Обновить задачу
  update(id: string, data: UpdateTodoDto): Observable {
    return this.http.put(`${this.apiUrl}/${id}`, data).pipe(
      catchError(this.handleError)
    );
  }

  // Удалить задачу
  delete(id: string): Observable {
    return this.http.delete(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError)
    );
  }

  // Переключить статус задачи
  toggle(id: string, completed: boolean): Observable {
    return this.update(id, { completed });
  }

  // Обработка ошибок
  private handleError(error: HttpErrorResponse): Observable {
    let errorMessage = 'Произошла ошибка';

    if (error.error instanceof ErrorEvent) {
      // Клиентская ошибка
      errorMessage = `Ошибка: ${error.error.message}`;
    } else {
      // Серверная ошибка
      errorMessage = `Код ошибки: ${error.status}, Сообщение: ${error.message}`;
    }

    console.error(errorMessage);
    return throwError(() => new Error(errorMessage));
  }
}

💡 Ключевые концепции Angular Service:

Шаг 5: Создание компонентов (3 минуты)

1 Главный компонент AppComponent

Файл: src/app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { TodoService } from './services/todo.service';
import { Todo, CreateTodoDto } from './models/todo.model';
                    Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
)

export class AppComponent implements OnInit {
  todos: Todo[] = [];
  loading = false;
  error: string | null = null;

  constructor(private todoService: TodoService) {}

  ngOnInit(): void {
    this.loadTodos();
  }

  loadTodos(): void {
    this.loading = true;
    this.error = null;

    this.todoService.getAll().subscribe({
      next: (todos) => {
        this.todos = todos;
        this.loading = false;
      },
      error: (err) => {
        this.error = err.message;
        this.loading = false;
      }
    });
  }

  onTodoAdded(title: string): void {
    const newTodo: CreateTodoDto = { title };

    this.todoService.create(newTodo).subscribe({
      next: (todo) => {
        this.todos.push(todo);
      },
      error: (err) => {
        this.error = err.message;
      }
    });
  }

  onTodoToggled(id: string): void {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      this.todoService.toggle(id, !todo.completed).subscribe({
        next: (updated) => {
          const index = this.todos.findIndex(t => t.id === id);
          if (index !== -1) {
            this.todos[index] = updated;
          }
        },
        error: (err) => {
          this.error = err.message;
        }
      });
    }
  }

  onTodoDeleted(id: string): void {
    this.todoService.delete(id).subscribe({
      next: () => {
        this.todos = this.todos.filter(t => t.id !== id);
      },
      error: (err) => {
        this.error = err.message;
      }
    });
  }

  get todosCount(): number {
    return this.todos.length;
  }

  get completedCount(): number {
    return this.todos.filter(t => t.completed).length;
  }
}

Файл: src/app/app.component.html

<div class="app">
  <h1>Angular Todo App</h1>

  <app-add-todo (todoAdded)="onTodoAdded($event)"></app-add-todo>

  <div *ngIf="loading">Загрузка...</div>
  <div *ngIf="error" class="error">{ error }</div>

  <app-todo-list
    [todos]="todos"
    (todoToggled)="onTodoToggled($event)"
    (todoDeleted)="onTodoDeleted($event)"
  ></app-todo-list>

  <div class="stats">
    Всего: { todosCount }, Выполнено: { completedCount }
  </div>
</div>

2 Компонент TodoList

# Генерируем компонент
ng generate component components/todo-list

Файл: src/app/components/todo-list/todo-list.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Todo } from '../../models/todo.model';

Component(
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
)
export class TodoListComponent {
  @Input() todos: Todo[] = [];
  @Output() todoToggled = new EventEmitter<string>();
  @Output() todoDeleted = new EventEmitter<string>();

  onToggle(id: string): void {
    this.todoToggled.emit(id);
  }

  onDelete(id: string): void {
    this.todoDeleted.emit(id);
  }
}

Файл: src/app/components/todo-list/todo-list.component.html

<ul class="todo-list">
  <app-todo-item
    *ngFor="let todo of todos"
    [todo]="todo"
    (toggled)="onToggle($event)"
    (deleted)="onDelete($event)"
  ></app-todo-item>
</ul>

3 Компонент AddTodo

ng generate component components/add-todo

Файл: src/app/components/add-todo/add-todo.component.ts

import { Component, Output, EventEmitter } from '@angular/core';

Component(
  selector: 'app-add-todo',
  templateUrl: './add-todo.component.html',
  styleUrls: ['./add-todo.component.css']
)
export class AddTodoComponent {
  title = '';
  @Output() todoAdded = new EventEmitter<string>();

  onSubmit(): void {
    if (this.title.trim()) {
      this.todoAdded.emit(this.title);
      this.title = '';
    }
  }
}

Файл: src/app/components/add-todo/add-todo.component.html

<form (ngSubmit)="onSubmit()" #todoForm="ngForm">
  <input
    [(ngModel)]="title"
    name="title"
    placeholder="Добавить задачу..."
    required
  />
  <button type="submit" [disabled]="!todoForm.valid">Добавить</button>
</form>

Важно: Добавьте FormsModule в app.module.ts для работы ngModel:

import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    // ...
    FormsModule
  ],
  // ...
})

Шаг 6: Error Handling и Interceptors (1 минута)

HTTP Interceptors позволяют перехватывать и модифицировать HTTP запросы и ответы. Это идеально для обработки ошибок, добавления заголовков, логирования.

1 Создайте HTTP Error Interceptor

ng generate interceptor interceptors/http-error

Файл: src/app/interceptors/http-error.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        let errorMessage = 'Произошла ошибка';

        if (error.error instanceof ErrorEvent) {
          errorMessage = `Ошибка: ${error.error.message}`;
        } else {
          errorMessage = `Ошибка ${error.status}: ${error.message}`;
        }

        console.error('HTTP Error:', errorMessage);
        return throwError(() => new Error(errorMessage));
      })
    );
  }
}

2 Зарегистрируйте Interceptor

Файл: src/app/app.module.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './interceptors/http-error.interceptor';

@NgModule({
  // ...
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true
    }
  ],
  // ...
})

Шаг 7: Deploy на Vercel (1 минута)

1 Соберите проект для production

# Сборка проекта
ng build --configuration production

# Проверьте, что dist/ папка создана
ls dist/todo-app/

2 Настройте Vercel

Создайте файл vercel.json в корне проекта:

{
  "buildCommand": "ng build --configuration production",
  "outputDirectory": "dist/todo-app",
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ],
  "env": {
    "API_URL": "https://lightboxapi.ru/your-workspace"
  }
}

3 Deploy на Vercel

  1. Установите Vercel CLI: npm i -g vercel
  2. Выполните: vercel
  3. Следуйте инструкциям
  4. Или используйте GitHub интеграцию Vercel для автоматического деплоя

Результат 🎉

Поздравляем! Вы создали полноценное Angular приложение с API интеграцией за 15 минут. Приложение работает с реальными HTTP запросами к Mock API и готово к работе.

Что мы изучили:

Продвинутые техники 🚀

1. RxJS операторы для оптимизации

import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

// Поиск с debounce
search(term: string): Observable {
  return this.http.get<Todo[]>(`${this.apiUrl}?search=${term}`).pipe(
    debounceTime(300),
    distinctUntilChanged()
  );
}

2. Кэширование с RxJS

import { shareReplay } from 'rxjs/operators';

private todosCache$?: Observable;

getAll(): Observable {
  if (!this.todosCache$) {
    this.todosCache$ = this.http.get<{ todos: Todo[] }>(this.apiUrl).pipe(
      map(response => response.todos),
      shareReplay(1)  // Кэшируем результат
    );
  }
  return this.todosCache$;
}

3. Loading состояние с RxJS

import { BehaviorSubject } from 'rxjs';

private loading$ = new BehaviorSubject<boolean>(false);

getLoading(): Observable<boolean> {
  return this.loading$.asObservable();
}

getAll(): Observable {
  this.loading$.next(true);
  return this.http.get<{ todos: Todo[] }>(this.apiUrl).pipe(
    map(response => response.todos),
    finalize(() => this.loading$.next(false))
  );
}

Следующие шаги 🚀

Создайте Mock API за 2 минуты

Не ждите backend — начните разработку Angular приложения прямо сейчас с LightBox API. Бесплатный старт для всех разработчиков.

Попробовать бесплатно →
← Вернуться к статьям