#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. Спасибо!