HI WELCOME TO SIRIS
Showing posts with label angular. Show all posts
Showing posts with label angular. Show all posts

Observable retry on error

Leave a Comment

In this video we will how to resubscribe and retry an Observable if there is an error.


Please change getEmployeeByCode() method in employee.service.ts file to return an Observable instead of a Promise as shown below.

getEmployeeByCode(empCode: string): Observable<IEmployee> {
    return this._http.get("http://localhost:24535/api/employees/" + empCode)
        .map((response: Response) => <IEmployee>response.json())
        .catch(this.handleError);
}


To cause an error, stop the Web API Service. At this point if you navigate to http://localhost:12345/src/employees/emp101, the application display a message stating - Problem with the service. Please try again after sometime. In the "Console" tab of the "Browser Developer Tools" you will see - ERR_CONNECTION_REFUSED.
Observable retry on error

To resubscribe to the Observable and retry, use the rxjs retry operator. The changes in employee.component.ts file are commented and self explanatory.

import { Component, OnInit } from '@angular/core';
import { IEmployee } from './employee';
import { EmployeeService } from './employee.service';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';

// Import rxjs retry operator
import 'rxjs/add/operator/retry';


@Component({
    selector: 'my-employee',
    templateUrl: 'app/employee/employee.component.html',
    styleUrls: ['app/employee/employee.component.css']
})
export class EmployeeComponent implements OnInit {
    employee: IEmployee;
    statusMessage: string = 'Loading data. Please wait...';
    retryCount: number = 1;

    constructor(private _employeeService: EmployeeService,
        private _activatedRoute: ActivatedRoute,
        private _router: Router) { }

    ngOnInit() {
        let empCode: string = this._activatedRoute.snapshot.params['code'];

        this._employeeService.getEmployeeByCode(empCode)
            // Chain the retry operator to retry on error.
            .retry()
            .subscribe((employeeData) => {
                if (employeeData == null) {
                    this.statusMessage =
                        'Employee with the specified Employee Code does not exist';
                }
                else {
                    this.employee = employeeData;
                }
            },
            (error) => {
                this.statusMessage =
                    'Problem with the service. Please try again after sometime';
                console.error(error);
            });
    }

    onBackButtonClick(): void {
        this._router.navigate(['/employees']);
    }
}

The downside of this approach is that the application keeps on retrying forever. If we start the Web API service, the call succeeds and the observable completes with employee data displayed on the web page.

Now if your requirement is not to retry forever, but only retry for a specific number of times if there is an error then we can use another variation of retry as shown below. Notice in this case we are passing number 3 to the retry opertaor indicating that we only want to retry 3 times. After the 3rd attempt the observable completes with an error.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // Retry only 3 times if there is an error
        .retry(3)
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

The problem with the retry operator is that, it immidiately retries when there is an error. In our case it is a connection issue with the service. Retrying again immediately in our case does not make much sense, as most likely it might fail again. So in situations like this we may want to retry after a short delay, may be after a second or so. This is when we use the retryWhen rxjs operator. retryWhen operator allows us to specify delay in milli-seconds and can be used as shown below. Please donot forget to import retryWhen and delay operators.

import 'rxjs/add/operator/retrywhen';
import 'rxjs/add/operator/delay';

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // Retry with a delay of 1000 milliseconds (i.e 1 second)
        .retryWhen((err) => err.delay(1000))
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

Now if you are wondering can't we use delay opertor with retry operator, the answer is NO, we can't. The delay operator in the following example will not work as expected. As you can see from the browser console, it immediately retries instead of waiting for 5 seconds before a retry attempt.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        // The delay operator will not work with retry
        .retry().delay(5000)
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

If you want to retry every 1000 milli-seconds only for a miximum of 5 times then we can use rxjs scan operator along with the take operator. While retrying we also want to show the retry attempt number to the user on the web page as shown below.
observable retry example

After all the retry attempts are exhausted, the application should stop retrying and display the error message to the user as shown below.
observable retry with delay

If the connection becomes available between the retry attempts, the application should display employee data.

Here is the complete code.

import { Component, OnInit } from '@angular/core';
import { IEmployee } from './employee';
import { EmployeeService } from './employee.service';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';

import 'rxjs/add/operator/retrywhen';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/scan';

@Component({
    selector: 'my-employee',
    templateUrl: 'app/employee/employee.component.html',
    styleUrls: ['app/employee/employee.component.css']
})
export class EmployeeComponent implements OnInit {
    employee: IEmployee;
    statusMessage: string = 'Loading data. Please wait...';
    retryCount: number = 1;

    constructor(private _employeeService: EmployeeService,
        private _activatedRoute: ActivatedRoute,
        private _router: Router) { }

    ngOnInit() {
        let empCode: string = this._activatedRoute.snapshot.params['code'];

        this._employeeService.getEmployeeByCode(empCode)
            // Retry 5 times maximum with a delay of 1 second
            // between each retry attempt
            .retryWhen((err) => {
                return err.scan((retryCount, val) => {
                    retryCount += 1;
                    if (retryCount < 6) {
                        this.statusMessage = 'Retrying...Attempt #' + retryCount;
                        return retryCount;
                    }
                    else {
                        throw (err);
                    }
                }, 0).delay(1000)
            })
            .subscribe((employeeData) => {
                if (employeeData == null) {
                    this.statusMessage =
                        'Employee with the specified Employee Code does not exist';
                }
                else {
                    this.employee = employeeData;
                }
            },
            (error) => {
                this.statusMessage =
                    'Problem with the service. Please try again after sometime';
                console.error(error);
            });
    }

    onBackButtonClick(): void {
        this._router.navigate(['/employees']);
    }
}

In our next video, we will discuss how to allow the end user to cancel retry attempts at any point using the unsubscribe method.

Angular promises vs observables

Leave a Comment

In Angular 2, to work with asynchronous data we can use either Promises or Observables. In our previous videos in this series, we discussed using both Observables and Promises. There are several differences between Promises and Observables. In this video let's discuss these differences.


As a quick summary the differences are shown in the table below
PromiseObservable
Emits a single valueEmits multiple values over a period of time
Not LazyLazy. An Observable is not called until we subscribe to the Observable
Cannot be cancelledCan be cancelled using the unsubscribe() method
Observable provides operators like map, forEach, filter, reduce, retry, retryWhen etc.


A Promise emits a single value where as an Observable emits multiple values over a period of time. You can think of an Observable like a stream which emits multiple items over a period of time and the same callback function is called for each item emitted. So with an Observable we can use the same API to handle asynchronous data whether that data is emitted as a single value or multiple values over a period of time.

A Promise is not lazy where as an Observable is Lazy. Let's prove this with an example. Consider this method getEmployeeByCode() in employee.service.ts. Notice this method returns an Observable.

getEmployeeByCode(empCode: string): Observable<IEmployee> {
    return this._http.get("http://localhost:31324/api/employees/" + empCode)
        .map((response: Response) => <IEmployee>response.json())
        .catch(this.handleError);
}

Here is the consumer code of the above service. In our example, this code is in employee.component.ts in ngOnInit() method. Notice we are subscribing to the Observable using the subscribe() method. An Observable is lazy because, it is not called and hence will not return any data until we subscribe using the subscribe() method. At the moment we are using the subscribe() method. So the service method should be called and we should receive data.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        .subscribe((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

To prove this 
1. Launch Browser developer tools by pressing F12 while you are on the browser. 
2. Navigate to /src/employees/emp101
3. Click on the Network Tab
4. In the Filter textbox, type "api/employees"
5. At this point, you should only see the request issued to EmployeeService in the table below
6 Hover the mouse over "emp101" under "Name" column in the table
7. Notice the request to employee service (/api/employees/emp101) is issued

lazy observable

Instead of hovering over the request, if you click on it, you can see the response data as shown below. Make sure you select the "preview" tab.

lazy observablecollection

Now in employee.component.ts file, comment the subscribe() method code block as shown below. Notice we are still calling the getEmployeeByCode() method of the EmployeeService. Since we have not subscribed to the Observable, a call to the EmployeeService will not be issued over the network.

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        //.subscribe((employeeData) => {
        //    if (employeeData == null) {
        //        this.statusMessage =
        //            'Employee with the specified Employee Code does not exist';
        //    }
        //    else {
        //        this.employee = employeeData;
        //    }
        //},
        //(error) => {
        //    this.statusMessage =
        //        'Problem with the service. Please try again after sometime';
        //    console.error(error);
        //});
}

With the above change in place, reload the web page. Notice no request is issued over the network to the EmployeeService. You can confirm this by looking at the network tab in the browser developer tools. So this proves that an Observable is lazy and won't be called unless we subscribe using the subscribe() method.

Now, let's prove that a Promise is NOT Lazy. I have modified the code in employee.service.ts to return a Promise instead of an Observable. The modified code is shown below.

getEmployeeByCode(empCode: string): Promise<IEmployee> {
    return this._http.get("http://localhost:31324/api/employees/" + empCode)
        .map((response: Response) => <IEmployee>response.json())
        .toPromise()
        .catch(this.handlePromiseError);
}

Here is the consumer code of the above service. In our example, this code is in employee.component.ts in ngOnInit() method. 

ngOnInit() {
    let empCode: string = this._activatedRoute.snapshot.params['code'];

    this._employeeService.getEmployeeByCode(empCode)
        .then((employeeData) => {
            if (employeeData == null) {
                this.statusMessage =
                    'Employee with the specified Employee Code does not exist';
            }
            else {
                this.employee = employeeData;
            }
        },
        (error) => {
            this.statusMessage =
                'Problem with the service. Please try again after sometime';
            console.error(error);
        });
}

Because a promise is eager(not lazy), calling employeeService.getEmployeeByCode(empCode) will immediately fire off a request across the network to the EmployeeService. We can confirm this by looking at the network tab in the browser tools. 

Now you may be thinking, then() method in this case is similar to subscribe() method. If we comment then() method code block, will the service still be called. The answer is YES. Since a Promise is NOT LAZY, Irrespective of whether you have then() method or not, calling employeeService.getEmployeeByCode(empCode) will immediately fire off a request across the network to the EmployeeService. You can prove this by commenting then() method code block and reissuing the request.

Promises in angular 2 example

Leave a Comment

In Angular we can use either Promises or Observables. By default the Angular Http service returns an Observable. To prove this, hover the mouse over the get() method of the Http service in employee.service.ts file. Notice from the intellisense, that it returns Observable<Response>



Promises in angular 2 example

To use Promises instead of Observables we will have to first make a change to the service to return a Promise instead of an Observable.

In employee.service.ts file modify getEmployeeByCode() method as shown below. The changes are commented so they are self-explanatory

import { Injectable } from '@angular/core';
import { IEmployee } from './employee';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/Observable/throw';
// import toPromise operator
import 'rxjs/add/operator/toPromise';

@Injectable()
export class EmployeeService {

    constructor(private _http: Http) { }

    getEmployees(): Observable<IEmployee[]> {
        return this._http.get('http://localhost:24535/api/employees')
            .map((response: Response) => <IEmployee[]>response.json())
            .catch(this.handleError);
    }

    // Notice we changed the return type of the method to Promise<IEmployee>
    // from Observable<IEmployee>. We are using toPromise() operator to
    // return a Promise. When an exception is thrown handlePromiseError()
    // logs the error to the console and throws the exception again
    getEmployeeByCode(empCode: string): Promise<IEmployee> {
        return this._http.get("http://localhost:24535/api/employees/" + empCode)
            .map((response: Response) => <IEmployee>response.json())
            .toPromise()
            .catch(this.handlePromiseError);
    }

    // This method is introduced to handle exceptions
    handlePromiseError(error: Response) {
        console.error(error);
        throw (error);
    }

    handleError(error: Response) {
        console.error(error);
        return Observable.throw(error);
    }
}

Modify the code in employee.component.ts file as shown below. The code that we have changed is commented and is self-explanatory.

import { Component, OnInit } from '@angular/core';
import { IEmployee } from './employee';
import { EmployeeService } from './employee.service';
import { ActivatedRoute } from '@angular/router';
import { Router } from '@angular/router';

@Component({
    selector: 'my-employee',
    templateUrl: 'app/employee/employee.component.html',
    styleUrls: ['app/employee/employee.component.css']
})
export class EmployeeComponent implements OnInit {
    employee: IEmployee;
    statusMessage: string = 'Loading data. Please wait...';

    constructor(private _employeeService: EmployeeService,
        private _activatedRoute: ActivatedRoute,
        private _router: Router) { }

    ngOnInit() {
        let empCode: string = this._activatedRoute.snapshot.params['code'];
        // The only change that we need to make here is use
        // then() method instead of subscribe() method
        this._employeeService.getEmployeeByCode(empCode)
            .then((employeeData) => {
                if (employeeData == null) {
                    this.statusMessage =
                        'Employee with the specified Employee Code does not exist';
                }
                else {
                    this.employee = employeeData;
                }
            },
            (error) => {
                this.statusMessage =
                    'Problem with the service. Please try again after sometime';
                console.error(error);
            });
    }

    onBackButtonClick(): void {
        this._router.navigate(['/employees']);
    }
}

With the above changes, we are now using a Promise instead of an Observable and the application works the same way as before.

There are several differences between Observables and Promises. We will discuss these differences in our next video.