Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Comparing Angular 1 Components to the latest Angular Components

Cory Rylan

-

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

With Angular 2.x and later components are the primary building blocks for our apps. With Angular 1.5, Angular introduced a new component syntax that mimics similar behavior to Angular 2+ components. In this example we will take an Angular 1 component and compare it to an Angular 2+ component. By the end of this post you should have a better idea of what it takes to convert an Angular 1.x component to Angular 2 and later. First we are going to take a look at a simple Angular 1 component how it accepts inputs and emits output events to other Angular 1 components.

Angular 1 Component

In this example we have a simple application that lists a list of products for sale. We have two components a root app component and a product-item component. Using components our apps are formed into a tree structure of components. With this tree like structure it is easier to understand how an app is composed and data is passed between components. Lets take a look at what the rendered output will look like.

Now lets take a look at the app component source code. Our Angular 1 code is in ES5 while our Angular 2.x and later code will be written in ES6/TypeScript.


angular.module('app', [])
  .component('appComponent', {
    template: [
      '<div ng-repeat="product in $ctrl.products">',
        '<product-item product="product" on-select="$ctrl.selectedProduct = $event"></product-item>',
        '</div>',
        '<hr />',
        '<h3>{{$ctrl.selectedProduct.name}}</h3>',
        '<p>{{$ctrl.selectedProduct.price | currency}}</p>',
    ].join(''),
    controller: function () {
      this.products = [
        { name: 'iPhone', price: 500.00 },
        { name: 'iPad', price: 800.00 },
        { name: 'Macbook', price: 1200.00 }
      ];

      this.selectedProduct = this.products[0];
     }
});

So our App component has a list of products that we list using ng-repeat. We then pass our product to the product-item component. Our App component also listens for an on-select event from the product-item component to know what item was selected by the user and display that item. The $ctrl in our template references the methods and properties listed on our component's controller. So now lets look at the product-item source code.


angular
  .module('app')
  .component('productItem', {
    bindings: {
      product: '<',
      onSelect: '&'
    },
   controller: function () { },
     template: [
       '<div class="product">',
         '<button ng-click="$ctrl.onSelect({$event: $ctrl.product})">Buy</button> ',
         '{{$ctrl.product.name}}',
       '</div>'
     ].join('')
   });

Looking at our product-item component we have a property called bindings defined. This let us define an API to our component for how it will interact with other components. The first binding we have is product this is the product that is passed into the component from our parent app component. <product-item product="product"> The binding '<' is for binding as a one way property passing a reference of the product.

The next binding is the onSelect which uses the '&' notation. This means the incoming value should be treated as a expression or function to be executed. This allows the product-item to call a function that is being passed in to notify our parent app component. <product-item on-select="$ctrl.selectedProduct = $event">.

So when we click a buy button we call the onSelect and pass back the selected item to the parent component. This data flow is common and encouraged in Angular 2. We can visualize how the data flows through our app with the diagram below.

Example of Angular component data flow

So we can see we pass data along down to child components and the child components use events to notify their parent of a change or user action. We will see how this pattern is reenforced in our Angular 2 version.

Angular 2.x and newer Component

So now lets take a look at the newer Angular 2.x and later component that has the exact same functionality as our Angular 1 component. First we will look at our root app component.


import { Component } from '@angular/core';
import { ProductItemComponent } from 'app/product-item.component';

@Component({
  selector: 'demo-app',
  template: `
    <div *ngFor="let product of products">
      <product-item [product]="product" (select)="selectedProduct = $event"></product-item>
    </div>
    <hr />
    <h3>{{selectedProduct.name}}</h3>
    <p>{{selectedProduct.price | currency:'USD':true:'1.2-2'}}</p>
  `
})
export class AppComponent  {
  selectedProduct: any;
  products: { name: string, price: number }[];

  constructor() {
    this.products = [
      { name: 'iPhone', price: 500.00 },
      { name: 'iPad', price: 800.00 },
      { name: 'Macbook', price: 1200.00 }
    ];

    this.selectedProduct = this.products[0];
  }
}

In our Angular app we are taking advantage of ES6 and TypeScript to give us a nice clean syntax with improved IDE tooling. I wont be covering setup on an Angular project but you can check out the running demos and any number of Angular seed projects. First we are importing Angular modules and our ProductItemComponent using ES6 module syntax.

import { Component } from '@angular/core';
import { ProductItemComponent } from 'app/product-item.component';

Next we have what is called a decorator Component() on our Class. This decorator tells Angular that this ES6 class is a component and allows us to add meta data such as what our template is and what other components it may need to work.


@Component({
  selector: 'demo-app',
  template: `
    <div *ngFor="#product of products">
      <product-item [product]="product" (select)="selectedProduct = $event"></product-item>
      </div>
      <hr />
      <h3>{{selectedProduct.name}}</h3>
      <p>{{selectedProduct.price | currency:'USD':true:'1.2-2'}}</p>
  `
})

The first property in the component decorator is the selector this simply tells Angular what the HTML element should be. Ex: <demo-app></demo-app>.

Looking at our template it looks similar to the Angular 1 component with some slight differences. First ng-repeat is now ngFor. The * is used to signal that this directive is a structural directive and will change the DOM structure of our template. Next look at the product-item component.

Template Syntax, Properties and Events

<product-item
  [product]="product"
  (select)="selectedProduct = $event"
></product-item>

This is where things get a bit strange but it actually is a great improvement over Angular 1. First is our [] notation we see wrapped around [product]. This means we are passing in a product to our product-item component via a custom property. So in Angular when we want to pass data into components we use the brackets to signify that we are passing in data [product].

Next is the (select) on the product-item. The select is a custom event our product-item component raises to notify it's parent component. When we want to hook into events we reference using the () parens syntax. This applies to all events even browser events like click, ex: (click). We will see more of this once we go over our product-item component. One thing to note our (select) uses camel casing vs dashes. This is because of Angular's new HTML parser that allows our HTML to be case-sensitive. This removes the need of case conversions that we had in Angular 1.x.

So whats the benefit of this syntax? Well we can easily describe our components API. Data flows in as inputs to the component via [properties] and data flows as outputs via (events). We can look at a template and quickly understand the data flow between components. This will also help IDEs statically analyze and understand our template and give us hints such as possible missing properties and events on our component.

Now lets look at the class definition of the app component. This is a simple ES6/ES2015 class with a bit of TypeScript. We will ignore the TypeScript bit for now.

export class AppComponent {
  selectedProduct: any;
  products: { name: string; price: number }[];

  constructor() {
    this.products = [
      { name: 'iPhone', price: 500.0 },
      { name: 'iPad', price: 800.0 },
      { name: 'Macbook', price: 1200.0 }
    ];

    this.selectedProduct = this.products[0];
  }
}

So here we can see we are defining some properties on our component class. First is a list of products that we will pass into our ngFor. Next is the selectedProduct property. We set this value to the selected property our product-item component emits.

So lets look into the product-item source code and then dig into the template syntax a bit more.


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

@Component({
  selector: 'product-item',
  template: `
    <div class="product">
       <button (click)="selectItem()">Buy</button>
      {{product.name}}
    </div>
  `
})
export class ProductItemComponent {
  @Input() product: any;
  @Output() select = new EventEmitter();

  selectItem() {
    this.select.emit(this.product);
  }
}

The first line we are importing the pieces we need from Angular once again using the new ES6 module syntax. Next is our component decorator that we covered earlier. The template is fairly small. We display the product we get from our input property. The next part is the click event we create. Using the event syntax we don't need a bunch of Angularisms like ng-click, ng-whatever-event. The () lets Angular know we simply want a browser click event. Our click event calls a method on the component called select.

Now lets look at the component class.

export class ProductItemComponent {
  @Input() product: any; // incoming data  [product]
  @Output() select: EventEmitter; // outgoing data  (select)

  constructor() {
    this.select = new EventEmitter();
  }

  selectItem() {
    this.select.emit(this.product);
  }
}

As you can see we have two properties defined on our component. They are product and select. These properties are decorated with @Input and @Output decorators. This syntax is currently a TypeScript feature but there is an equivalent syntax for ES2015 code. These property decorators tell Angular their purpose. So the product is an input to our component accepting a product object. [product] -> @Input() product: any The onSelect is an output and tells Angular that we will be outputting values using the EventEmitter class. (select) -> @Output() onSelect: EventEmitter.

In our constructor we set the select property to a new EventEmitter. In the component's method select we emit a new event which will be our selected product. The select method is called in our (click) event on the template.

Conclusion

So lets take another look at our data flow diagram now with the updated Angular syntax.

Example of Angular component data flow

As we can see the new Angular 2 syntax directly corresponds to how data flows in our application making it easier to understand and debug. Here is a code snippet of our component with the Angular 1 and Angular 2 versions.


// Angular 1
angular
  .module('app')
  .component('productItem', {
    template: [
      '<div class="product">',
      '<button ng-click="$ctrl.onSelect({$event: $ctrl.product})">Buy</button> ',
      '{{$ctrl.product.name}}',
      '</div>'
    ].join(''),
    bindings: {
      product: '<',
      onSelect: '&'
    },
    controller: function () { }
  });

// Angular 2 and later
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'product-item',
  template: `
    <div class="product">
    <button (click)="selectItem()">Buy</button>
    {{product.name}}
    </div>
    `
})
export class ProductItemComponent {
  @Input() product: any;
  @Output() select: EventEmitter;

  constructor() {
    this.select = new EventEmitter();
  }

  selectItem() {
    this.select.emit(this.product);
  }
}

Migration Strategies

Many existing Angular 1 apps are not using components but controllers and directives. My recommendation is to convert controllers over to components if you are concerned about upgrading to Angular in the future. Another stepping stone to a Angular migration is using ES6 or TypeScript in your Angular 1 applications. Angular 2 and later will be very component centric and by using components in 1.5+ it will help guide your app closer to the vision of Angular.

You can check out both versions of the demo below.

Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article