AngularJS migration — hard reset and rebooting from Angular 2/4/5/6

How to not do rewrite while upgrading AngularJS to Angular

Intro

This article helps to pave a way towards a successful large-scale Angular 1.x code migration to Angular+. If you followed along “The Roadmap, this is the last stage to finally fire up your Angular+ application.

This article is published incomplete and on-hold for personal reasons. I will be updating relevant information as time allows.

For simplicity, Angular 2/4/5/6/7 and future versions will be generalized as Angular+.

The instability of Angular framework forced me to use React instead. In same spirit, I document the journey in “migration from AngularJS to React .

Synopsis

Clearly, every framework upgrade should not result in re-writes. Each major framework iteration introduced new severe breaking changes. Moreover, there were no clear path to upgrade to Angular+. I started documenting steps I used for my side project migration, till they grew into a big pile of notes. These series are based on those personal notes.

Your recommendation (👏 👏 clapping) is my motivation to followup with even more content. Leave any question you have in comments below, It will be a pleasure to help however I can!

These articles complement other resources you have at hand. I hope you some inspirations to sharpen your existing code migration strategy.


Problem statement

How do you keep sanity in an ever changing frontend landscape, and keep up with your planning and execution for a large scale(~20k+ LoC) migration from AngularJS to Angular+?


This is the 7th and last in a series of blog posts about code migration from Angular 1.x to Angular+. The First, second, third, forth, fifth and sixth are in those links.

TypeScript

The initial migration strategy was designed on a promise to use JavaScript(ES5) and targeted Angular 2/4 and early versions of 5.

It is unfortunate that the same strategy cannot work with latest angular 5. This commit prevents any efforts to use ES6, therefore forcing anyone to use TypeScript.

Apart from that, there have been so many tutorials about writing applications with JavaScript(ES6 and ES5), unfortunately Angular team made it imperative to write Angular+ with TypeScript. Some of those tutorials are still worth a try, especially if you are taking on an application that has been written using this approach: [1] , [2] [3] [4] [5] [6]


Problems

Among other things, some of common problems and questions you will be constantly asking yourself while planning code upgrade a.k.a migration are:

  • Where to start while doing migration from Angular 1.x to Angular+? What strategies can be used while migrating to any framework indeed?
  • From a bird’s-eye-view perspective , how is Angular 1.x different from Angular+?
  • How bootstrapping Angular Application differs from bootstrapping AngularJS?
  • How Modules, Components and Directives stack up in newer versions of Angular. From usability perspective, how Components differ from Directives.
  • How is the form processing done with the Angular+? How the newer frameworks affect old form validation and error handling? And how to upgrade old forms without a hustle?
  • If Filters became Pipes, what is the best and cheap way to upgrade to pipes. Is that even a thing?
  • How is Event Handling done in new Angular+? DOM based Event Handling, as well as DOM manipulation in general, have always been delegated to Directives. How events are bound to templates?
  • If the Angular+ adopted TypeScript, is it possible to keep my jQuery plugins that are not necessarily available in TypeScript? How can that be done?
  • How to write Angular+ Router with JavaScript
  • How to transition to Angular+ templates, is there a way to mix both Angular 1.x and Angular+ templates
  • How to upgrade Jasmine Test Cases?
  • How does data-binding works with Angular+? Is there a data binding strategy that looks closer to Angular 1.x? The short answer is [prop]=val can also be written as bind-prop=val which is similar to Angular 1.x notation(ng-bind=prop).
  • How to migrate forms ~ Forms come in three flavors: template based, model based and reactive forms.
  • How to migrate forms that have use attribute directives for validation
  • How to migrate the router
  • How to migrate the HTTP, and other AngularJS dependencies that have been removed from new API

How is Reflective Injection different from Static Injection? — Even though Angular team deprecated reflective injection, you may need to understand how it works, and why you should move to newer versions of Angular. To understand more on this subject, this reading list can help you Difference between Reflective Injector and Static Injector ~ on SO


Dependencies

You may be wondering “Which libraries are required to load an Angular+ in browser”. The code while running in browser looks like:

<script src="//unpkg.com/jquery/dist/jquery.min.js"/>
<script src="//unpkg.com/core-js/client/shim.min.js"/>
<script src="//unpkg.com/redux/dist/redux.min.js"/>
<script src="//unpkg.com/zone.js/dist/zone.js"/>
<script src="//unpkg.com/reflect-metadata/Reflect.js"/>
<script src="//unpkg.com/rxjs/bundles/Rx.js"/>
<script src="//unpkg.com/@angular/core/bundles/core.umd.js"/>
<script src="//unpkg.com/@angular/compiler/bundles/compiler.umd.js"/>
<script src="//unpkg.com/@angular/common/bundles/common.umd.js"/><script src="//unpkg.com/@angular/platform-browser/bundles/platform-browser.umd.js"/>
<script src="//unpkg.com/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"/>

To sum up, you will need zone.js, reflect-metadata(available as angular2-polyfills). If in addition you need state management, RxJS and Redux can be took into consideration. There is a shim library for ES6 features not available in ES5.


Zones WTF?

Quick and dirty answer is from Pete Mertz’s blog post: “What the hell is Zone.js and why is it in my Angular 2?

From Wikipedia, “A Zone is an execution context that persists across async tasks. You can think of it as thread-local storage for JavaScript VMs”.

From the same article above, Angular+ changes subscribes to onTurnDone() Zone’s event as change detectors, Angular+ calls tick to run subscriber callbacks.

On absence of Zones, tracking changes becomes impossible(no subscription possible),therefore there will be no callbacks when Angular calls tick(timeout).

In addition to JavaScript Event Loop, $digest was a sort of built in AngularJS specific Event Loop.

Dirty checking. The dirty checking idea was first charming but came with performance penalties on ever increasing applications. Every time a $scope bound variable changed, a corresponding $watch in $watch list was marked(dirty), for every t seconds, the event re-evaluates changes and apply those changes to corresponding UI elements.

Angular+ relies on zones to detect asynchronous events.

What is ZoneJS and how to integrate it with existing code. ZoneJS tells Angular+ how to efficiently update the view.

Zone powers change detection for View updates using Observable-s, instead of waiting for a digest cycles as it was the case in Angular 1.x.

Reflect-metadata uses annotations and reflection API as a way to inject dependencies into Angular+.


SystemJS

SystemJS is used to as module loader, since Angular+ doesn’t have its own loader as Angular 1.x. Why do I need System.JS, or why should I avoid it in my code?

For more on Zones and Digest cycle, you may find these two articles interesting: The Digest Loop and $apply — dirty checking and Zones on Rangle/io

Reflect metadata

These things use reflections, to make annotations work. One means of applying decorators is use of annotations. Since decorators can be done without annotations, It is possible to leverage decorators with plain old JavaScript.


AMD vs UMD vs Require, vs SystemJS etc

How module loading techniques defers from each other. The reason being: some features in core.umd uses rxjs Subject Object, and rxjs uses require to load its objects. That creates additional dependency to be loaded in browser. Rxjs dropped UMD for AMD, which made additional dependency to requires.js inevitable. Loading bundled rxjs seems to resolve the issue. issue 1678


RxJS or Redux

RxJS is defined as “[a] Reactive Extension for JavaScript”. Redux is defined as: “Redux is a predictable state container for JavaScript apps”.

It is a library, just like Underscore or LoDash, but for various applications of Observer and Observable Pattern.

Also, a library just like Underscore or LoDash, but for state management. It also implements Observer/Observable pattern.

Since the level these 2 libraries operates in, you don’t necessarily need to use them. In matter of fact, Redux is more of a strategy to manage state than a library. With RxJS, you can follow same strategy Redux uses to achieve same results.

Going deep the rabbit hole with this answer on “Why would you need Redux and Rx anyways?”. Find some examples on how to use RxJS, and if you are hungry for more: JS module showdown

Components versus Directives

What is the difference between @Component and @Directive?

Starting from Angular 1.3+, Components played a role of specialized directives.

From DOM manipulation perspective, there are two distinct kinds of directive: Those that manipulate DOM and those that don’t. There are also those Directive using host DOM as their root, and others having a template.

Moreover, restrict attribute allowed to use a directive as a class, attribute or new attribute. The new API allows you to use every Component as an element. Directives still have options to be declared as attributes or classes interchangeably, without the need to use restrict property.

The idea of keeping DOM manipulations in Directive’s Link function, slightly changed in Angular(2/4 and 5).

Angular 1.x relied on scope’s forced digest cycle to integrate state change into Angular environment.

A similar approach is used with Angular, but from inside Zones. Two helpers used for this are: zone.runOutsideAngular and zone.run.

State change outside Angular, relies on Zone’s Since directives that don’t feature a Link functions have been promoted to Components already, our focus is now on Directive.


How to migrate DOM manipulation Directives

How Link function of old directives fits into new Component Architecture.

Which lead us to wonder how attributes or class directives fits into Component programming model.

# DOM manipulation directive
...directive('dateSelector',
function(){
return{
link: function($scope, $element, $attrs, $ctrl){
var options = {...};
$element.datetime(options);
}
};
});
# No DOM manipulation directive
...directive('order', function(){
return {
template: `<div/>`,
controller: function(){}
};
});

Move code to Controller with $postLink function

This strategy doesn’t honor separation of concerns we used to have in Angular 1.x.

# DOM Manipulations in Component Controller
function DateSelectorController($scope, $element){
var options = {};
this.handler = function(){};

#Linking function
this.$postLink = function(){
$element.on('event', this.handler);
$element.datetime(options);
};
}

The question is how can we move these Code in Angular Component Architecture. The ideal is not to break the moto: No DOM manipulations outside Link function.

@Component({selector, template})
export class DateSelectorComponent{
constructor(private elt: ElementRef){}
# leveraging ngAfterViewInit Component Hook
ngAfterViewInit(){
# $ => jQuery reference
this.$element = $(this.elt.nativeElement);
#or this.$element = $(this.elt.nativeElement).datetime(options);
this.$element.datetime(options);
this.$element.on('event', this.handler);
}
# handling destroy
ngOnDestroy(){
# if element has a remove|destroy
this.$element.remove();
}
}

For performance reasons, running outside Angular using Zones helps Angular focusing on events that matter the most: meaning telling angular that out-sourced work has been completed, together with end-result.

# Injecting Zones.
constructor(private elt: ElementRef, zone: NgZone){}
# initialization with zones
ngAfterViewInit(){
this.zone.runOutsideAngular(()=>{
this.$element = $(this.elt.nativeElement).datetime(options);
#Event Handling
this.$element.on('event', (evt) => {
this.zone.run(this.handler);
});
});
}

How to write the above code block in JavaScript? There are two ways to solve this problem. The first, is to define Components on ng.core.Component, the second is to use annotations.

#@Component becomes
ng.core.Component({
selector, template, viewProviders[ElementRef, NgZone]
}).
Class({constructor:[ElementRef, NgZone,
function ComponentController(elt, zone){
# attaching injections to this
this.elt = elt; this.zone = zone;
# move implementation in these two functions as usual
this.ngAfterViewInit = function(){...};
this.ngOnDestroy = function(){...};
}]
});

Alternatively, the Annotations can be used.

var DateSelectorComponent = function(elt, zone){}
# Adding Component Decorators via Annotations
DateSelectorComponent.annotations = [
new ng.core.Component({selector, template})
];
# Adding Injected Parameters.
DateSelectorComponent.parameters = [
[new ng.core.Inject(ElementRef)], [new core.Inject(NgZone)]
]

  • How Code in new Linking function should be migrated into new Component Architecture. Obviously, we can do better. The way to make it happen is to inherit from AfterViewInit or using Directive Annotation.
  • How Code in new Linking function should be migrated into new Component Architecture. Obviously, we can do better. The way to make it happen is to inherit from AfterViewInit or using Directive Annotation.
  • Components are elements, and not attributes. How restricted to Attribute will be handled? E.g: “return { restrict: ‘A’, link: link}”. There is 3 types of Directives: Components, Structural Directives and Attribute Directives. The answer from SO.

Reading list


  • platform-browser-dynamic library is used with apps that compile templates on fly, whereas platform-browser is used with pre-compiled templates.source.
  • Since Angular+ have many ways to bootstrap, which one makes more sense you may ask? How to bootstrap Angular App in a way that is compatible with Angular 2/4.x/5.x.
# Angular 4+
ng.
platformBrowserDynamic.
platformBrowserDynamic().
bootstrapModule(HelloApp);
# Angular 2
ng.
platformBrowserDynamic.
bootstrap(HelloApp);
# In both cases ng.platformBrowserDynamic imports platformBrowser
# In both instances, DOMContentLoaded is used to load Angular+
document.addEventListener("DOMContentLoaded", function() {
#do bootstrapping here
});
  • Note: ng.platformBrowserDynamic.bootstrap is not available in latest Angular. How to run Angular+ in production mode, or how to avoid “Angular is running in the development mode. Call enableProdMode() to enable the production mode” error? This “friendly remainder” can be silenced by calling ng.core.enableProdMode() before bootstrapping the application.
document.addEventListener(..., function(){
if(env.production) ng.core.enableProdMode();
...bootstrapModule(AppHello);
});

Note: ng.platformBrowserDynamic.bootstrap is not available in latest Angular. Another section but not related to this blog is “How to configure AngularJS apps with environment variables”.


Component Decorator

Component with decorators vs the old way

# ng.core.Component does same thing @Component do
@Component({selector:'t', template:'<li>{{tab}}</li>'})
class Tab(){
constructor(){
}
}
# equivalent to 
var Tab = ng.core.Component({
selector:'t', template: '...'
}).Class({
constructor: function Tab(){}
})

Annotations, Decorators and Injections

Is it possible to use Angular 1.x annotations that are compatible with Angular 2/4.x/5.x? How annotations, decorators and injections differ from each others, and how to use them in JavaScript?

Reading list


Modules(NgModule) and Components

how do they stack up? How to write Angular+ Components with JavaScript.

“Angular Modules help organize an application into cohesive blocks of functionality.” source — copied verbatim from John Papa’s Blog.

Reading list:


Configuration

How to configure your AngularJS application using environment variables. This is good to configure same the app depending on environment(prod/dev/test), context(browser/mobile) and device it runs on. It also allows to keep dev configurations out of production code.


Reading list


Outro

There is no single cardinal way to do the migration from any framework to another. It all comes down to case by case requirements. This article took the migration from a practical standpoint, taking into account tricks that worked for other people, especially in tight budgets and resources scenarios. I hope you come up with your own re-mix as well.