How to inject different service based on certain build environment in Angular2?
Solution 1:
See below my solution is based on @peeskillet one.
Create an interface that both the mock and real service implement, for your example IHeroService
.
export interface IHeroService {
getHeroes();
}
export class HeroService implements IHeroService {
getHeroes() {
//Implementation goes here
}
}
export class HeroMockService implements IHeroService {
getHeroes() {
//Mock implementation goes here
}
Assuming you've created the Angular app using Angular-CLI, in your environment.ts
file add the appropriate implementation, e.g.:
import { HeroService } from '../app/hero.service';
export const environment = {
production: false,
heroService: HeroService
};
For every different environment.(prod|whatever).ts
you have to define a heroService
pointing to the implementation and add the import.
Now, say you want to import the service in the AppModule class (you can do it on the component where you need the service as well)
@NgModule({
declarations: [
AppComponent,
],
imports: [
FormsModule,
HttpModule,
AlertModule.forRoot()
],
providers: [
{
provide: 'IHeroService',
useClass: environment.heroService
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
The important part is the provider:
providers: [
{
provide: 'IHeroService',
useClass: environment.heroService
}
Now wherever you want to use the service you have to do the following:
import { IHeroService } from './../hero.service';
export class HeroComponent {
constructor(@Inject('IHeroService') private heroService: IHeroService) {}
Sources: Is it possible to inject interface with angular2? https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be http://tattoocoder.com/angular-cli-using-the-environment-option/
Solution 2:
IMO, a better option would be to use the angular-in-memory-web-api.
note: this project was pulled into angular/angular from its old location.
It mocks the backend that Http
uses, so instead of making an actual XHR call, it just grabs the data that you provide to it. To get it, just install
npm install --save angular-in-memory-web-api
To create the database you implement the createDb
method in your 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 };
}
Then configure it
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
@NgModule({
imports: [
HttpModule,
InMemoryWebApiModule.forRoot(MockData, {
passThruUnknownUrl: true
}),
]
})
Now when you use Http
and make a request to /api/cats
it will get all the cats from the db. If you go to /api/cats/1
it will get the first cat. You can do all the CRUD operations, GET, POST, PUT, DELETE.
One thing to note is that it expects a base path. In the example /api
is the base path. You can also configure a root (this is different from base) path, in the configuration
InMemoryWebApiModule.forRoot(MockData, {
rootPath: 'root',
passThruUnknownUrl: true // forwards request not in the db
})
Now you can use /root/api/cats
.
UPDATE
In regards to the question about how to switch from dev to production, you can use a factory to create the providers. Same would be true if you were to use your mock service instead of the 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 ]
]
As far as the in-memory-web-api, I need to get back to you (I need to test a theory). I just started using it, and haven't gotten to the point where I need to switch to production. Right now I just have the above configuration. But I'm sure there's a way to make it work without having to change anything
UPDATE 2
Ok so for the im-memory-web-api what we can do instead of importing the Module, is to just provide the XHRBackend
that the module provides. The XHRBackend
is the service that Http
uses to make XHR calls. The in-memory-wep-api mocks that service. That's all the module does. So we can just provide the service ourselves, using a factory
@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 {
}
Notice the BrowserXhr
, XSRFStrategy
, and ResponseOptions
dependencies. This is how the original XHRBackend
is created. Now instead of importing the HttpModule
into your app module, just import the AppHttpModule
.
As far as the environment
, that's something you need to figure out. With angular-cli, the is already an environment that gets automatically switched to production when we build in production mode.
Here's the complete example I used to test with
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 {
}