Угловое условие в провайдере типа с AOT

У меня есть проект Angular, который я компилирую с помощью AOT. Я хочу иметь возможность зарегистрировать ClassProvider, который разрешается динамически в соответствии с конфигурацией. Я использую упрощенный код:

const isMock = Math.random() > 0.5;

@NgModule({
  // ...
  providers: [
    { provide: MyServiceBase, useClass: (isMock) ? MyServiceMock : MyService },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Проблема в том, что когда я компилирую это с помощью AOT, я всегда получаю один и тот же сервис. Я ожидаю получить другую услугу при нажатии F5 (из-за randomness в первой строке). При компиляции без AOT он ведет себя так, как я ожидаю.

Вот весь пример кода на github: https://github.com/vdolek/angular-test/tree/aot-conditioned-provider-problem. Он ведет себя по-разному с ng serve и ng serve --aot.

Как я могу этого добиться? Я знаю, что мог бы использовать FactoryProvider, но тогда мне пришлось бы дублировать зависимости сервисов (параметры фабричной функции и свойство deps на FactoryProvider).


person Martin Volek    schedule 21.02.2018    source источник


Ответы (2)


Чтобы добиться динамического характера ваших требований, вам необходимо использовать фабричных поставщиков через атрибут useFactory.

Я разветвил ваш репозиторий и изменил вашу ссылку app.module.ts следующим образом для работы в AOT.

Изменить app.module.ts следующим образом.

export let myServiceFactory = () => {

  const isMock = Math.random() > 0.5;

  return isMock ? new MyServiceMock() : new MyService();
}; 

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [
    {provide: MyServiceBase, useFactory: myServiceFactory},
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

В случае, если ваша служба зависит от других служб, что, скорее всего, так и будет, вы можете использовать аргумент deps, чтобы передать требуемые зависимости.

Предположим, что MyServiceBase зависит от двух сервисов, MyService1 и MyService2... Ваша фабричная функция будет выглядеть следующим образом:

export let myServiceFactory = (service1:MyService1, service2:MyService2) => {

  const isMock = Math.random() > 0.5;

  return isMock ? new MyServiceMock(service1, service2) : new MyService(service1, service2);
}; 

и декларация вашего провайдера будет выглядеть следующим образом

providers: [
    {
       provide: MyServiceBase, 
       useFactory: myServiceFactory,
       deps: [MyService1, MyService2]
    },
]

Это руководство содержит дополнительную информацию о различных способах внедрения зависимостей в Angular.

person JeanPaul A.    schedule 27.02.2018
comment
Спасибо за Ваш ответ. Как я уже упоминал в своем вопросе, я знаком с FactoryProviders. Но мне не нравится, что мне нужно трижды перечислять зависимости (в реальной службе, в заводской подписи и в свойстве deps). Я хочу избежать дублирования кода - это и было целью вопроса. Свойство deps даже не проверяется статически при сборке, поэтому, когда кто-то изменяет зависимости службы, он должен знать, чтобы изменить свойство deps. - person Martin Volek; 27.02.2018
comment
Я понимаю, я должен признать, что я пропустил это последнее предложение (моя мысль автоматически пришла к этому решению). Насколько мне известно, нет альтернативного способа добиться того, что вам нужно. - person JeanPaul A.; 27.02.2018
comment
Angular 5 уменьшает ограничения на синтаксис таких вещей, как фабричные функции при использовании AOT. - person Matt Strom; 27.02.2018

Я думаю, как сказал @jeanpaul-a, у вас нет другого выбора, кроме как использовать factory. Но управление зависимостями может быть не очень чистым. Но вы можете использовать Injector. Я пойду с чем-то вроде:

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  providers: [
    Dep1Service,
    Dep2Service,
    { provide: MyServiceBase, useFactory: createService, deps: [Injector] }
  ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

export function createService(injector: Injector) {
  const isMock = Math.random() > 0.5;
  if (mock) {
    return new MyService1(injector.get(Dep2Service));
  } else {
    return new MyService2(injector.get(Dep1Service));
  }
}

Вы также можете установить MyServiceBase в качестве интерфейса и использовать InjectionToken. Вы найдете рабочий пример здесь (но не имя вашего класса).

person JEY    schedule 27.02.2018
comment
Спасибо за Ваш ответ. Это не стопроцентное мое желание. Но, по крайней мере, он выдает ошибки сборки каждый раз, когда меняются зависимости, поэтому человек знает, что требуется обновление factory. - person Martin Volek; 27.02.2018
comment
Да, я знаю, но вы не можете использовать вызов функции с useClass, и ваше выражение будет статическим при компиляции с помощью AOT. Так что, если вам нужна динамика, вам нужна фабрика - person JEY; 27.02.2018