ASP.NET Core 1.0 Typescript AngularJS application with a Grunt production configuration

This article demonstrates how to implement an AngularJS application using Typescript and grunt in ASP.NET Core 1.0. The application is built using Typescript and based on this example. The application uses grunt as its task runner. The grunt task runner produces a production configuration and can also be used in a CI process on any build server.

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

2016.07.01: Updated to ASP.NET Core 1.0 RTM
2015.06.11: Updated to ASP.NET Core 1.0 RC2 dotnet
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

grunt setup

The grunt is setup so that the application uses only 4 files for all its javascript and css dependencies. This could also be optimized down to 2 files if preferred. The index.html file is implemented as follows: (You can see from the file that angular and angular-ui-router is used.)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo typescript ASP.NET5</title>
    <link href="lib/mycode.min.css" rel="stylesheet" />
    <link href="lib/vender.min.css" rel="stylesheet" />
</head>
<body>
    
    <div ng-app="myapp">
        <div ui-view></div>
    </div>

    <script src="lib/vendor.min.js"></script>
    <script src="lib/mycode.min.js"></script>
</body>
</html>

This index file uses the javacript and the css files produced in the grunt tasks.The grunt file has two main tasks: development and buildserver. The development task creates the javascript files from the typescript files using grunt-ts, then concats all the vendor javascript files into a single file, uses uglify to produce a single javascript file from all the source javascript files and uses cssmin to create a single css minified file from the different css sources. The only difference between the buildserver and the development task is the watch task. This is not needed for the buildserver.

    grunt.registerTask('development', ['ts', 'concat', 'uglify', 'cssmin', 'watch']);
    grunt.registerTask('buildserver', ['ts', 'concat', 'uglify', 'cssmin']);

The following grunt npm tasks are required, defined at the top of the grunt file:

  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks("grunt-bower-task");
  grunt.loadNpmTasks("grunt-contrib-concat");
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks("grunt-ts");

The grunt npm packages are defined in the package.json file.

{
    "name": "package",
    "version": "1.0.0",
    "private": true,
    "devDependencies": {
        "grunt": "0.4.5",
        "grunt-bower-install": "^1.6.0",
        "grunt-bower-task": "0.4.0",
        "grunt-contrib-cssmin": "^0.12.2",
        "grunt-contrib-uglify": "0.9.1",
        "grunt-contrib-watch": "0.6.1",
        "grunt-contrib-concat": "0.5.1",
        "uglify-js": "2.4.20",
        "grunt-ts": "4.0.1"
    }
}

Once the npm packages have been downloaded, the grunt.initConfig can be used to define each of the single steps.

grunt.initConfig({
});

grunt-ts

The grunt-ts is configured to just search for all ts files in a certain folder (app) and produce the javascript files with maps from the source files.

ts: {
   default : {
                src: ["app/**/*.ts"]
            }
},

grunt concat

The concat task concats all the third party min javascript files into one single vendor.min.js file. This saves http traffic when loading the html page.

concat: {
            vendorjs: {
                src: [
                    "lib/jquery/jquery.js",
                    "lib/angular/angular.min.js",
                    "lib/angular-ui-router/angular-ui-router.min.js",
                    "lib/angular-animate/angular-animate.min.js",
                    "lib/bootstrap/bootstrap.min.js",
                    "lib/angular-bootstrap/ui-bootstrap-tpls.min.js"
                ],
                dest: "wwwroot/lib/vendor.min.js"
            }
        },

grunt cssmin

The cssmin task is used to produce two css files, one for the vendor files and one for the application css. This could be optimized down to a single css file.

 cssmin: {
            options: {
                shorthandCompacting: false,
                roundingPrecision: -1
            },
            mycode: {
                files: {
                    'wwwroot/lib/mycode.min.css': [
                       "content/main.css"
                    ]
                }
            },
            vendor: {
                files: {
                    'wwwroot/lib/vendor.min.css': [

                          "lib/bootstrap/bootstrap.min.css",
                    ]
                }
            }
        },

grunt uglify

The uglify task uses all the javascript files produced in the grunt-ts task and produces a single min file for production.

 uglify: {
            my_min_files: {
                files: {
                    'wwwroot/lib/mycode.min.js': [
                        "app/app.js",
                        "app/services/FastestAnimalService.js",
                        "app/controllers/DetailsController.js",
                        "app/controllers/OverviewController.js",

                    ]
                }
            }
        },

grunt watch

The watch task is used for development and runs every time a javascript or a css file is changed so that the application can be tested on the fly.

watch: {
            appFolderScripts: {
                files: ['app/**/*.js'],
                tasks: ['uglify']
            },
            appFolderCss: {
                files: ['content/**/*.css'],
                tasks: ['cssmin']
            }
        },

Build server

The build server task can be run on any build server. The npm needs to be installed globally on the build server. This is done as follows:

npm install -g grunt-cli

Then the grunt buildserver task can be executed from a build server command line task in the root directory of the web application. If the build agent uses a different user, for example system, the path to the grunt.cmd needs to be included in the build server cmd task. git also needs to be added to the build server windows path.

grunt bower

The bower task is configured to run bower and deploy to the lib folder. This lib folder is outside the wwwroot folder, so it will not be included in a release package. The other grunt tasks use these to produce the vendor.min.js and vender.min.css which are deployed inside the wwwroot folder.

bower: {
            install: {
                options: {
                    targetDir: "lib",
                    layout: "byComponent",
                    cleanTargetDir: false
                }
            }
        },

Because a productive deployment is used, the default bower exports need to be reconfigured to include the min files as most packages don’t export this for some strange reason. This is done in the bower.json file.

{
    "name": "bower",
    "license": "Apache-2.0",
    "private": true,
    "dependencies": {
        "bootstrap": "3.3.2",
        "angular": "1.3.15",
        "angular-ui-router": "0.2.14",
        "angular-animate": "1.3.15",
        "angular-bootstrap": "0.13.0",
        "DefinitelyTyped": "*"
    },
    "exportsOverride": {
        "bootstrap": {
            "": "dist/**/*.*"
        },
        "angular": {
            "": "angular{.js,.min.js,.min.js.map, .min.js.gzip}"
        },
        "angular-animate": {
            "": "angular-animate{.js,.min.js,.min.js.map}"
        },
        "angular-ui-router": {
            "": "release/angular-ui-router.{js,min.js,min.js.map}"
        },
        "angular-bootstrap": { 
            "": "*.*"
        }
    }
}

The full grunt file can be viewed here.

taskrunner_01

Now that the build is setup, some Typescript files can be implemented. The DetailsController is implemented in Typescript as follows:

module demo {
    'use strict';

    export interface IDetailsController extends ng.IScope {
        Vm: DetailsController;
    }

    export class DetailsController {

        private scope: ng.IScope;
        private log: any;

        public Animal: IFastestAnimal;
        public Message: string;
        public Vm: IDetailsController;

        constructor(scope: any, log: any, fastestAnimal: IFastestAnimal) {
            scope.Vm = this;
            this.log = log;
            this.Message = "Animal Details";
            this.log.info("DetailsController called");
            this.Animal = fastestAnimal;
        }
    }

    var app = angular.module('myapp');

    app.controller('DetailsController', ["$scope", "$log", "fastestAnimal", DetailsController]);
};

And the Angular FastestAnimalService which uses the Web API back end is implemented as follows:

module demo
{
    'use strict';

    export class FastestAnimalService
    {
        constructor(private $http: any, private $log: any)
        {
            this.$log.info("FastestAnimalService called");
        }

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

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

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

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

The app.ts uses the angular defined type which can be downloaded as a bower package.

/// <reference path="../bower_components/DefinitelyTyped/angularjs/angular.d.ts"/>

module demo {
    'use strict';

    var demoapp = angular.module('myapp', [
        'ngAnimate',
        'ui.router',
        'ui.bootstrap',

    ]);

    demoapp.config(["$stateProvider", "$urlRouterProvider", ($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", (FastestAnimalService) => {
                    return FastestAnimalService.getAnimals();
                }]
            }
        })
        .state("details", {
            parent: "overview", url: "/details/:animalId", templateUrl: "/templates/details.html", controller: "DetailsController",
            resolve: {
                FastestAnimalService: "FastestAnimalService",

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

The application runs as before, but with only four extra http request for the javascript and the css dependencies:

aspnet5_typescript_02

Conclusion

When producing web applications, you should not have too many javascript and css links which produces lots of http requests to the server for a single page. These should be optimized down to single files where possible. This example shows you how to do this. Most examples link the javascript files directly in the production files which is bad practice.

When using Typescript, one problem with this approach is that the Typescript files cannot be debugged in Visual Studio using IE. No matter how the map files are configured, the single Typescript files cannot be debugged using IE and Visual Studio once uglified to a single file. Maybe someone has a solution and I would be very grateful…

Another problem is that in Visual Studio 2015 RC, editing Typescript files in Visual Studio causes major performance issues in Visual Studio. Hopefully this will be fixed before the official release.

Links

TypeScript, AngularJS, Gulp and Bower in Visual Studio 2015

4 comments

  1. Reblogged this on Doctor Who.

  2. Thank you Damien for this well explained tutorial! Learned alot.

  3. […] file using grunt in a ASP.NET 5 application. The post uses the grunt build configuration from this blog, ( ASP.NET 5 Typescript AngularJS application with a grunt production configuration […]

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 )

Facebook photo

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

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: