Get Angular 1 Features in Angular 2

Quickly learn how to implement common Angular 1 app features in Angular 2!

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

Introduction

If you have solid experience with AngularJS 1.x, you're well aware of the framework's features and intricacies. Many of us have plenty of Angular 1 projects in development and production. Angular 2 was officially released in mid-September 2016. Maybe you've dabbled with the Angular 2 Tour of Heroes tutorial, but if you haven't had the opportunity to build a real-world Angular 2 app, let's face it: the prospect can be daunting.

The entire Angular framework has been rewritten into a brand new platform. Change detection is better. New dependencies and understanding are required for development (TypeScript, RxJS, ES6, a new style guide, etc.). Many of the old standbys are gone (no more $scope, no $rootScope.broadcast, no filter or orderBy pipes, etc.).

There aren't many shortcuts for an Angular 1 developer looking to learn Angular 2 quickly. You may have heard about the difficulties of upgrading an existing codebase from Angular 1.x to Angular 2. Many developers have better luck migrating to a clean Angular 2 build. This is a great way to learn a new platform, though it requires a certain climate with ample time, budget, and stability testing.

So how can we decrease the learning curve when transitioning from Angular 1 to Angular 2?

About This Guide

The purpose of this tutorial is to provide guidance for implementing common features of Angular 1 in Angular 2. Some things have been removed or replaced. Some require a mental model shift. We'll cover the basics and hopefully you'll be able to get up and running more quickly with your own Angular 2 apps.

What We'll Cover

We'll address several features that many real-world Angular apps require:

Note: The Angular 1 code samples use AngularJS version 1.5.x.

What We Won't Cover

We aren't going to go indepth explaining Angular 2 project setup, Angular CLI, TypeScript, ES6, RxJS, functional reactive programming (FRP), or testing.

However, this tutorial does assume a basic understanding of Angular 2 prerequisites. This includes Angular 2 project architecture, TypeScript, ES6, and RxJS. To familiarize yourself, check out some of these resources:

For a full migration tutorial including all setup steps, check out Migrating an Angular 1 App to Angular 2 - Part 1, Part 2, and Part 3.

Code Repository

Sample Angular 2 code is available for each section in the migrating-angular-features-to-angular2 GitHub repo.

Dependencies

To use the code samples in your own project, you'll need to set up an Angular 2 project. You can generate a boilerplate app using the Angular CLI. There are also various seed projects available, such as AngularClass/angular2-webpack-starter and mgechev/angular-seed.

Samples in this tutorial are simplified so that templates are included in the component TypeScript. In a real-world project, you would likely want to separate the TS, HTML, and CSS into their own files.

The Angular CLI can set this up quickly and easily for you. To create your own starter project and take advantage of automatic component generation, check out the Angular CLI GitHub README. The Ultimate Angular CLI Reference Guide is also a great resource.

Passing Data from Parent to Child

Download Angular 2 code samples: parent-to-child-component-communication

One of the most basic features of any app is communication. Angular 1 leverages the concept of componetization but not nearly to the extent of Angular 2. In Angular 1, it's simple to allow hierarchical controllers and directives access to the $scope of other app components. In Angular 2, some component communication is very similar to Angular 1 and some is different.

First let's examine parent-to-child component communication.

Scenario: In our parent component, we want to use a repeater to iterate over an array of objects and pass each object to a child component. The child should then display the data.

Parent to Child Communication in Angular 1

In Angular 1, our approach probably involves setting up (or fetching) data in a parent controller and assigning the collection to a bindable member (ie., parent.items). We would use ng-repeat to loop over a child directive and pass the item to it as an attribute. The child directive's template would then display the data.

In AngularJS 1.5, our child directive might resemble the following:

// Angular 1 - child.directive.js

angular.module('ng1-app').directive('child', child);

function child() {
  return {
    restrict: 'EA',
    replace: true,
    template: '<div>{{child.data.name}}</div>',
    controller: childCtrl,
    controllerAs: 'child',
    bindToController: true,
    scope: {
      data: '<'
    }
  };
}
function childCtrl() {
  var child = this;
  console.log('Data for child:', child.data);
}

The markup containing the parent might look something like this:

<!-- Angular 1 - parent-to-child markup -->
<div ng-controller="ParentCtrl as parent">
  <child ng-repeat="item in parent.items" data="item"></child>
</div>

Parent to Child Communication in Angular 2

The implementation for this is similar in Angular 2, but we need to use input binding with the @Input decorator.

In the parent component, we'll set up our items data and then use the NgFor directive to repeat the child component and pass items to it:

// Angular 2 - parent.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <app-child *ngFor="let item of items" [data]="item"></app-child>
  `
})
export class ParentComponent {
  items: Object[] = [
    { name: 'Allosaurus' },
    { name: 'Brachiosaurus' },
    { name: 'Dionychus' },
    { name: 'Elasmosaurus' },
    { name: 'Parasaurolophus' }
  ];
}

Our items property has a type annotation of Object[] signifying that it will be an array of objects.

The selector for this component is app-parent because we need custom elements to be hyphenated as per the W3C spec for custom elements and Angular 2 Style Guide. Not doing so will result in errors; this is to prevent conflicts when W3C implements new tags in the future.

The square brackets in [data] are one-way data binding punctuation. You can read more about binding syntax in the Angular 2 docs.

Our child component might then look like this:

// Angular 2 - child.component.ts

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

@Component({
  selector: 'app-child',
  template: `
    <div>{{data.name}}</div>
  `
})
export class ChildComponent implements OnInit {
  @Input() data: Object;

  ngOnInit() {
     console.log('Data for child:', this.data);
  }
}

We'll import the Input class from @angular/core. Then we'll give the @Input() decorator a name (data) and an Object type annotation.

The Angular 1 and Angular 2 examples are now functionally equivalent. At a glance, we can see how the lack of $scope makes the Angular 2 example refreshingly simple.

Passing Data from Child to Parent

Download Angular 2 code samples: child-to-parent-component-communication

We often need to pass data from a child to a parent. Consider this hypothetical scenario:

Scenario: We have a property that adds or removes an element in a component. We have a button in a child component that must be able to toggle the parent's element.

Child to Parent Communication in Angular 1

In Angular 1, there are several ways to tackle this scenario. We can allow children to inherit the parent scope. We can broadcast and emit events. We can two-way data bind to a directive's scope. Angular 1's automagic two-way data binding was one of the most oft-demoed features when Angular first appeared in the JavaScript framework landscape. If you're experienced with Angular 1, you should be familiar with how the digest cycle / dirty-checking works.

Child to Parent Communication in Angular 2

In Angular 2, two-way data binding is no longer built in. Angular 2 components are modular and encapsulated. After Angular 1's emphasis on two-way binding, its absence from Angular 2 can seem challenging. The key is understanding component communication and the greater control we have when binding doesn't happen automagically.

If we want a child component to notify a parent of changes, we can use the EventEmitter API and @Output decorator. The parent can then bind to the event outputted by the child to update its data.

Note: This method should be used for component-to-component communication but not service-to-component interactions. We'll cover communication with services shortly.

Our parent might look like this:

// Angular 2 - parent.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-parent',
  template: `
    <div *ngIf="elementShow">Show this conditionally in parent template!</div>
    <app-child (elementToggled)="elementToggleHandler($event)"></app-child>
  `
})
export class ParentComponent {
  elementShow: boolean;

  elementToggleHandler(e: boolean) {
    this.elementShow = e;
  }
}

We have an element that is being conditionally stamped with the NgIf directive if the elementShow property is truthy. Then we have a child component with an (elementToggled) event listener. When this event is detected in the parent component, it executes a handler that updates the elementShow property with the value of the event parameter. But where does this elementToggled event come from?

Note: The parentheses in (elementToggled) are data binding punctuation for listening for events. You can read more about binding syntax in the Angular 2 docs.

Our child component emits the elementToggled event that the parent is listening for. In this case, the child might look like this:

// Angular 2 - child.component.ts

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

@Component({
  selector: 'app-child',
  template: `
    <button (click)="toggleElement()">Toggle Parent from Child</button>
  `
})
export class ChildComponent {
  @Output() elementToggled = new EventEmitter();
  elementShow: boolean = false;

  toggleElement() {
    this.elementShow = !this.elementShow;
    this.elementToggled.emit(this.elementShow);
  }
}

We need to import the Output and EventEmitter APIs. In our template, we'll listen for a (click) event and execute a toggleElement() method when the user interacts with the button. We'll use the @Output() decorator to create a new event emitter. We also need a boolean elementShow property to track the state of the toggle.

Finally, we'll define the click event handler toggleElement(). This method should toggle the elementShow property and emit the elementToggled event with the current state of elementShow.

We can now toggle the parent from the child:

Migrating Angular 1 features to Angular 2: communication from child to parent component

Note: To see a practical, real-world use case, please check out Migrating an Angular 1 App to Angular 2 - Part 1. You can also read Two-way Binding in Angular 2 for more on this topic.

Hopefully you can see the benefits of this approach. What's happening is more transparent and less magical when things like $scope and $watch aren't in the picture.

More Component Communication Techniques

These are not the only approaches to component communication. Please check out Component Interaction in the Angular 2 docs for more information.

In addition, parent components can be injected into child components to provide access to their methods. This tightly couples the components, so loose coupling is advised. However, injection may be preferred in some cases. You can read more about this here: Find a parent component by injection.

Global Communication with Services

Download Angular 2 code samples: global-communication-with-service

Most apps that grow to a certain scale require some kind of global communication. Scope (and root scope) are gone in Angular 2, but we can still implement app-wide communication with services.

Scenario: We have some data and associated methods for setting and getting it. We want to be able to display and manipulate that same data from anywhere in our app. We also want to react to data changes in script.

Global Communication in Angular 1

With Angular 1, there are several options for managing app-wide data. We can use service (and factory) singletons to get and set global data and provide methods. We can also use $rootScope to store data and emit and broadcast events.

Global Communication with Services in Angular 2

One of the interesting things about Angular 2 is that services can be singletons or they can create multiple instances depending on how we provide them. For globals, let's assume that a singleton is exactly what we want.

Let's create a trusty standby, the good old counter example:

// Angular 2 - counter.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class CounterService {
  count: number = 0;
  private countSource = new BehaviorSubject<number>(this.count);

  inc() {
    this.count++;
    this.updateCountSbj(this.count);
  }
  dec() {
    this.count--;
    this.updateCountSbj(this.count);
  }
  get getCount(): number {
    return this.count;
  }

  count$ = this.countSource;

  private updateCountSbj(value) {
    this.countSource.next(value);
  }
}

The simple part: our service is Injectable and has methods to increment, decrement, and return an integer. There's nothing fancy there.

However, we've also imported BehaviorSubject. We want to be able to subscribe to count changes in our components. We need to be able to do this if we want to execute script logic in response to changes in global data. In Angular 1, we might do this by emitting an event or by $watch()ing in a directive/controller. In Angular 2, services should not use EventEmitter, and $scope.$watch is gone. An RxJS BehaviorSubject extends an observable and allows us to create subscriptions in our components.

Note: Subjects are both observers and observables. You can learn more about subjects here.

Now let's say we have two components, Cmpt1Component and Cmpt2Component. Each of these components should have buttons to increment, decrement, and display the counter. They can subscribe to the count$ subject to execute logic. They both need access to a single instance of CounterService.

To do this, we need to provide our counter service globally in our app.module.ts:

// Angular 2 - app.module.ts
...
import { CounterService } from './counter.service';

@NgModule({
  ...,
  providers: [CounterService],
  ...
})
export class AppModule { }

We import CounterService and then provide it in the providers array of our app's @NgModule. Now we can use it in any component belonging to this module and trust that they'll share the same instance.

For the sake of example, say both of our components are essentially the same. They might look like this:

// Angular 2 - cmpt1.component.ts / cmpt2.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-[cmpt1/cmpt2]',
  template: `
    <div>
      <h2>Counter</h2>
      <button (click)="counter.dec()">-</button>
      {{counter.getCount}}
      <button (click)="counter.inc()">+</button>
    <div>
  `
})
export class [Cmpt1/Cmpt2]Component implements OnInit, OnDestroy {
  countSub: Subscription;

  constructor(private counter: CounterService) { }

  ngOnInit() {
    this.countSub = this.counter.count$.subscribe(
      value => {
        console.log('global counter value changed:', value);
      }
    );
  }

  ngOnDestroy() {
    this.countSub.unsubscribe();
  }
}

Because we provided the service in the app module, we only need to import it to use it in our components. We'll make it available in our constructor() function. Now we can use its methods in the component templates.

We can also create a Subscription to the count$ subject to execute component-level logic. We then need to unsubscribe when the component is destroyed to prevent memory leaks.

If we display both components, they'll look and behave like this:

Migrating Angular 1 features to Angular 2: counter service as a singleton

Incrementing or decrementing either component's counter will affect the other. We now have globally shared data!

Angular 2: Services with Multiple Instances

Download Angular 2 code samples: service-with-multiple-instances

We mentioned above that services can be provided in a way that creates multiple instances. Doing this was a hassle in Angular 1 and thankfully, Angular 2 easily solves this with its use of classes.

Scenario: We have a service that provides properties and methods for a counter. We want more than one counter in our app. Manipulating one counter should not affect the others.

We'll use our Angular 2 CounterService from the previous example. Instead of providing it in the app module @NgModule, we'll add it to each @Component in a providers array like this:

// Angular 2 - cmpt1.component.ts / cmpt2.component.ts
...
@Component({
  ...,
  providers: [CounterService]
})
...

The result is unique counter instances in Cmpt1Component and Cmpt2Component:

Angular 2 counter service with multiple instances

To do this in Angular 1, we have to use factories as APIs that return collections with getters and setters. Doing so isn't simple or elegant. Angular 2 solves this nicely!

Using Native Events and DOM Properties

Download Angular 2 code samples: using-native-events-and-dom-properties

In Angular 2, it's easier to hook into native events and DOM properties. In Angular 1, doing this is often fraught with $scope hazards and the danger of bad practices. We'll use the window.resize event as an example. Consider this common scenario:

Scenario: When a user resizes the browser, we want to dynamically set the minimum height of a DOM element.

Window Resize Event in Angular 1

In Angular 1, we can bind directly to an Angular 1 $window resize event in a directive or we can use a factory API such as angular-resize. We have to manage $scope and carefully clean up our listeners on $destroy. Then we either have to use JS to set the DOM property (min-height in this case) or we have to add watchers by binding in the template. None of this is ideal.

Window Resize Event in Angular 2

We'll demonstrate using an event observable in Angular 2:

// Angular 2 - app.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
  selector: 'app-root',
  template: `
    <div class="fullHeight" [style.min-height]="minHeight"></div>
  `,
  styles: [`
    .fullHeight { background: red; }
  `]
})
export class AppComponent implements OnInit {
  minHeight: string;
  private initWinHeight: number = 0;

  ngOnInit() {
    Observable.fromEvent(window, 'resize')
      .debounceTime(200)
      .subscribe(event => this.resizeFn(event)
    );
    this.initWinHeight = window.innerHeight;
    this.resizeFn(null);
  }
  private resizeFn(e) {
    let winHeight: number = e ? e.target.innerHeight : this.initWinHeight;
    this.minHeight = `${winHeight}px`;
  }
}

First we'll import dependencies. We're going to use the OnInit lifecycle hook from @angular/core to manage the observable and implement initial height. Then we need Observable from the RxJS library which is packaged with Angular 2.

Important Note: Angular 2 binds to DOM properties, not HTML attributes. This may seem counter-intuitive because we declaratively add things like [style.min-height] or [disabled] to our markup, but these refer to properties, not attributes. Please read Binding syntax: An overview to learn more.

We can bind our minHeight member to the [style.min-height] DOM property on the <div class="fullHeight"> element. (For the sake of example, we're assuming that a global CSS reset has removed default margins and padding on the body.)

We're using an RxJS observable to subscribe to the window.resize event and execute a debounced function that sets a min-height. The window.resize event doesn't fire on page load, so we then need to trigger the handler in ngOnInit().

Note: You've already seen native (click) event bindings in earlier examples. We could do the same with (window.resize), but we're subscribing to an observable instead because we want to debounce the handler.

Router Events

Download Angular 2 code samples: router-events

Angular 2 routing is thoroughly covered in the Angular 2 docs and the Tour of Heroes tutorial. There are also real-world examples in Migrating an Angular 1 App to Angular 2 - Part 2 and Part 3. Therefore, we'll only cover router events here.

Scenario: Whenever a navigation change is initiated, we want to execute some functionality (for example, closing a navigation menu).

Navigation Events in Angular 1

In Angular 1, we can listen for navigation events such as $locationChangeStart or $locationChangeSuccess like so:

// Angular 1 - on location change

$scope.$on('$locationChangeStart', function(event, newUrl, oldUrl) {
  // do something every time a location change is initiated
});

Navigation Events in Angular 2

We can do something similar in Angular 2, but now router events are observables. We need to subscribe to them:

// Angular 2 - app.component.ts

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart } from '@angular/router';

@Component({
  selector: 'app-root',
  template: ``
})
export class AppComponent implements OnInit {

  constructor(private router: Router) { }

  ngOnInit() {
    this.router.events
      .filter(event => event instanceof NavigationStart)
      .subscribe(event => {
        // do something every time a location change is initiated
      });
  }
}

We need to import Router and NavigationStart from @angular/router. Next we need to make private router: Router available in our constructor() function.

Router.events is an observable of route events. We'll filter for when the event is an instance of NavigationStart. Then we can subscribe and execute our desired functionality. Other navigation events can be found in the Angular 2 router event documentation.

Note: To learn about a great use-case, check out Dynamic page titles in Angular 2 with router events.

Calling an API

Download Angular 2 code samples: calling-an-api

Many Angular applications utilize external APIs. Calling an API is not significantly different between Angular 1 and Angular 2. Angular 1 uses promises whereas Angular 2 uses observables.

Scenario: We want to use the reddit API to display the titles and links of posts from the reddit front page.

You can check out the data we'll be using here: https://www.reddit.com/.json.

Calling an API in Angular 1

Most Angular 1 apps use a factory or service to make API calls. The controllers or directives then inject and use that service to fetch or send data. We react to successes or failures using promises.

Calling an API in Angular 2

We'll do something very similar in Angular 2. First we'll create the API service:

// Angular 2 - api.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class ApiService {
  private baseUrl = 'https://www.reddit.com/';

  constructor(private http: Http) { }

  getFrontPage$() {
    return this.http
      .get(`${this.baseUrl}.json`)
      .map(this.handleSuccess)
      .catch(this.handleError);
  }
  private handleSuccess(res: Response) {
    return res.json().data.children;
  }
  private handleError(err: Response | any) {
    let errorMsg = err.message || 'Unable to retrieve data';
    return Observable.throw(errorMsg);
  }
}

This is pretty straightforward and isn't much different from our Angular 1 implementation. Starting from the top: we import our dependencies. Services are injectable. We also need Http and Response from @angular/http, Observable from RxJS, and map and catch operators.

Note: RxJS observables are preferable over promises. Angular 2's http.get returns an observable but we could convert it to a promise with .toPromise() if we had to (but we won't in this tutorial).

We set our private API baseUrl property and make private http: Http available in the constructor.

Then we define our getFrontPage$() function. The $ at the end of the function name indicates that an observable is returned and we can subscribe to it.

Finally we manage successes and errors. The map operator processes the result from the observable. In our case, we're returning the response body as JSON. The reddit API returns a data property and inside that, we want the value of children, which is an array of the posts. We'll use the catch operator to handle failed API responses and generate an observable that terminates with an error.

The API service should be a singleton so we'll provide it in the app module:

// Angular 2 - app.module.ts
...
import { ApiService } from './api.service';

@NgModule({
  ...,
  providers: [ApiService],
  ...
})
export class AppModule { }

Now we can use the service in a component to display reddit post titles:

// Angular 2 - reddit.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from './api.service';

@Component({
  selector: 'app-reddit',
  template: `
    <ul>
      <li *ngFor="let post of redditFP">
        <a href="http://reddit.com{{post.data.permalink}}">{{post.data.title}}</a>
      </li>
    </ul>
  `
})
export class RedditComponent implements OnInit {
  redditFP: Object[];

  constructor(private redditApi: ApiService) { }

  ngOnInit() {
    this.getRedditFront();
  }
  getRedditFront() {
    this.redditApi
      .getFrontPage$()
      .subscribe(
        res => this.redditFP = res,
        err => console.log('An error occurred', err)
      );
  }
}

First we import our new ApiService. Then we’ll declare that the redditFP property should be an array of objects. We’ll add the private redditApi: ApiService to the constructor parameters.

The template should display an unordered list with the title of each post linked to its location on reddit.

We can then write the getRedditFront() method to subscribe to the redditApi.getFrontPage$() observable and assign the response to the redditFP property. We’ll call the getRedditFront() method in our ngOnInit() lifecycle hook.

Note: We're using a third party API for this example. However, when we have tighter control over our own APIs, we can use TypeScript to set defined models for the data we expect. To learn more about using models, please check the "Calling an API in Angular 2" section of Migrating an Angular 1 App to Angular 2 - Part 2.

When this component is displayed, we should see a list of links to the current posts on reddit's front page.

Filtering by Search Query

Download Angular 2 code samples: filtering

You may have heard about Angular 2 pipes. Pipes transform displayed values within a template. In Angular 1, we use the pipe character (|) to do similar things with filters. However, filters are gone in Angular 2.

Let's address the following scenario:

Scenario: We have an array of objects and want the user to be able to filter the array using a search query.

Filtering in Angular 1

In Angular 1, we can give an input an ng-model and then use a filter on the repeater:

<label for="search">Search:</label>
<input id="search" type="text" ng-model="query">

<div ng-repeat="item in array | filter:query">
  {{item.name}}
</div>

Simple, right?

Well, yes and no. Angular 1 apps can take a huge performance hit if care isn't taken when filtering. If you've ever filtered hundreds or thousands of items (or implemented faceted search), you're probably familiar with the pitfalls of Angular 1's built-in filter.

In such cases, you may have used a service to implement filtering or added your filtering logic right in your controller. We can regain some performance by exerting tighter control over how and when filtering is executed.

Filtering in Angular 2

The Angular 2 team recommends against replicating Angular 1 filter functionality with a custom pipe due to concerns over performance and minification. Instead, we'll create a service that performs filtering.

Note: You can read more about why the filter pipe was removed in the "No FilterPipe or OrderByPipe" section of the Pipes docs (at the very bottom).

Let's create a FilterService. The first step is establishing some rules regarding implementation. Let's say we want to ensure the following:

  • We expect to filter an array of objects.
  • Search function should accept the array and a query predicate (filter criteria) and return an array with all objects that contain a match.
  • Search should be case-insensitive.
  • We'll only search string values for matches.

With these simple guidelines, our FilterService might look like this:

// Angular 2 - filter.service.ts

import { Injectable } from '@angular/core';

@Injectable()
export class FilterService {
  search(array: Object[], query: string) {
    let lQuery = query.toLowerCase();

    if (!query) {
      return array;
    } else if (array) {
      let filteredArray = array.filter(item => {
        for (let key in item) {
          if ((typeof item[key] === 'string') && (item[key].toLowerCase().indexOf(lQuery) !== -1)) {
            return true;
          }
        }
      });
      return filteredArray;
    }
  }

}

This is very straightforward and we have direct control over how filtering works. If we want to include other value types in the future (such as numbers or dates), we'd modify search() or create new methods.

We typically would want a singleton FilterService, so we should provide it in our app.module.ts file:

// Angular 2 - app.module.ts
...
import { FilterService } from './filter.service';

@NgModule({
  ...,
  providers: [FilterService],
  ...
})
export class AppModule { }

We can then use it in our components like this:

// Angular 2 - list.component.ts

import { Component } from '@angular/core';
import { FilterService } from './filter.service';

@Component({
  selector: 'app-list',
  template: `
    <label for="search">Search:</label>
    <input id="search" type="text" [(ngModel)]="query" />
    <button (click)="search()" [disabled]="!query">Go</button>
    <button (click)="reset()" [disabled]="!query">Reset</button>

    <ul>
      <li *ngFor="let item of filteredArray">{{item.name}}</li>
    </ul>
  `
})
export class ListComponent {
  array: Object[] = [
    { id: 1, name: 'Jon Snow' },
    { id: 2, name: 'Sansa Stark' },
    { id: 3, name: 'Arya Stark' },
    { id: 4, name: 'Bran Stark' },
    { id: 5, name: 'Petyr Baelish' },
    { id: 6, name: 'Danaerys Targaryen' },
    { id: 7, name: 'Jaime Lannister ' },
    { id: 8, name: 'Cersei Lannister' },
    { id: 9, name: 'Samwell Tarly' },
    { id: 10, name: 'Sandor Clegane' }
  ];
  filteredArray: Object[] = this.array;
  query: string;

  constructor(private filter: FilterService) { }

  search() {
    this.filteredArray = this.filter.search(this.array, this.query);
  }
  reset() {
    this.query = '';
    this.filteredArray = this.array;
  }
}

We now have working filtering:

Migrating Angular 1 features to Angular 2: filtering by search query

This example uses a button to trigger filtering. If we wanted to mimic Angular 1's native filter, we could run the search on (keyup) on the input and remove the "Go" button, like so:

<input
  id="search"
  type="text"
  [(ngModel)]="query"
  (keyup)="search()" />

This is less performant, but may be a desirable user experience in some cases.

Migrating Angular 1 features to Angular 2: filtering by search query with keyup

Note: You'd never want to use (keyup) if each search query involved expensive processing, huge amounts of data, or API calls. These situations cause problems with the built-in filter in Angular 1 and we don't want to recreate them in Angular 2.

Aside: User Authentication with Auth0 Lock

Download Angular 2 code samples: user-authentication-with-auth0

Let's learn how to implement user authentication in Angular 2 with Auth0. We'll take advantage of Auth0's Lock widget to manage user identity.

Scenario: We need user authentication to secure our app. Users should be able to sign up and log in with username and password or with third-party Oauth credentials.

Auth0 Lock implemented in an Angular 2 app

Configure Your Auth0 Client

The first thing you'll need is an Auth0 account. Follow these simple steps to get started:

  1. Sign up for a free Auth0 account.
  2. In your Auth0 Dashboard, create a new client.
  3. Name your new app and select "Single Page Web Applications".
  4. In the Settings for your newly created app, add http://localhost:4200 to the Allowed Callback URLs and Allowed Origins (CORS).
  5. If you'd like, you can set up some social connections. You can then enable them for your app in the Client options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter.

Setup and Dependencies

You will need Node.js with npm. The current LTS version is recommended.

We'll add the Auth0 Lock CDN link to our app's index.html file. We're using version 10.6 for our tutorial:

<!-- Angular 2 - index.html -->
...
  <!-- Auth0 Lock widget -->
  <script src="https://cdn.auth0.com/js/lock/10.6/lock.min.js"></script>
</head>
...

Next we need the angular2-jwt helper library. Install this in your Angular 2 project with npm:

$ npm install angular2-jwt --save

Create an Authentication Service

We need an Angular 2 service to implement login functionality and authentication methods. For a simple implementation, our AuthService should look like this:

// Angular 2 - auth.service.ts

import { Injectable } from '@angular/core';
import { tokenNotExpired } from 'angular2-jwt';

// Avoid name not found warnings
declare var Auth0Lock: any;

@Injectable()
export class AuthService {
  lock = new Auth0Lock('[YOUR_AUTH0_CLIENT_ID]', '[YOUR_AUTH0_CLIENT_DOMAIN]', {});
  userProfile: Object;

  constructor() {
    this.userProfile = JSON.parse(localStorage.getItem('profile'));

    // Add callback for lock 'authenticated' event
    this.lock.on('authenticated', (authResult) => {
      if (authResult.idToken) {
        // Successful authentication result
        localStorage.setItem('id_token', authResult.idToken);

        // Get user profile
        this.lock.getProfile(authResult.idToken, (error, profile) => {
          if (error) {
            throw Error('There was an error retrieving profile data.');
          }

          localStorage.setItem('profile', JSON.stringify(profile));
          this.userProfile = profile;
        });
      }
    });
  }

  login() {
    // Call the show method to display the Lock widget
    this.lock.show();
  }

  logout() {
    // Remove token and profile
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
    this.userProfile = undefined;
  }

  get authenticated() {
    // Check if there's an unexpired JWT
    // This searches for an item in localStorage with key == 'id_token'
    return tokenNotExpired();
  }

}

Let's talk about the imports. Injectable is necessary for any injectable service. We also need tokenNotExpired from angular2-jwt to get a user's authentication state.

In order to avoid TypeScript name not found warnings, we'll declare Auth0Lock's type annotation as any.

Then we'll create a new lock instance:

lock = new Auth0Lock('[YOUR_AUTH0_CLIENT_ID]', '[YOUR_AUTH0_CLIENT_DOMAIN]', {});

Replace [YOUR_AUTH0_CLIENT_ID] with your Auth0 client ID and [YOUR_AUTH0_CLIENT_DOMAIN] with your Auth0 domain. These can both be found in your Auth0 dashboard client settings.

Now we'll add some functionality to the constructor function. We'll check local storage for existing data. If the user's profile is available, let's set it.

Next we need to implement an authenticated Lock event callback. On successful authentication, we'll do the following:

  • Save the ID token to local storage.
  • Use the token to get the user's profile.
  • Save the profile to local storage and set our userProfile property.

Now we need a login() method. This will be called when the user clicks a button. We'll show the Lock widget so they can sign up or log in.

We also need a logout() method. This will simply remove the user's token and profile.

We'll provide our AuthService in the app module:

// Angular 2 - app.module.ts
...
import { AuthService } from './auth.service';

@NgModule({
  ...,
  providers: [AuthService],
  ...
})
export class AppModule { }

Add Login, Greeting, and Logout

Now we need to use our AuthService to give users a way to log in and out. A simplified component might contain code like this:

// Angular 2 - app.component.ts

import { Component } from '@angular/core';
import { AuthService } from './auth.service';

@Component({
  selector: 'app-root',
  template: `
    <button *ngIf="!auth.authenticated" (click)="auth.login()">Log In</button>

    <div *ngIf="auth.authenticated">
      <p *ngIf="auth.userProfile">Hello, {{auth.userProfile.name}}!</p>
      <button (click)="auth.logout()">Log Out</button>
    </div>
  `
})
export class AppComponent {
  constructor(private auth: AuthService) { }
}

This component shows a login button when the user is not authenticated. It shows a personalized greeting and a logout button when the user is logged in. We now have user authentication in an Angular 2 app!

What Next?

With Auth0 authentication in place, we can do things like authorize route access and call protected APIs with authenticated HTTP requests. To learn how to implement this kind of functionality, explore these resources:

Note: If you'd like to see a more robust implementation of Auth0 Lock with Angular 2, check out the Migrating an Angular 1 App to Angular 2 - Part 3 section Aside: Authentiating an Angular 2 App with Auth0 Lock. The example demos a real-world app with routing and dynamic redirection when the login is located in a global header component.

Conclusion

We've now discussed several common features of Angular 1 apps and how to implement them in Angular 2. This guide focused on features that:

  • are needed the soonest when building an app,
  • are used very frequently,
  • differ significantly from Angular 1 implementation, OR
  • may not be documented as well as others.

Hopefully this guide has helped you feel more comfortable in the Angular 2 space. For the whole picture, the docs are an excellent source of information. Now let's go build!

Kim Maida

Technical Writer @Auth0 (front-end technologies). Senior UI Engineer. Formerly: Animal Behavior & Neurobiology.