Добавить сервисы¶
28.02.2022
Компонент HeroesComponent
в Туре Героев получает и отображает поддельные данные.
Рефакторинг HeroesComponent
сосредоточен на поддержке представления и облегчении юнит-тестирования с помощью имитационного сервиса.
Пример приложения, которое описывается на этой странице, см.:
Почему сервисы¶
Компоненты не должны получать или сохранять данные напрямую и уж точно не должны представлять заведомо ложные данные. Они должны сосредоточиться на представлении данных и делегировать доступ к ним сервису.
В этом руководстве создается HeroService
, который все классы приложения могут использовать для получения героев. Вместо того чтобы создавать сервис с помощью ключевого слова new
, используйте dependency injection, который поддерживает Angular, чтобы внедрить его в конструктор HeroesComponent
.
Сервисы — это отличный способ обмена информацией между классами, которые не знают друг друга. Создайте следующий MessageService
и инжектируйте его в эти два места.
-
Inject в
HeroService
, который использует сервис для отправки сообщения -
Вставьте в
MessagesComponent
, который отображает это сообщение, а также отображает ID, когда пользователь нажимает на героя.
Создайте HeroService
¶
Запустите ng generate
для создания сервиса под названием hero
.
1 |
|
Команда генерирует скелет класса HeroService
в src/app/hero.service.ts
следующим образом:
1 2 3 4 5 6 7 8 |
|
@Injectable()
сервисы¶
Обратите внимание, что новый сервис импортирует символ Angular Injectable
и аннотирует класс декоратором @Injectable()
. Это отмечает класс как участвующий в системе инъекции зависимостей. Класс HeroService
будет предоставлять инжектируемый сервис, и он также может иметь свои собственные инжектируемые зависимости.
Пока у него нет никаких зависимостей.
Декоратор @Injectable()
принимает объект метаданных для сервиса, так же, как декоратор @Component()
для ваших классов компонентов.
Получение данных героя¶
Сервис HeroService
может получать данные героя откуда угодно, например, из веб-сервиса, локального хранилища или имитируемого источника данных.
Удаление доступа к данным из компонентов означает, что вы можете в любой момент изменить свое мнение о реализации, не трогая никаких компонентов. Они не знают, как работает сервис.
Реализация в этом учебнике продолжает предоставлять макет героев.
Импортируйте Hero
и HEROES
.
1 2 |
|
Добавьте метод getHeroes
, чтобы вернуть макет героев.
1 2 3 |
|
Предоставьте HeroService
¶
Вы должны сделать HeroService
доступным для системы инъекции зависимостей, прежде чем Angular сможет инжектировать его в HeroesComponent
, зарегистрировав провайдера. Провайдер — это то, что может создавать или предоставлять услугу. В данном случае он инстанцирует класс HeroService
для предоставления услуги.
Чтобы убедиться, что HeroService
может предоставить эту услугу, зарегистрируйте его с помощью инжектора. Инжектор — это объект, который выбирает и внедряет провайдера там, где это требуется приложению.
По умолчанию ng generate service
регистрирует провайдера с корневым инжектором для вашего сервиса, включая метаданные провайдера, это providedIn: 'root'
в декораторе @Injectable()
.
1 2 3 |
|
Когда вы предоставляете сервис на корневом уровне, Angular создает единственный, общий экземпляр HeroService
и внедряет его в любой класс, который его запрашивает. Регистрация провайдера в метаданных @Injectable
также позволяет Angular оптимизировать приложение, удаляя сервис, если он не используется.
Чтобы узнать больше о провайдерах, смотрите раздел Провайдеры. Чтобы узнать больше об инжекторах, см. руководство Dependency Injection guide.
Теперь HeroService
готов к подключению к HeroesComponent
.
Это промежуточный пример кода, который позволяет вам предоставлять и использовать HeroService
. На данном этапе код отличается от HeroService
в финальном обзоре кода.
Обновление HeroesComponent
¶
Откройте файл класса HeroesComponent
.
Удалите импорт HEROES
, потому что он вам больше не понадобится. Вместо этого импортируйте HeroService
.
1 |
|
Замените определение свойства heroes
объявлением.
1 |
|
Инжектируйте HeroService
¶
Добавьте приватный параметр heroService
типа HeroService
в конструктор.
1 |
|
Параметр одновременно определяет частное свойство heroService
и идентифицирует его как место инъекции HeroService
.
Когда Angular создает HeroesComponent
, система Dependency Injection устанавливает параметр heroService
в синглтон экземпляра HeroService
.
Добавьте getHeroes()
¶
Создайте метод для получения героев из сервиса.
1 2 3 |
|
Вызовите его в ngOnInit()
¶
Хотя вы можете вызвать getHeroes()
в конструкторе, это не лучшая практика.
Оставьте конструктор для минимальной инициализации, например, для подключения параметров конструктора к свойствам. Конструктор не должен делать ничего.
Он определенно не должен вызывать функцию, которая делает HTTP-запросы к удаленному серверу, как это сделала бы реальная служба данных.
Вместо этого вызовите getHeroes()
внутри ngOnInit lifecycle hook и позвольте Angular вызвать ngOnInit()
в подходящее время после создания экземпляра HeroesComponent
.
1 2 3 |
|
Посмотрите, как это работает¶
После обновления браузера приложение должно работать как прежде, показывая список героев и подробное представление героя, когда вы нажимаете на имя героя.
Наблюдаемые данные¶
Метод HeroService.getHeroes()
имеет синхронную подпись, что подразумевает, что HeroService
может получать героев синхронно. Компонент HeroesComponent
потребляет результат getHeroes()
, как если бы герои могли быть получены синхронно.
1 |
|
Этот подход не будет работать в реальном приложении, использующем асинхронные вызовы. Сейчас он работает, потому что ваш сервис синхронно возвращает макет героев.
Если getHeroes()
не может немедленно вернуться с данными о героях, он не должен быть синхронным, потому что это заблокирует браузер в ожидании возврата данных.
У HeroService.getHeroes()
должна быть какая-то асинхронная подпись.
В этом руководстве HeroService.getHeroes()
возвращает Observable
, чтобы можно было использовать метод Angular HttpClient.get
для получения героев
и чтобы HttpClient.get()
возвращал Observable
.
Observable HeroService
¶
Observable
является одним из ключевых классов в библиотеке RxJS.
В учебнике по HTTP вы можете увидеть, как методы Angular HttpClient
возвращают объекты RxJS Observable
. Этот учебник имитирует получение данных с сервера с помощью функции RxJS of()
.
Откройте файл HeroService
и импортируйте символы Observable
и of
из RxJS.
1 |
|
Замените метод getHeroes()
на следующий:
1 2 3 4 |
|
of(HEROES)
возвращает Observable<Hero[]>
, который выдает единственное значение, массив подражаемых героев.
В HTTP tutorial показано, как вызвать HttpClient.get<Hero[]>()
, который также возвращает Observable<Hero[]>
, выдающий единственное значение, массив героев из тела HTTP-ответа.
Подписаться в HeroesComponent
¶
Метод HeroService.getHeroes
раньше возвращал Hero[]
. Теперь он возвращает Observable<Hero[]>
.
Вам нужно настроить свое приложение для работы с этим изменением в HeroesComponent
.
Найдите метод getHeroes
и замените его следующим кодом. новый код показан рядом с текущей версией для сравнения.
1 2 3 4 |
|
1 2 3 |
|
Критическим отличием является Observable.subscribe()
.
В предыдущей версии массив героев присваивается свойству heroes
компонента. Присвоение происходит синхронно, как будто сервер может вернуть героев мгновенно или браузер может заморозить пользовательский интерфейс в ожидании ответа сервера.
Это не работает, когда HeroService
действительно делает запросы к удаленному серверу.
Новая версия ждет, пока Observable
не выдаст массив героев, что может произойти сейчас или через несколько минут. Метод subscribe()
передает массив героев в обратный вызов,
который устанавливает свойство компонента heroes
.
Этот асинхронный подход работает, когда HeroService
запрашивает героев с сервера.
Показать сообщения¶
Этот раздел поможет вам сделать следующее:
- Добавление компонента
MessagesComponent
, который отображает сообщения приложения в нижней части экрана. - Создание инжектируемого, общеприкладного
MessageService
для отправки сообщений на экран - Инжектирование
MessageService
вHeroService
. - Отображение сообщения, когда
HeroService
успешно получает героев
Создайте MessagesComponent
¶
Используйте ng generate
для создания MessagesComponent
.
1 |
|
ng generate
создает файлы компонента в каталоге src/app/messages
и объявляет MessagesComponent
в AppModule
.
Отредактируйте шаблон AppComponent
для отображения MessagesComponent
.
1 2 3 |
|
Вы должны увидеть параграф по умолчанию из MessagesComponent
в нижней части страницы.
Создайте MessageService
¶
Используйте ng generate
для создания MessageService
в src/app
.
1 |
|
Откройте MessageService
и замените его содержимое на следующее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Сервис раскрывает свой кэш messages
и два метода:
- Один для
add()
сообщения в кэш. - Другой — для
clear()
кэша.
Вставьте его в HeroService
¶
В HeroService
импортируйте MessageService
.
1 |
|
Отредактируйте конструктор с параметром, который объявляет приватное свойство messageService
. Angular инжектирует синглтон MessageService
в это свойство при создании HeroService
.
1 |
|
Это пример типичного сценария сервис в сервисе, в котором вы вводите MessageService
в HeroService
, который вводится в HeroesComponent
.
Отправка сообщения из HeroService
¶
Отредактируйте метод getHeroes()
для отправки сообщения при получении героев.
1 2 3 4 5 |
|
Отображение сообщения от HeroService
¶
Компонент MessagesComponent
должен отображать все сообщения, включая сообщение, отправленное HeroService
при получении героев.
Откройте MessagesComponent
и импортируйте MessageService
.
1 |
|
Отредактируйте конструктор с параметром, который объявляет публичное свойство messageService
. Angular инжектирует синглтон MessageService
в это свойство при создании MessagesComponent
.
1 |
|
Свойство messageService
должно быть public, потому что вы собираетесь привязать его к шаблону.
Angular привязывается только к публичным свойствам компонентов.
Привязка к MessageService
¶
Замените шаблон MessagesComponent
, созданный ng generate
, на следующий.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Этот шаблон напрямую связывается с messageService
компонента.
Подробности | |
---|---|
*ngIf | Отображать область сообщений только в том случае, если есть сообщения для показа. |
*ngFor | Представляет список сообщений в повторяющихся элементах <div> . |
Angular event binding | Связывает событие нажатия кнопки с MessageService.clear() . |
Сообщения выглядят лучше после добавления частных CSS-стилей в messages.component.css
, как указано в одной из вкладок "final code review" ниже.
Добавление сервиса MessageService в компонент HeroesComponent¶
В следующем примере показано, как отобразить историю каждого нажатия пользователем на героя. Это поможет при переходе к следующему разделу Routing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
Обновите браузер, чтобы увидеть список героев, и прокрутите страницу вниз, чтобы увидеть сообщения от HeroService. Каждый раз, когда вы нажимаете на героя, появляется новое сообщение для записи выбора.
Используйте кнопку Очистить сообщения, чтобы очистить историю сообщений.
Окончательный обзор кода¶
Вот файлы кода, обсуждаемые на этой странице.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 |
|
Резюме¶
- Вы рефакторизовали доступ к данным в классе
HeroService
. - Вы зарегистрировали
HeroService
как провайдера своего сервиса на корневом уровне, чтобы его можно было внедрить в любое место приложения. - Вы использовали Angular Dependency Injection, чтобы внедрить его в компонент.
- Вы придали методу
HeroService
get data
асинхронную сигнатуру. - Вы открыли для себя
Observable
и библиотеку RxJSObservable
. - Вы использовали RxJS
of()
для возвратаObservable<Hero[]>
, наблюдаемой модели героев. - Хук жизненного цикла компонента
ngOnInit
вызывает методHeroService
, а не конструктор. - Вы создали
MessageService
для свободно связанного взаимодействия между классами. - Внедренный в компонент
HeroService
создается вместе с другим внедренным сервисом,MessageService
.