Как вводить различные службы на основе определенной среды сборки в 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
- Для каждого сервиса создайте абстрактный класс или маркер значения и интерфейс. Создать фиктивный сервис и реальный сервис, который реализует абстрактный класс/интерфейс.
- Создайте MockModule, который предоставляет все ваши фиктивные сервисы, и RealModule, который предоставляет все ваши реальные сервисы (обязательно используйте
useClass
/provide
для предоставления интерфейса/абстрактного класса) - В соответствующей
environment.*.ts
файлы environment.*.ts
загружают либо RealModule, либо MockModule. - Внесите изменения в файл
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, он не сработал.
Надеюсь, это поможет кому-то!