Typical Use Cases Observed Objects In Components And Services In Angular 4
We are presenting to your attention the typical options of using Observable objects in components and services of Angular 4.
Task: When you open the example.com/#/users/42 page, by userId retrieve the user’s data.
Solution: When initializing the UserDetailsComponent components, we subscribe to the router parameters. That is if userId will change – the bumper will trigger our subscription. Using the received userId, we from the userService service get Observable with user data.
// UserDetailsComponent ngOnInit() { this.route.params .pluck('userId') // get userId from the parameters .switchMap(userId => this.userService.getData(userId)) .subscribe(user => this.user = user); }
Task: When you open example.com/#/users/42?regionId=13, you need to execute the load (userId, region) function. Where userId we get from the router, and regionId – from the request parameters.
Solution: We have two event sources, so use the Observable.combineLatest function, which will fire when each source generates an event.
ngOnInit() { Observable.combineLatest(this.route.params, this.route.queryParams) .subscribe(([params, queryParams]) => { // the received array is structured const userId = params['userId']; const regionId = queryParams['regionId']; this.load(userId, regionId); }); }
Note that the created subscriptions to the router, when the object is destroyed, are deleted, followed by an angular, so you do not need to unsubscribe from the router parameters:
The Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up, when the component is closed, protecting against memory leaks, so we do not need to unsubscribe from the route params Observable. Mark Rajcok
Task: Show the download icon after you start saving data and hide it when data is saved or an error occurs.
Solution: For loading the loader, the loading variable responds, after clicking on the button, set it to true. And to set it to false, we use Observable.finally the functions that are executed after the subscription is completed or if an error occurred.
save() { this.loading = true; this.userService.save(params) .finally(() => this.loading = false) .subscribe(user => { // Successfully saved }, error => { // Error saving }); }
Task: Create a variable lang$ in configService, to which other components will subscribe and respond when the language changes.
Solution: We use the BehaviorSubject class to create the variable lang$;
The Differences a BehaviorSubject from Subject:
- BehaviorSubject must be initialized with the initial value;
- The subscription returns the last value of Subject;
- You can get the last value directly via the getValue() function.
Create a variable lang$ and immediately initialize it. Also, add the setLang function to set the language.
// configService lang$: BehaviorSubject<Language> = new BehaviorSubject<Language>(DEFAULT_LANG); setLang(lang: Language) { this.lang$.next(this.currentLang); // here we put it }
Signing up for changing the language in the component. The variable lang$ is a “hot” Observable object, that is, a subscription requires a deletion when the object is destroyed.
private subscriptions: Subscription[] = []; ngOnInit() { const langSub = this.configService.lang$ .subscribe(() => { // ... }); this.subscriptions.push(langSub); } ngOnDestroy() { this.subscriptions .forEach(s => s.unsubscribe()); }
You can unsubscribe and more elegant option, especially if the component has more than two subscriptions:
private ngUnsubscribe: Subject<void> = new Subject<void>(); ngOnInit() { this.configService.lang$ .takeUntil(this.ngUnsubscribe) // unsubscribe by condition .subscribe(() => { // ... }); } ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); }
That is, not to lose memory on hot subscriptions, the component will work until the value of ngUnsubscribe does not change. And it will change when ngOnDestroy is called. The pros of this option are that in each of the subscriptions it is enough to add just one line so that the answer works on time.
Task: Show page suggestions when entering data on a form
Solution: Let’s subscribe to the change of the form data, take only the changing data of the intuitive, put a small delay so that events are not too much and send a request to Wikipedia. The result is output to the console. An interesting point is that switchMap will cancel the previous query if new data has arrived. This is very useful for avoiding non-fire effects from slow queries, if, for example, the penultimate query was 2 seconds long, and after 0.2 seconds, the result of the last request will be displayed in the console.
ngOnInit() { this.form.valueChanges .takeUntil(this.ngUnsubscribe) // unsubscribe after destruction .map(form => form['search-input']) // the data of the input .distinctUntilChanged() // select the changed data .debounceTime(300) // do not react immediately .switchMap(this.wikipediaSearch) // switch Observable to query in Wiki .subscribe(data => console.log(data)); } wikipediaSearch = (text: string) => { return Observable .ajax('https://api.github.com/search/repositories?q=' + text) .map(e => e.response); }
Task: You need to cache Observable query
Solution: Use the bindings publishReplay and refCount. The first function caches one function value for 2 seconds, and the second will count the created subscriptions. That is, Observable will end when all subscriptions are executed. Here you can read more.
// tagService private tagsCache$ = this.getTags() .publishReplay(1, 2000) // caches one value for 2 seconds .refCount() // consider references .take(1); // take 1 value getCachedTags() { return tagsCache$; }
Task: Critical situation on the server! Backend team reported that for correct product updates it is necessary to perform strictly sequentially:
- Updating product data (title and description);
- Updating the product tag list;
- Updating the list of product categories;
Solution: We have 3 Observable received from the productService. We use concatMap:
const updateProduct$ = this.productService.update(product); const updateTags$ = this.productService.updateTags(productId, tagList); const updateCategories$ = this.productService.updateCategories(productId, categoryList); Observable .from([updateProduct$, updateTags$, updateCategories$]) .concatMap(a => a) // update sequentially .toArray() // Return an array from the sequence .subscribe(res => console.log(res)); // res contains an array of query results
Try that solve this task:
If you have a desire to practice a little, solve the previous problem, but to create a product. That is, first create a product, then update the tags, and only then – categories.