Angular: сервисы, инъекции и получение данных с серверва внутри сервиса
Сервисы – это классы, которые выполняют особые задачи. Например, выполняют запросы на сервер и возвращают данные в виде обсервеблов, содержат сабджекты и методы для работы с ними, а также они способны выполнять передачу данных между несвязанными компонентами. Своего рода играют роль глобального стора, имхо, позволяя обходиться без библиотек типа редакса (в Ангуляре это ngRx и другие менее известные).
1. Создание сервиса
Создаем файл сервиса в папке с компонентом (дочерным или корневым, зависимо от «глобальности» самого сервиса) nameComponent/nameComponent.service.ts:
import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; export interface Movies { page: number; results: any[]; total_result: number; total_pages: number; } export interface NavItems { title: string; icon: string; genreId: number; isActive: boolean; } @Injectable() export class FurnitureService { constructor(private http: HttpClient) {} navItems: NavItems[] = [ { title: 'Action', icon: 'weekend', genreId: 28, isActive: true }, { title: 'Adventure', icon: 'bed', genreId: 12, isActive: false }, { title: 'Comedy', icon: 'table', genreId: 35, isActive: false }, { title: 'Drama', icon: 'event_seat', genreId: 18, isActive: false }, { title: 'Family', icon: 'single_bed', genreId: 10751, isActive: false }, ]; getMoviesList(genreId: number): Observable<Movies> { return this.http.get<Movies>( `https://api.themoviedb.org/3/discover/movie?api_key=2457bcf1079900ec3973765a5a***&with_genres=${genreId}&page=1` ); } }
Данный сервис содержит пукнты меню для навигации (navItems), а также функцию для получения данных с сервера по запросу с компоненты.
Директива @Injectable() нужна для сервисов, которые подключаются в другие сервисы (объявляются в них аналогично, как внутри компонент) для совмесной работы. Но в примере выше эта директива присутствует, так как без нее выдает ошибку, видимо это связано из наблюдателем (Observable) сервера.
2. Подключаем сервер к основному компоненту
Под основным имеется в виду корневой (app.component.spec.ts) или какой-то другой важный, у которого обычно еще есть дочерные. В результате потом соединять с сервисом можно дочерные компоненты. Но есть важные нюансы, которые описаны будут ниже.
nameComponent.component.ts:
import { Component, OnInit } from '@angular/core'; import { FurnitureService, Movies } from './furniture.service'; <------- здесь @Component({ selector: 'app-furniture', templateUrl: './furniture.component.html', styleUrls: ['./furniture.component.scss'], providers: [FurnitureService], // <------- здесь }) export class FurnitureComponent implements OnInit { activeNavItemIndex: number = 0; genreId = 28; moviesList: Movies = { page: 0, results: [], total_result: 0, total_pages: 0, }; navItems = this.furnitureService.navItems; constructor(private furnitureService: FurnitureService) {} <------- здесь getFurnitureList(genreId: number = this.genreId): void { this.furnitureService .getMoviesList(genreId) .subscribe((movies: Movies) => (this.moviesList = movies)); } ngOnInit(): void { this.getFurnitureList(); } changeNavItem(idx: number) { this.navItems[this.activeNavItemIndex].isActive = false; this.activeNavItemIndex = idx; this.navItems[idx].isActive = true; this.genreId = this.navItems[idx].genreId; this.getFurnitureList(); } }
Стрелочками в комментариях в блоке кода показаны 3 места, где надо упомянуть сервер. Теперь к нему можно обращаться, как к классу, получая с него данные и используюя методы для изменения этих данных.
А в функции getFurnitureList видно, как можно брать данные со слушателя. Если бы мы данные с сервера получали прямо здесь, то проблем таких бы не было и код получения данных выглядел бы проще:
getMoviesList(): void { this.http .get<Movies>( `https://api.themoviedb.org/3/discover/movie?api_key=2457bcf1079900ec3973765a5a***&with_genres=${this.genreId}&page=1` ) .subscribe((response) => { this.moviesList = response; }); }
3. Подключение в других компонентах (дочерных к этому)
В дочерных компонентах подключается сервис практически таким же образом, за исключением того, что его не надо добавлять в providers (без особой необходимости)! Дело в том, что тогда при выполнении методов для изменения массива, отображающий элементы из этого массива компонент не будет обновляться! А если не добавлять в providers, то такой проблемы не будет.
Связь двух любых компонент, подключенных к одному сервису
Все будет работать хорошо! Но более правильный способ для такой связки – Observable!!!
Компоненты могут создавать события и подписываться на них, если они подключены к одному сервису.
1. Создаем генератор событий в самом сервисе по аналогии с опрокидыванием данных вверх, но без прописывания в начале @Output():
statusUpdated = new EventEmitter<string>();
2. В компоненте, которая будет генерировать событие (отправлять данные) вызываем этот созданный экземпляр класса и присваем передаваемое значение свойству emit:
//в provide класс accountsService.statusUpdated можно не добавлять, если выше он уже добавлен this.accountsService.statusUpdated.emit(status)
3. Теперь подписываемся на изменения статуса в другой компоненте, где тоже импортирован этот класс, и назначем функцию для обработки полученных данных:
//в provide класс accountsService.statusUpdated можно не добавлять, если выше он уже добавлен this.accountService.statusUpdated.subscribe( (status: string) => alert('New Status ' + status) );
Похожую связку можно сделать и при помощи Sybject.