Переписав Angular di.js під TypeScript-версію

На бекенді під Node.js постійно використовую Angular DI.js. Дуже зручна річ, але останнім часом на нових версіях Node.js він почав конфліктувати... то із pm2, то із node-lru-cache. І як вияснилось, конфліктує не сам Angular DI, а код після роботи транспайлера traceur.

Так і не дочекався відповіді від команди Angular, чи збираються вони продовжувати підтримувати Angular DI як окремий модуль. Придивлявся до inversify.io, але щось він здався складнішим, ніж це мені треба.

Source code Angular DI.js написано на ES2015, то ж було досить просто його підігнати під TypeScript-версію (майже не зачіпаючи початковий код). А вже TypeScript його транспайлить в робочу неконфліктну версію JavaScript.

Може кому теж цікаво спробувати — welcome!

Github project

Встановлення:

npm install ts-di --save

Використання:

import {Inject, Injector} from 'ts-di';

class A{}

// All dependencies in @Inject listed separated by commas
@Inject(A)
class B
{
  constructor(private a: A){}

  getValue()
  {
    console.log(`There should be a instance of class A:`, this.a);
  }
}

let injector = new Injector();

// Instance class B and resolve dependency
let instance = injector.get(B);

Travis фейлиться (див. іконку «build failing») через... думаю через баг TypeScript, але ця помилка не впливає на роботу даного модуля.

UPD:
Зараз модуль транспільовано для ES2015. Щоб перекомпілювати його, наприклад, під ES5, то треба скачати проект із github, запустити:

npm install
tsc --target es5
npm pack

Ця команда зробить архів та видасть його ім’я. Потім переходите у проект, де потрібен DI, робите

npm install /path/to/project/from-github/ts-di-1.0.11.tgz

Все, модуль готовий до використання.
P.S. Зрозуміло що номер версії може з часом змінюватись.

Оновлення від 25.01.2017

Кхм, взагалі-то мій ts-di є дуже маленьким, швидким, і зручним, але бачу що йому не вистачає того, що я спочатку забракував для себе у теперішньому Angular DI: визначення провайдерів залежностей окремим масивом.

На даний момент ts-di, наприклад при тестуванні, вміє підмінювати провайдерів залежностей наступним чином:

import
{
  Inject
  ,Injector
  ,Provide
} from 'ts-di';

class Engine {}

@Inject(Engine)
class Car
{
  constructor(public engine: Engine){}

  start() {}
}

// Таким чином провайдер Engine підмінюється провайдером MockEngine
@Provide(Engine)
class MockEngine
{
  start() {}
}

var injector = new Injector([MockEngine]);
var car = injector.get(Car);

/**
 * Expect `car` to be instance of class `Car`
 * and `car.engine` to be instance of class `MockEngine`
 */

console.log(car);

Але проблема в тому, що інколи треба підмінювати не класи, а вже готові значення, наприклад, об’єктів. На даний момент, ts-di такого не вміє робити, але це вміє робити теперішня версія Angular DI, хоча й має певні накладки:

import 'reflect-metadata';

import
{
  Injectable
  ,ReflectiveInjector
} from '@angular/core';

class Engine {}

@Injectable()
class Car
{
  constructor(public engine: Engine){}

  start() {}
}

var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
var car = injector.get(Car);

/**
 * Expect `car` to be instance of class `Car`
 * and `car.engine` to be instance of class `Engine`
 */

console.log(car);

Під «накладками» я маю на увазі, що для інжектора треба указувати абсолютно усіх провайдерів, навіть якщо їх є пару десятків.

Здавалося б що в даному прикладі указувати Engine другий раз буде зайвим, бо інжектор же начебто міг би й сам прочитати анотації, залишені через @Injectable(), що Car залежить від Engine, але це робиться щоб можна було підмінювати замість Engine — MockEngine

import 'reflect-metadata';

import
{
  Injectable
  ,ReflectiveInjector
} from '@angular/core';

class Engine {}

@Injectable()
class Car
{
  constructor(public engine: Engine){}

  start() {}
}

class MockEngine
{
  start() {}
}

var injector = ReflectiveInjector.resolveAndCreate([Car, {provide: Engine, useClass: MockEngine}]);
var car = injector.get(Car);

/**
 * Expect `car` to be instance of class `Car`
 * and `car.engine` to be instance of class `MockEngine`
 */

console.log(car); // Car { engine: MockEngine {} }
👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

приятно, что народ правильными вещами замарачивается... когда из-за специфики двух проектов пришлось с NG2 на Vue 2 перейти, иерархического IoC ангуляра жутко не хватало (хотя возможно, просто по привычки)

ну буду иметь ввиду, если что ;)

Цікаво що показник скачувань мого форку ts-di падав лише на перше й друге число цього місяця. В інші дні, кількість скачувань в середньому коливається біля 60 разів на день...

Саме цікаве, що ця статистика була з самого першого дня, коли я ніде про нього не розказував, навіть спочатку тегів не написав був. Можна припустити, що до десятка за день можуть накручувати веб-павуки, а решта звідки?

Та десь так само і я подумав, просто трохи дивно що навіть без тегів знайшли...

Тільки тепер вже видно чому була така статистика.

Схоже, що з кожним релізом будь-якого модуля на npmjs.com, спрацьовує хук, який сповіщає різні сервіси про цю подію і вони починають скачувати даний реліз. І оскільки за перших пару днів своїх тестів я міг по шість разів робити «релізи» (1.0.1, 1.0.2, 1.0.3...) на npmjs.com, то відповідно й була статистика: до шести десятків скачувань за день. Як тільки я перестав це робити, то й статистика устаканилась (десь біля 15 скачувань за день).

Стосовно „build failing” (іконка вгорі репозиторія з тестів модуля через travis).

Бачили таке? Контрібутор TypeScript каже, що в наступному коді помилка через те що:

Your inner function is closing over a mutable binding that is scoped outside the if block and is thus not always going to be an instance of ClassA.
class ClassA
{
  propA: any;
}

class ClassB
{
  propB: any;
}

function fn( arr: Array<ClassA | ClassB> )
{
  for( let element of arr )
  {
    if( element instanceof ClassA )
    {
      element.propA = true; // Work as expected

      () =>
      {
        element.propA; // Unexpected error
      }
    }
  }
}

А кто может подсказать, как DI реализовано во 2ом ангуляре? Что из себя представляет контейнер? DI реализовано через паттерн — локатор или же inversion of control? И как обстоят дела, если я хочу сделать inject 2х обьектов, которые реализуют 1 интерфейс?

Даний DI якраз писався під Angular 2, але його життя «як окремий модуль» відділений від ядра Angualar 2 завершилось у травні минулого року (останній коміт був 2015.05.16).

Але на даний момент Angualar 2 DI став значно функціональнішим, хоча й обріс різними залежностями, специфічними для його роботи в контексті саме Angular.

Документація Angular 2 вже стабілізувалась, там можна прочитати багато про DI: Dependency injection, Hierarchical Dependency Injection

а как с управлением временем жизни

Хм. Чесно кажучи, перший раз таке чую по відношенню до функціональності DI. Якщо я вас правильно зрозумів, то цей DI такого не вміє.

внедрением значений

Ну якщо пишете код для ES5, то через функцію annotate, а якщо для ES2015, то через декоратор @Inject у проекті на github показано обидва варіанти. Всього ж на даний момент, доступно 5 різних декораторів.

фабрики

Ви маєте на увазі створювати не singleton, а різні інстанси на різних рівнях? Мабуть це не доступно для даного DI (по крайній мірі я про це не знаю).

недавно как раз искал контейнер для сервиса на основе swagger-tools. то ли di-ts, то ли ts-di

Мабуть ви таки використовували di-ts, бо я залив свій модуль ts-di на npmjs.com зовсім не давно, мабуть три дні тому. А я теж бачив di-ts, але при першому ж тестуванні він повів себе «не дуже», то ж прийшлось писати своє рішення.

До речі, зараз модуль транспільовано для ES2015. Щоб перекомпілювати його під ES5/ES3, то треба скачати проект із github, запустити таке:

tsc --target es5
npm pack

Ця команда зробить архів та видасть його ім’я. Потім переходите у проект, де потрібен DI, робите

npm install /path/to/project/from-github/ts-di-1.0.11.tgz

Все, модуль готовий до використання.

P.S. Зрозуміло що номер версії може з часом змінюватись.

Підписатись на коментарі