Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Angular 2 Cookbook

You're reading from   Angular 2 Cookbook Discover over 70 recipes that provide the solutions you need to know to face every challenge in Angular 2 head on

Arrow left icon
Product type Paperback
Published in Jan 2017
Publisher Packt
ISBN-13 9781785881923
Length 464 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Matthew Frisbie Matthew Frisbie
Author Profile Icon Matthew Frisbie
Matthew Frisbie
Patrick Gillespie Patrick Gillespie
Author Profile Icon Patrick Gillespie
Patrick Gillespie
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. Strategies for Upgrading to Angular 2 FREE CHAPTER 2. Conquering Components and Directives 3. Building Template-Driven and Reactive Forms 4. Mastering Promises 5. ReactiveX Observables 6. The Component Router 7. Services, Dependency Injection, and NgModule 8. Application Organization and Management 9. Angular 2 Testing 10. Performance and Advanced Concepts

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
You have been reading a chapter from
Angular 2 Cookbook
Published in: Jan 2017
Publisher: Packt
ISBN-13: 9781785881923
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image