1

I'm working on an AngularJs/MVC app with Web API etc. which is using a CDN. I have managed to whitelist two URLs for Angular to use, a local CDN and a live CDN (web app hosted in Azure).

I can successfully ng-include a template from my local CDN domain, but the problem arises when I push the site to a UAT / Live environment, I cant be using a template on Localhost.

I need a way to be able to dynamically get the base url for the templates. The location on the server will always be the same, eg: rooturl/html/templates. I just need to be able to change the rooturl depending on the environment.

I was thinking if there was some way to store a global variable, possibly on the $rootScope somewhere that I can get to when using the templates and then set that to the url via Web API which will get return a config setting.

For example on my dev machine the var could be http://Localhost:52920/ but on my uat server it could be https://uat-cdn.com/

Any help would be greatly appreciated as I don't want to store Js, css, fonts etc on the CDN but not the HTML as it feels nasty.

Thanks I'm advance!

Matt Brewerton
  • 866
  • 1
  • 6
  • 23
  • You can use the $location service to retrieve the host, port and the protocol if you whish – Raulucco Jan 26 '16 at 09:20
  • Of course! Would you recommend using $location to build up the CDN url and then storing it on the $rootScope for access in the HTML? – Matt Brewerton Jan 26 '16 at 09:42
  • Maybe do it on a service, factory, value or constant that will contain the url, better than the rootScope – Raulucco Jan 26 '16 at 09:44
  • @Raulucco That seems like a much better approach, would I be able to use the factory/service directly from the HTML? – Matt Brewerton Jan 26 '16 at 10:06
  • No, for that you need a filter. Lets say you create the factory and then you create a filter that uses the factory to build the url so you can create links on your page – Raulucco Jan 26 '16 at 10:07

2 Answers2

2

I think it's good practice to keep environment and global config stuff outside of Angular altogether, so it's not part of the normal build process and is harder to accidentally blow away during a deploy. One way is to include a script file containing just a single global variable:

var config = {
    myBaseUrl: '/templates/',
    otherStuff: 'whatever'
}

...and expose it to Angular via a service:

angular.module('myApp')
    .factory('config', function () {
        var config = window.config ? window.config : {}; // (or throw an error if it's not found)

        // set defaults here if useful
        config.myBaseUrl = config.myBaseUrl || 'defaultBaseUrlValue';
        // etc

        return config;
}

...so it's now injectable as a dependency anywhere you need it:

.controller('fooController', function (config, $scope), {
    $scope.myBaseUrl = config.myBaseUrl;
}

Functionally speaking, this is not terribly different from dumping a global variable into $rootScope but I feel like it's a cleaner separation of app from environment.

Daniel Beck
  • 20,653
  • 5
  • 38
  • 53
  • Hi, thanks for the answer. I managed to get it to work by using the $rootScope in my main app file. It's not much different to what you suggested, just kept it on the $rootScope. – Matt Brewerton Feb 03 '16 at 09:23
  • What I actually ended up doing was I create a resource and a service in Angular that gets my URL via Web API and then adds that to the rootscope as `$rootscope.cdnUrl` in a top-level controller that's on a `
    ` that wraps my app. I can then access `$rootScope.cdnUrl` from anywhere in my app.
    – Matt Brewerton Feb 03 '16 at 13:04
  • Nice, that works. (Now that you mention it, my factory could as easily get its data via ajax instead of from a `window` variable; though I'd then have to do the usual Promises dance while it loads.) If your code is intended to be shared as a module in other apps, you'll want to avoid depending on `$rootScope` of course, but for standalone apps, hey, it's there, why not use it – Daniel Beck Feb 03 '16 at 13:21
1

If you decide to create a factory then it would look like this:

angular.module('myModule', [])
.factory('baseUrl', ['$location', function ($location) {

  return {
    getBaseUrl: function () {
     return $location.hostname;
    }
  };
}]);

A provider could be handy if you want to make any type of customization during config. Maybe you want to build the baseurl manually instead of using hostname property.

If you want to use it on the templates then you need to create a filter that reuses it:

 angular.module('myModule').filter('anchorBuilder', ['baseUrl', function (baseUrl) {
   return function (path) {
     return baseUrl.getBaseUrl() + path;
   }
}]);

And on the template:

<a href="{{'/some/path'|anchorBuilder}}"></a>

EDIT

The above example was to create links but if you want to use it on a ng-include directive then you will have a function on your controller that uses the factory and returns the url.

// Template

<div ng-include src="urlBuilder('path')"></div> 

//Controller

$scope.urlBuilder = function (path) {
   return BaseUrl.getBaseUrl() + path;
};

Make sure to inject the factory in the controller

Raulucco
  • 3,406
  • 1
  • 21
  • 27
  • Thanks for the example, I'll give that a try. – Matt Brewerton Jan 28 '16 at 21:46
  • I tried this and while I was successfully able to get the url from the back-end and create the url within the filter, but it doesn't seem to work in the front-end. I can't even write it out to the DOM with: {{'/html/testtemplate.html' | cdnUrl}}. When I try to do it with
    it just doesn't show.
    – Matt Brewerton Feb 01 '16 at 13:40
  • Here's my filter: `return function (path) { cdnUrlResource.getCdnUrl().$promise.then( function (url) { var fullPath = url.url + path; console.log("returning: ", fullPath); return fullPath; } ); };` From my console.log I get this: "`returning: http://localhost:55986/html/testtemplate.html`" – Matt Brewerton Feb 01 '16 at 13:43
  • @MattBrewerton I added the `ng-include` example to the response – Raulucco Feb 01 '16 at 13:52
  • I tried that as well, but I got a Syntax error: `Syntax Error: Token '{' invalid key at column 2 of the expression [{{cdnUrl('/html/testtemplate.html')}}] starting at [{cdnUrl('/html/testtemplate.html')}}].` – Matt Brewerton Feb 01 '16 at 13:59
  • @MattBrewerton My bad. The src attibute is already an expresion so no need to wrapp it on double curly braces. Try `
    ` I removed it on the unswer
    – Raulucco Feb 01 '16 at 14:06
  • I'm trying it like that, but it just fails silently :( I'm not getting my console.log from my filter either, this is so frustrating! I'm obviously doing something really small wrong :/ – Matt Brewerton Feb 01 '16 at 14:18
  • @MattBrewerton Maybe the function call doesn't work as i expected. Still you can try to build it directly on the src attribute: ``. And instead of using the the function save the baseUrl on a variable: `$scope.baseUrl = BaseUrl.getBaseUrl();` – Raulucco Feb 01 '16 at 14:24
  • @MattBrewerton I found that the function call should work. It could be just a typo some where. http://stackoverflow.com/questions/16616545/angularjs-nginclude-and-dynamic-urls – Raulucco Feb 01 '16 at 14:26