Using TypeScript in AngularJS
Guide to using TypeScript in AngularJS applications.
October 30, 2019
Supporting older AngularJS applications doesn’t mean you can’t take advantage of modern tools like TypeScript. Not every file in your application needs to be written in TypeScript at once, you just can rename all your JavaScript files to have a .ts extension.
Depending on the state of your application, there are steps you may need to take to begin using TypeScript.
Modularize Your Application
John Papa’s styleguide is a great reference point to begin refactoring and modularizing your app. The styleguide is very robust and has code examples of different AngularJS patterns, but at a high level three things to focus on are:
- Practice Single Responsibility
- Use named functions
- Write Controller view as “vm” syntax
Practicing single responsibility includes having one component per function. Have separate files for each of your services, factories, controllers, modules, router, etc. Naming structure should be app.module.js
, queen.service.js
, home.component.js
, etc.
It’s a good idea to have a single file as an entry point to your application containing your root app module and that brings in other files you need, vs. a list of JavaScript files loaded in an index.html page
.
//app.module.js
import angular from 'angular';
import '@uirouter/angularjs';
angular.module('ngQueenApp', [
'ui.router'
]);
Instead of writing anonymous functions when creating components, use named ones.
Don’t do this:
angular.module('ngQueenApp', [
'ui.router'
])
.component('homeComponent', () {
...component code here
})
Instead do this:
angular.module('ngQueenApp', [
'ui.router'
])
.component('homeComponent', homeComponentFunction)
homeComponentFunction() {
...component code here
}
CONTROLLER VM AS SYNTAX
Compile TypeScript Files in Your Build Process
TypeScript files need to be compiled into plain browser-readable JavaScript. You can use a command to compile the files when you need to:
tsc my-typescript-file.ts
It tends to be more convenient to have a process that compiles files for you as you save using tools like Grunt, Gulp, or Webpack.
Grunt
If you’re already using Grunt, you can simply add TypeScript compilation
npm install grunt-typescript typescript --save-dev
In your Grunt file set up the configuration to take source files.
grunt.initConfig({
...
typescript: {
base: {
src: ['path/to/typescript/files/**/*.ts'],
dest: 'where/you/want/your/js/files',
options: {
module: 'amd', //or commonjs
target: 'es5', //or es3
basePath: 'path/to/typescript/files',
sourceMap: true,
declaration: true
}
}
},
...
});
Gulp
If you’re using Gulp, you can add a task to compile your TypeScript files and pipe in any configuration options.
npm install gulp-typescript typescript --save-dev
var gulp = require('gulp');
var ts = require('gulp-typescript');
gulp.task('default', function () {
return gulp.src('src/**/*.ts')
.pipe(ts({
noImplicitAny: true,
outFile: 'output.js'
}))
.pipe(gulp.dest('built/local'));
});
Webpack
If you’re not already using a build process, I’d recommend implementing Webpack. Here is my preferred setup for Webpack:
npm install webpack webpack-cli webpack-dev-server --save-dev
npm install typescript ts-loader html-webpack-plugin html-loader --save-dev
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: {
'index': path.join(__dirname, 'app/index.ts')
},
output: {
filename: '[name]-bundle.js',
path: '/',
devtoolLineToLine: true,
pathinfo: true,
sourceMapFilename: '[name].js.map'
},
devServer: {
contentBase: path.join(__dirname, '/public'),
port: 8080,
compress: true
},
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({
template: './app/index.html',
inject: 'body',
title: 'testing',
hash: true,
minify: false
})
],
module: {
rules: [
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: [':data-src']
}
}
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
}
};
This setup uses one file, index.ts
, as a point of entry that imports the main app module, compiles the files, outputs as a hash-named bundled it dynamically writes to a generated index.html file based on the index.html template file I pass in.
Install Angular Types
As we write AngularJS code, we want to be able to typecheck not only our code, but the AngularJS framework code we’re writing. We can install Angular types that give us type definitions for AngularJS.
npm install @types/angular
SUMMARY HERE
Make HTML Files Importable
We’ll also want to be able to import HTML files to use in our controllers as templates. We can do this by creating a TypeScript Declaration file.
// html.d.ts
declare module '*.html';
Begin Using TypeScript in Files
You can now split components out into separate files, import them to the file containing the root app module, and use TypeScript features.
angular.
module('ngQueenApp').
component('queensComponent', {
template: '<ul>' +
'<li ng-repeat="queen in $ctrl.queens">' +
'<span>{{queen.name}}</span>' +
'<p>{{queen.quote}}</p>' +
'</li>' +
'</ul>',
controller: function QueensController() {
this.queens = [
{name: 'Bianca Del Rio', quote: `I will show you versatility
when Santino wins a sewing competition and Visage
wears a fucking turtle neck!`},
{name: 'BenDeLaCreme', quote:'Excuse me, we originated the language'},
{name: 'Courtney Act', quote: `Sewing is not my forte but...
everything else is.` }
];
}
})
becomes:
import queensTemplate from './queens.component.html';
QueenCtrl.$inject = ['QueenService'];
function QueensCtrl(QueenService: any) {
var vm = this;
QueenService.getQueens().then((res: Reponse) => {
vm.queens = res;
})
}
var QueensComponent = {
controller: QueensCtrl,
controllerAs: "vm",
template: queensTemplate
}
export default QueensComponent