1. Введение
Если бы Angular-приложение не умело делать HTTP-запросы, оно было бы похоже на чат-бота, который разговаривает сам с собой. В реальных приложениях мы почти всегда получаем или отправляем данные через интернет: будь то список товаров, регистрация пользователя или удаление котика из базы данных (надеюсь, только из базы!).
В Angular для работы с HTTP-запросами используется сервис HttpClient. Он позволяет отправлять запросы на сервер и получать ответы, а также работать с асинхронными потоками данных через RxJS.
Как подключить HttpClientModule
Перед тем как отправлять запросы, убедитесь, что вы подключили HttpClientModule в главный модуль приложения (обычно это app.module.ts):
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
// ... другие модули
HttpClientModule
],
})
export class AppModule { }
Без этого Angular будет смотреть на ваши попытки отправить запросы как на попытки сварить борщ в чайнике.
Сервис HttpClient: базовые методы
HttpClient предоставляет методы для всех основных HTTP-запросов:
- get<T>(url, options?) — получить данные.
- post<T>(url, body, options?) — создать новый ресурс.
- put<T>(url, body, options?) — заменить существующий ресурс.
- delete<T>(url, options?) — удалить ресурс.
Все эти методы возвращают Observable, а значит, мы можем подписываться на результат, обрабатывать ошибки, использовать RxJS-операторы и получать данные, когда они придут.
2. Практика: создаём сервис для работы с сервером
Допустим, мы разрабатываем приложение для списка задач (todo-list). Давайте создадим сервис, который будет отправлять HTTP-запросы к серверу.
Шаг 1. Генерируем сервис
ng generate service services/todo
или коротко:
ng g s services/todo
Шаг 2. Пример сервиса с CRUD-методами
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Todo {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class TodoService {
private apiUrl = 'https://jsonplaceholder.typicode.com/todos';
constructor(private http: HttpClient) {}
// Получить все задачи
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.apiUrl);
}
// Получить одну задачу по id
getTodo(id: number): Observable<Todo> {
return this.http.get<Todo>(`${this.apiUrl}/${id}`);
}
// Добавить новую задачу
addTodo(todo: Partial<Todo>): Observable<Todo> {
return this.http.post<Todo>(this.apiUrl, todo);
}
// Обновить задачу
updateTodo(todo: Todo): Observable<Todo> {
return this.http.put<Todo>(`${this.apiUrl}/${todo.id}`, todo);
}
// Удалить задачу
deleteTodo(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
Примечание: Мы используем JSONPlaceholder — это бесплатный фейковый REST API для тестирования и обучения.
3. Использование сервиса в компоненте
Давайте теперь используем наш сервис в компоненте. Например, выведем список задач и добавим кнопку для удаления.
Шаг 1. Импортируем сервис
import { Component, OnInit } from '@angular/core';
import { TodoService, Todo } from '../services/todo.service';
@Component({
selector: 'app-todo-list',
template: `
<h2>Список задач</h2>
<ul>
<li *ngFor="let todo of todos">
<input type="checkbox" [checked]="todo.completed" />
{{ todo.title }}
<button (click)="delete(todo.id)">Удалить</button>
</li>
</ul>
<button (click)="addSample()">Добавить задачу</button>
`
})
export class TodoListComponent implements OnInit {
todos: Todo[] = [];
constructor(private todoService: TodoService) {}
ngOnInit() {
this.todoService.getTodos().subscribe((data) => {
this.todos = data.slice(0, 10); // Ограничим до 10 задач для примера
});
}
delete(id: number) {
this.todoService.deleteTodo(id).subscribe(() => {
this.todos = this.todos.filter(todo => todo.id !== id);
});
}
addSample() {
const newTodo: Partial<Todo> = { title: 'Новая задача', completed: false };
this.todoService.addTodo(newTodo).subscribe((created) => {
// Обычно сервер вернёт новый объект с id
this.todos.unshift(created);
});
}
}
4. Разбор каждого метода
GET-запрос: получение данных
this.http.get<Todo[]>(this.apiUrl)
- Используется для загрузки данных с сервера.
- Возвращает Observable, который эмитит массив задач.
- Можно обработать результат через subscribe() или через async pipe в шаблоне.
POST-запрос: создание ресурса
this.http.post<Todo>(this.apiUrl, todo)
- Используется для создания новой задачи.
- Вторым параметром указывается тело запроса (обычно объект).
- Сервер возвращает созданный объект (часто с новым id).
PUT-запрос: обновление ресурса
this.http.put<Todo>(`${this.apiUrl}/${todo.id}`, todo)
- Используется для полного обновления существующего ресурса.
- URL содержит идентификатор задачи.
- В теле запроса — новый объект задачи.
DELETE-запрос: удаление ресурса
this.http.delete<void>(`${this.apiUrl}/${id}`)
- Используется для удаления задачи по id.
- Обычно сервер возвращает пустой ответ (void).
5. Пример: форма для добавления задачи
Давайте добавим форму для создания новой задачи. Для простоты — прямо в шаблон компонента:
<form (ngSubmit)="addTodo()" #todoForm="ngForm">
<input name="title" [(ngModel)]="newTitle" placeholder="Название задачи" required />
<button type="submit">Добавить</button>
</form>
newTitle = '';
addTodo() {
if (!this.newTitle.trim()) return;
const todo: Partial<Todo> = { title: this.newTitle, completed: false };
this.todoService.addTodo(todo).subscribe((created) => {
this.todos.unshift(created);
this.newTitle = '';
});
}
6. Полезные нюансы
Работа с HTTP-опциями: заголовки, параметры
Иногда нужно добавить заголовки (например, для авторизации) или query-параметры. Для этого используется третий параметр методов http.get, http.post и т.д. — объект опций.
import { HttpHeaders, HttpParams } from '@angular/common/http';
const headers = new HttpHeaders().set('Authorization', 'Bearer my-token');
const params = new HttpParams().set('limit', '10');
this.http.get<Todo[]>(this.apiUrl, { headers, params }).subscribe(...);
Обработка ошибок HTTP-запросов
Сеть — штука капризная, серверы иногда падают, а программисты (да-да, мы!) иногда ошибаются в адресах. Поэтому важно уметь обрабатывать ошибки.
В Angular для этого удобно использовать RxJS-оператор catchError:
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';
this.todoService.getTodos().pipe(
catchError(error => {
console.error('Ошибка загрузки задач', error);
return of([]); // Возвращаем пустой массив вместо ошибки
})
).subscribe(data => this.todos = data);
Советы и нюансы
- Не забывайте отписываться! Если вы создаёте подписки вручную (например, в сервисах или компонентах с долгим временем жизни), используйте ngOnDestroy и Subscription.unsubscribe(). Или используйте async pipe — он всё сделает за вас.
- Типизация — ваш друг! Всегда указывайте типы данных (<Todo[]>, <Todo>), чтобы избежать неожиданностей.
- Работайте с сервером асинхронно. Все методы HttpClient возвращают Observable — не пытайтесь получить данные "сразу", используйте подписки.
- Не отправляйте секретные данные в открытом виде! Для авторизации используйте токены, HTTPS и серверные проверки.
7. Типичные ошибки при работе с HTTP-запросами
Ошибка №1: забыли подключить HttpClientModule.
Angular выдаст загадочную ошибку вроде "NullInjectorError: No provider for HttpClient!" — не забудьте добавить модуль в imports.
Ошибка №2: не подписались на Observable.
Методы http.get, http.post и т.д. не выполняются, пока вы не подпишетесь (subscribe()) или не используете async pipe. Observable — это как письмо, которое лежит на почте, пока вы не придёте его забрать.
Ошибка №3: неправильный URL.
Пропущен слэш, опечатка или забыли указать протокол (http://). Сервер не отвечает, а вы ищете баг в Angular.
Ошибка №4: не обработали ошибку запроса.
Если сервер вернёт ошибку (404, 500 и т.д.), подписка получит ошибку, и если её не обработать — приложение может "упасть" или зависнуть.
Ошибка №5: забыли задать тип данных.
Не указали тип <Todo[]>, получили any, и потом долго ловили баги, связанные с неправильной структурой данных.
Ошибка №6: попытка изменить массив напрямую.
Например, после удаления задачи забыли обновить локальный массив задач. Сервер удалил, а на экране всё осталось по-старому.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ