Replace angularJS DI with events to allow conditional build
While unifying the codebase for Gemnasium SaaS and Gemnasium Enterprise I have encountered situations where angularJS dependency injection prevents me from doing what I want.
A simple use case is the configuration of some services like analytics, monitoring or support tools when user logs in and out.
The point is: some services are not used within our Enterprise version and I don’t want to ship that code there.
Using the angularJS dependency injection in such cases is forcing me to always inject these services and condition their use at runtime.
Here is a simplified example with a sessionService that always injects an analyticsService to configure it when user logs in:
function login() {
/* Do some stuff that gives you a real user */
const user = { id: 1, name: ‘John Doe’} if (SAAS) { /* Only setup analytics on SAAS environment */
analyticsService.configure(user);
}
}
While it works, this is not how I want it to be and I already use Webpack power to only load what’s necessary for each environment.
A quick workaround is to use $injector.get(‘analyticsService’) to inject service at runtime instead. But be aware that this call must be wrapped in a try/catch block!
try {
const analyticsService = $injector.get('analyticsService');
} catch() {}function login() {
/* Do some stuff that gives you a real user */
const user = { id: 1, name: ‘John Doe’}
/* Only setup analytics if analyticsService exists */
if (analyticsService) {
analyticsService.configure(user);
}
}
While I can now load that code only for the SAAS environment I still get the same kind of runtime conditioning and I prefer to avoid that in my code: it’s harder to read and harder to test.
So I thought about adding an abstraction layer like a proxy service that would allow me to keep using dependency injection and call the specific service under the hood depending on the environment.
Here is an example of how a commonAnalyticsService would look like:
import analyticsService from ‘./analytics.service-APP_TARGET’;function configure(user) {
analyticsService.configure(user);
}
The SAAS specific service:
function configure(user) {
$window.ga(‘set’, ‘userId’, user.id);
}The ENTERPRISE specific service:
function configure() {
/* Do nothing here */
}If you’re wondering what this -APP_TARGET means in the import directive and how it works you need to read my previous article: Conditional build with Webpack.
As you can see, the drawback here is that I have to create a specific service for each environment which in that case ends up with a dummy service that does nothing :/
And this is when I remember:
The less code I have to maintain the happier I am!
So I considered using events instead. And it’s really easy to implement: I just have to emit a kind of “loggedIn” event when user logs in and listen to it somewhere to make the specific service to react on it.
Here is the (still simplified) sessionService that now injects $rootScope:
function login() {
/* Do some stuff that gives you a real user */
const user = { id: 1, name: ‘John Doe’}
$rootScope.emit('loggedIn', user);
}Then I use an angularJS run bloc that injects both analyticsService and $rootScope to initialize the event listener by calling this init function.
function init() {
/* Do some Stuff */
$rootScope.$on(‘loggedIn’, (event, user) => {
analyticsService.configure(user);
});
}The one and only analyticsService (which is the same as SAAS version):
function configure(user) {
$window.ga(‘set’, ‘userId’, user.id);
}And finally to only load that in the SAAS environment, I put the analyticsService and the run bloc in a dedicated analytics module which is included in the SAAS features module and not in the ENTERPRISE one (using the same Webpack conditional build).
The SAAS features module:
import analyticsModule from ‘app/analytics/analytics.module’;export default angular.module('features', [
analyticsModule.name
]);
The ENTERPRISE features module:
import fooModule from ‘app/foo/foo.module’;export default angular.module('features', [
fooModule.name
]);
So now in the SAAS environment the analytics stuff is initialized and will react to the event and in the ENTERPRISE environment… well, that code doesn’t exist so nothing happens :)
And the “proxy way” is not a total loss as it’s useful when the same feature is needed for both environments but its implementation differs . So I now have multiple ways to implement conditional features for Gemnasium Enterprise and Gemnasium SaaS depending on whether they are exclusive or just handled differently.
By the way if you have a better option, please feel free to share, and don’t forget to keep your projects in shape!

