Концепция сервисов в Ember

Сервисы в Ember.js — это особая категория объектов, предназначенных для централизованного управления состоянием и логикой, которые могут быть использованы в различных частях приложения. Они служат для инкапсуляции бизнес-логики, работы с API, управления состоянием приложения и других аспектов, которые не зависят от жизненного цикла компонента или маршрута.

Основные принципы сервисов

  1. Долговечность: Сервис существует на протяжении всего времени работы приложения, что отличает его от компонентов и маршрутов, которые создаются и уничтожаются в зависимости от жизненного цикла страницы или компонента.
  2. Инжекция: Сервисы могут быть инжектированы в любые объекты, включая компоненты, контроллеры и маршруты. Это делает их удобным способом для обмена данными и логикой между различными частями приложения.
  3. Состояние и бизнес-логика: Сервисы часто используются для хранения глобального состояния (например, авторизации пользователя) и для выполнения задач, таких как выполнение HTTP-запросов или взаимодействие с веб-сокетами.

Создание сервиса

Для создания сервиса в Ember.js используется командой:

ember generate service имя_сервиса

Эта команда создаёт файл сервиса в директории app/services. Структура сервиса обычно включает только основные методы и свойства, которые необходимы для реализации нужной логики. Сервисы в Ember наследуют от базового класса Service.

Пример сервиса:

// app/services/authentication.js
import Service from '@ember/service';

export default class AuthenticationService extends Service {
  isAuthenticated = false;

  login() {
    this.isAuthenticated = true;
  }

  logout() {
    this.isAuthenticated = false;
  }
}

В этом примере сервис authentication управляет состоянием аутентификации пользователя, предоставляя методы для входа и выхода.

Инжекция сервисов

Сервисы инжектируются в компоненты, маршруты и контроллеры через систему зависимостей, предоставляемую Ember. Это делается с помощью декоратора @service.

Пример использования сервиса в компоненте:

// app/components/login-form.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

export default class LoginFormComponent extends Component {
  @service authentication;

  get isAuthenticated() {
    return this.authentication.isAuthenticated;
  }

  login() {
    this.authentication.login();
  }

  logout() {
    this.authentication.logout();
  }
}

В этом примере сервис authentication инжектируется в компонент login-form и используется для управления состоянием аутентификации.

Сервисы как фабрики

Сервисы в Ember.js работают как фабрики. Это означает, что экземпляр сервиса создается один раз в рамках приложения и используется повторно во всех частях приложения, где он инжектирован.

Если необходимо создать несколько экземпляров сервиса с различными состояниями, можно использовать опцию “сервисов фабричного типа”. Для этого вместо стандартного Service используется кастомная реализация или библиотека, позволяющая создавать такие экземпляры. Однако, в стандартных случаях Ember создает только один экземпляр сервиса на всё приложение.

Доступ к сервисам через маршруты

Сервисы могут быть полезны не только в компонентах и контроллерах, но и в маршрутах. Чтобы использовать сервис в маршруте, его нужно инжектировать аналогичным способом:

// app/routes/application.js
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class ApplicationRoute extends Route {
  @service authentication;

  beforeModel() {
    if (!this.authentication.isAuthenticated) {
      this.transitionTo('login');
    }
  }
}

В этом примере маршрут application проверяет, авторизован ли пользователь, с помощью сервиса authentication, и, если нет, перенаправляет его на страницу входа.

Сервисы и глобальное состояние

Сервисы часто используются для хранения глобального состояния, которое должно быть доступно на протяжении всей жизни приложения. Например, сервис может отслеживать данные о текущем пользователе, настройки приложения или состояние глобального уведомления.

Пример сервиса, управляющего состоянием темы оформления:

// app/services/theme.js
import Service from '@ember/service';

export default class ThemeService extends Service {
  currentTheme = 'light';

  toggleTheme() {
    this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
  }
}

Этот сервис позволяет переключать темы в приложении. Состояние темы будет доступно во всех компонентах и маршрутах, которые инжектируют этот сервис.

Сервисы и асинхронная логика

Сервисы могут содержать асинхронную логику, такую как запросы к API. Например, сервис может обрабатывать HTTP-запросы, получать данные с сервера и предоставлять эти данные компонентам и маршрутам.

Пример асинхронного сервиса для работы с API:

// app/services/api.js
import Service from '@ember/service';
import fetch from 'fetch';

export default class ApiService extends Service {
  async fetchData(url) {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  }
}

Этот сервис предоставляет метод fetchData, который делает асинхронный запрос к API и возвращает данные. Он может быть использован в любом компоненте или маршруте, который нуждается в этих данных.

Использование сервисов в тестах

Сервисы в Ember могут быть также использованы в тестах. Для того чтобы инжектировать сервис в тест, используется метод setupTest из Ember Test Framework. Сервисы обычно инжектируются через this.owner.lookup или через предоставление их в контексте теста.

Пример теста с использованием сервиса:

// tests/unit/services/authentication-test.js
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';

module('Unit | Service | authentication', function(hooks) {
  setupTest(hooks);

  test('it exists', function(assert) {
    let service = this.owner.lookup('service:authentication');
    assert.ok(service);
  });

  test('login and logout', function(assert) {
    let service = this.owner.lookup('service:authentication');
    service.login();
    assert.ok(service.isAuthenticated);

    service.logout();
    assert.notOk(service.isAuthenticated);
  });
});

Тест проверяет, что сервис аутентификации работает корректно, включая методы login и logout.

Заключение

Сервисы в Ember.js предоставляют мощный инструмент для централизованного управления состоянием и бизнес-логикой приложения. Они позволяют инкапсулировать логику, которая должна быть доступна в различных частях приложения, и обеспечивают долгосрочную доступность состояния на протяжении всего времени работы приложения. Инжекция сервисов в компоненты, маршруты и контроллеры упрощает взаимодействие между различными частями приложения и способствует улучшению структуры кода.