Migrating Angular1.x to 2 with TypeScript

Whichever release candidate your using, or the final release. This a quick heads up guide on how to port over some simple tasks, something the Hero example app doesn’t show in full.

Upgrade path

I tried using the upgrader but in reality if your strapped for time I think using that is a very lengthy time consuming process and only appeals to projects that are running continuous delivery (that’s if you can get it set up in the first place).

As your adding the overhead of creating the upgrade/dowgrade code and then after that having to refactor.

I opted for a re-build of the current app refactoring service by service etc, it also allows you to make some architectural changes to the api of your app along the way, how much dependant on the time you have.

The biggest challenges are where the previous app uses $q, $watch and where you’ve used angular’s built in methods like angular.extend some can be fairly quickly swapped for lodash alternatives though or another framework, but overall it’s the sheer amount of typing you will need to do that gets tedious especially for Typescript.

Loading config

In Angular 1 you might have had some app wide config going on that sat in the main app.js e.g

angular.module('myApp')

    .config(function (appConfig) {
//configure stuff here

And also through a constant

angular.module('myApp').constant('appConfig', {
    "version": "1.0"
})

In Angular2 there’s two ways to solve this problem one is to provide the value on the root level app component like so:

providers: [
        { provide: VERSION, useValue: '1.0' }
    ],

You can then get that value by doing

var version = injectorValue.get(VERSION);

That’s great for small one liner configurations but a better way would be to load it from a local file. We can do that with a simple service.

import { Injectable } from [email protected]/core';
var config = require('./env.json');

@Injectable()
export class AppConfigService {

    private _config: Object;

    constructor() {
        this._config = config;
    }

    get(key: any) {
        return this._config[key];
    };
    getAll() {
        return this._config;
    }
}

In this case the env.json is generated externally from a gulp task. If you wanted to load an external file over http you could do something like below, here we would need to provide the endPoint value as we did above to the main app component.

providers: [
        { provide: endPoint, useValue: 'http://127.0.0.1/api' }
    ],
import {Http} from [email protected]/http';
import {Injectable, Inject} from [email protected]/core';

@Injectable()
export class AppConfig {

    constructor(private http: Http, 
                @Inject('AppConfig') private endPoint: string) { }

    getConfig(): {
        return this.http.get(`${this.endPoint}/`)
            .then((response) => {
                //do something with the response
            });
    } 
    // the rest of the code...
}

2. Promises

I won’t go into too much detail on this as there are some excellent guides around but where your code uses $q in Angular1 you will need to convert that to using an ES6 promise.

var promise = $q.when([]);
promise = promise.then(function() {
                        return getSomeData();
                    });
promise = promise.then(function() {
// do something else
});

In ES6 we could convert it like this

return new Promise((resolve,reject) => {
    getSomeData().then((result) => {
       //do something with result
       resolve(result);
}).catch((err) => {
       reject(err);
}
)};

or chain them together

let promise = new Promise((resolve,reject) => {
    getSomeData().then((result) => {
       //do something with result
       resolve(result);
}).catch((err) => {
       reject(err);
}
)};
promise.then((res) => {
   //do another thing
});

3. using $this _self etc

In most cases you should avoid doing that anyways either using .bind(this) although there is the odd case you need to, but with ES6 it makes it much easier to avoid through using arrows. In simple terms it’s shorthand for a function() {}, but the added feature of keeping the context of this to the parent. E.g

var $this = this;
Catalogue.getChildren(catId)
            .then(function(categories) {
                availableFilters.subCategories = categories;
            }).then(function(categories) {
                return CatalogueRepository.getFilters($this.getCriteria());
            })

Using arrows we no longer need $this

Catalogue.getChildren(catId)
            .then((categories) => {
                availableFilters.subCategories = categories;
            }).then(function(categories) => {
                return CatalogueRepository.getFilters(this.getCriteria());
            });

4. Angular forEach,copy etc..

for(var data in key) {
   key //key
   data[key] //value
}

Whilst there is vanilla js alternatives for these the quickest solution is to just import lodash or other library, lodash has _.deepClone _.forEach I use it like so after the imports.

var _ = require('lodash');

5. Window

For RC4 Provide window in your root component

providers: [
        { provide: Window, useValue: window }
    ],

In your component constructor use

constructor(
     @Inject(Window) private _window: Window
)

There may be a better way of doing the above, please let me know if you do it a different way.

6. Using pipe inside a service

Import the pipe and then call it like below.

import {TranslatePipe} from "../pipes/translate.pipe";
constructor(private translate: TranslatePipe) {
}
myFunction() {
this.translate.transform('some text');
}

7. Global variables

If for example your using another framework or something else in the global namespace you can use this to avoid TS errors, e.g using Ionic with Cordova.

declare var cordova: any;

@Injectable()
export class MyService {}

8. Code organisation

The sheer amount of imports can become a bit bewildering, to overcome this use barrel files, you have a bunch of services in /services then create a file inside there called index.ts

Inside this put:

export {appService} from "./appService";
export {configService} from "./configService";
export {dbService} from "./dbService";
export {UserDb} from "./userDb";

You can import it into your components like so:

import * as coreServices from "./services/";

You can provide or bootstrap them using [coreServices.UserDb,coreServices.dbService] etc..

One disadvantage to this is that you can’t quickly see the list of services upfront, so it’s best to group them for example /db /file /network and have the corresponding index files.

import * as network from "./network/";
import * as db from "./db/";
import * as files from "./file/";

This is much more readable.

9. Data in templates

If you need to load some data before it’s available to the template in Angular2 and want to avoid the undefined errors, there are a few ways of solving this.

1. ngOnInit( this.state = getMyState(); );
2. “The Elvis Operator” before your data e.g {{state?.name}}
3 ngIf e.g *ngIf=”state”
4. construct your model which will give you an empty object. this.state = new myModel();

10. Global services

In angular1 every service was a singleton, v2 has the same concept if you inject the service at the app level, but you can also provide it separately to to individual areas of the app through the providers:[], read more about that here

That’s it for now, let me know if you have any other simple things.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*