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

September 20, 2016 in JavaScript Articles

Written by Eric Greene


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.


Written by Eric Greene

Eric Greene

Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.



Contact Us:

Accelebrate’s training classes are available for private groups of 3 or more people at your site or online anywhere worldwide.

Don't settle for a "one size fits all" public class! Have Accelebrate deliver exactly the training you want, privately at your site or online, for less than the cost of a public class.

For pricing and to learn more, please contact us.

Contact Us Train For Us

Toll-free in US/Canada:
877 849 1850
International:
+1 678 648 3113

Toll-free in US/Canada:
866 566 1228
International:
+1 404 420 2491

925B Peachtree Street, NE
PMB 378
Atlanta, GA 30309-3918
USA

Subscribe to our Newsletter:

Never miss the latest news and information from Accelebrate:

Microsoft Gold Partner

Please see our complete list of
Microsoft Official Courses

Recent Training Locations

Alabama

Huntsville

Montgomery

Birmingham

Alaska

Anchorage

Arizona

Phoenix

Tucson

Arkansas

Fayetteville

Little Rock

California

San Francisco

Oakland

San Jose

Orange County

Los Angeles

Sacramento

San Diego

Colorado

Denver

Boulder

Colorado Springs

Connecticut

Hartford

DC

Washington

Florida

Fort Lauderdale

Miami

Jacksonville

Orlando

Saint Petersburg

Tampa

Georgia

Atlanta

Augusta

Savannah

Idaho

Boise

Illinois

Chicago

Indiana

Indianapolis

Iowa

Ceder Rapids

Des Moines

Kansas

Wichita

Kentucky

Lexington

Louisville

Louisiana

Baton Rouge

New Orleans

Maine

Portland

Maryland

Annapolis

Baltimore

Hagerstown

Frederick

Massachusetts

Springfield

Boston

Cambridge

Michigan

Ann Arbor

Detroit

Grand Rapids

Minnesota

Saint Paul

Minneapolis

Mississippi

Jackson

Missouri

Kansas City

St. Louis

Nebraska

Lincoln

Omaha

Nevada

Reno

Las Vegas

New Jersey

Princeton

New Mexico

Albuquerque

New York

Buffalo

Albany

White Plains

New York City

North Carolina

Charlotte

Durham

Raleigh

Ohio

Canton

Akron

Cincinnati

Cleveland

Columbus

Dayton

Oklahoma

Tulsa

Oklahoma City

Oregon

Portland

Pennsylvania

Pittsburgh

Philadelphia

Rhode Island

Providence

South Carolina

Columbia

Charleston

Spartanburg

Greenville

Tennessee

Memphis

Nashville

Knoxville

Texas

Dallas

El Paso

Houston

San Antonio

Austin

Utah

Salt Lake City

Virginia

Richmond

Alexandria

Arlington

Washington

Tacoma

Seattle

West Virginia

Charleston

Wisconsin

Madison

Milwaukee

Alberta

Edmonton

Calgary

British Columbia

Vancouver

Nova Scotia

Halifax

Ontario

Ottawa

Toronto

Quebec

Montreal

Puerto Rico

San Juan

© 2013-2019 Accelebrate, Inc. All Rights Reserved. All trademarks are owned by their respective owners.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.