AngularJS + Typescript

How to set up AngularJS + Typescript + Jasmine in Visual Studio

Sample Project in GitHub

Resources and Conventions

John Papa's Angular style guide

Microsoft's TypeScript Coding Guidelines

Pluralsight Course

Nugets

Recommended

  • angularjs
  • jquery.TypeScript.DefinitelyTyped
  • angularjs.TypeScript.DefinitelyTyped

Nice to Have

  • ui-select -> bootstrap style select box
  • angular-toastr -> toaster messages
  • toastr.TypeScript.DefinitelyTyped

Extensions

Web Essentials

TypeScript, LESS, Markdown, Javascript  tools and all kinds of other goodies

Chutzpah Test Adapter for the Test Explorer

This extension allows users to run QUnit, Jasmine and Mocha unit tests inside of the new Unit Test Explorer

Conferences Application

Overview

  • Simple conference and speaker listing with CRUD capabilities
  • Uses ASP.NET MVC and Angular
  • Not a SPA app
  • Doesn't use Angular routing

Angular Structure

app.module

application modules and 3rd party references

((): void => {
    "use strict";

    angular.module("app", [
        'app.services',
        'app.conferences',
        'app.components',
        'ngSanitize',
        'ngAnimate',
        'ui.select',
        'toastr'
    ]);
})(); 

Services

  • dataservice for AJAX calls
  • see structure on next slide
module app.services {
    'use strict';

    export interface IDataService {        
        ...
    }

    class DataService implements IDataService {
      
        ...

        constructor(private $http: ng.IHttpService, private $q: ng.IQService, private toastr: Toastr) {
        }
    }

    factory.$inject = [
        '$http',
        '$q',
        'toastr'
    ];

    function factory($http: ng.IHttpService, $q: ng.IQService, toastr: Toastr) {
        return new DataService($http, $q, toastr);
    }

    angular
        .module('app.services')
        .factory('dataservice',
            factory);
}

Example of $http.post

createConference(conference: server.Conference): ng.IPromise<any> {
    return this.$http
        .post('/Home/Conference/Create/', conference)
        .then((response: ng.IHttpPromiseCallbackArg<any>): ng.IPromise<any> => {
            return response.data;
        })
        .catch((reason: any): any => {
            this.toastr.error('Error when creating conference: ' + reason.statusText);
            return this.$q.reject(reason);
        });
}

Directives

Directive for displaying conferences

module app.components {
    'use strict';
   
    class ConferenceThumbnailDirective implements ng.IDirective {
        static instance(): ng.IDirective {
            return new ConferenceThumbnailDirective;
        }

        restrict = 'E';
        templateUrl = '../app/components/conference.directive.html';
        
        scope = { conference: "=conference"};
    }

    angular
        .module('app.components')
        .directive('appConferenceThumbnail', ConferenceThumbnailDirective.instance);
}

Features

Controllers for conference listing, details, editing, creating

module app.conferences {
    'use strict';

    interface IConferenceScope {
        // Variables
        conferences: server.Conference[];
    }

    class ConferenceController implements IConferenceScope {

        // Variables
        conferences: server.Conference[];

        static $inject = ['app.conferences.data'];

        constructor(data: server.Conference[], dataservice: app.services.IDataService) {
            var vm = this;            
            vm.conferences = data;                       
        }
    }
    angular
        .module('app.conferences')
        .controller('Conference', ConferenceController);
}

Bootstrapping and References

<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Conference app</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body ng-app="app" ng-cloak>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
        ...
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Scripts.Render("~/bundles/angular")
    @Scripts.Render("~/bundles/app")
    @RenderSection("scripts", false)
</body>
</html>

ControllerAs

<div ng-controller="Conference as conference">
    <div data-ng-repeat="conference in conference.conferences">
        <app-conference-thumbnail conference="conference"></app-conference-thumbnail>
    </div>
</div> <!-- /conference -->

Injecting Model

@model List<AngularAndTypeScriptExample.Models.Conference>

...

@section scripts {
    <script type="text/javascript">
        angular.module("app.conferences").value("app.conferences.data", 
            @Html.Raw(CamelCaseJsonConvert.SerializeObject(Model)));
    </script>
}

Testing

Unit Testing

  • You can create unit tests with Jasmine
  • https://github.com/jasmine/jasmine
  • For directives testing, use Karma. Why? Because at unit testing loading the directive's html template is a pain

References

  • You need to add references of angular-, 3rd party and app files in your unit test
  • Easy way is to add them to a _references file, and add that as a reference in the unit test
/// <reference path="../Scripts/jquery-2.1.4.js"/>
/// <reference path="../Scripts/underscore.js"/>
/// <reference path="../Scripts/angular.js" />
/// <reference path="../Scripts/angular-mocks.js" />
/// <reference path="../Scripts/angular-sanitize.js" />
/// <reference path="../Scripts/angular-animate.js" />
...

Unit Test Structure

/// <reference path="_references.js" />
describe('Dataservice', function () {
    var $httpBackend, loggerMock, mockDataservice;
    beforeEach(function () {
        module('app');       
        loggerMock = jasmine.createSpyObj('toastr', ['error']);        

        module(function ($provide) {            
            $provide.value('toastr', loggerMock);            
        });
    });

    beforeEach(inject(function (_$httpBackend_, dataservice) {
        $httpBackend = _$httpBackend_;
        mockDataservice = dataservice;
    }));

    it('should succesfully get speakers', function () {
        ...
    });
});

Example Test

it('should succesfully get speakers', function () {

    var speakers = [{id: 1, name: 'test'}];

    $httpBackend.whenGET('/api/Speakers').respond(200, speakers);

    var promise = mockDataservice.getSpeakers();
    var actual;
    promise.then(function(data) {
        actual = data;
    });
    $httpBackend.flush();

    expect(actual).toEqual(speakers);
});

Jasmine and Test Explorer

With a Chutzpah extension, you can run the tests using VS Test Explorer

AngularJS + Typescript

By Jarmo Jokela

AngularJS + Typescript

How to set up Visual Studio environment to run AngularJS, Typescript and Jasmine

  • 1,894