ASP.NET Core 1.0 AngularJS application using angular-ui-router

This article shows how to use angular-ui-router in an ASP.NET Core 1.0 application. The ui-router module is integrated into the application using bower and grunt and implements hierarchical routes with data resolves in different states.

Code: https://github.com/damienbod/AspNet5AngularJSDynamicRoutes

2016.07.01: Updated to ASP.NET Core 1.0 RTM
2016.05.19: Updated to ASP.NET Core 1.0 RC2
2015.11.18: Updated to ASP.NET Core 1.0 RC1
2015.10.20: Updated to ASP.NET Core 1.0 beta 8
2015.09.20: Updated to ASP.NET Core 1.0 beta 7

Project setup

The application is setup as described in the excellent article from Stephan Walter. Once the grunt uglify, bower and watch tasks are setup, angular-ui-router can be added using bower.

package.json

The npm package.json requires the following npm packages:

{
    "version": "0.0.0",
    "name": "",
    "devDependencies": {
        "grunt": "0.4.5",
		"grunt-bower-task": "0.4.0",
		"grunt-contrib-uglify": "0.8.0",
        "grunt-contrib-watch": "0.6.1"
	
    }
}

gruntfile.js

The grunt file is configured as described by Stephan Walter. The uglify task deploys everything as a single javascript file and the watch checks for changes in the app folder and deploys the changes to the wwwroot folder using uglify.

The gruntfile.js is as follows:

module.exports = function (grunt) {

	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-watch');

	grunt.initConfig({
		bower: {
			install: {
				options: {
					targetDir: "wwwroot/lib",
					layout: "byComponent",
					cleanTargetDir: false
				}
			}
		},
		uglify: {
			my_target: {
				files: { 'wwwroot/app.js': ['app/app.js', 'app/**/*.js'] }
			}
		},
		watch: {
			scripts: {
				files: ['app/**/*.js'],
				tasks: ['uglify']
			}
		}
	});

	grunt.registerTask("default", ["bower:install"]);
	grunt.registerTask('default', ['uglify', 'watch']);

	grunt.loadNpmTasks("grunt-bower-task");
};

bower.json

angular and angular-ui-router are added to the dependencies in the bower file as follows:

"dependencies": {
  "angular": "1.3.15",
  "angular-ui-router": "0.2.13"
},

Once the bower dependencies have been added, you need to update the packages in the Dependencies/Bower folder inside the ASP.NET 5 project.

Then the bower exportsOverride can be set for your specific requirements.

"angular": {
   "": "angular{.js,.min.js,.min.js.map, .min.js.gzip}",
   "css": "angular-csp.css"
},
"angular-ui-router": {
   "": "release/angular-ui-router.{js,min.js,min.js.map}"
}

Here’s the full bower.json file which is used in the application:

{
	"name": "WebApplication",
	"private": true,
	"dependencies": {
		"bootstrap": "3.3.2",
		"jquery": "1.10.2",
		"jquery-validation": "1.11.1",
		"jquery-validation-unobtrusive": "3.2.2",
		"hammer.js": "2.0.4",
		"bootstrap-touch-carousel": "0.8.0",
		"angular": "1.3.15",
		"angular-ui-router": "0.2.13"
	},
	"exportsOverride": {
		"bootstrap": {
			"js": "dist/js/*.*",
			"css": "dist/css/*.*",
			"fonts": "dist/fonts/*.*"
		},
		"bootstrap-touch-carousel": {
			"js": "dist/js/*.*",
			"css": "dist/css/*.*"
		},
		"jquery": {
			"": "jquery.{js,min.js,min.map}"
		},
		"jquery-validation": {
			"": "jquery.validate.js"
		},
		"jquery-validation-unobtrusive": {
			"": "jquery.validate.unobtrusive.{js,min.js}"
		},
		"angular": {
			"": "angular{.js,.min.js,.min.js.map, .min.js.gzip}",
			"css": "angular-csp.css"
		},
		"angular-ui-router": {
			"": "release/angular-ui-router.{js,min.js,min.js.map}"
		}
	}
}

Adding ui.router to the angularJS application

To use ui.router, it needs to be added to your angular module inside the app.js file.

var mainApp = angular.module("mainApp", ["ui.router"]);

Once the route module has been added, the first thing you should do, is add some error handling for the routing. The will save a lot of time when debugging resolve errors or incorrect routing URLs. This can be added in the module run method. The $rootScope contains the events for the ui.router module. The code example, logs exceptions to the browser console. It can be removed in production, or you could implement the logging in a different way.

mainApp.run(["$rootScope", function ($rootScope) {

	$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
		console.log(event);
		console.log(toState);
		console.log(toParams);
		console.log(fromState);
		console.log(fromParams);
		console.log(error);
	})

	$rootScope.$on('$stateNotFound', function (event, unfoundState, fromState, fromParams) {
		console.log(event);
		console.log(unfoundState);
		console.log(fromState);
		console.log(fromParams);
	})

}]);

Now the routing definitions can be added. It is important that it is implemented inside an array so that it can be compressed by uglify. You could also implement this using $inject. The following example adds three states; home, overview and details. The home state is the main state and also an abstract state. The overview state is a child state of the home state. This implements a resolve which gets a list of the fastest animals from the server. The details state is a child of the overview state and can use its parent’s resolve data.

mainApp.config(["$stateProvider", "$urlRouterProvider",
	function ($stateProvider, $urlRouterProvider) {
		$urlRouterProvider.otherwise("/home/overview");

		$stateProvider
			.state("home", { abstract: true, url: "/home", templateUrl: "/templates/home.html" })
				.state("overview", {
					parent: "home", url: "/overview", templateUrl: "/templates/overview.html", controller: "OverviewController",
					resolve: {

						FastestAnimalService: "FastestAnimalService",

						fastestAnimals: ["FastestAnimalService", function (FastestAnimalService) {
							return FastestAnimalService.getAnimals();
						}]
					}
				})
					.state("details", {
						parent: "overview", url: "/details/:animalId", templateUrl: "/templates/details.html", controller: "DetailsController",
						resolve: {
							FastestAnimalService: "FastestAnimalService",

							fastestAnimal: ["FastestAnimalService", "$stateParams", function (FastestAnimalService, $stateParams) {
								var animalId = $stateParams.animalId;
								console.log($stateParams.animalId);
								return FastestAnimalService.getAnimal({ animalId: animalId });
							}]
						}
					})
		}
]);

The details state uses a state parameter called animalId. This parameter is also used in the resolve method which returns the selected fastest animal. The resolve is executed and then the state is loaded with the view template. Again the fastestAnimal object is implemented inside an array so that the uglify will work.

.state("details", {
	parent: "overview", url: "/details/:animalId", templateUrl: "/templates/details.html", controller: "DetailsController",
	resolve: {
		FastestAnimalService: "FastestAnimalService",

		fastestAnimal: ["FastestAnimalService", "$stateParams", function (FastestAnimalService, $stateParams) {
			var animalId = $stateParams.animalId;
			console.log($stateParams.animalId);
			return FastestAnimalService.getAnimal({ animalId: animalId });
		}]
	}
})

This fastestAnimal object can then be used inside the DetailsController. The object is added in the constructor and added to the scope. This can then be used in the details template view.

(function () {
	'use strict';

	var module = angular.module('mainApp');

	// this code can be used with uglify
	module.controller('DetailsController',
		[
			'$scope',
			'$log',
			'fastestAnimal',
			DetailsController
		]
	);

	function DetailsController($scope, $log, fastestAnimal) {
		$log.info("DetailsController called");
		$scope.message = "Animal Details";

		$scope.animal = fastestAnimal;

	}

})();

The child states are added using the ui-view directive. If using child states, the parent view template should contain this. Here’s an example of the home template which contains a ui-view directive for its child view overview:

<div><h4 class="panel-heading">Home View</h4></div>

<hr />

<div class="col-xs-6" ui-view></div>

The overview child states are added using the ng-repeat directive. These could also be added as a dynamic menu or whatever.

<tr style="height:20px;" ng-repeat="animal in animals">
  <td><a href="#/home/overview/details/{{animal.Id}}">{{animal.AnimalName}}</a></td>
  <td>{{animal.Speed}}</td>
</tr>

The FastestAnimalService which is used inside the state resolve methods, is a service which sends HTTP requests the the Web API service on the server. The FastestAnimalService has 2 public methods, getAnimals and getAnimal which are implemented inside an iffy. The service is added to the mainApp module, also inside an array so that it can be used by uglify.

(function () {
	'use strict';

	function FastestAnimalService($http, $log) {

		$log.info("FastestAnimalService called");

		var getAnimals = function () {
			$log.info("FastestAnimalService getAnimals called");
			return $http.get("/api/FastestAnimal")
			.then(function (response) {
				return response.data;
			});
		}

		var getAnimal = function (animalId) {
			$log.info("FastestAnimalService getAnimal called: " + animalId);
			$log.info(animalId);
			return $http.get("/api/FastestAnimal/" + animalId.animalId)
			.then(function (response) {
				return response.data;
			});
		}

		return {
			getAnimals: getAnimals,
			getAnimal: getAnimal
		}
	}

	var module = angular.module('mainApp');

	// this code can be used with uglify
	module.factory("FastestAnimalService",
		[
			"$http",
			"$log",
			FastestAnimalService
		]
	);

})();

The C# service controller is implemented as follows:

using System;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using AspNet5AngularJSDynamicRoutes.Models;

namespace Controllers
{
	[Route("api/[controller]")]
	public class FastestAnimalController : Controller
	{

		[HttpGet]
		[Route("")]
		public IEnumerable<FastestAnimal> Get()
		{
			return _animals;
		}

		[HttpGet]
		[Route("{id}")]
		public FastestAnimal Get(long id)
		{
			return _animals.Find(t => t.Id == id);
		}
	}
}

The application can then be viewed.
AspNet5AngularJsRouting

Conclusion

ASP.NET 5 provides great support for the standard frontend package management and provides a great development experience. Angular-ui-router is a good module which provides many advantages over the ngRoute module. Nested views or pre-loading, post-loading events are supported out of the box and provides a good state management structure which should support most UI requirements.

Links:

ASP.NET 5 and AngularJS Part 1, Configuring Grunt, Uglify, and AngularJS

https://github.com/angular-ui/ui-router/wiki

http://plnkr.co/edit/4E1Jy7XCvLRbOSu0muvL?p=preview

https://d2eip9sf3oo6c2.cloudfront.net/pdf/egghead-io-ui-router-cheat-sheet.pdf

2 comments

  1. […] ASP.NET 5 angularJS application using angular-ui-router – ‘damienbod’ is also looking at Angular Routing, looking at getting it all working alongside an ASP.NET 5 application […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: