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
- Guide for syntax, conventions, and structuring Angular applications
- https://github.com/johnpapa/angular-styleguide
Microsoft's TypeScript Coding Guidelines
- Useful in most parts, I disagree with some guidelines like Do not use "I" as a prefix for interface names
- https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines
Pluralsight Course
- Using TypeScript for Large AngularJS Applications
- This course examines how to use TypeScript with a large AngularJS application and uncovers the positives and negatives along the way.
- Uses the John Papa's style guide as a base
- http://www.pluralsight.com/courses/using-typescript-large-angularjs-apps
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,976