I present to your attention the typical options for using Observable objects in components and services of Angular 4.
Task: When opening the example.com/#/users/42
page, get user data by userId
.
Solution: When initializing the UserDetailsComponent
component UserDetailsComponent
we subscribe to the parameters of the router. That is, if the userId
changes, our subscription will trigger. Using the obtained userId
, we get Observable
with user data from the userService
service.
// UserDetailsComponent ngOnInit() { this.route.params .pluck('userId') // userId .switchMap(userId => this.userService.getData(userId)) .subscribe(user => this.user = user); }
Task: When you open the example.com/#/users/42?regionId=13
page, you need to execute the load(userId, regionId)
function load(userId, regionId)
. Where userId
we get from the router, and regionId
- from the request parameters.
Solution: We have two sources of events, so let's use the Observable.combineLatest function, which will work when each of the sources generates an event.
ngOnInit() { Observable.combineLatest(this.route.params, this.route.queryParams) .subscribe(([params, queryParams]) => { // const userId = params['userId']; const regionId = queryParams['regionId']; this.load(userId, regionId); }); }
Please note that the created subscriptions to the router will be deleted when the object is destroyed, an angular is following this, so you do not need to unsubscribe from the parameters of the router:
Router manages it observables it provides and localizes the subscriptions. Do you need to keep track of the observable params? Mark rajcok
Task: Show download icon after starting saving data and hide it when data is saved or an error occurs.
Solution: We have the loading
variable responsible for displaying the bootloader; after clicking on the button, we set it to true
. And to set it to false
use the Observable.finally
functions, which is executed after the subscription is completed or if an error has occurred.
save() { this.loading = true; this.userService.save(params) .finally(() => this.loading = false) .subscribe(user => { // }, error => { // }); }
Task: Create a lang$
variable in configService
, to which other components will subscribe and respond when the language changes.
Solution: Use the BehaviorSubject
class to create the lang$
variable;
Differences between BehaviorSubject
and Subject
:
BehaviorSubject
must be initialized with an initial value;Subject
a;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); // }
Subscribing to change the language in the component. The variable lang$
is a "hot" Observable object, that is, a subscription requires unsubscribe 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 with a more elegant option , especially if there are more than two subscriptions in the component:
private ngUnsubscribe: Subject<void> = new Subject<void>(); ngOnInit() { this.configService.lang$ .takeUntil(this.ngUnsubscribe) // .subscribe(() => { // ... }); } ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); }
That is, in order not to lose memory on hot subscriptions, the component will work until the value of ngUnsubscribe
changes. And it will change when ngOnDestroy is ngOnDestroy
. The advantages of this option are that it is enough to add just one line to each of the subscriptions so that the answer works on time.
Task: Show page suggestions when entering data on a form
Solution: Sign up for changing the form data, take only the variable input data, set a small delay so that there are not too many events and send the request to Wikipedia. The result is displayed in the console. An interesting point is that switchMap
will cancel the previous request if new data arrived. This is very useful, to avoid non-spam effects from slow queries, if, for example, the penultimate query was executed for 2 seconds and the last 0.2 seconds, then the result of the last query will be displayed in the console.
ngOnInit() { this.form.valueChanges .takeUntil(this.ngUnsubscribe) // .map(form => form['search-input']) // .distinctUntilChanged() // .debounceTime(300) // .switchMap(this.wikipediaSearch) // Observable .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 request
Solution: Let's use a bunch of publishReplay
and refCount
. The first function will cache one function value for 2 seconds, and the second will read the created subscriptions. That is, the Observable will end when all subscriptions are completed. Here you can read more.
// tagService private tagsCache$ = this.getTags() .publishReplay(1, 2000) // 2 .refCount() // .take(1); // 1 getCachedTags() { return tagsCache$; }
Task: Critical situation on the server! The backend team said that in order to correctly update the product, it is necessary to perform strictly consistently:
Solution: We have 3 Observables obtained from the productService
. 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) // .toArray() // .subscribe(res => console.log(res)); // res
If you have a desire to practice a little, solve the previous problem, but to create a product. That is, first we create the product, then we update the tags, and only then - the categories.
Source: https://habr.com/ru/post/337512/
All Articles