Angular 2 Cookbook

5 (2 reviews total)
By Matt Frisbie
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Strategies for Upgrading to Angular 2

About this book

Angular 2 introduces an entirely new way to build applications. It wholly embraces all the newest concepts that are built into the next generation of browsers, and it cuts away all the fat and bloat from Angular 1. This book plunges directly into the heart of all the most important Angular 2 concepts for you to conquer. In addition to covering all the Angular 2 fundamentals, such as components, forms, and services, it demonstrates how the framework embraces a range of new web technologies such as ES6 and TypeScript syntax, Promises, Observables, and Web Workers, among many others.

This book covers all the most complicated Angular concepts and at the same time introduces the best practices with which to wield these powerful tools. It also covers in detail all the concepts you'll need to get you building applications faster. Oft-neglected topics such as testing and performance optimization are widely covered as well. A developer that reads through all the content in this book will have a broad and deep understanding of all the major topics in the Angular 2 universe.

Publication date:
January 2017
Publisher
Packt
Pages
464
ISBN
9781785881923

 

Chapter 1. Strategies for Upgrading to Angular 2

This chapter will cover the following recipes:

  • Componentizing directives using the controllerAs encapsulation

  • Migrating an application to component directives

  • Implementing a basic component in AngularJS 1.5

  • Normalizing service types

  • Connecting Angular 1 and Angular 2 with UpgradeModule

  • Downgrading Angular 2 components to Angular 1 directives with downgradeComponent

  • Downgrading Angular 2 providers to Angular 1 services with downgradeInjectable

 

Introduction


The introduction of Angular 2 into the Angular ecosystem will surely be interpreted and handled differently for all developers. Some will stick to their existing Angular 1 codebases, some will start brand new Angular 2 codebases, and some will do a gradual or partial transition.

It is recommended that you become familiar with the behavior of Angular 2 components before you dive into these recipes. This will help you frame mental models as you adapt your existing applications to be more compliant with the Angular 2 style.

 

Componentizing directives using controllerAs encapsulation


One of the unusual conventions introduced in Angular 1 was the relationship between directives and the data they consumed. By default, directives used an inherited scope, which suited the needs of most developers just fine. While this was easy to use, it had the effect of introducing extra dependencies in the directives, and also the convention that directives often did not own the data they were consuming. Additionally, the data interpolated in the template was unclear in relation to where it was being assigned or owned.

Angular 2 utilizes components as the building blocks of the entire application. These components are class-based and are therefore in some ways at odds with the scope mechanisms of Angular 1. Transitioning to a controller-centric directive model is a large step towards compliance with the Angular 2 standards.

Note

The code, links, and a live example related to this recipe are available at http://ngcookbook.herokuapp.com/8194.

Getting ready

Suppose your application contains the following setup that involves the nested directives that share data using an isolate scope:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.directive('article', function() { 
  return { 
    controller: function($scope) { 
      $scope.articleData = { 
        person: {firstName: 'Jake'}, 
        title: 'Lesotho Yacht Club Membership Booms' 
     }; 
   }, 
    template: ` 
      <h1>{{articleData.title}}</h1> 
      <attribution author="articleData.person.firstName"> 
      </attribution> 
    ` 
 }; 
}) 
.directive('attribution', function() { 
  return { 
    scope: {author: '='}, 
    template: `<p>Written by: {{author}}</p>` 
 }; 
}); 

How to do it...

The goal is to refactor this setup so that templates can be explicit about where the data is coming from and so that the directives have ownership of this data:

[app.js] 
 
angular.module('articleApp', []) 
.directive('article', function() { 
   return { 
   controller: function() { 
      this.person = {firstName: 'Jake'}; 
      this.title = 'Lesotho Yacht Club Membership Booms'; 
   }, 
    controllerAs: 'articleCtrl', 
    template: ` 
      <h1>{{articleCtrl.title}}</h1> 
      <attribution></attribution> 
    ` 
 }; 
}) 
.directive('attribution', function() { 
   return { 
    template: `<p>Written by: {{articleCtrl.author}}</p>` 
 }; 
}); 

In this second implementation, anywhere you use the article data, you are certain of its origin. This is better, but the child directive is still referencing the parent controller, which isn't ideal since it is introducing an unneeded dependency. The attribution directive instance should be provided with the data, and it should instead interpolate from its own controller instance:

[app.js]  
 
angular.module('articleApp', []) 
.directive('article', function() { 
  return { 
    controller: function() { 
      this.person = {firstName: 'Jake'}; 
      this.title = 'Lesotho Yacht Club Membership Booms'; 
   }, 
    controllerAs: 'articleCtrl', 
    template: ` 
      <h1>{{articleCtrl.title}}</h1> 
      <attribution author="articleCtrl.person.firstName"> 
      </attribution> 
    ` 
 }; 
}) 
.directive('attribution', function() { 
  return { 
    controller: function() {}, 
    controllerAs: 'attributionCtrl', 
    bindToController: {author: '='}, 
    template: `<p>Written by: {{attributionCtrl.author}}</p>` 
 }; 
}); 

Much better! You provide the child directive with a stand-in controller and give it an alias in the attributionCtrl template. It is implicitly bound to the controller instance via bindToController in the same way you would accomplish a regular isolate scope; however, the binding is directly attributed to the controller object instead of the scope.

Now that you have introduced the notion of data ownership, suppose you want to modify your application data. What's more, you want different parts of your application to be able to modify it. A naïve implementation of this would be something as follows:

[app.js] 
 
angular.module('articleApp', []) 
.directive('attribution', function() { 
  return { 
    controller: function() { 
      this.capitalize = function() { 
        this.author = this.author.toUpperCase(); 
     } 
   }, 
    controllerAs: 'attributionCtrl', 
    bindToController: {author: '='}, 
    template: ` 
      <p ng-click="attributionCtrl.capitalize()"> 
        Written by: {{attributionCtrl.author}} 
      </p>` 
 }; 
}); 

The desired behavior is for you to click on the author, and it will become capitalized. However, in this implementation, the article controller's data is modified in the attribution controller, which does not own it. It is preferable for the controller that owns the data to perform the actual modification and instead supply an interface that an outside entity—here, the attribution directive—could use:

[app.js] 
 
angular.module('articleApp', []) 
.directive('article', function() { 
  return { 
    controller: function() { 
      this.person = {firstName: 'Jake'}; 
      this.title = 'Lesotho Yacht Club Membership Booms'; 
      this.capitalize = function() { 
        this.person.firstName =   
          this.person.firstName.toUpperCase(); 
     }; 
   }, 
    controllerAs: 'articleCtrl', 
    template: ` 
      <h1>{{articleCtrl.title}}</h1> 
      <attribution author="articleCtrl.person.firstName" 
                   upper-case-author="articleCtrl.capitalize()"> 
      </attribution> 
    ` 
 }; 
}) 
.directive('attribution', function() { 
  return { 
    controller: function() {}, 
    controllerAs: 'attributionCtrl', 
    bindToController: { 
      author: '=', 
      upperCaseAuthor: '&' 
   }, 
    template: ` 
      <p ng-click="attributionCtrl.upperCaseAuthor()"> 
        Written by: {{attributionCtrl.author}} 
      </p>` 
 }; 
});

Vastly superior! You are still able to namespace within the click binding, but the owning directive controller is providing a method to outside entities instead of just giving them direct data access.

How it works...

When a controller is specified in the directive definition object, one will be explicitly instantiated for each directive instance that is created. Thus, it is natural for this controller object to encapsulate the data that it owns and for it to be delegated the responsibility of passing its data to the members that require it.

The final implementation accomplishes several things:

  • Improved template namespacing: When you use the $scope properties that span multiple directives or nestings, you are creating a scenario where multiple entities can manipulate and read data without being able to concretely reason about where it is coming from or what is controlling it.

  • Improved testability: If you look at each of the directives in the final implementation, you'll find they are not too difficult to test. The attribution directive has no dependencies other than what are explicitly passed to it.

  • Encapsulation: Introducing the notion of data ownership in your application affords you a much more robust structure, better reusability, and additional insight and control involving pieces of your application interacting.

  • Angular 2 style: Angular 2 uses the @Input and @Output annotations on component definitions. Mirroring this style will make the process of transitioning to an application easier.

There's more...

You will notice that $scope has been made totally irrelevant in these examples. This is good as there is no notion of $scope in Angular 2, which means you are heading towards having an upgradeable application. This is not to say that $scope does not still have utility in an Angular 1 application, and surely, there are scenarios where this is unavoidable, like with $scope.$apply().

However, thinking about the application pieces in this component style will allow you to be more adequately prepared to adopt Angular 2 conventions.

See also

  • Migrating an application to component directives demonstrates how to refactor Angular 1 to a component style

  • Implementing a basic component in AngularJS 1.5 details how to write an Angular 1 component

  • Normalizing service types gives instruction on how to align your Angular 1 service types for Angular 2 compatibility

 

Migrating an application to component directives


In Angular 1, there are several built-in directives, including ngController and ngInclude, that developers tend to lean on when building applications. While not anti-patterns, using these features moves away from having a component-centric application.

All these directives are actually subsets of component functionality, and they can be entirely refactored out.

Note

The code, links, and a live example related to this recipe are available at http://ngcookbook.herokuapp.com/1008/.

Getting ready

Suppose your initial application is as follows:

[index.html] 
 
<div ng-app="articleApp"> 
  <ng-include src="'/press_header.html'"></ng-include> 
  <div ng-controller="articleCtrl as article"> 
    <h1>{{article.title}}</h1> 
    <p>Written by: {{article.author}}</p> 
  </div> 
  <script type="text/ng-template"  
          id="/press_header.html"> 
  <div ng-controller="headerCtrl as header"> 
    <strong> 
      Angular Chronicle - {{header.currentDate | date}} 
    </strong> 
   <hr /> 
  </div> 
  </script> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.controller('articleCtrl', function() { 
  this.title = 'Food Fight Erupts During Diplomatic Luncheon'; 
  this.author = 'Jake'; 
}) 
.controller('headerCtrl', function() { 
   this.currentDate = new Date(); 
}); 

Note

Note that this example application contains a large number of very common Angular 1 patterns; you can see the ngController directives sprinkled throughout. Also, it uses an ngInclude directive to incorporate a header. Keep in mind that these directives are not inappropriate for a well-formed Angular 1 application. However, you can do better, and this involves refactoring to a component-driven design.

How to do it...

Component-driven patterns don't need to be frightening in appearance. In this example (and for essentially all Angular 1 applications), you can do a component refactor while leaving the existing template largely intact.

Begin with the ngInclude directive. Moving this to a component directive is simple—it becomes a directive with templateUrl set to the template path:

[index.html] 
 
<div ng-app="articleApp"> 
  <header></header> 
  <div ng-controller="articleCtrl as article"> 
    <h1>{{article.title}}</h1> 
    <p>Written by: {{article.author}}</p> 
  </div> 
  <script type="text/ng-template"  
          id="/press_header.html"> 
  <div ng-controller="headerCtrl as header"> 
   <strong> 
      Angular Chronicle - {{header.currentDate | date}} 
    </strong> 
   <hr /> 
  </div> 
  </script> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.controller('articleCtrl', function() { 
  this.title = 'Food Fight Erupts During Diplomatic Luncheon'; 
  this.author = 'Jake'; 
}) 
.controller('headerCtrl', function() { 
   this.currentDate = new Date(); 
}) 
.directive('header', function() { 
   return { 
   templateUrl: '/press_header.html' 
 }; 
}); 

Next, you can also refactor ngController everywhere it appears. In this example, you find two extremely common appearances of ngController. The first is at the head of the press_header.html template, acting as the top-level controller for that template. Often, this results in needing a superfluous wrapper element just to house the ng-controller attribute. The second is ngController nested inside your primary application template, controlling some arbitrary portion of the DOM. Both of these can be refactored to component directives by reassigning ngController to a directive controller:

[index.html] 
 
<div ng-app="articleApp"> 
  <header></header> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.directive('header', function() { 
  return { 
    controller: function() { 
      this.currentDate = new Date(); 
    }, 
    controllerAs: 'header', 
    template: ` 
      <strong> 
        Angular Chronicle - {{header.currentDate | date}} 
      </strong> 
      <hr /> 
    ` 
 }; 
}) 
.directive('article', function() { 
   return { 
   controller: function() { 
      this.title = 'Food Fight Erupts During Diplomatic Luncheon'; 
      this.author = 'Jake'; 
   }, 
    controllerAs: 'article', 
    template: `     
      <h1>{{article.title}}</h1> 
      <p>Written by: {{article.author}}</p> 
    ` 
 }; 
}); 

Tip

Note that templates here are included in the directive for visual congruity. For large applications, it is preferred that you use templateUrl and locate the template markup in its own file.

How it works...

Generally speaking, an application can be represented by a hierarchy of nested MVC components. ngInclude and ngController act as subsets of a component functionality, and so it makes sense that you are able to expand them into full component directives.

In the preceding example, the ultimate application structure is comprised of only components. Each component is delegated its own template, controller, and model (by virtue of the controller object itself). Sticklers will dispute whether or not Angular belongs to true MVC style, but in the context of component refactoring, this is irrelevant. Here, you have defined a structure that is completely modular, reusable, testable, abstractable, and easily maintainable. This is the style of Angular 2, and the value of this should be immediately apparent.

There's more...

An alert developer will notice that no attention is paid to scope inheritance. This is a difficult problem to approach, mostly because many of the patterns in Angular 1 are designed for a mishmash between a scope and controllerAs. Angular 2 is built around strict input and output between nested components; however, in Angular 1, scope is inherited by default, and nested directives, by default, have access to their encompassing controller objects.

Thus, to truly emulate an Angular 2 style, one must configure their application to explicitly pass data and methods to children, similar to the controllerAs encapsulation recipe. However, this does not preclude direct data access to ancestral component directive controllers; it merely wags a finger at it since it adds additional dependencies.

See also

  • Componentizing directives using controllerAs encapsulation shows you a superior method of organizing Angular 1 directives

  • Implementing a basic component in AngularJS 1.5 details how to write an Angular 1 component

  • Normalizing service types gives instruction on how to align your Angular 1 service types for Angular 2 compatibility

 

Implementing a basic component in AngularJS 1.5


The 1.5 release of AngularJS introduced a new tool: the component. While it isn't exactly similar to the concept of the Angular 2 component, it does allow you to build directive-style pieces in an explicitly componentized fashion.

Note

The code, links, and a live example related to this recipe are available at http://ngcookbook.herokuapp.com/7756/.

Getting ready

Suppose your application had a directive defined as follows:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.directive('article', function() { 
  return { 
    controller: function() { 
      this.person = {firstName: 'Jake'}; 
      this.title = 'Police Bust Illegal Snail Racing Ring'; 
      this.capitalize = function() { 
        this.person.firstName =   
          this.person.firstName.toUpperCase(); 
     }; 
   }, 
    controllerAs: 'articleCtrl', 
    template: ` 
      <h1>{{articleCtrl.title}}</h1> 
      <attribution author="articleCtrl.person.firstName" 
                   upper-case-author="articleCtrl.capitalize()"> 
      </attribution> 
    ` 
 }; 
}) 
.directive('attribution', function() { 
  return { 
    controller: function() {}, 
    controllerAs: 'attributionCtrl', 
    bindToController: { 
      author: '=', 
      upperCaseAuthor: '&' 
   }, 
    template: ` 
      <p ng-click="attributionCtrl.upperCaseAuthor()"> 
        Written by: {{attributionCtrl.author}} 
      </p>` 
 }; 
}); 

How to do it...

Since this application is already organized around the controllerAs encapsulation, you can migrate it to use the component() definition introduced in the Angular 1.5 release.

Components accept an object definition similar to a directive, but the object does not demand to be returned by a function—an object literal is all that is needed. Components utilize the bindings property in this object definition object in the same way that bindToController works for directives. With this, you can easily introduce components in this application instead of directives:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.component('article', { 
  controller: function() { 
    this.person = {firstName: 'Jake'}; 
    this.title = ' Police Bust Illegal Snail Racing Ring '; 
    this.capitalize = function() { 
      this.person.firstName =   
        this.person.firstName.toUpperCase(); 
   }; 
 }, 
  controllerAs: 'articleCtrl', 
  template: ` 
    <h1>{{articleCtrl.title}}</h1> 
    <attribution author="articleCtrl.person.firstName" 
                 upper-case-author="articleCtrl.capitalize()"> 
   </attribution>` 
}) 
.component('attribution', { 
  controller: function() {}, 
  controllerAs: 'attributionCtrl', 
  bindings: { 
    author: '=', 
    upperCaseAuthor: '&' 
 }, 
  template: ` 
    <p ng-click="attributionCtrl.upperCaseAuthor()"> 
      Written by: {{attributionCtrl.author}} 
    </p> 
  ` 
}); 

How it works...

Notice that since your controller-centric data organization matches what a component definition expects, no template modifications are necessary. Components, by default, will utilize an isolate scope. What's more, they will not have access to the alias of the surrounding controller objects, something that cannot be said for component-style directives. This encapsulation is an important offering of the new component feature, as it has direct parity to how components operate in Angular 2.

There's more...

Since you have now entirely isolated each individual component, there is only a single controller object to deal with in each template. Thus, Angular 1.5 automatically provides a convenient alias for the component's controller object, namely—$ctrl. This is provided whether or not a controllerAs alias is specified. Therefore, a further refactoring yields the following:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.component('article', { 
  controller: function() { 
    this.person = {firstName: 'Jake'}; 
    this.title = 'Police Bust Illegal Snail Racing Ring'; 
    this.capitalize = function() { 
      this.person.firstName =   
        this.person.firstName.toUpperCase(); 
   }; 
 }, 
  template: ` 
    <h1>{{$ctrl.title}}</h1> 
    <attribution author="$ctrl.person.firstName" 
                 upper-case-author="$ctrl.capitalize()"> 
   </attribution> 
  ` 
}) 
.component('attribution', { 
  controller: function() {}, 
  bindings: { 
    author: '=', 
    upperCaseAuthor: '&' 
 }, 
  template: ` 
    <p ng-click="$ctrl.upperCaseAuthor()"> 
      Written by: {{$ctrl.author}} 
    </p> 
  ` 
}); 

See also

  • Componentizing directives using controllerAs encapsulation shows you a superior method of organizing Angular 1 directives

  • Migrating an application to component directives demonstrates how to refactor Angular 1 to a component style

  • Normalizing service types gives instruction on how to align your Angular 1 service types for Angular 2 compatibility

 

Normalizing service types


Angular 1 developers will be quite familiar with the factory/service/provider trifecta. In many ways, this has gone largely unaltered in Angular 2 conceptually. However, in the interest of upgrading an existing application, there is one thing that should be done to make the migration as easy as possible: eliminate factories and replace them with services.

Note

The code, links, and a live example related to this recipe are available at http://ngcookbook.herokuapp.com/5637/.

Getting ready

Suppose you had a simple application as follows:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.factory('ArticleData', function() { 
  var title = 'Incumbent Senator Ousted by Stalk of Broccoli'; 
  return { 
    getTitle: function() { 
      return title; 
   }, 
    author: 'Jake' 
 }; 
}) 
.component('article', { 
  controller: function(ArticleData) { 
    this.title = ArticleData.getTitle(); 
    this.author = ArticleData.author; 
 }, 
  template: ` 
    <h1>{{$ctrl.title}}</h1> 
    <p>Written by: {{$ctrl.author}}</p> 
  ` 
}); 

How to do it...

Angular 2 is class-based, and it includes its service types as well. The example here does not have a service type that is compatible with a class structure. So it must be converted. Thankfully, this is quite easy to do:

[index.html] 
 
<div ng-app="articleApp"> 
  <article></article> 
</div> 
[app.js] 
 
angular.module('articleApp', []) 
.service('ArticleData', function() { 
   var title = 'Incumbent Senator Ousted by Stalk of Broccoli'; 
   this.getTitle = function() { 
   return title; 
 }; 
  this.author = 'Jake'; 
}) 
.component('article', { 
  controller: function(ArticleData) { 
    this.title = ArticleData.getTitle(); 
    this.author = ArticleData.author; 
 }, 
  template: ` 
    <h1>{{$ctrl.title}}</h1> 
    <p>Written by: {{$ctrl.author}}</p> 
  ` 
}); 

How it works...

You still want to keep the notion of title private, but you also want to maintain the API that the injected service type is providing. Services are defined by a function that acts as a constructor, and an instance created from this constructor is what is ultimately injected. Here, you are simply moving getTitle() and author to be defined on the this keyword, which thereby makes it a property on all instances. Note that the use in the component and template does not change in any way, and it shouldn't.

There's more...

The simplest to implement service types, Angular 1 factories were often used first by many developers, including myself. Some developers might take offense at the following claim, but I don't think there was ever a good reason for both factories and services to exist. Both have a high degree of redundancy, and if you dig down into the Angular source code, you will see that they essentially converge to the same methods.

Why services over factories then? The new world of JavaScript, ES6, and TypeScript is being built around classes. They are a far more elegant way of expressing and organizing logic. Angular 1 services are an implementation of prototype-based classes, which when used correctly function in essentially the same way as formal ES6/TypeScript classes. If you stop here, you will have modified your services to be more extensible and comprehensible. If you intend to upgrade, you will find that Angular 1 services will cleanly upgrade to Angular 2 services.

See also

  • Componentizing directives using controllerAs encapsulation shows you a superior method for organizing Angular 1 directives

  • Migrating an application to component directives demonstrates how to refactor Angular 1 to a component style

  • Implementing a basic component in AngularJS 1.5 details how to write an Angular 1 component

 

Connecting Angular 1 and Angular 2 with UpgradeModule


Angular 2 comes with the ability to connect it to an existing Angular 1 application. This is obviously advantageous since this will allow you to utilize existing components and services in Angular 1 in tandem with Angular 2's components and services. UpgradeModule is the tool that is supported by Angular teams to accomplish such a feat.

Note

The code, links, and a live example in relation to this recipe are available at http://ngcookbook.herokuapp.com/4137/.

Getting ready

Suppose you had a very simple Angular 1 application as follows:

[index.html] 
 
<!DOCTYPE html> 
<html> 
  <head> 
    <!-- Angular 1 scripts --> 
    <script src="angular.js"></script> 
  </head> 
  <body> 
    <div ng-app="hybridApp" 
         ng-init="val='Angular 1 bootstrapped successfully!'"> 
      {{val}}   
    </div> 
  </body> 
</html> 

This application interpolates a value set in an Angular expression so you can visually confirm that the application has bootstrapped and is working.

How to do it...

Begin by declaring the top-level angular module inside its own file. Instead of using a script tag to fetch the angular module, require Angular 1, import it, and create the root Angular 1 module:

[ng1.module.ts] 
 
import 'angular' 
 
export const Ng1AppModule = angular.module('Ng1AppModule', []); 

Angular 2 ships with an upgrade module out of the box, which is provided inside upgrade.js. The two frameworks can be connected with UpgradeModule.

Note

This recipe utilizes SystemJS and TypeScript, the specifications for which lie inside a very complicated config file. This is discussed in a later chapter, so don't worry about the specifics. For now, you are free to assume the following:

  • SystemJS is configured to compile TypeScript (.ts) files on the fly

  • SystemJS is able to resolve the import and export statements in TypeScript files

  • SystemJS is able to resolve Angular 1 and 2 library imports

Angular 2 requires a top-level module definition as part of its base configuration:

[app/ng2.module.ts] 
 
import {NgModule} from '@angular/core'; 
import {BrowserModule} from '@angular/platform-browser'; 
import {UpgradeModule} from '@angular/upgrade/static';  
import {RootComponent} from './root.component'; 
 
@NgModule({ 
  imports: [ 
    BrowserModule, 
    UpgradeModule, 
  ], 
  bootstrap: [ 
    RootComponent 
  ], 
  declarations: [ 
    RootComponent 
  ] 
}) 
export class Ng2AppModule { 
  constructor(public upgrade: UpgradeModule){} 
} 
export class AppModule {} 

Tip

The reason why this module definition exists this way isn't critical for understanding this recipe. Angular 2 modules are covered in Chapter 7, Services, Dependency Injection, and NgModule.

Create the root component of the Angular 2 application:

[app/root.component.ts] 
import {Component} from '@angular/core'; 
 
@Component({ 
  selector: 'root', 
  template: ` 
    <p>Angular 2 bootstrapped successfully!</p> 
  ` 
}) 
export class RootComponent {} 

Since Angular 2 will often bootstrap from a top-level file, create this file as main.ts and bootstrap the Angular 2 module:

[main.ts] 
import {platformBrowserDynamic}  
  from '@angular/platform-browser-dynamic'; 
 
import {Ng1AppModule} from './app/ng1.module'; 
import {Ng2AppModule} from './app/ng2.module'; 
 
platformBrowserDynamic() 
  .bootstrapModule(Ng2AppModule); 

Connecting Angular 1 to Angular 2

Don't use an ng-app to bootstrap the Angular 1 application; instead, do this after you bootstrap Angular 2:

[main.ts] 
 
import {platformBrowserDynamic}  
  from '@angular/platform-browser-dynamic'; 
 
import {Ng1AppModule} from './app/ng1.module'; 
import {Ng2AppModule} from './app/ng2.module'; 
 
platformBrowserDynamic() 
  .bootstrapModule(Ng2AppModule) 
  .then(ref => { 
    ref.instance.upgrade 
      .bootstrap(document.body, [Ng1AppModule.name]); 
  }); 

With this, you'll be able to remove Angular 1's JS script, the ng-app directive, and add in the root element of the Angular 2 app:

[index.html] 
 
<!DOCTYPE html> 
<html> 
  <head> 
    <!-- Angular 2 scripts --> 
    <script src="zone.js "></script> 
    <script src="reflect-metadata.js"></script> 
    <script src="system.js"></script> 
    <script src="system-config.js"></script> 
  </head> 
  <body> 
    <div ng-init="val='Angular 1 bootstrapped successfully!'"> 
      {{val}}   
    </div> 
    <root></root> 
  </body> 
</html> 

Note

The new scripts listed here are dependencies of an Angular 2 application, but understanding what they're doing isn't critical for understanding this recipe. This is explained later in the book.

With all this, you should see your Angular 1 application template compile and the Angular 2 component render properly again. This means that you are successfully running Angular 1 and Angular 2 frameworks side by side.

How it works...

Make no mistake, when you use UpgradeModule, you create an Angular 1 and Angular 2 app on the same page and connect them together. This adapter instance will allow you to connect pieces from each framework and use them in harmony.

More specifically, this creates an Angular 1 application at the top level and allows you to uses pieces of an Angular 2 application inside it.

There's more...

While useful for experimentation and upgrading purposes, this should not be a solution that any application should rely on in a production context. You have effectively doubled the framework payload size and introduced additional complexity in an existing application. Although Angular 2 is a far more performant framework, do not expect to have the same pristine results with the UpgradeModule cross-pollination.

That said, as you will see in subsequent recipes, you can now use Angular 2 components in an Angular 1 application using the adapter translation methods.

See also

  • Downgrading Angular 2 components to Angular 1 directives with downgradeComponent demonstrates how to use an Angular 2 component inside an Angular 1 application

  • Downgrade Angular 2 providers to Angular 1 services with downgradeInjectable, which demonstrates how to use an Angular 2 service inside an Angular 1 application

 

Downgrading Angular 2 components to Angular 1 directives with downgradeComponent


If you have followed the steps in Connecting Angular 1 and Angular 2 with UpgradeModule, you should now have a hybrid application that is capable of sharing different elements with the opposing framework.

Tip

If you are unfamiliar with Angular 2 components, it is recommended that you go through the components chapter before you proceed.

This recipe will allow you to fully utilize Angular 2 components inside an Angular 1 template.

Note

The code, links, and a live example in relation to this recipe are available at http://ngcookbook.herokuapp.com/1499/.

Getting ready

Suppose you had the following Angular 2 component that you wanted to use in an Angular 1 application:

[app/article.component.ts] 
 
import {Component, Input} from '@angular/core'; 
 
@Component({ 
  selector: 'ng2-article', 
  template: ` 
    <h1>{{title}}</h1> 
    <p>Written by: {{author}}</p> 
  ` 
}) 
export class ArticleComponent { 
  @Input() author:string 
  title:string = 'Unicycle Jousting Recognized as Olympic Sport'; 
} 

Begin by completing the Connecting Angular 1 and Angular 2 with UpgradeModule recipe.

How to do it...

Angular 1 has no comprehension of how to utilize Angular 2 components. The existing Angular 2 framework will dutifully render it if given the opportunity, but the definition itself must be connected to the Angular 1 framework so that it may be requested when needed.

Begin by adding the component declarations to the module definition; this is used to link the two frameworks:

[app/app.module.ts] 
 
import {NgModule} from '@angular/core'; 
import {BrowserModule} from '@angular/platform-browser'; 
import {UpgradeModule} from '@angular/upgrade/static'; 
import {RootComponent} from './root.component'; 
import {ArticleComponent} from './article.component'; 
 
@NgModule({ 
  imports: [ 
    BrowserModule, 
    UpgradeModule, 
  ], 
  declarations: [ 
    RootComponent, 
    ArticleComponent 
  ], 
  bootstrap: [ 
    RootComponent 
  ] 
}) 
export class Ng2AppModule { 
  constructor(public upgrade: UpgradeModule){} 
} 

This connects the component declaration to the Angular 2 context, but Angular 1 still has no concept of how to interface with it. For this, you'll need to use downgradeComponent() to define the Angular 2 component as an Angular 1 directive. Give the Angular 1 directive a different HTML tag to render inside so you can be certain that it's Angular 1 doing the rendering and not Angular 2:

[main.ts] 
 
import {Component, Input} from '@angular/core'; 
import {downgradeComponent} from '@angular/upgrade/static'; 
import {Ng1AppModule} from './ng1.module'; 
 
@Component({ 
  selector: 'ng2-article', 
  template: ` 
    <h1>{{title}}</h1> 
    <p>Written by: {{author}}</p> 
  ` 
}) 
export class ArticleComponent { 
  @Input() author:string 
  title:string = 'Unicycle Jousting Recognized as Olympic Sport'; 
} 
 
Ng1AppModule.directive( 
  'ng1Article', 
  downgradeComponent({component: ArticleComponent})); 

Finally, since this component has an input, you'll need to pass this value via a binding attribute. Even though the component is still being declared as an Angular 1 directive, you'll use the Angular 2 binding syntax:

[index.html] 
 
<!DOCTYPE html> 
<html> 
  <head> 
    <!-- Angular 2 scripts --> 
    <script src="zone.js "></script> 
    <script src="reflect-metadata.js"></script> 
    <script src="system.js"></script> 
    <script src="system-config.js"></script> 
  </head> 
  <body> 
    <div ng-init="authorName='Jake Hsu'"> 
      <ng1-article [author]="authorName"></ng1-article> 
    </div> 
    <root></root> 
  </body> 
</html> 

The input and output must be explicitly declared at the time of conversion:

[app/article.component.ts] 
 
import {Component, Input} from '@angular/core'; 
import {downgradeComponent} from '@angular/upgrade/static'; 
import {Ng1AppModule} from './ng1.module'; 
 
@Component({ 
  selector: 'ng2-article', 
  template: ` 
    <h1>{{title}}</h1> 
    <p>Written by: {{author}}</p> 
  ` 
}) 
export class ArticleComponent { 
  @Input() author:string 
  title:string = 'Unicycle Jousting Recognized as Olympic Sport'; 
} 
 
Ng1AppModule.directive( 
  'ng1Article', 
  downgradeComponent({ 
    component: ArticleComponent,  
    inputs: ['author'] 
  })); 

These are all the steps required. If done properly, you should see the component render along with the author's name being interpolated inside the Angular 2 component through Angular 1's ng-init definition.

How it works...

You are giving Angular 1 the ability to direct Angular 2 to a certain element in the DOM and say, "I need you to render here." Angular 2 still controls the component view and operation, and in every sense, the main thing we really care about is a full Angular 2 component adapted for use in an Angular 1 template.

Tip

downgradeComponent() takes an object specifying the component as an argument and returns the function that Angular 1 is expecting for the directive definition.

See also

  • Connecting Angular 1 and Angular 2 with UpgradeModule shows you how to run Angular 1 and 2 frameworks together

  • Downgrade Angular 2 providers to Angular 1 services with downgradeInjectable demonstrates how to use an Angular 2 service inside an Angular 1 application

 

Downgrade Angular 2 providers to Angular 1 services with downgradeInjectable


If you have followed the steps in Connecting Angular 1 and Angular 2 with UpgradeModule, you should now have a hybrid application that is capable of sharing different elements with the opposing framework. If you are unfamiliar with Angular 2 providers, it is recommended that you go through the dependency injection chapter before you proceed.

Like with templated components, interchangeability is also offered to service types. It is possible to define a service type in Angular 2 and then inject it into an Angular 1 context.

Note

The code, links, and a live example in relation to this recipe are available at http://ngcookbook.herokuapp.com/2824/.

Getting ready

Begin with the code written in Connecting Angular 1 and Angular 2 with UpgradeModule.

How to do it...

First, define the service you would like to inject into an Angular 1 component:

[app/article.service.ts] 
 
import {Injectable} from '@angular/core'; 
 
@Injectable() 
export class ArticleService { 
  article:Object = { 
    title: 'Research Shows Moon Not Actually Made of Cheese', 
    author: 'Jake Hsu' 
  }; 
} 

Next, define the Angular 1 component that should inject it:

[app/article.component.ts] 
 
export const ng1Article = { 
  template: ` 
    <h1>{{article.title}}</h1> 
    <p>{{article.author}}</p> 
  `, 
  controller: (ArticleService, $scope) => { 
    $scope.article = ArticleService.article; 
  } 
}; 

ArticleService won't be injected yet though, since Angular 1 has no idea that this service exists. Doing this is very simple, however. First, you'll list the service provider in the Angular 2 module definition as you normally would:

[app/ng2.module.ts] 
 
import {NgModule} from '@angular/core'; 
import {BrowserModule} from '@angular/platform-browser'; 
import {UpgradeModule} from '@angular/upgrade/static'; 
import {RootComponent} from './root.component'; 
import {ArticleService} from './article.service'; 
 
@NgModule({ 
  imports: [ 
    BrowserModule, 
    UpgradeModule, 
  ], 
  declarations: [ 
    RootComponent 
  ], 
  providers: [ 
    ArticleService 
  ], 
  bootstrap: [ 
    RootComponent 
  ] 
}) 
export class Ng2AppModule { 
  constructor(public upgrade: UpgradeModule){} 
} 

Still, Angular 1 does not understand how to use the service.

In the same way you convert an Angular 2 component definition into an Angular 1 directive, convert an Angular 2 service into an Angular 1 factory. Use downgradeInjectable and add the Angular 1 component and the converted service to the Angular 1 module definition:

[app/ng1.module.ts] 
 
import 'angular'; 
import {ng1Article} from './article.component'; 
import {ArticleService} from './article.service'; 
import {downgradeInjectable} from '@angular/upgrade/static'; 
 
export const Ng1AppModule = angular.module('Ng1AppModule', []) 
  .component('ng1Article', ng1Article) 
  .factory('ArticleService', downgradeInjectable(ArticleService)); 

That's all! You should be able to see the Angular 1 component render with the data passed from the Angular 2 service.

See also

  • Connecting Angular 1 and Angular 2 with UpgradeModule shows you how to run Angular 1 and 2 frameworks together

  • Downgrading Angular 2 components to Angular 1 directives with downgradeComponent demonstrates how to use an Angular 2 component inside an Angular 1 application

About the Author

  • Matt Frisbie

    Matt Frisbie is currently a software engineer at Google. He was the author of the Packt Publishing bestseller AngularJS Web Application Development Cookbook and also has published several video series through O'Reilly. He is active in the Angular community, giving presentations at meetups and doing webcasts.

    Browse publications by this author

Latest Reviews

(2 reviews total)
Very thorough and good examples
Good written, well structured.
Book Title
Unlock this book and the full library for only $5/m
Access now