angular1to2-part2.png

Migrating from Angular 1 to 2: Part 2, Components – Sortable Table

Since we’ve last touch based on Angular 2’s standing, there have been many changes made to one of many beloved products of Google. Most notably, a few of Angular 2’s ever-elusive release candidates have finally been released to the product. A whole new router has been released, new *ngFor syntax, a revamped module-based dependencies, and many new features. Between all of these changes and features, one thing has stayed the same: the idea of a component-driven driven framework.

Pipes are still working as expected, so be sure to go back and check out part 1 of this series if you haven’t already!

Angular 2 Components: Sortable Table Component

In Angular 1, we focused on creating reusable directives that we could simply add wherever we wanted. In Angular 2, we have similar tools, namely components. Components are the basis of nearly everything you want to do in Angular 2. Components are where you start your views, they declare your routing, and they are what will interact with anything behind the scenes.

Components are such an important part of all Angular 2 projects, that we have started creating an open source library of Bootstrap 4 components, directives, and pipes called Fuel-UI!

To get us started with components, we’re going to port our Sortable Table Directive from Angular 1 to be used in Angular 2 as a component.

Simple Component

The structure of a component looks like this.

import {Component} from '@angular/core'

@Component({
  selector: 'our-component',
  template: '<div>Hello my name is {{name}}<div>'
})
export class OurComponent {
  constructor() {
    this.name = 'Cory'
  }
}

We first import the Component class from @angular/core. You’ll notice that this looks different from before because Angular has switched to using module-based dependencies. So everything that we import from the Angular dependency will begin with @angular/.

Component Decorator

The Component class is used as a decorator of OurComponent. A decorator can be added on top of classes, methods, accessors, properties, and even parameters. Decorators are used in the form of @decoratorName before any of the previously stated types. You’ll notice over the course of creating our new component, that decorators are a powerful part of TypeScript and Angular 2.

The Component decorator takes in an object as a parameter that explains how to call and instantiate the component.

The selector property is how we can call this component in the view. It is a string, that can be comma-delimited, and can has two different types: element and attribute. An element type can simply be written out in a string like we have done, and the way to call this is by creating a DOM element that matches the string. So our example would be . The way to set the component to be called by an attribute is to wrap it in brackets: [our-component]. Then you simply add the attribute to any element:


The template property represents the actual view that will be displayed to the user. The template syntax is very similar to Angular 1’s syntax, and we have gone over it briefly in previous blogs. You’ll see that template binding is still mostly the same, and still uses double braces to represent template binding. We are outputting this.name from OurComponent class by simply calling {{name}} in the template. Views have access to all of the class’ properties and methods. Any local vars in the class will not be directly capable of being implemented into the view. More on that later.

Our Old Directive

Let’s take a look at the previous sortable table directive.


There are three attributes we use on the directive to act as parameters to pass into the directive: columns, data, and sort. We are going to do the same thing with our new component.

New and Improved Component

Very similar to before except for a small change.


The main difference is you’ll see our attributes are wrapped in brackets. This is to represent to the view that the value of these attributes is a variable that needs to be pulled from the class. If the attributes were not wrapped in brackets, the actual string will be passed to the component. This can be useful for times where we actually do want just a string, but each of these attributes are complex objects so we will stick to using brackets here.

Declaring Inputs to Component

Let’s go ahead and update our component to what we will actual use.

import {Component, Input} from '@angular/core';
import {OrderBy} from './orderBy';
import {Format} from './format';

@Component({
    selector: 'table-sortable',
    templateUrl: 'src/tableSortable.html',
    pipes: [OrderBy, Format]
})
export class TableSortable {
    @Input() columns: any[];
    @Input() data: any[];
    @Input() sort: any;
}

We simply import the normal Component class from before, but we now are also import Input from @angular/core. Input is another decorator to signify that a class’ property can be used as an attribute to act as a parameter of the Component. We changed template to templateUrl and set the property to the path of our HTML file. This helps keep our code clean and separate. As you know from our previous blog about Angular 2 Pipes, we have to add each of the used pipes to the pipes array of the Component decorator so that the view has access to use the pipe. We haven’t ported our Format filter yet from Angular 1 to an Angular 2 Pipe yet, so more on that later.

Moving down to the actual TableSortable class, you’ll see we declare our 3 needed properties, then we add the Input decorator to each. We define columns and data to arrays of any type, and sort is just any type.

TableSortable Methods

Now we need to add the methods that we use in the view to interact with the data we display.

export class TableSortable {
    @Input() columns: any[];
    @Input() data: any[];
    @Input() sort: any;

    selectedClass(columnName): string{
        return columnName == this.sort.column ? 'sort-' + this.sort.descending : false;
    }
  
    changeSorting(columnName): void{
        var sort = this.sort;
        if (sort.column == columnName) {
            sort.descending = !sort.descending;
        } else {
            sort.column = columnName;
            sort.descending = false;
        }
    }
  
    convertSorting(): string{
        return this.sort.descending ? '-' + this.sort.column : this.sort.column;
    }

}

These are the same methods as our Angular 1 directive, removing anything having to do with $scope since this is removed altogether in Angular 2. We also change each method to directly update the properties of the class using this. so that our template reflects the changes.

TableSortable Template

Reviewing our Angular 1 directive’s template:


 

{{column.display}}
{{object[columns[$index].variable] | customFilter : getFilterOfColumn(columns[$index].variable)}}

The main changes we are going to see, are the for loops and attribute bindings. The structure will still remain similar.


 

{{column.display}}
{{object[column.variable] | format : column.filter}}

In Angular 2, all ng- attributes have been removed. Our Angular 1 ng-for attribute now becomes *ngFor and is used as *ngFor="let dataToUse of arrayToLoop".

We can now directly bind to standard DOM attributes as well. Look at the table’s header for loop for example. We are binding to two different attributes. The standard class attribute is being bound with brackets, and runs our component’s class’ selectedClass method to correctly set the class of the header. You’ll also see that there is a second attribute that we bind to using parentheses. Parentheses says we are binding to event, and in this case we are binding to click events on the table header. It’s similar to us using onclick="", so we can use other events including onhover, onfocus, etc., just be sure to remove on when binding to the event.

Format Filter to Pipe

Now using the knowledge from what we learned in part 1 of this series, we migrated our Format filter to this.

import {Pipe, PipeTransform} from '@angular/core';
import {DatePipe, DecimalPipe} from '@angular/common';

@Pipe({
  name: 'format'
})
export class FormatPipe implements PipeTransform  {
  
  datePipe: DatePipe = new DatePipe();
  decimalPipe: DecimalPipe = new DecimalPipe();
  
  transform(input:string, args:any): any {
    var format = '';
    var parsedFloat = 0;
    var pipeArgs = args.split(':');
    for(var i = 0; i < pipeArgs.length; i++){ pipeArgs[i] = pipeArgs[i].trim(' '); } switch(pipeArgs[0].toLowerCase()) { case 'text': return input; case 'decimal': case 'number': parsedFloat = !isNaN(parseFloat(input)) ? parseFloat(input) : 0; format = pipeArgs.length > 1 ? pipeArgs[1] : null;
        return this.decimalPipe.transform(parsedFloat, format);
      case 'percentage':
        parsedFloat = !isNaN(parseFloat(input)) ? parseFloat(input) : 0;
        format = pipeArgs.length > 1 ? pipeArgs[1] : null;
        return this.decimalPipe.transform(parsedFloat, format) + '%';
      case 'date':
      case 'datetime':
        var date = !isNaN(parseInt(input)) ? parseInt(input) : new Date(input);
        format = 'MMM d, y h:mm:ss a';
        if(pipeArgs.length > 1)
        {
            format = '';
            for(var i = 1; i < pipeArgs.length; i++){
                format += pipeArgs[i];
            }
        }
        return this.datePipe.transform(date, format);
      default:
        return input;
    }
  }
}

One main difference is that we had to pull DecimalPipe and DatePipe from @angular/core, which is a little different from how we did it in Angular 1. Number/Decimal pipes in Angular 2 now also take in a different kind of parameter. Rather than simply taking in the number of decimal places, you pass a string with the syntax minIntegerDigits.minFractionDigits-maxFractionDigits to output a formatted number.

The Class to Call the Component

Now that we have our component finished, we need a class that has the TableSortable component in its view.

import {Component} from '@angular/core'
import {CORE_DIRECTIVES} from '@angular/common'
import {TableSortable} from './tableSortable'

@Component({
  selector: 'my-app',
  templateUrl: 'src/app.html',
  directives: [CORE_DIRECTIVES, TableSortable]
})
export class App {
  
  rows: any[] = [
    {
      Name: 'Data 1',
      Amount: 100.23,
      Date: 1433588216000
    },
    {
      Name: 'Data 2',
      Amount: 0.875623,
      Date: 1432387616000
    },
    {
      Name: 'Data 3',
      Amount: .010123,
      Date: 1445487616000
    },
    {
      Name: 'Data 4',
      Amount: 1873.02301,
      Date: 1461820116000
    },
    {
      Name: 'Data 5',
      Amount: -93,
      Date: 1423128616000
    }
  ];
  columns: any[] = [
    {
      display: 'Column 1', //The text to display
      variable: 'Name', //The name of the key that's apart of the data array
      filter: 'text' //The type data type of the column (number, text, date, etc.)
    },
    {
      display: 'Column 2', //The text to display
      variable: 'Amount', //The name of the key that's apart of the data array
      filter: 'decimal : 1.0-2' //The type data type of the column (number, text, date, etc.)
    },
    {
      display: 'Column 3', //The text to display
      variable: 'Date', //The name of the key that's apart of the data array
      filter: 'dateTime' //The type data type of the column (number, text, date, etc.)
    }
  ];
  sorting: any = {
    column: 'Name', //to match the variable of one of the columns
    descending: false
  };
}

And our app.html is the same as earlier.


Finished Product

And that’s it! You should now have a fully functional Angular 2 TableSortable component!

Error: Embedded data could not be displayed.

Fuel-UI

We have added this component to our open source library of Bootstrap 4 components, directives, and pipes, Fuel-UI! We would love for you all to fork our repo and add to our growing collection. We hope the community will be able to use our library with their own projects!

Be sure to go back and check out part 1 of this series to better understand pipes if you haven’t already!

Here at Fuel, we are backers of Angular 2, and already have tools that use this fast, beneficial, and state-of-the-art technology on products like our Fuel Gauge Hotel Marketing Dashboard! Check out our Github for projects similar to this!

Leave a comment below!

comments

Cory ShawMigrating from Angular 1 to 2: Part 2, Components – Sortable Table
Share this post