один экземпляр / внедрение $ mdToast в базовый класс

#angularjs #typescript #angular-material

#angularjs #typescript #angular-материал

Вопрос:

У меня есть конкретный вопрос о помещении одного экземпляра $ mdToast (из Angular Material) в базовый класс (Typescript). У меня есть пять вкладок в моем пользовательском интерфейсе, и я предоставил каждому отдельный экземпляр контроллера (т. Е. Отдельные объявления inject и ctor). Имело смысл переместить объявление $ mdToast в базовый класс, а не отдельно объявлять его везде. Вы увидите, что базовый класс имеет свой собственный «$inject», но, по-видимому, он заменяется тем, который находится в производном классе. Я просто пытаюсь найти самый чистый способ переместить $ mdToast в общий базовый класс. Каков наилучший способ? Вот как выглядит мой код в настоящее время.

Обратите внимание, что исходные строки $mdToast закомментированы:

 export class MainController extends BaseController {
static $inject = [
  'tableService',
  '$mdSidenav', 
  //'$mdToast', 
  '$mdDialog', 
  '$mdMedia',
  '$mdBottomSheet'];

constructor(
  private tableService: ITableService,
  private $mdSidenav: angular.material.ISidenavService,
  //private $mdToast: angular.material.IToastService,
  private $mdDialog: angular.material.IDialogService,
  private $mdMedia: angular.material.IMedia,
  private $mdBottomSheet: angular.material.IBottomSheetService) {
  super();
  var self = this;
}}
  

со следующим базовым классом. Обратите внимание на внедрение $mdToast и объявление $mdToast вне конструктора.

 export class BaseController {
static $inject = [
  '$mdToast'];

constructor(
  ) {
  var self = this;
}

private $mdToast: angular.material.IToastService;

openToast(message: string): void {
  this.$mdToast.show(
    this.$mdToast.simple()
      .textContent(message)
      .position('top right')
      .hideDelay(3000)
  );
}}
  

Я видел умное использование $injector в другом месте в SO, но у меня это не сработало. Все ответы с радостью получены!

Ответ №1:

Это может быть шаблон, подобный этому:

 export class BaseController {
    static $inject = [...];
    ...
}

export class MainController extends BaseController {
    static $inject = [...BaseController.$inject,
      ...
    ];

    constructor(...deps) {
        const superDeps = BaseController.$inject.map((dep, i) => deps[i]);

        super(...superDeps);

        const thisDeps = deps.slice(superDeps.length);
        const thisDepNames = this.constructor.$inject.slice(superDeps.length);
        ...


    }

    ...
}
  

Для удобства он может быть упакован в некоторый базовый класс для инъекционных объектов или декоратора, если он используется более одного или двух раз, но речь всегда идет о разборе двух массивов, $inject и deps , и назначении зависимостей this .

Этот метод не является типобезопасным.

Для TypeScript предпочтительнее сохранять вещи ВЛАЖНЫМИ, но безопасными для типов. Мы всегда хотим, чтобы зависимости для родительского класса были на первом месте для согласованности:

 export class BaseController {
    static $inject = ['$mdToast'];

    constructor(protected $mdToast: angular.material.IToastService) { ... }
}

export class MainController extends BaseController {
    static $inject = [
      '$mdToast'

      'tableService',
      ...  
    ];

    constructor(
        $mdToast: angular.material.IToastService,

        private tableService: ITableService,
        ...
    ) {
        super($mdToast);
    }}
    ...
}
  

Комментарии:

1. Интересная мудрость в ваших заявлениях. Вы дали мне много поводов для размышлений. Второй пример по-прежнему страдает от необходимости вводить $mdToast дважды, чего я пытался избежать. Как я могу гарантировать, что получу только один экземпляр $ mdToast во всех классах, которые наследуются от BaseController? (кстати, я проголосовал за)

2. Обман $mdToast здесь правильный. В любом случае он будет передан инжектором в качестве аргумента конструктора (это можно исправить с помощью декоратора класса, но, честно говоря, оно того не стоит). Но он должен быть назначен this только один раз и не может переопределять видимость (обратите внимание, что protected $mdToast в конструкторе родительского класса есть, но только $mdToast в конструкторе дочернего класса). Конечно, $mdToast будет введен только один раз для каждого экземпляра контроллера, не имеет значения, был ли класс унаследован или нет, вот как работает наследование.

3. Во время суеты выходных и всех беженцев от урагана Мэтью я решил внедрить ваши изменения, и они работают точно так, как было обещано. Я все еще ненавижу быть МОКРЫМ, но ваш комментарий о том, что это лучший способ сохранить типобезопасность TypeScript, нашел отклик у меня. Я родом из жесткого C / Java / C #, и думать таким образом в Javascript / TypeScript — это хорошее место, где мне нужно быть более гибким, чем я был в прошлом.

4. Конечно, DI может быть действительно аккуратным, обрабатывая аргументы $inject array и constructor с помощью декораторов и базовых классов (см. goo.gl/ubLAqy например), это отлично работает для ES * и Babel, но небезопасно для типов в TS (может быть терпимым, в зависимости от того, сколько получается от DRY / WETкомпромисс). Вопрос не показывает ничего достаточно ВЛАЖНОГО, чтобы отказаться от безопасности типов, но, возможно, ваш реальный случай отличается.

Ответ №2:

Этот метод, конечно, является взломом, но он выполнит работу довольно простым способом. Используйте инструкции импорта / экспорта ES6, чтобы сделать эту службу доступной там, где она вам нужна.

 export let $injector;

class injectorConfig {
    static $inject = ['$injector'];
    constructor (private $originalInjector) {
        $injector = $originalInjector;
    }
}

app.config(injectorConfig);
  

Тогда ваш файл BaseController.ts будет выглядеть следующим образом

 import {$injector} from '../yourfilename';

export class BaseController {
    private $mdToast = $injector.get('$mdToast');
    constructor( ) {
        var self = this;
    }
}
  

То, что это работает, не означает, что вы должны полагаться на это, но в подобных ситуациях я думаю, что этот метод имеет смысл. Имейте в виду, что $injector не будет доступен, пока Angular не запустит этот блок конфигурации, поэтому вы не сможете использовать его в providers или любом другом коде, который выполняется до запуска блока конфигурации.

Комментарии:

1. Это выглядит как простой способ обеспечить одноэлементное значение для $mdToast. Спасибо!