Introduction
So I've recently ramped up my interaction with Angular JS. One of the first things that I thought had to be improved and made much much easier was how the scripts get added to your index page.
This is totally dependent on your approach to Angular JS, and currently with my approach, this is a solution to a problem that occurred for me.
I tackle Angular JS using the conventions set out in the Angular Seed App.
Angular Construction
File Structure
So, I separate out my files for each "area", one file for area.controllers.js, one file for area.services.js so I build up a file structure like so:
- Angular\
- Account\
- account.controllers.js
- account.services.js
- Blog\
- blog.controllers.js
- blog.services.js
- App.js
So with this method of naming conventions and separations, it makes it incredibly easy to find a particular place in my app. If I got an error, I can be like "that happened in my account page, let's get to Account -> account.controllers.js" and there is no need to scan one file for the line that specified .controllers('accountController', function ()).
Code Structure
In my files, I like to carry on the naming convention:
angular.module('account.controllers', [])
.controllers('accountController', ['$scope', function ( $scope ) {
}]);
There are also other conventions I would like to point out, I use the dependency injection that Angular provides, apparently this helps .NET with minification. Also I use the []
in the module parameter, this instantiates a new module and in my mind a new scope.
So now you've seen []
you may ask how do you then use services that you create, etc.
Answer: I instantiate my Angular app like so:
angular.module('myApp', [
'ngRoute',
'ngResources',
'account.services',
'account.controllers'
]);
This way, all the dependencies get loaded up and everything works.
ASP.NET Problem
The BundleConfig.cs is the big problem here. This is the code:
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.validate*"));
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new StyleBundle("~/bundles/main/css").Include(
"~/Content/site.css",
"~/Content/font-awesome.css"));
}
}
Okay, so not the biggest problem in the world, you just create the bundle and add all the files to it.
bundles.Add(new ScriptBundle("~/bundles/angularapp").Include(
"~/Angular/Account/account.controllers.js"));
Even the small example here was a bit laborious to type out. This could easily be prone to mis-types.
One solution I have is this:
public static class BundleBuilder
{
public static Bundle Build(string bundleName, KeyValuePair<string, string[]>[] angularDictionary)
{
var virtualPaths = new List<string>();
foreach (var reference in angularDictionary)
{
foreach (var file in reference.Value)
{
var bundle = new StringBuilder();
bundle.Append("~/Angular/");
bundle.Append(reference.Key);
bundle.Append("/");
bundle.Append(reference.Key.ToLower());
bundle.Append(".");
bundle.Append(file);
bundle.Append(".js");
virtualPaths.Add(bundle.ToString());
}
}
var bundlePath = new StringBuilder();
bundlePath.Append("~/bundles/");
bundlePath.Append(bundleName.ToLower());
return new ScriptBundle(bundlePath.ToString()).Include(virtualPaths.ToArray());
}
}
public static class AccountBundle
{
public static KeyValuePair<string, string[]> Scripts()
{
return new KeyValuePair<string, string[]>("Account", new[] { "controllers", "filter" });
}
}
So now the BundleConfig.cs will look like this:
bundles.Add(new ScriptBundle("~/bundles/angularapp").Include(
"~/Angular/Account/account.controllers.js"));
bundles.Add(
BundleBuilder.Build(
bundleName: "angularapp",
angularDictionary: new[]
{
AccountBundle.Scripts()
}));
Points of Interest
- Code Readability
- Repetition
This way is very readable, you know there is an AccountBundle
for example and you know it generates the paths for the Scripts.
There are standards that you define when creating virtual paths and very little repitition. This minimises human error when writing the path out. Less of that "why isn't the whole functionality working, why hasn't the script been included, oh I missed a dot".
So now in the Razor view, we add this line:
<!---->
@Scripts.Render("~/bundles/angularapp");
Conclusion
So all in all, a pretty neat solution, I'm not saying it's the best, or the most optimised, just something I whipped up on the fly.