github.com/seanhess/angularjs-typescript
Sean Hess
Code and Startups
- Twitter: @seanhess
- Blog: seanhess.github.io
- Github: github.com/seanhess
- started fast with JS
- house of cards
- testing was hard
- like waking up on Christmas with well-written, fast tests
- formalize your choices as you go
- modern features make it easy: optional, inferred
see mistakes right away, in context
autocomplete
- basically compiles ES6 to JavaScript
- plus a modern type system
Install Typescript
> npm install -g typescript
It is just JavaScript/ES6
function hello(name) {
alert("hello " + name)
}
hello("world")
Compile It
> tsc test.ts
Try these in the Typescript Playground: www.typescriptlang.org/Playground
var population:number = 3
var name:string = "hello"
var names:string[] = [name];
var user:User;
var couldBeAnything;
Produces output that looks the same
Make some rules and keep them
interface User {
firstName: string;
lastName: string;
}
// interface matches any object with the right fields
var user:User = {firstName:"Very", lastName:"User"}
var user2:User = {name:"Very User"} // error
function fullName(user:User):string {
return user.firstName + " " + user.lastName;
}
The compiler can read your mind
// name is a string
var name = fullName(user)
function sum(a:number, b:number) {
return a + b
}
var result = sum(name, 4)
Code for what things have in common
function firstValue<T>(array:T[]):T {
return array[0]
}
// these will give errors.
var one:string = firstValue([1,2,3,4,5])
var two:number = firstValue(["one", "two"])
External Modules: can output CommonJS or AMD
// users.ts
export function fullName(user:User):string {
return user.firstName + " " + user.lastName
}
// main.ts
import users = require("./users")
var name = users.fullName(user)
Internal Modules
module users {
export function fullName(user:User):string {
return user.firstName + " " + user.lastName
}
}
var name = users.fullName(user)
Classes
class Animal {
public size:number;
constructor() {
this.size = 0;
}
}
class Kitten extends Animal {
public furriness:number
devour(animal:Animal) {
this.size += animal.size
}
get isCute() {
return (this.size < 10 && this.furriness > 5)
}
}
Fat Arrow Functions
var service = {
names: [],
loadNames: function() {
$.get("/users", (users) => {
// cheap inline functions
var firstNames = users.map((user) => user.firstName)
// "this" still works!
this.names = firstNames
})
}
}
External type definition files for many libraries on DefinitelyTyped
declare module ng {
interface IScope {
$parent:IScope;
$eval(expressions:string):any;
$watch(expressions:string):any;
...
}
}
function MyController(scope:ng.IScope) {}
Error checking and Autocomplete:
- Sublime Text 3 - T3S - IDE-like
- Sublime Text 3 - Build Errors - Building, Errors
- Vim [1] [2]
- WebStorm - IDE
- Visual Studio - IDE
Start with TodoMVC, let's add Typescript to it
http://todomvc.com/architecture-examples/angularjs/
Make a file with shared application types: types.ts
. Data first design.
interface Todo {
completed: boolean;
title: string;
}
Add :Todo
to function signatures
function createTodo(text):Todo {
return {
title: text,
completed: false
}
}
- Get them from DefinitelyTyped
- Add
angular.d.ts
andjquery.d.ts
to a types folder - Add links to
types.ts
/// <reference path="./types/jquery/jquery.d.ts"/>
/// <reference path="./types/angularjs/angular.d.ts"/>
/// <reference path="./types/angularjs/angular-route.d.ts"/>
interface Todo {
completed: boolean;
title: string;
}
Start with todoCtrl.js
todomvc.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, todoStorage, filterFilter) {
var todos = $scope.todos = todoStorage.get();
...
})
todoCtrl.ts
: Add some interfaces for the scope and params
interface TodoCtrlScope extends ng.IScope {
todos:Todo[];
newTodo:string;
editedTodo:Todo;
originalTodo:Todo;
remainingCount:number;
completedCount:number;
allChecked:boolean;
status:string;
statusFilter:{completed:boolean};
addTodo();
editTodo(todo:Todo);
doneEditing(todo:Todo);
revertEditing(todo:Todo);
removeTodo(todo:Todo);
clearCompletedTodos(todo);
markAll(completed:boolean);
}
interface TodoCtrlRouteParams {
status:string;
}
Add types to the parameters
todomvc.controller('TodoCtrl', function TodoCtrl($scope:TodoCtrlScope, $routeParams:TodoCtrlRouteParams, todoStorage:TodoStorage, filterFilter) {
var todos = $scope.todos = todoStorage.get();
...
})
To use classes consider the view model method
Start with todoStorage.js
todomvc.factory('todoStorage', function () {
var STORAGE_ID = 'todos-angularjs';
return {
get: function () {
return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]');
},
put: function (todos) {
localStorage.setItem(STORAGE_ID, JSON.stringify(todos));
}
};
});
To make todoStorage.ts
, let's use a class
class TodoStorage {
static STORAGE_ID = 'todos-angularjs';
// dependencies would be injected here
constructor() {
}
get():Todo[] {
return JSON.parse(localStorage.getItem(TodoStorage.STORAGE_ID) || '[]');
}
put(todos:Todo[]) {
localStorage.setItem(TodoStorage.STORAGE_ID, JSON.stringify(todos));
}
}
for classes use .service()
instead of .factory()
todomvc.service('todoStorage', TodoStorage)
Lets you formalize an API
Alternatively, you could just add an interface
Add a build step to your Gruntfile.js
grunt.initConfig({
exec: {
tsPublic: { cmd: 'node_modules/.bin/tsc public/js/app.ts public/js/**/*.ts -t ES5'},
},
watch: {
public: {
files: ["public/**/*.ts"],
tasks: ["exec:tsPublic"],
options: { livereload: true },
},
}
})
grunt.registerTask('default', ['exec:tsPublic', 'watch'])
Then just run grunt
, and start saving files
> grunt
<!-- should be .title, but no error -->
<label>{{todo.text}}</label>
- simplest: just include all the generated .js files
- add a build script:
grunt concat
- namespaces? use internal modules
- with browserify or AMD: use external modules
- Let's add promises! What could go wrong?
- Typescript will save us!
- There can be only one
- was worth giving up Coffeescript
- Dart has a cool type system, but it is all-in.
- Rich models with classes
github.com/seanhess/angularjs-typescript
Concat Me: @seanhess
TODO: paste in images of IDEs