A little while ago I started using Typescript with the Angular 1.5 app I'm working on, to help ease the migration path to Angular 2. Here's how I did it. We'll go example by example through migrating real world code living in a large, mostly non-Typescript codebase.
Let's start with a few of the basic angular building blocks, then we'll go through some of the higher level patterns we derived.
// earlierThan.js:
// Filter a collection of times, and return the
// ones earlier than or equal to the given time.
angular.module('myModule').filter('earlierThan', function() {
// (items: Array<Date>|void, value: Date) => Array<Date>|void
return function earlierThan (items, value) {
return items.filter(function (item) {
return item < value
})
}
})
Let's start with porting just the filter function to TS - almost no change, just some ES6 sugar:
// earlierThan.ts:
export function earlierThan (items: Date[], value: Date): Date[] {
return items.filter(item => item < value)
}
Notice the export
at the beginning! This allows us to directly import the filter function, and get type safety. We'll also need to tell Angular about our filter. I like to do this in a filter "bootstrap" file that imports all of my filters. This is nice because if we choose to migrate the application off Angular at some point in the future, everything but the bootstrap files is fully reusable!
// filters.ts:
import {IFilterService, module} from 'angular'
import {earlierThan} from './earlierThan'
// Tell Angular about our filter
module('myModule/filters', [])
.filter('earlierThan', () => earlierThan)
// (more filters go here)
// Tell TypeScript about our filter
export interface MyFilterService extends IFilterService {
(name: 'earlierThan'): earlierThan
// (more filters go here)
}
Finally, here's how we would consume our filter from our code:
// consumer.ts:
import {MyFilterService} from './filters'
class ConsumerService {
constructor ($filter: MyFilterService) {
const date = ...
const dates = [...]
const result: Date[] = $filter('earlierThan')(dates, date)
}
}
With this approach we have full type safety when we consume our filter in code, either as earlierThan(...)
or as $filter('earlierThan')(...)
.
Assuming you're using components, not directives, the migration path is very straight forward. Let's look at an example.
// myComponent.js:
angular.module('myModule').component('myComponent', {
bindings: {
url: '<'
},
template: '<h1>We have a result!</h1><span>{{$ctrl.res}}</span>',
controller: function ($http, $scope) {
this.fetch = function (url) {
return $http.get(url).then(function (res) {
return res.data
})
}
$scope.$watch('url', function (url) {
this.fetch(url).then(function (res) {
this.res = res
}.bind(this))
})
}
})
// MyComponent.ts:
import {IHttpService, IPromise} from 'angular'
export const MyComponent = {
bindings: {
url: '<'
},
template: `
<h1>We have a result!</h1>
<span>{{$ctrl.res}}</span>
`,
controller: MyComponentController
})
export class MyComponentController {
constructor (
private $http: IHttpService
) {}
fetch (url: string): IPromise<string> {
return this.$http.get(url).then(_ => _.data)
}
set url (url: string) {
this.fetch(url).then(res =>
this.res = res
)
}
}
Hey, this is pretty cool! Note a few things about the changes we've made:
- We've rewritten our controller function as a class
- We've rewritten watchers (
scope.$watch('prop')
) as setters (set prop
) - Dependencies are declared as the controller class' constructor parameters, rather than as our controller function's parameters
- We're exporting our controller class, so other code can consume it directly in a type safe way
Other than these minor syntactic changes, the TS code is almost identical to our old JS version.
And as before, let's keep the Angular-specific wrappers in a separate bootstrap file:
// components.ts:
import {module} from 'angular'
import {MyComponent} from './MyComponent'
module('myModule/components')
.component('myComponent', MyComponent)
// (more components go here)
@qqilihq - the OP forgot
typeof
. Please take a look the below example