问题
Context
In an Angular 9 project, I am working with two environments: production & mock.
In the Core Module, I check for mock environment.
If build is made with mock configuration I inject mocked services that return mocked data, so no external http requests are made.
If build is made with prod configuration, real services are injected.
I do it like this:
core.module.ts
@NgModule({
declarations: [],
providers: [],
imports: [BrowserModule, HttpClientModule],
exports: [],
})
export class CoreModule {}
country.service.proxy.ts
const countryServiceFactory = (
_http: HttpClient,
_errorUtil: ErrorUtilService
) => {
return isMock
? new ServiceMock()
: new Service(_http, _errorUtil);
};
@Injectable({
providedIn: CoreModule,
useFactory: countryServiceFactory,
})
export abstract class CountryServiceProxy {
abstract getCountries(): Observable<CountryWithLanguages[]>;
}
Where ServiceMock
and Service
implement the same interface.
This works.
Problem
Code is not tree shakeable. The result is that in my bundle (when I run ng build --prod
) even the mock services are included.
I want to switch each service from mock to prod during development.
Goal
How can I make Angular to bundle only the service that it is going to be used?
I am using:
Angular CLI: 9.0.4
Node: 13.6.0
OS: darwin x64
Ivy Workspace: Yes
Thank you! :)
回答1:
I have just tried one approach that seems to work:
- Declare the relevant service factory in your
environment.{env}.ts
files - Use the environment factory as your service provider
My test setup:
base class
@Injectable()
export abstract class TestService {
abstract environment: string;
}
dev service
@Injectable()
export class DevTestService extends TestService {
environment = 'qwertydev';
}
prod service
@Injectable()
export class ProdTestService extends TestService {
environment = 'qwertyprod';
}
environment.ts
export const environment = {
testServiceFactory: () => new DevTestService()
};
environment.production.ts
export const environment = {
testServiceFactory: () => new ProdTestService()
};
app.module.ts
providers: [
{ provide: TestService, useFactory: environment.testServiceFactory }
],
app.component.ts
constructor(private testService: TestService) {}
ngOnInit() {
console.log(this.testService.get());
}
When I inspect my build files, I only find qwertydev
in the dev build, and qwertprod
in the prod build, which suggests that they have been tree-shaken.
I used the strings qwerty*
to make it easy to search the build files after minification.
Declaring services in the module
I have declared the provider in the module to avoid circular references. It is easy to introduce a circular reference by declaring a service as providedIn: Module
.
You can work around this by declaring a third-party module, but this seems overkill.
I have demonstrated this in an older answer: @Injectable() decorator and providers array
Alternative approaches
It doesn't quite feel right declaring service factories in the environment files. I did it for testing just for simplicity. You could create you own set of environment-specific files that are overwritten at build time in the same way as the environment files, but quite frankly this sounds like a maintenance nightmare.
来源:https://stackoverflow.com/questions/60562363/how-to-make-angular-mock-service-tree-shakeable