Механизм внедрения зависимостей на клиенте

В данном разделе рассмотрены особенности передачи клиентских сервисов с использованием механизма внедрения зависимостей.

В Web-клиенте внедрение зависимостей связано с таким понятием, как контейнер сервисов, упрощенная реализация которого приведена ниже.

var serviceContainer = {
   urlStore: new UrlStroe(),
   requestManager: new ReqeustManager(),
   router: new Router()
}

Данный контейнер содержит три сервиса, которые могут быть переданы в метод при его вызове:

sendToAcquaintance(serviceContainer);

Полноценная реализация такого контейнера в Web-клиенте определена в классе ServiceContainer, экземпляр которого доступен из статического свойства WebClient.App.Instance и глобальной переменной WebClient.app.

Данный контейнер содержит все стандартные сервисы Web-клиента (приведены в JSDoc API).

Отличительной особенностью реализации контейнера ServiceContainer является то, что каждое свойство в нем имеет get-функцию, в которой динамически вычисляется искомое значение. В частности, для каждого сервиса в данном контейнере создаётся объект ServiceDescriptor, содержащий информацию о том, как должно вычисляться его значение.

Класс ServiceContainer предоставляет функции для создания и регистрация дескрипторов, которые в WebClient.App.Instance представлены в виде статически методов, пример использования которых приведён ниже.

WebClient.App.registerService("urlStore", new UrlStore()); (1)

WebClient.App.addService<$UrlStore>({ urlStore: new UrlStore()}); (2)

WebClient.App.registerServiceFactory("urlStore", (services) => new UrlStore()); (3)

WebClient.App.addServiceFactory<$UrlStore>({ urlStore: (services) => new UrlStore()}); (4)

WebClient.App.registerServiceAccessors("now", () => new Date()); (5)

let serviceDescriptor = { (6)
    name: "urlStore",
    instance: new UrlStore()
};
WebClient.App.registerServiceDesciptor(serviceDescriptor);

var myUrlStore = WebClient.app.with<$UrlStore>().urlStore; (7)
1 Регистрация с передачей имени свойства и значения.
2 Вариант регистрации с передачей объекта.
3 Регистрация фабрики сервиса с передачей имени. Фабрика будет вызвана при первом обращении к сервису, после чего значение будет кэширована.

Функция принимает объект контейнера, актуального на момент её вызова.

4 Вариант регистрации фабрики сервиса с передачей объекта.
5 Регистрация динамически вычисляемого значения.

Функция будет вызываться при каждом обращении к сервису. Можно также передать set-функцию.

6 Передача дескриптора сервиса созданного вручную.
7 Использование зарегистрированного сервиса.

Сервисы в разметке

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

WebClient.App.addServiceFactory<$RequestManager>( (1)
    { requestManager:  (services: $Layout) => new RequestManager(services) },
    WebClient.LAYOUT_SERVICE
);
1 Сервис специфичный для разметки. Передаем дополнительным параметром метаданные — объект WebClient.LAYOUT_SERVICE

При открытии разметки, будет создана копия контейнера WebClient.App.Instance, в этой копии все сервисы зарегистрированные как фабрики с передачей WebClient.LAYOUT_SERVICE будут созданы повторно. То есть, фабричная функция будет вызвана для получения нового экземпляра, специфичного для данной разметки.

Для доступа к контейнеру сервисов разметки можно либо напрямую обратиться к параметру services в элементе Layout, либо объявить параметр services в своем элементе управления (см. пункт Получение сервисов в клиентском компоненте).

export class AcquaintancePanelParams extends PanelParams {
    @rw sendButtonText: string;
    @r standardCssClass?: string = "acquaintance-panel";

    @rw services?: $UrlStore & $RequestManager; (1)
}
1 Объявляем, что для ЭУ необходимы сервисы UrlStore и RequestManager

После этого, можно обращаться к этим сервисам через this.state.services:

sendToAcquaintance() {
    var url = this.state.services.urlStore.urlResolver.resolveUrl("SendToAcquaintance", "LayoutBusinessProcess");
    var data = { ...  };
    return this.state.services.requestManager.post(url, JSON.stringify(data));
}

Т.к. services является обычным параметром, его можно передать элементу управления и при этом, если требуется, переопределить сервисы, с которыми работает элемент управления:

let layout = layoutManager.cardLayout;
let myServices = layout.params.services.clone();
myServices.requestManager = new MyRequestManager();
layout.controls.acquaintancePanel1.params.services = myServices;
Не следует модифицировать существующий контейнер для подмены сервисов, т.к. это приведёт к изменению сервисов на уровне всего приложения.