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.
-
Step 1: Call the
pipe()
method of the Observable. Every Observable has apipe()
method. -
Step 2: Import the
map
fromrxjs/operators
. -
Step 3: Do the logic in the map method, and substitute the previous Observable.
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.
- Step 1: Create a Subject in the service class instead of an EventEmitter.
@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.
- Step 2: Call
next()
method instead ofemit()
.
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.