exhtml
1/25/2019 - 6:23 AM

Angular2 Snippets - Observables

Observables

  • An observable can be thought as data source. You use them to handle asynchronous tasks (as you do with callbacks or promises)
  • Observer pattern: observable > stream/timeline of events/data packages > observer
  • Three ways of handling data packages: data / error / completion. In this hooks is where your code gets executed, through anonymous callback functions for each case

For example in routing when we subscribe to route params:

this.route.params
  .subscribe(
    (params: Params) => { // data 
      this.id = +params['id'];
    },
    () => { // error
    },
    () => { // completion
    }
  );

Creating our first simple observable

  • There are tons of different ways of creating an observable. Here we'll emit numbers on an interval:
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx'; //we also need to import rxjs/Rx. In general you'll be working with the observables provided by angular. You only need this import when you want to work with observable operators, that are part of the rxjs library
...
ngOnInit() {
    // OBSERVABLE
    const myNumbers = Observable.interval(1000); //RxJS, has some methods for creating observables. 'interval' will emit a new piece of data every second
    // OBSERVER
    myNumbers.subscribe(
      (number: number) => {
        console.log(number);
      }
    );
  }

Building an observable from scratch

  • observable example that will fire after 2, 4 seconds, and then will fail.

// 1) Create our custom observable
const myObservable = Observable.create((observer: Observer<string>) => { //'create' takes a function as argument, and this function should hold your asynchronous call. 
    // Receives an argument of type Observer. We are telling here to the observer when does it have to react. 
    // When you build an observable from scratch you need to build this bridge between the observable and the observer. 
    // We also configure the type of data it wille emit, a string
  setTimeout(() => {
    observer.next('first package'); //'next' emits a normal data package, pushes the next data package
  }, 2000);
  setTimeout(() => {
    observer.next('second package');
  }, 4000);
  setTimeout(() => {
    // observer.error('this does not work'); // emit an error
    observer.complete(); // emit completion
  }, 5000);
  setTimeout(() => {
    observer.next('third package'); // this never arrives as we've 'completed' the observable in the previous step
  }, 6000);
});

// 2) Subscribe to the observable
myObservable.subscribe(
  (data: string) => { console.log(data); },
  (error: string) => { console.log(error); },
  () => { console.log('completed'); }
);

Unsubscribe

  • Always unsubscribe from an observable when you leave the area where you are interested to be subscribed to it. For this, you need to store your subscriptions in properties of type Subscription:
customObsSubscription: Subscription;
...
ngOnInit() {
  ...
  this.customObsSubscription = myObservable.subscribe(
    ...    
  );
}

Then unsubscribe from them onDestroy your component:

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

** Observables built in in Angular clean themselves automatically**, but is a good practice to clean them on your own, and always when you're configuring your custom observables.

Using subjects to pass and listen to data

  • A subject is like an observable, but it allows you to conveniently push it to emit a new data during your code.

user.service.ts

import { Subject } from 'rxjs/Subject';

export class UserService {
    userActivated = new Subject();
}

In our user component we'll use the service:

`<button (click)="onActivate()">Activate</button>`

onActivate() {
    this.userService.userActivated.next(this.id); // A subject is the observable and the observer at the same time. 
    // Here we use the OBSERVER part, calling 'next' and passing a value, pushing a new data package
}

In our app component:

<a>User 1 {{ user1activated ? '(Active)' : '' }}</a>
<a>User 2 {{ user2activated ? '(Active)' : '' }}</a>

user1activated = false;
user2activated = false;

ngOnInit() {
    this.userService.userActivated.subscribe( // userActivated is observer and observable at the same time, 
        // here we use the OBSERVABLE part and subscribe to it
      (id: number) => {
        if (id === 1) {
          this.user1activated = true;
        } else if (id === 2) {
          this.user2activated = true;
        }
      }
    );
  }
  • Can be compared to angular EventEmitter (indeed it is built on top of a Subject). Next time you want to do cross-component communication, is best practice to use Subject instead of EventEmitter, and use next() instead of emit() to push a new value, and subscribe() to consume it. You can also use the other functions (complete, error) and react to them. Also, don't forget to unsubscribe when done.

Observable Operators

  • RxJS operators allows you to transform the data you receive, and still stay inside the observable world.
  • Since Operators return new Observables, you can also chain these operators.
// make sure you import 'rxjs/Rx'
const myNumbers = Observable.interval(1000)
  .map( //maps the data you get back, into a new observable with any transformation of your choice
    (data: number) => {
      return data * 2;
    }
  );