Jerry's Blog

Recording what I learned everyday

View on GitHub


28 June 2019

Angular (15) -- Observable

by Jerry Zhang

Day 23: Observable

Observable

Observables can be used for handling async tasks. Observables have one major advantage, the operators. With an Observable, we can decide what to do when we receive some data, some error, or something has completed.

import {Component, OnDestroy, OnInit} from '@angular/core';
import {interval, Subscription} from 'rxjs';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit , OnDestroy{

  private firstObsSubscription: Subscription;

  constructor() { }

  ngOnInit() {
    this.firstObsSubscription = interval(1000).subscribe(count => {
      console.log(count);
    });
  }

  ngOnDestroy(): void {
    this.firstObsSubscription.unsubscribe();
  }

}

If we go to another path, the counter will be still running. The observable generally should be unsubscribe manually. We need to declare a new attribute of type Subscription, we named firstObsSubscription above, and save the observable in this attribute. Then, we can unsubscribe the observable using this attribute.

For Observables that Angular provides, such as params, we do not need to unsubscribe because Angular does that for us.

And also we should implement OnDestroy.

Custom Observable

ngOnInit() {
    // this.firstObsSubscription = interval(1000).subscribe(count => {
    //   console.log(count);
    // });
    const customIntervalObservable = new Observable(observer => {
      let count = 0;
      setInterval( () => {
        observer.next(count);
        count++;
      }, 1000);
    });

    this.firstObsSubscription = customIntervalObservable.subscribe(data => {
      console.log(data);
    });
  }

Throw an error

ngOnInit() {
    // this.firstObsSubscription = interval(1000).subscribe(count => {
    //   console.log(count);
    // });
    const customIntervalObservable = new Observable(observer => {
      let count = 0;
      setInterval( () => {
        observer.next(count);
        if (count === 2) {
          observer.complete();
        }
        if (count > 3) {
          observer.error(new Error('Count is greater than 3!'));
        }
        count++;
      }, 1000);
    });

When an observable throw an error, it is dead, so no need to unsubscribe.

Like the above code:

if (count > 3) {
  observer.error(new Error('Count is greater than 3!'));
}

Handling errors

The second param in the subscribe method is error.

this.firstObsSubscription = customIntervalObservable.subscribe(data => {
      console.log(data);
    }, error => {
      console.log(error);
      alert(error.message);
    });

This time, the error is logged but not an error any more.

When an Observable cancels due to an error, it is canceled and does not complete.

In summary, Observer has three frequently used method: next(), error(), complete().

Operators

By now, we have learned that Observers listen to an Observable with a subscription. We sometimes need to process the raw data, like transform it or filter out it. Of course, we can do it in the subscribe function. However, there is a better way to do it, Operators.

this.firstObsSubscription = customIntervalObservable.pipe(map( (data: number) => {
      return 'Round: ' + (data + 1);
    })).subscribe(data => {
      console.log(data);
    }, error => {
      console.log(error);
      alert(error.message);
    }, () => {
      console.log('Completed!');
    });

We can also use multiple operators in the pipe.

this.firstObsSubscription = customIntervalObservable.pipe(filter(data => {
      return data > 0;
    }), map( (data: number) => {
      return 'Round: ' + (data + 1);
    })).subscribe(data => {
      console.log(data);
    }, error => {
      console.log(error);
      alert(error.message);
    }, () => {
      console.log('Completed!');
    });

Subjects

Previously, if we want to display something depending on clicking some button of another component, we have to use an EventEmitter and build a very long chain. Subjects is a better way to do this.

@Injectable({
  providedIn: 'root'
})
export class UserService {
  // activatedEmitter = new EventEmitter<boolean>();
  activatedEmitter = new Subject<boolean>();
}

Notice that we add a providedIn: ‘root’. This is the same as we add this service in the provider array of app-root component.

onActivate() {
    this.userService.activatedEmitter.next(true);
  }

This is in the component that user click a button.

In Observer, the next method can only be called inside the Observable. However, for a subject, we can call the next () method outside.

Tip 1: Should use Subject instead of EventEmitter.

Tip 2: Should unsubscribe it.

Tip 3: Only use Subject when using a service cross component. Should not be used in an @output case.

tags: Angular