Создание сервисных фабрик, совместимых с AoT

Я пытаюсь создать фабрику услуг для службы кэширования. Основное требование состоит в том, что один сервис может быть каждый раз создан с другой строкой.

В конечном результате будет несколько служб кэширования, каждая из которых определяется уникальным databaseName. В каждом кэше может быть одно или несколько хранилищ, также определяемых уникальным storeName. Другие сервисы смогут использовать эти магазины:

mainCache                         = new Cache('main')
  ├── userStore                   = new Store(mainCache, 'user')
  │     ├── userService
  │     └── authenticationService
  │
  └── taskStore                   = new Store(mainCache, 'task')
        └── taskService

fooCache                          = new Cache('foo')
  └── barStore                    = new Store(otherCache, 'bar')

Мои текущие реализации выглядят так:

//indexeddb-cache.ts
import { Injectable } from '@angular/core';

@Injectable()
export class IndexedDBCache {
  constructor(
    protected databaseName : string
  ) {}
}

// indexeddb-store.ts
import { Injectable } from '@angular/core';
import { IndexedDBCache } from './indexeddb-cache';

@Injectable()
export class IndexedDBStore {
  constructor(
    protected database : IndexedDBCache,
    protected storeName : string
  ) {}
}

Услуги фабричные:

// test-api.cache.factory.ts
import { IndexedDBCache } from '../indexeddb-cache/indexeddb-cache';
import { IndexedDBStore } from '../indexeddb-cache/indexeddb-store';

// factory functions for FactoryProviders as exports instead of inline:
// https://github.com/rangle/angular-2-aot-sandbox#func-in-providers-usefactory-top
export function mainIndexedDBCacheFactory() { return new IndexedDBCache('main'); }
export function userIndexedDBStoreFactory(testIndexedDBCache: IndexedDBCache) { return new IndexedDBStore(testIndexedDBCache, 'user'); }
export function taskIndexedDBStoreFactory(testIndexedDBCache: IndexedDBCache) { return new IndexedDBStore(testIndexedDBCache, 'task'); }

А затем предоставил:

// test-api.cache.ts
import { InjectionToken, Provider } from '@angular/core';
import { IndexedDBCache } from '../indexeddb-cache/indexeddb-cache';
import { IndexedDBStore } from '../indexeddb-cache/indexeddb-store';
import { mainIndexedDBCacheFactory,
         taskIndexedDBStoreFactory,
         userIndexedDBStoreFactory } from './test-api.cache.factory';

// Caches

export const mainIndexedDBCache = new InjectionToken<IndexedDBCache>('mainIndexedDBCache');

export let mainIndexedDBCacheProvider : Provider = {
  provide: mainIndexedDBCache,
  useFactory: mainIndexedDBCacheFactory
};

// Stores

export const userIndexedDBStore = new InjectionToken<IndexedDBStore>('userIndexedDBStore');

export let userIndexedDBStoreProvider : Provider = {
  provide: userIndexedDBStore, deps: [mainIndexedDBCache],
  useFactory: userIndexedDBStoreFactory
};

export const taskIndexedDBStore = new InjectionToken<IndexedDBStore>('taskIndexedDBStore');

export let taskIndexedDBStoreProvider : Provider = {
  provide: taskIndexedDBStore, deps: [mainIndexedDBCache],
  useFactory: taskIndexedDBStoreFactory
};

Объявлено в основном модуле:

// test-api-module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { mainIndexedDBCacheProvider,
         taskIndexedDBStoreProvider,
         userIndexedDBStoreProvider } from './test-api.cache';

@NgModule({
  imports: [], declarations: [], exports: []
})
export class TestAPIModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: TestAPIModule,
      providers: [
        mainIndexedDBCacheProvider,
        userIndexedDBStoreProvider,
        taskIndexedDBStoreProvider
      ]
    };
  }
}

И, наконец, используется в сервисе:

//user-service/user.service.ts
import { Inject, Injectable } from '@angular/core';
import { IndexedDBStore } from '../../indexeddb-cache/indexeddb-store';
import { userIndexedDBStore } from '../test-api.cache';

@Injectable()
export class UserService {
  constructor(
    @Inject(userIndexedDBStore) protected cache : IndexedDBStore
  ) {}
}

Все отлично работает при использовании JIT-компиляторов. Когда я пытаюсь выполнить компиляцию AoT, я начинаю получать Warning: Can't resolve all parameters for x in y: (?). This will become an error in Angular v5.x для строковых параметров службы кеша и хранилища.

Я уже подробно описывал свою проблему в этом вопросе: Токенизация для компиляции AoT. Теперь похоже, что использование строк в качестве параметров для @Injectable() совершенно невозможно с компилятором AoT. Есть ли другой способ создать фабрику для служб, инициализированных с помощью строки, как я описал?

Я создал рабочий пример в этом репозитории. Проверьте ветку aot, и npm install, за которой следует npm run build.prod.rollup.aot, должны работать точно так же.


person Raven    schedule 09.06.2017    source источник
comment
А что такое x и y в «x в y»?..   -  person Estus Flask    schedule 09.06.2017
comment
@estus IndexedDBCache и местонахождение indexeddb-cache.ts.   -  person Raven    schedule 09.06.2017
comment
Я не уверен, как должен работать protected storeName : string. Нет string провайдера. Это не специфичная для AoT функция, я ожидаю, что она потерпит неудачу и в JiT, потому что это просто неправильно. Если вы используете IndexedDBCache только как new IndexedDBCache, это не должно быть Injectable. Попробуйте удалить Injectable из классов, которые не выполняют DI в конструкторах, и проверьте, работает ли это для вас.   -  person Estus Flask    schedule 09.06.2017
comment
@estus Это работает так в angularjs и безотказно работает в JIT. В этом примере он даже компилируется в AoT, но выдает предупреждение. Основная идея заключается в том, что я могу создать несколько хранилищ из одного определения (› factory) и что несколько компонентов могут взаимодействовать с одним и тем же экземпляром хранилища. Если это неправильный подход, не могли бы вы привести пример правильного подхода для этой структуры?   -  person Raven    schedule 09.06.2017
comment
Angular (2 и выше) — это не AngularJS, это апельсины и яблоки. Просто удалите @Injectable из IndexedDBCache и IndexedDBStore. Это не инъекция, потому что параметры конструктора не являются надлежащими службами. См. это объяснение для декоратора Injectable stackoverflow.com/a/39029435/3731501   -  person Estus Flask    schedule 09.06.2017
comment
@estus Хм .. Думаю, я понимаю твою точку зрения. сейчас попробую   -  person Raven    schedule 09.06.2017


Ответы (1)


Основываясь на отзывах пользователей @estus и @Toxicable, класс, который будет создан только Factory, не должен быть @Injectable самим собой. Удаление этого тега из классов IndexedDBCache и IndexedDBStore решит проблему.


Factory также должен быть в определенной форме

export function mainIndexedDBCacheFactory() { return new IndexedDBCache('main'); }

Другие итерации этого определения не будут работать.

export const mainIndexedDBCacheFactory = function() { return new IndexedDBCache('main'); };
export let mainIndexedDBCacheFactory   = function() { return new IndexedDBCache('main'); };
export let mainIndexedDBCacheFactory   = () => new IndexedDBCache('main');

Префикс export требуется даже если определение находится в том же файле, что и Provider.

person Raven    schedule 12.06.2017