It’s said that when learning a new language or framework it is best to approach it fresh, with no preconceived ideas from other languages. So completely ignoring that advice here is my implementation of an ASP.NET web.config file in AngularJS.
I’m used to squirrelling away my config in one external file and I want to do that again in AngularJS. Really it’s the appSettings part of the web.config file that I’m implementing but the point is that all my config for the application is in one file that I can use throughout my application. I like it in .NET and I want to do it again here.
The implementation
App.Config.js
The first step is to implement a config object in an external file like so…
window.appConfig = { apiRoot: "http://localhost:3000/api/", debug: 0 };
We have just 2 config items at the moment, the root of the API and if it is in debug mode or not. In the normal run of things I wouldn’t want to pollute the global scope with JSON objects. I would wrap them in an IIFE. However this is a special case as it is genuinely global so I’m explicitly placing it in the global scope i.e. using the window object.
As it is an external file I need to remember to reference in the main application page.
<script src="app/config/app.config.js"></script>
App.Module.js
The next task is to reference the config in the main application file – app.module.js in this case. This is how I’m going to make it available throughout the entire application. Strictly speaking I don’t need to do this. As it is on the global scope I could leave it like that and directly reference it via the window object. I’d rather not do that. It’s going to make unit testing problematic in the future and anyway I don’t like it. Instead, I’m going to use the app.constant property in the main app.module file thus
(function () { app.config(function ($routeProvider, $locationProvider) { //.. set up the application – routing etc.. }); app.constant("appConfig", window.appConfig); })();
My naughty accessing of a global object is here and nowhere else. The config object is placed into app.constant which is then available throughout the application via dependency injection. I could have used app.value and it would work in much the same way. However I’m never going to change these values during the lifetime of the application so the constant property is more appropriate.
Consuming the config
The config is then available via dependency injection in controllers, services, directives e.g.
angular.module('app') .controller('MyController', ['appConfig', function (appConfig) { //.. app.config is now available });
In a service
The consumption in a service is the same. The use case here is a service can use the config to set the API url which could, should and will change in different environments.
angular.module('app') .service('MyService', ['$http', 'appConfig', function ($http, appConfig) { var serviceRoot = appConfig.apiRoot; this.get = function(id) { var url = serviceRoot + "myAPIMethod/" + id; return $http.get(url); }; //.. more methods }]);
In a directive
I’ve previous blogged about a debug panel that can be toggled on and off. In that post I used a query string parameter to turn the display of the debug information on and off. In truth I don’t really like that. This is the debug panel directive implemented with the web.config style config. As before it is injected in and it does a bit of DOM manipulation to remove the panel in the link method if debug is on. I prefer this way rather than my crazy query string implementation from before.
angular .module('app') .directive('debugDisplay', ['appConfig', function(appConfig){ return { scope: { display : "=display" }, templateUrl: "/views/directives/debugDisplay.html", link: function(scope, elem, attrs) { if(appConfig.debug == 0){ for(var i = 0; i < elem.length; i++) elem[i].innerText = ''; } } }; }]);
Deployment
One of the reasons that I like external config files is that I can have different settings in different versions of the files. I can then use some sort of task runner or deployment script to swap them out as it moves through different environments. So I could have the different app.configs in my project i.e.
- app.config (standard development one)
- app.devtest.config
- app.qa.config
- app.preprod.config
- app.prod.config
I can then delete the app.config file and rename the environment specific file so it is picked up in different environments. So when deploying to QA I would ….
- delete app.config
- rename app.qa.config to app.config
And as if by deployment magic I get different settings in QA. I currently use a bunch of PowerShell scripts to do this so it’s all automated but it can be done with whatever script or tool is most familiar or fashionable or nearest to hand.
Alternative implementations
As always, I’m not claiming that this is the best implementation for consuming application config. Global scope and config is going to be something that can be done in any number of ways. In fact I can think of several just while sat here typing in my pyjamas.
Global objects
I could just access the config directly wherever I like by using the global windows object like so..
angular.module('app') .service(‘MyService’, ['$http', function ($http) { var serviceRoot = window.appConfig.apiRoot; this.get = function(id) { var url = serviceRoot + "myAPIMethod/" + id; return $http.get(url); }; //.. more CRUD methods }]);
I personally don’t like having the windows object scattered through my code but it could be done. It’s not going to work well with any kind of unit testing though so unless you’re a no test kind of developer it’s probably best avoided.
Global scope
We could use $rootScope instead of app.constant to stash the external config file there. So accessing it would be a matter of grabbing the config from $rootScope thus
angular.module('app') .service(‘MyService’, [‘$rootScope’, '$http', function ($rootScope, $http) { var serviceRoot = $rootScope.appConfig.apiRoot; //.. CRUD methods }]);
I still like the idea of only the config being available in the method. I just get what I need, not the entire rootScope – that said this would work perfectly well.
Log Provider
This isn’t a complete replacement but it’s worth noting that debug config could be implemented through the $logProvider component if we wanted. To toggle on and off we would do this
$logProvider.debugEnabled(false);
It’s not a complete replacement but in the interests of full disclosure there are other (better??) ways to get debug information in and out of angular that have their own config.
Useful Links
https://ilikekillnerds.com/2014/11/constants-values-global-variables-in-angularjs-the-right-way/
Article on app.constant vs app.value
http://stackoverflow.com/questions/6349232/whats-the-difference-between-a-global-var-and-a-window-variable-in-javascript
Useful stack overflow question comparing the windows object and global scope.
http://odetocode.com/Articles/345.aspx
Ode to Code on appSettings and web.config. That’s the part of the web.config I’m aiming to emulate in this post.
https://thinkster.io/egghead/testing-overview
Testing with angular. Here’s what you might miss if you want to implement this with a global object.
http://stackoverflow.com/questions/18880737/how-do-i-use-rootscope-in-angular-to-store-variables
Using rootscope to store variables
https://thinkster.io/egghead/index-event-log
logProvider examples – about halfway down.