Добавить сервисы¶
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, чтобы внедрить его в компонент.
- Вы придали методу
HeroServiceget dataасинхронную сигнатуру. - Вы открыли для себя
Observableи библиотеку RxJSObservable. - Вы использовали RxJS
of()для возвратаObservable<Hero[]>, наблюдаемой модели героев. - Хук жизненного цикла компонента
ngOnInitвызывает методHeroService, а не конструктор. - Вы создали
MessageServiceдля свободно связанного взаимодействия между классами. - Внедренный в компонент
HeroServiceсоздается вместе с другим внедренным сервисом,MessageService.