Как вводить различные службы на основе определенной среды сборки в Angular2?

У меня есть HeroMockService, который вернет издеваемые данные и HeroService, которые будут вызывать службу конца для извлечения героев из базы данных.

Предполагая, что Angular2 имеет среду сборки, я планирую ввести HeroMockService в AppComponent, если текущая среда сборки "dev-mock". Если текущая среда сборки "dev-rest", HeroService следует вставить вместо AppComponent.

Я хотел бы знать, как я могу это достичь?

Ответы

Ответ 1

ИМО, лучшим вариантом будет использование angular-in-memory-web-api. Он копирует бэкэнд, который использует Http, поэтому вместо фактического вызова XHR он просто захватывает данные, которые вы ему предоставляете. Чтобы получить его, просто установите

npm install --save angular-in-memory-web-api

Чтобы создать базу данных, вы реализуете метод createDb в InMemoryDbService

import { InMemoryDbService } from 'angular-in-memory-web-api'

export class MockData implements InMemoryDbService {
  let cats = [
    { id: 1, name: 'Fluffy' },
    { id: 2, name: 'Snowball' },
    { id: 3, name: 'Heithcliff' },
  ];
  let dogs = [
    { id: 1, name: 'Clifford' },
    { id: 2, name: 'Beethoven' },
    { id: 3, name: 'Scooby' },
  ];
  return { cats, dogs, birds };
}

Затем настройте его

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';

@NgModule({
  imports: [
    HttpModule,
    InMemoryWebApiModule.forRoot(MockData, {
      passThruUnknownUrl: true
    }),
  ]
})

Теперь, когда вы используете Http и делаете запрос к /api/cats он получает всех котов из БД. Если вы перейдете в /api/cats/1 он получит первого кота. Вы можете делать все операции CRUD, GET, POST, PUT, DELETE.

Стоит отметить, что он ожидает базовый путь. В примере /api это базовый путь. Вы также можете настроить корневой путь (это отличается от базового), в конфигурации

InMemoryWebApiModule.forRoot(MockData, {
  rootPath: 'root',
  passThruUnknownUrl: true // forwards request not in the db
})

Теперь вы можете использовать /root/api/cats.


ОБНОВИТЬ

Что касается вопроса о том, как перейти с dev на производство, вы можете использовать фабрику для создания провайдеров. То же самое было бы верно, если бы вы использовали свой фиктивный сервис вместо in-memory-web-api

providers: [
  Any,
  Dependencies
  {
    // Just inject 'HeroService' everywhere, and depending
    // on the environment, the correct on will be chosen
    provide: HeroService, 
    useFactory: (any: Any, dependencies: Dependencies) => {
      if (environment.production) {
        return new HeroService(any, dependencies);
      } else {
        return new MockHeroService(any, dependencies);
      }
    },
    deps: [ Any, Dependencies ]
]

Что касается in-memory-web-api, мне нужно вернуться к вам (мне нужно проверить теорию). Я только начал использовать его, и не дошел до того, что мне нужно перейти к производству. Прямо сейчас у меня просто вышеупомянутая конфигурация. Но я уверен, что есть способ заставить это работать без необходимости что-либо менять

ОБНОВЛЕНИЕ 2

Итак, для im-memory-web-api мы можем вместо импорта модуля просто предоставить XHRBackend который предоставляет модуль. XHRBackend - это служба, используемая Http для выполнения вызовов XHR. In-memory-wep-api высмеивает этот сервис. Это все, что делает модуль. Таким образом, мы можем просто предоставить услугу самостоятельно, используя фабрику

@NgModule({
  imports: [ HttpModule ],
  providers: [
    {
      provide: XHRBackend,
      useFactory: (injector: Injector, browser: BrowserXhr,
                   xsrf: XSRFStrategy, options: ResponseOptions): any => {
        if (environment.production) {
          return new XHRBackend(browser, options, xsrf);
        } else {
          return new InMemoryBackendService(injector, new MockData(), {
            // This is the configuration options
          });
        }
      },
      deps: [ Injector, BrowserXhr, XSRFStrategy, ResponseOptions ]
    }
  ]
})
export class AppHttpModule {
}

Обратите внимание на BrowserXhr, XSRFStrategy и ResponseOptions. Вот как XHRBackend оригинальный XHRBackend. Теперь вместо импорта HttpModule в модуль приложения просто импортируйте AppHttpModule.

Что касается environment, это то, что вам нужно выяснить. С Angular-Cli, это уже среда, которая автоматически переключается на производство, когда мы строим в режиме производства.

Вот полный пример, который я использовал для тестирования

import { NgModule, Injector } from '@angular/core';
import { HttpModule, XHRBackend, BrowserXhr,
         ResponseOptions,  XSRFStrategy } from '@angular/http';

import { InMemoryBackendService, InMemoryDbService } from 'angular-in-memory-web-api';

let environment = {
  production: true
};

export class MockData implements InMemoryDbService {
  createDb() {
    let cats = [
      { id: 1, name: 'Fluffy' }
    ];
    return { cats };
  }
}

@NgModule({
  imports: [ HttpModule ],
  providers: [
    {
      provide: XHRBackend,
      useFactory: (injector: Injector, browser: BrowserXhr,
                   xsrf: XSRFStrategy, options: ResponseOptions): any => {
        if (environment.production) {
          return new XHRBackend(browser, options, xsrf);
        } else {
          return new InMemoryBackendService(injector, new MockData(), {});
        }
      },
      deps: [ Injector, BrowserXhr, XSRFStrategy, ResponseOptions ]
    }
  ]
})
export class AppHttpModule {
}

Ответ 2

См. ниже мое решение основано на @peeskillet one.

Создайте интерфейс, который реализуется как макет, так и реальный сервис, для вашего примера IHeroService.

export interface IHeroService {
    getHeroes();
}

export class HeroService implements IHeroService {
    getHeroes() {
    //Implementation goes here
    }
}

export class HeroMockService implements IHeroService {
    getHeroes() {
    //Mock implementation goes here
}

Предполагая, что вы создали приложение Angular с помощью Angular -CLI, в вашем файле environment.ts добавьте соответствующую реализацию, например:

import { HeroService } from '../app/hero.service';

export const environment = {
  production: false,
  heroService: HeroService
};

Для каждого другого environment.(prod|whatever).ts вам нужно определить a heroService, указывающий на реализацию, и добавить импорт.

Теперь скажите, что вы хотите импортировать службу в класс AppModule (вы можете сделать это на компоненте, где вам нужна услуга)

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    FormsModule,
    HttpModule,
    AlertModule.forRoot()
  ],
  providers: [
    {
      provide: 'IHeroService',
      useClass: environment.heroService
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Важной частью является поставщик:

  providers: [
    {
      provide: 'IHeroService',
      useClass: environment.heroService
    }

Теперь, когда вы хотите использовать эту услугу, вы должны сделать следующее:

import { IHeroService } from './../hero.service';

export class HeroComponent {

constructor(@Inject('IHeroService') private heroService: IHeroService) {}

Источники: Возможно ли внедрить интерфейс с помощью angular2? https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be http://tattoocoder.com/angular-cli-using-the-environment-option/

Ответ 3

Если вы упорядочиваете свой код в модулях Angular2, вы можете создать дополнительный модуль для импорта издевающихся сервисов, например:

@NgModule({
   providers: [
      { provide: HeroService, useClass: HeroMockService }
   ]
})
export class MockModule {}

Предполагая, что вы объявляете импорт для обычных сервисов в своем основном модуле:

@NgModule({
   providers: [ HeroService ]
})
export class CoreModule {}

Пока вы импортируете MockModule после CoreModule, тогда значение, которое будет введено для токена HeroService, будет HeroMockService. Это связано с тем, что Angular будет использовать последнее значение, если для одного токена есть два провайдера.

Затем вы можете настроить импорт для MockModule на основе определенного значения (представляющего среду сборки), например:

// Normal imported modules
var importedModules: Array<any> = [
   CoreModule,
   BrowserModule,
   AppRoutingModule
];

if (process.env.ENV === 'mock') {
   console.log('Enabling mocked services.');
   importedModules.push(MockModule);
}

@NgModule({
    imports: importedModules
})
export class AppModule {}

Ответ 4

Многие из этих ответов верны, но не удастся встряхнуть дерево. Ложные услуги будут включены как часть окончательного заявления, которое обычно не то, что требуется. Кроме того, нелегко включать и выключать режим имитации.

Я создал рабочий пример, который решает эти проблемы: https://github.com/westonpace/angular-example-mock-services

  1. Для каждого сервиса создайте абстрактный класс или маркер значения и интерфейс. Создать фиктивный сервис и реальный сервис, который реализует абстрактный класс/интерфейс.
  2. Создайте MockModule, который предоставляет все ваши фиктивные сервисы, и RealModule, который предоставляет все ваши реальные сервисы (обязательно используйте useClass/provide для предоставления интерфейса/абстрактного класса)
  3. В соответствующей environment.*.ts файлы environment.*.ts загружают либо RealModule, либо MockModule.
  4. Внесите изменения в файл angular.json используемый angular-cli для создания нового целевого mock сборки, который MockModule с помощью файла окружения mock, который внедряет MockModule. Создайте новую конфигурацию serve которая обслуживает сборку макета, чтобы вы могли выполнять ng serve -c mock. Измените конфигурацию транспортира по умолчанию так, чтобы она использовала цель ng e2e обслуживания, чтобы ng e2e работал против ваших поддельных служб.

Ответ 5

Для таких, как я, возникает следующая ошибка:

ERROR in Error encountered resolving symbol values statically.
Function calls are not supported. Consider replacing the function
or lambda with a reference to an exported function

Для моей цели это было для ErrorHandler:

{
    provide: ErrorHandler,
    useFactory: getErrorHandler,
    deps: [ Injector ]
}
...
export function getErrorHandler(injector: Injector): ErrorHandler {
    if (environment.production) {
        return new MyErrorHandler(injector);
    } else {
        return new ErrorHandler();
    }
}

Ответ 6

На этом посту представлено большое и простое решение Luuk G.

https://hackernoon.com/conditional-module-imports-in-angular-518294aa4cc

Вкратце:

let dev = [
StoreDevtoolsModule.instrument({
    maxAge: 10,
  }),
];

// if production clear dev imports and set to prod mode
if (process.env.NODE_ENV === 'production') {
  dev = [];
  enableProdMode();
}

@NgModule({
  bootstrap: [
    Base,
  ],
  declarations: [
    Base,
  ],
  imports: [
    Home,
    RouterModule.forRoot(routes, { useHash: true }),
    StoreModule.forRoot(reducers.reducerToken),
    ...dev,
  ],
  providers: [
  ],
})
export class Root { }

Ответ 7

Это сработало для меня в Angular 6. Использует подход @void Interface (где и настоящий сервис, и фиктивный сервис реализуют интерфейс сервиса). В принятом ответе не упоминается, что целью фабрики является возвращение реализации интерфейса.

heroservice/heroservice.module.ts

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IHeroService } from './ihero.service';
import { HeroService } from './hero.service';
import { MockHeroService } from './mock-hero.service';


@NgModule({
  imports: [
    CommonModule
  ],
})
export class HeroServiceModule {
  static forRoot(mock: boolean): ModuleWithProviders {
    return {
      ngModule: HeroServiceModule ,
      providers: [
        {
          provide: IHeroService,
          useClass: mock ? MockHeroService : HeroService,
        },
      ],
    };
  }
}

app.module.ts

import { NgModule, isDevMode } from '@angular/core';
import { HeroServiceModule } from './heroservice/heroservice.module';

// ...

  imports: [
    BrowserModule,
    HttpClientModule,
    // ...
    HeroServiceModule.forRoot(
      isDevMode() // use mock service in dev mode
    ),
  ],

// ...

некоторые /some.component.ts

Импортируйте и используйте интерфейс так же, как и реальный сервис (реализация будет "обеспечена" во время выполнения).

import { IHeroService } from '../heroservice/iheroservice.service';

// ...

Ответ 8

@Inject ('IHeroService') не подходит для производства. В моем случае я просто сделал это:

import { Injectable } from '@angular/core';
import * as Priority from 'priority-web-sdk';


@Injectable()
export class PriorityService
{
    priority;
    constructor( ){
        this.priority=Priority;
    }
}

В библиотеке Priority, которую я хотел импортировать, не было экспортированного модуля, поэтому мне нужно было немного обмануть. Сначала я попробовал метод @Inject('IHeroService'), но когда я скомпилировал его как prod, он не сработал.

Надеюсь, это поможет кому-то!