US & Canada: 877 849 1850
International: +1 678 648 3113

Accelebrate Blog

ACCELERATED LEARNING, CELEBRATED RESULTS

Component-driven Development with Angular 1 and Angular 2 (Part 2 of 2)

In the first part of this two-part series, the future of web development with web components in Angular 1 and Angular 2 was related. We also explored the specifics of creating components and customizing component user interfaces using Angular 1 and Angular 2. With the user interface understood, the next major topic of component-driven development is the sharing of data between components.

Sharing Data and Actions between Components

Components by definition prescribe the data and actions they support and are generally tied to the template. When working with multiple components, the setting up of a predictable, standardized data flow is critical. Angular 1 used a lot of magic to share data and actions between directives. Between two-way data binding, the digest loop and watches, prototype inheritance of scopes, and isolated scope with multiple bindings, following the flow of data and actions in an Angular 1 programming can be confusing. The new component API in Angular 1.5 great simplifies all of this. First, the component uses isolated scope which means data does not bleed into the component from parent scope objects. This data bleed resulted in a lot of confusion for developers, and unnecessary tight coupling between child scopes and parent scopes reducing reusability and testability of these discrete UI units.

Angular 1.5 introduced a new configuration option named bindings for the former directive scope configuration and bind to controller configurations options: scope and bindToController, respectively. The new bindings option is used to configure the inputs and outputs between the parent component and child component. Also, controllerAs is turned on by default.

The next big change was the addition of a one-way scope binding. Previously, Angular 1 supported a literal binding (sometimes referred to as one-way, but really it was just the passing in of a literal value), two-way binding, and function binding. With the new component API, the one-way binding can be used as a replacement for the literal and two-way bindings. Through the one-way binding, outside information can be passed in, treated as immutable, and future updates will be easily received. This allows for a predictable and understandable flow of data from a parent component to a child component.

The function binding can then be used to output data from the child component through the execution of events which supply data back to the parent. These work the same in the new component API as they did before.

The new component API uses bindToController and controllerAs by default, so the data and actions will be defined on the controller object, and the isolated scope bindings will be available as properties on the controller object.

Because the new component API merely sits on top of the existing Angular 1 architecture, a similar configuration can be set up for Angular 1.3 and 1.4 using isolated scope, two-way bindings (treated as one-way), controller-as, and the usual function bindings.

Angular 1 Component Data Sharing – HTML

<div ng-app="myApp" ng-controller="myCtrl as $ctrl">
  <!-- colors are passed in as items, and addColor as addItem -->
  <!-- think of colors and addColor as arguments to a function -->
  <!-- think of items and addItem as parameters of a function -->
  <!-- inputs: data is passed in via attributes -->
  <list-box items="$ctrl.colors" add-item="$ctrl.addColor(item)">
    <header>
      <h2>List of Colors</h2>
    </header>
    <footer>
      <small>Source: Sample Color Institute&lgt;/small>
    </footer>
  </list-box>
</div>

Angular 1 Component Data Sharing – JavaScript

angular.module('myApp', [])
  .controller("myCtrl", function() {

    this.colors = ['red','blue','green','yellow','orange'];

    // outputs: data is received from child via function (event handler or action)
    this.addColor = function(newColor) {
      // mutating colors in the parent is allowed, but not the child (list-box)
      this.colors.push(newColor);
    };

  })
  .component('listBox', {
    transclude: {
      'header': 'header',
      'footer': 'footer'
    },
    template: `<ng-transclude ng-transclude-slot='header'></ng-transclude>
      <ul>
        <li ng-repeat='item in $ctrl.items'>{{item}}</li>
      </ul>
      <form>
        <label>New Item: <input ng-model="$ctrl.newItem" type="text"></label>
        <button ng-click="$ctrl.addItem({ item: $ctrl.newItem}); $ctrl.newItem=''">Add</button>
      </form>
        <ng-transclude ng-transclude-slot='footer'></ng-transclude>`,
  bindings: {
    items: '>', // one-way items array, should not be mutated inside list-box
    addItem: '&' // function bind to send new item to parent to be added to list
  }
});

Angular 2 uses the same concepts of inputs and outputs. Inputs are immutable values passed through the component attributes, and outputs are functions passed into the child components and executed with data to pass to the parent component sent through the function arguments.

Angular 2 Component Data Sharing – HTML

@Component({
  selector: 'my-app',
  template: `<list-box [items]="colors" (addItem)="addColor($event)">
      <header>
        <h1>List of Colors</h1>
      </header>
      <footer>
        <small>Source: Sample Color Institute
      </footer>
    </list-box>`,
  directives: [ListBox]
})
export class AppComponent {

  colors = ['red','blue','green','yellow','orange'];

  addColor(newColor) {
    this.colors.push(newColor);
  }
}

Angular 2 Component Data Sharing – TypeScript

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { FORM_DIRECTIVES } from '@angular/forms';

@Component({
  selector: 'list-box',
  template: `<ng-content select="header"></ng-content>
    <ul>
      <li *ngFor='let item of items'>{{item}}</li>
    </ul>
    <form>
      <label>New Item: <input [(ngModel)]="newItem" [ngModelOptions]="{standalone: true}" type="text"></label>
      <button (click)="addItem.emit(newItem); newItem = ''">Add</button>
    </form>
    <ng-content select="footer"></ng-content>`,
  directives: [FORM_DIRECTIVES]
})
export class ListBox {

	@Input()
	items = [];

	@Output()
	addItem = new EventEmitter();
}

There is one additional way to share data and actions in Angular 1: using the require option and gaining access to the parent’s (or other ancestor’s) controller object. This approach is not recommended as it results in the same bleeding of scope that occurs with prototype inheritance of the scope. Bleeding in the sense that there is no specifically defined set of inputs and outputs. The require option is a hold over feature from earlier versions of Angular 1 where the controller of a directive served the purpose of directive communication – not data/action sharing.

Conclusion

In summary, components are directives with templates and controllers. From the first post, the templates describe what the user sees. From this post, controller describes the data and actions of the component. In Angular 1, the controller is represented by a function that serves as a constructor for a JavaScript object. In Angular 2, the controller is represented by a class that is really just a constructor function in an ES2015 disguise.

Components should be isolated from their surrounding container and should interact with the container through inputs and outputs. Inputs are immutable data and actions passed into the component. The data is displayed in the template, and the actions are bound as event handlers to UI elements, such as buttons. Outputs are when the actions are executed in response to events, and the output data is passed into the actions as arguments. Therefore, incoming data is immutable, and outgoing data is passed into actions as arguments, creating a predictable and easily understood data flow.

This flow of data is repeated for each component in the system regardless of its location in the hierarchy. It’s better to create smaller, more specific, and more focused components that are composed together, than it is to build larger single components. Smaller, focused components can be reused over and over again and are more easily tested.

Ideally, state is always maintained in a service or the root component of the component tree. The only exception is the state related to capturing form data as the user interacts with a form (ngModel should be used for this). As much as possible, all other components should interact with inputs and outputs in a pure way. Pure means all inputs are immutable and passed in via one-way bindings, and all outputs are returned via arguments to actions linked by function bindings.

While all of this was possible in earlier versions of Angular 1, Angular 1.5 provides a new component API that should be used when possible. For older versions of Angular, a directive with the restrict option set on the element, and isolated scope combined with a template and controller, will suffice for creating a component. Also, strongly consider using controller-as and bind-to-controller to further approximate the new component API. Angular 2 is built around the concept of components from the ground up.

One final cool aspect about components is that when you learn to development with components in any particulate framework or library, most of that knowledge is easily transferred to other frameworks and libraries. Learning how to do it conceptually in Angular 1, you can easily jump to Angular 2 or even React, and vice versa. Components are the not only the future, they are for today as well.


Author: Eric Greene, one of Accelebrate’s instructor

Accelebrate offers private AngularJS training and JavaScript training for groups and instructor-led online JavaScript classes for individuals.

Categories: JavaScript Articles
Tags: , , , , , ,

Leave a Reply

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

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

*



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Please contact us for GSA pricing.
Contract #GS-35F-0307T

Please see our complete list of
Microsoft Official Courses