Angular 2 runOutsideAngular still change the UI - javascript

From my understanding of runOutsideAngular(), if I need to run something that won't trigger the Angular change detection, I need to use this function. My code is not working, however; when I click the button, the UI is changing and the number is 2.
#Component({selector: 'my-cmp',
template: `<h1>{{num}}</h1>
<button (click)="onClick()">Change number</button>`})
class MyComponent implements OnChanges {
num = 1;
constructor(private _ngZone: NgZone ) {
}
onClick() {
this._ngZone.runOutsideAngular(() => {
this.num = 2;
}}));
}
}

If anything is causing change detection, and a bound event like (click)="onClick()" does cause change detection, then Angular will detect the change.
runOutsideAngular doesn't mean Angular won't see the change, it only means that the code run this way doesn't cause change detection, but because the click event already does, it's meaningless in your example.

[In short] you need to change one line in your current code
onClick() {
this._ngZone.runOutsideAngular(() => {
setTimeout(()=>this.num = 2,0); // instead of this.num = 2;
}}));
}
now if you click the on the <button>, this.num will become 2, but you won't see any change in the UI (a temporary inconsistency between view and model)
[Explanation] without runOutsideAngular(), async functions like addEventListener() or setTimeout() behaves differently (monkey patched). their callbacks will try to update UI with Angular after running user's code.
For example, you can treat (click)="onClick()" as:
addEventListener("click",function modifiedCallback(){
onClick();
updateUIifModelChanges(); //call to Angular
})
In order to not triggered UI update we need to satisfy the following two conditions:
not modify model in function onClick (so, modify inside setTimeout())
when the model is indeed modified, do not invoke updateUIifModelChanges (call setTimeout() inside runOutsideAngular)
[More] of cause, the explanation I gave is a very very...simplified version of what happens. setTimeout() has the same function signature whether it's running inside runOutsideAngular() or not. The reason that it behaves differently is because it's running in a different Zone

If you want to prevent change detection then you can
1) subscribe on ngZone.onMicrotaskEmpty like this:
import { NgZone, ChangeDetectorRef } from '#angular/core';
import 'rxjs/add/operator/first';
...
export class MyComponent {
constructor(private ngZone: NgZone, private cdRef: ChangeDetectorRef) {}
onClick() {
// to do something
this.cdRef.detach();
this.ngZone.onMicrotaskEmpty.first().subscribe(() => {
// reattach changeDetector after application.tick()
this.cdRef.reattach();
});
}
}
This handler will run after Application.tick
See also Plunker Example
2) use custom directive like this:
#Directive({
selector: '[outSideEventHandler]'
})
class OutSideEventHandlerDirective {
private handler: Function;
#Input() event: string = 'click'; // pass desired event
#Output('outSideEventHandler') emitter = new EventEmitter();
constructor(private ngZone: NgZone, private elRef: ElementRef) {}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.handler = $event => this.emitter.emit($event);
this.elRef.nativeElement.addEventListener(this.event, this.handler);
});
}
ngOnDestory() {
this.elRef.nativeElement.removeEventListener(this.event, this.handler);
}
}
and then in template you can write:
<button (outSideEventHandler)="onClick()">Click outside zone</button>
or
<button event="mousedown" (outSideEventHandler)="onClick()">Click outside zone</button>
Plunker
3) write custom DOM event handler as described in this article.
https://medium.com/#TheLarkInn/creating-custom-dom-events-in-angular2-f326d348dc8b#.bx4uggfdy
Other solutions see here:
Angular 2 how to keep event from triggering digest loop/detection cycle?

Using ngZone.run is a bit better than the setTimeout solutions since it uses angular specific functionality. Run is meant to be used within ngZone.runOutsideAngular functions.
From the docs:
Running functions via run allows you to reenter Angular zone from a
task that was executed outside of the Angular zone (typically started
via {#link #runOutsideAngular}).
This is actually a very practical example of say a button that increments a number by one but only triggers change detection when the number is even.
#Component({selector: 'my-cmp',
template: `<h1>{{num}}</h1>
<button (click)="onClick()">Change number</button>`})
class MyComponent implements OnChanges {
num = 1;
constructor(private _ngZone: NgZone ) {
}
onClick() {
this._ngZone.runOutsideAngular(() => {
if(this.num % 2 === 0){
// modifying the state here wont trigger change.
this.num++;
} else{
this._ngZone.run(() => {
this.num++;
})
}
}}));
}
}

...
constructor(
private ngZone: NgZone
){
ngZone.runOutsideAngular(() => {
setInterval(()=>{
this.num= new Date().Format('yyyy-MM-dd HH:mm:ss');
},1000);
});
}
...

This is how I have tried to check the difference insideAngular and OutsideAngular
constructor(private zone: NgZone) { }
setProgressOutsideAngular() {
this.zone.runOutsideAngular(() => {
setInterval(() => { ++this.progress, console.log(this.progress) }, 500)
})
}
setProgressInsideAngular() {
this.zone.run(() => setInterval(() => { ++this.progress, console.log(this.progress) }, 500))
}

Related

DOM is not updated with EventListener using Angular 5

Context : I'm using an Angular PWA to communicate with an iOS native application through WKWebview. I'm using messageHandlers to be able to share data between typescript files and the Swift logic code.
Problem : I'm using addEventListener to listen a specific event on the window object. From my component, I subscribe to an observable to listen the change. But my component doesn't apply variable changes inside the subscribe method.
myService.ts
public myValue$ = new Subject<number>();
window.addEventListener('didDeviceDisconnected', (e) => {
...
this.dispatchInfo(someInfo);
});
private dispatchInfo(value: number) {
this.myValue$.next(value);
}
public getValue(): Observable<number> {
return this.myValue$.asObservable();
}
myComponent.ts
// Wait for the notification
this.myValueSubscription = this.myService.getValue().subscribe(value => {
this.myValue = value;
alert("myValue : " + this.myValue);
})
myComponent.html
{{ myValue }}
The alert displays correctly the value but the DOM shows that the value is undefined. I also tried to add setTimeout inside the subscribe function without success. How can I apply the change from the subscribe method ? Is it outside the angular scope ?
yes it is outside the angular scope.
you can try this..
myComponent.ts
import { Component , NgZone } from '#angular/core';
......
constructor(public ngZone: NgZone)
......
this.myValueSubscription = this.myService.getValue().subscribe(value => {
this.ngZone.run(()=> {
this.myValue = value;
});
})
I resolved this issue by using ChangeDetectorRef which provides change detection functionality.
import { Component, ChangeDetectorRef } from '#angular/core';
constructor(.., private cdr: ChangeDetectorRef){}
this.myValueSubscription = this.myService.getValue().subscribe(value => {
this.myValue = value;
alert("myValue : " + this.myValue);
this.cdr.detectChanges(); // <= ADDED
})

Will take(1).subscribe()-subscription live forever if nothing is emitted? [duplicate]

When should I store the Subscription instances and invoke unsubscribe() during the ngOnDestroy life cycle and when can I simply ignore them?
Saving all subscriptions introduces a lot of mess into component code.
HTTP Client Guide ignore subscriptions like this:
getHeroes() {
this.heroService.getHeroes()
.subscribe(
heroes => this.heroes = heroes,
error => this.errorMessage = <any>error);
}
In the same time Route & Navigation Guide says that:
Eventually, we'll navigate somewhere else. The router will remove this component from the DOM and destroy it. We need to clean up after ourselves before that happens. Specifically, we must unsubscribe before Angular destroys the component. Failure to do so could create a memory leak.
We unsubscribe from our Observable in the ngOnDestroy method.
private sub: any;
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = +params['id']; // (+) converts string 'id' to a number
this.service.getHero(id).then(hero => this.hero = hero);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
TL;DR
For this question there are two kinds of Observables - finite value and infinite value.
http Observables produce finite (1) values and something like a DOM event listener Observable produces infinite values.
If you manually call subscribe (not using async pipe), then unsubscribe from infinite Observables.
Don't worry about finite ones, RxJs will take care of them.
Sources:
I tracked down an answer from Rob Wormald in Angular's Gitter here.
He states (I reorganized for clarity and emphasis is mine):
if its a single-value-sequence (like an http request)
the manual cleanup is unnecessary (assuming you subscribe in the controller manually)
i should say "if its a sequence that completes" (of which single value sequences, a la http, are one)
if its an infinite sequence, you should unsubscribe which the async pipe does for you
Also he mentions in this YouTube video on Observables that "they clean up after themselves..." in the context of Observables that complete (like Promises, which always complete because they are always producing one value and ending - we never worried about unsubscribing from Promises to make sure they clean up XHR event listeners, right?)
Also in the Rangle guide to Angular 2 it reads
In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription. The default behavior of Observable operators is to dispose of the subscription as soon as .complete() or .error() messages are published. Keep in mind that RxJS was designed to be used in a "fire and forget" fashion most of the time.
When does the phrase "our Observable has a longer lifespan than our subscription" apply?
It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable completes.
I read this as meaning if we subscribe to an http request or an Observable that emits 10 values and our component is destroyed before that http request returns or the 10 values have been emitted, we are still OK!
When the request does return or the 10th value is finally emitted the Observable will complete and all resources will be cleaned up.
If we look at this example from the same Rangle guide we can see that the subscription to route.params does require an unsubscribe() because we don't know when those params will stop changing (emitting new values).
The component could be destroyed by navigating away in which case the route params will likely still be changing (they could technically change until the app ends) and the resources allocated in subscription would still be allocated because there hasn't been a completion.
In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. He also mentions the http service and ActivatedRoute.params in this video from November 2016.
The Angular tutorial, the Routing chapter now states the following:
The Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so we don't need to unsubscribe from the route params Observable.
Here's a discussion on the GitHub Issues for the Angular docs regarding Router Observables where Ward Bell mentions that clarification for all of this is in the works.
I spoke with Ward Bell about this question at NGConf (I even showed him this answer which he said was correct) but he told me the docs team for Angular had a solution to this question that is unpublished (though they are working on getting it approved). He also told me I could update my SO answer with the forthcoming official recommendation.
The solution we should all use going forward is to add a private ngUnsubscribe = new Subject<void>(); field to all components that have .subscribe() calls to Observables within their class code.
We then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); in our ngOnDestroy() methods.
The secret sauce (as noted already by #metamaker) is to call takeUntil(this.ngUnsubscribe) before each of our .subscribe() calls which will guarantee all subscriptions will be cleaned up when the component is destroyed.
Example:
import { Component, OnDestroy, OnInit } from '#angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';
#Component({
selector: 'app-books',
templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
private ngUnsubscribe = new Subject<void>();
constructor(private booksService: BookService) { }
ngOnInit() {
this.booksService.getBooks()
.pipe(
startWith([]),
filter(books => books.length > 0),
takeUntil(this.ngUnsubscribe)
)
.subscribe(books => console.log(books));
this.booksService.getArchivedBooks()
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(archivedBooks => console.log(archivedBooks));
}
ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
Note: It's important to add the takeUntil operator as the last one to prevent leaks with intermediate Observables in the operator chain.
More recently, in an episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. The discussion starts at about 1:05:30.
Ward mentions "right now there's an awful takeUntil dance that takes a lot of machinery" and Shai Reznik mentions "Angular handles some of the subscriptions like http and routing".
In response Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events and Ward suggests an Observable of lifecycle events that a component could subscribe to as a way of knowing when to complete Observables maintained as component internal state.
That said, we mostly need solutions now so here are some other resources.
A recommendation for the takeUntil() pattern from RxJs core team member Nicholas Jamieson and a TSLint rule to help enforce it: https://ncjamieson.com/avoiding-takeuntil-leaks/
Lightweight npm package that exposes an Observable operator that takes a component instance (this) as a parameter and automatically unsubscribes during ngOnDestroy: https://github.com/NetanelBasal/ngx-take-until-destroy
Another variation of the above with slightly better ergonomics if you are not doing AOT builds (but we should all be doing AOT now): https://github.com/smnbbrv/ngx-rx-collector
Custom directive *ngSubscribe that works like async pipe but creates an embedded view in your template so you can refer to the 'unwrapped' value throughout your template: https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f
I mention in a comment to Nicholas' blog that over-use of takeUntil() could be a sign that your component is trying to do too much and that separating your existing components into Feature and Presentational components should be considered. You can then | async the Observable from the Feature component into an Input of the Presentational component, which means no subscriptions are necessary anywhere. Read more about this approach here.
You don't need to have bunch of subscriptions and unsubscribe manually. Use Subject and takeUntil combo to handle subscriptions like a boss:
import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"
#Component({
moduleId: __moduleName,
selector: "my-view",
templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
componentDestroyed$: Subject<boolean> = new Subject()
constructor(private titleService: TitleService) {}
ngOnInit() {
this.titleService.emitter1$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something 1 */ })
this.titleService.emitter2$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something 2 */ })
//...
this.titleService.emitterN$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe((data: any) => { /* ... do something N */ })
}
ngOnDestroy() {
this.componentDestroyed$.next(true)
this.componentDestroyed$.complete()
}
}
Alternative approach, which was proposed by #acumartini in comments, uses takeWhile instead of takeUntil. You may prefer it, but mind that this way your Observable execution will not be cancelled on ngDestroy of your component (e.g. when you make time consuming calculations or wait for data from server). Method, which is based on takeUntil, doesn't have this drawback and leads to immediate cancellation of request. Thanks to #AlexChe for detailed explanation in comments.
So here is the code:
#Component({
moduleId: __moduleName,
selector: "my-view",
templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
alive: boolean = true
constructor(private titleService: TitleService) {}
ngOnInit() {
this.titleService.emitter1$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something 1 */ })
this.titleService.emitter2$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something 2 */ })
// ...
this.titleService.emitterN$
.pipe(takeWhile(() => this.alive))
.subscribe((data: any) => { /* ... do something N */ })
}
ngOnDestroy() {
this.alive = false
}
}
The Subscription class has an interesting feature:
Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.
Additionally, subscriptions may be grouped together through the add() method, which will attach a child Subscription to the current Subscription. When a Subscription is unsubscribed, all its children (and its grandchildren) will be unsubscribed as well.
You can create an aggregate Subscription object that groups all your subscriptions.
You do this by creating an empty Subscription and adding subscriptions to it using its add() method. When your component is destroyed, you only need to unsubscribe the aggregate subscription.
#Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
private subscriptions = new Subscription();
constructor(private heroService: HeroService) {
}
ngOnInit() {
this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
this.subscriptions.add(/* another subscription */);
this.subscriptions.add(/* and another subscription */);
this.subscriptions.add(/* and so on */);
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
Some of the best practices regarding observables unsubscriptions inside Angular components:
A quote from Routing & Navigation
When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.
There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions.
The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.
Feel free to unsubscribe anyway. It is harmless and never a bad practice.
And in responding to the following links:
(1) Should I unsubscribe from Angular 2 Http Observables?
(2) Is it necessary to unsubscribe from observables created by Http methods?
(3) RxJS: Don’t Unsubscribe
(4) The easiest way to unsubscribe from Observables in Angular
(5) Documentation for RxJS Unsubscribing
(6) Unsubscribing in a service is kind of pointless since there is no chance of memory leaks
(7) Do we need to unsubscribe from observable that completes/errors-out?
(8) A comment about the http observable
I collected some of the best practices regarding observables unsubscriptions inside Angular components to share with you:
http observable unsubscription is conditional and we should consider the effects of the 'subscribe callback' being run after the component is destroyed on a case by case basis. We know that angular unsubscribes and cleans the http observable itself (1), (2). While this is true from the perspective of resources it only tells half the story. Let's say we're talking about directly calling http from within a component, and the http response took longer than needed so the user closed the component. The subscribe() handler will still be called even if the component is closed and destroyed. This can have unwanted side effects and in the worse scenarios leave the application state broken. It can also cause exceptions if the code in the callback tries to call something that has just been disposed of. However at the same time occasionally they are desired. Like, let's say you're creating an email client and you trigger a sound when the email is done sending - well you'd still want that to occur even if the component is closed (8).
No need to unsubscribe from observables that complete or error. However, there is no harm in doing so(7).
Use AsyncPipe as much as possible because it automatically unsubscribes from the observable on component destruction.
Unsubscribe from the ActivatedRoute observables like route.params if they are subscribed inside a nested (Added inside tpl with the component selector) or dynamic component as they may be subscribed many times as long as the parent/host component exists. No need to unsubscribe from them in other scenarios as mentioned in the quote above from Routing & Navigation docs.
Unsubscribe from global observables shared between components that are exposed through an Angular service for example as they may be subscribed multiple times as long as the component is initialized.
No need to unsubscribe from internal observables of an application scoped service since this service never get's destroyed, unless your entire application get's destroyed, there is no real reason to unsubscribe from it and there is no chance of memory leaks. (6).
Note: Regarding scoped services, i.e component providers, they are destroyed when the component is destroyed. In this case, if we subscribe to any observable inside this provider, we should consider unsubscribing from it using the OnDestroy lifecycle hook which will be called when the service is destroyed, according to the docs.
Use an abstract technique to avoid any code mess that may be resulted from unsubscriptions. You can manage your subscriptions with takeUntil (3) or you can use this npm package mentioned at (4) The easiest way to unsubscribe from Observables in Angular.
Always unsubscribe from FormGroup observables like form.valueChanges and form.statusChanges
Always unsubscribe from observables of Renderer2 service like renderer2.listen
Unsubscribe from every observable else as a memory-leak guard step until Angular Docs explicitly tells us which observables are unnecessary to be unsubscribed (Check issue: (5) Documentation for RxJS Unsubscribing (Open)).
Bonus: Always use the Angular ways to bind events like HostListener as angular cares well about removing the event listeners if needed and prevents any potential memory leak due to event bindings.
A nice final tip: If you don't know if an observable is being automatically unsubscribed/completed or not, add a complete callback to subscribe(...) and check if it gets called when the component is destroyed.
It depends. If by calling someObservable.subscribe(), you start holding up some resource that must be manually freed-up when the lifecycle of your component is over, then you should call theSubscription.unsubscribe() to prevent memory leak.
Let's take a closer look at your examples:
getHero() returns the result of http.get(). If you look into the angular 2 source code, http.get() creates two event listeners:
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
and by calling unsubscribe(), you can cancel the request as well as the listeners:
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
Note that _xhr is platform specific but I think it's safe to assume that it is an XMLHttpRequest() in your case.
Normally, this is enough evidence to warrant a manual unsubscribe() call. But according this WHATWG spec, the XMLHttpRequest() is subject to garbage collection once it is "done", even if there are event listeners attached to it. So I guess that's why angular 2 official guide omits unsubscribe() and lets GC clean up the listeners.
As for your second example, it depends on the implementation of params. As of today, the angular official guide no longer shows unsubscribing from params. I looked into src again and found that params is a just a BehaviorSubject. Since no event listeners or timers were used, and no global variables were created, it should be safe to omit unsubscribe().
The bottom line to your question is that always call unsubscribe() as a guard against memory leak, unless you are certain that the execution of the observable doesn't create global variables, add event listeners, set timers, or do anything else that results in memory leaks.
When in doubt, look into the implementation of that observable. If the observable has written some clean up logic into its unsubscribe(), which is usually the function that is returned by the constructor, then you have good reason to seriously consider calling unsubscribe().
Angular 2 official documentation provides an explanation for when to unsubscribe and when it can be safely ignored. Have a look at this link:
https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service
Look for the paragraph with the heading Parent and children communicate via a service and then the blue box:
Notice that we capture the subscription and unsubscribe when the AstronautComponent is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a AstronautComponent is the same as the lifetime of the app itself. That would not always be true in a more complex application.
We do not add this guard to the MissionControlComponent because, as the parent, it controls the lifetime of the MissionService.
I hope this helps you.
Based on : Using Class inheritance to hook to Angular 2 component lifecycle
Another generic approach:
export abstract class UnsubscribeOnDestroy implements OnDestroy {
protected d$: Subject<any>;
constructor() {
this.d$ = new Subject<void>();
const f = this.ngOnDestroy;
this.ngOnDestroy = () => {
f();
this.d$.next();
this.d$.complete();
};
}
public ngOnDestroy() {
// no-op
}
}
And use :
#Component({
selector: 'my-comp',
template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {
constructor() {
super();
}
ngOnInit(): void {
Observable.of('bla')
.takeUntil(this.d$)
.subscribe(val => console.log(val));
}
}
For observables that complete directly after emitting the result like AsyncSubject or for example observables from http requests and such you don't need to unsubscribe.
It doesn't hurt to to call unsubscribe() for those, but if the observable is closed the unsubscribe method will simply not do anything:
if (this.closed) {
return;
}
When you have long-lived observables that emit several values over time (like for example a BehaviorSubject or a ReplaySubject) you need to unsubscribe to prevent memory leaks.
You can easily create an observable that completes directly after emitting a result from such long lived observables using a pipe operator.
In some answers here the take(1) pipe is mentioned. But I prefer the first() pipe. The difference to take(1) is that it will:
deliver an EmptyError to the Observer's error callback if the Observable completes before any next notification was sent.
Another advantage of the first pipe is that you can pass a predicate that will help you to return the first value that satisfies certain criteria:
const predicate = (result: any) => {
// check value and return true if it is the result that satisfies your needs
return true;
}
observable.pipe(first(predicate)).subscribe(observer);
First will complete directly after emitting the first value (or when passing a function argument the first value that satisfies your predicate) so there is no need to unsubscribe.
Sometimes you are not sure about whether you have a long lived observable or not. I am not saying it is good practice but you could then always add the first pipe just to make sure you won't need to manually unsubscribe. Adding an additional first pipe on an observable that will emit only one value doesn't hurt.
During development you can use the single pipe that will fail if source observable emits several events. This can help you to explore the type of observable and whether it is necessary to unsubscribe from it or not.
observable.pipe(single()).subscribe(observer);
The first and single seem very similar, both pipes can take an optional predicate but the differences are important and nicely summarized in this stackoverflow answer here:
First
Will emit as soon as first item appears. Will complete right after that.
Single
Will fail if source observable emits several events.
Note I tried to be as accurate and complete as possible in my answer with references to the official documentation, but please comment if something important is missing...
Since seangwright's solution (Edit 3) appears to be very useful, I also found it a pain to pack this feature into base component, and hint other project teammates to remember to call super() on ngOnDestroy to activate this feature.
This answer provide a way to set free from super call, and make "componentDestroyed$" a core of base component.
class BaseClass {
protected componentDestroyed$: Subject<void> = new Subject<void>();
constructor() {
/// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
let _$ = this.ngOnDestroy;
this.ngOnDestroy = () => {
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
_$();
}
}
/// placeholder of ngOnDestroy. no need to do super() call of extended class.
ngOnDestroy() {}
}
And then you can use this feature freely for example:
#Component({
selector: 'my-thing',
templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
constructor(
private myThingService: MyThingService,
) { super(); }
ngOnInit() {
this.myThingService.getThings()
.takeUntil(this.componentDestroyed$)
.subscribe(things => console.log(things));
}
/// optional. not a requirement to implement OnDestroy
ngOnDestroy() {
console.log('everything works as intended with or without super call');
}
}
The official Edit #3 answer (and variations) works well, but the thing that gets me is the 'muddying' of the business logic around the observable subscription.
Here's another approach using wrappers.
Warining: experimental code
File subscribeAndGuard.ts is used to create a new Observable extension to wrap .subscribe() and within it to wrap ngOnDestroy().
Usage is the same as .subscribe(), except for an additional first parameter referencing the component.
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {
// Define the subscription
const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);
// Wrap component's onDestroy
if (!component.ngOnDestroy) {
throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
}
const saved_OnDestroy = component.ngOnDestroy;
component.ngOnDestroy = () => {
console.log('subscribeAndGuard.onDestroy');
sub.unsubscribe();
// Note: need to put original back in place
// otherwise 'this' is undefined in component.ngOnDestroy
component.ngOnDestroy = saved_OnDestroy;
component.ngOnDestroy();
};
return sub;
};
// Create an Observable extension
Observable.prototype.subscribeAndGuard = subscribeAndGuard;
// Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
declare module 'rxjs/Observable' {
interface Observable<T> {
subscribeAndGuard: typeof subscribeAndGuard;
}
}
Here is a component with two subscriptions, one with the wrapper and one without. The only caveat is it must implement OnDestroy (with empty body if desired), otherwise Angular does not know to call the wrapped version.
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import './subscribeAndGuard';
#Component({
selector: 'app-subscribing',
template: '<h3>Subscribing component is active</h3>',
})
export class SubscribingComponent implements OnInit, OnDestroy {
ngOnInit() {
// This subscription will be terminated after onDestroy
Observable.interval(1000)
.subscribeAndGuard(this,
(data) => { console.log('Guarded:', data); },
(error) => { },
(/*completed*/) => { }
);
// This subscription will continue after onDestroy
Observable.interval(1000)
.subscribe(
(data) => { console.log('Unguarded:', data); },
(error) => { },
(/*completed*/) => { }
);
}
ngOnDestroy() {
console.log('SubscribingComponent.OnDestroy');
}
}
A demo plunker is here
An additional note:
Re Edit 3 - The 'Official' Solution, this can be simplified by using takeWhile() instead of takeUntil() before subscriptions, and a simple boolean rather than another Observable in ngOnDestroy.
#Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {
iAmAlive = true;
ngOnInit() {
Observable.interval(1000)
.takeWhile(() => { return this.iAmAlive; })
.subscribe((data) => { console.log(data); });
}
ngOnDestroy() {
this.iAmAlive = false;
}
}
A Subscription essentially just has an unsubscribe() function to release resources or cancel Observable executions.
In Angular, we have to unsubscribe from the Observable when the component is being destroyed. Luckily, Angular has a ngOnDestroy hook that is called before a component is destroyed, this enables devs to provide the cleanup crew here to avoid hanging subscriptions, open portals, and what nots that may come in the future to bite us in the back
#Component({...})
export class AppComponent implements OnInit, OnDestroy {
subscription: Subscription
ngOnInit () {
var observable = Rx.Observable.interval(1000);
this.subscription = observable.subscribe(x => console.log(x));
}
ngOnDestroy() {
this.subscription.unsubscribe()
}
}
We added ngOnDestroy to our AppCompoennt and called unsubscribe method on the this.subscription Observable
If there are multiple subscriptions:
#Component({...})
export class AppComponent implements OnInit, OnDestroy {
subscription1$: Subscription
subscription2$: Subscription
ngOnInit () {
var observable1$ = Rx.Observable.interval(1000);
var observable2$ = Rx.Observable.interval(400);
this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));
this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));
}
ngOnDestroy() {
this.subscription1$.unsubscribe()
this.subscription2$.unsubscribe()
}
}
I tried seangwright's solution (Edit 3)
That is not working for Observable that created by timer or interval.
However, i got it working by using another approach:
import { Component, OnDestroy, OnInit } from '#angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';
import { MyThingService } from '../my-thing.service';
#Component({
selector: 'my-thing',
templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
private subscriptions: Array<Subscription> = [];
constructor(
private myThingService: MyThingService,
) { }
ngOnInit() {
const newSubs = this.myThingService.getThings()
.subscribe(things => console.log(things));
this.subscriptions.push(newSubs);
}
ngOnDestroy() {
for (const subs of this.subscriptions) {
subs.unsubscribe();
}
}
}
You usually need to unsubscribe when the components get destroyed, but Angular is going to handle it more and more as we go, for example in new minor version of Angular4, they have this section for routing unsubscribe:
Do you need to unsubscribe? As described in the
ActivatedRoute: the one-stop-shop for route information section of the
Routing & Navigation page, the Router manages the observables it
provides and localizes the subscriptions. The subscriptions are
cleaned up when the component is destroyed, protecting against memory
leaks, so you don't need to unsubscribe from the route paramMap
Observable.
Also the example below is a good example from Angular to create a component and destroy it after, look at how component implements OnDestroy, if you need onInit, you also can implements it in your component, like implements OnInit, OnDestroy
import { Component, Input, OnDestroy } from '#angular/core';
import { MissionService } from './mission.service';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'my-astronaut',
template: `
<p>
{{astronaut}}: <strong>{{mission}}</strong>
<button
(click)="confirm()"
[disabled]="!announced || confirmed">
Confirm
</button>
</p>
`
})
export class AstronautComponent implements OnDestroy {
#Input() astronaut: string;
mission = '<no mission announced>';
confirmed = false;
announced = false;
subscription: Subscription;
constructor(private missionService: MissionService) {
this.subscription = missionService.missionAnnounced$.subscribe(
mission => {
this.mission = mission;
this.announced = true;
this.confirmed = false;
});
}
confirm() {
this.confirmed = true;
this.missionService.confirmMission(this.astronaut);
}
ngOnDestroy() {
// prevent memory leak when component destroyed
this.subscription.unsubscribe();
}
}
Following the answer by #seangwright, I've written an abstract class that handles "infinite" observables' subscriptions in components:
import { OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';
export abstract class InfiniteSubscriberComponent implements OnDestroy {
private onDestroySource: Subject<any> = new Subject();
constructor() {}
subscribe(observable: Observable<any>): Subscription;
subscribe(
observable: Observable<any>,
observer: PartialObserver<any>
): Subscription;
subscribe(
observable: Observable<any>,
next?: (value: any) => void,
error?: (error: any) => void,
complete?: () => void
): Subscription;
subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
return observable
.takeUntil(this.onDestroySource)
.subscribe(...subscribeArgs);
}
ngOnDestroy() {
this.onDestroySource.next();
this.onDestroySource.complete();
}
}
To use it, just extend it in your angular component and call the subscribe() method as follows:
this.subscribe(someObservable, data => doSomething());
It also accepts the error and complete callbacks as usual, an observer object, or not callbacks at all. Remember to call super.ngOnDestroy() if you are also implementing that method in the child component.
Find here an additional reference by Ben Lesh: RxJS: Don’t Unsubscribe.
It's always recommended to make an unsubscription from your observable subscriptions for performance reason to avoid memory leaks , and there different ways of doing that ,
By the way I read most of the answers, and I did"t find someone who is talking about the async pipe, it's recommended Rxjs pattern with Angular apps because its provides automatically subscription and subscription when leaving the component who will be destroyed :
Please find an example how it can be implemented
app.compoennt.ts:
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { BookService } from './book.service';
import { Book } from './book';
#Component({
selector: 'app-observable',
templateUrl: './observable.component.html'
})
export class AppComponent implements OnInit {
books$: Observable<Book[]>
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.books$ = this.bookService.getBooksWithObservable();
}
}
app.compoennt.html:
<h3>AsyncPipe with Promise using NgFor</h3>
<ul>
<li *ngFor="let book of books$ | async" >
Id: {{book?.id}}, Name: {{book?.name}}
</li>
</ul>
I like the last two answers, but I experienced an issue if the the subclass referenced "this" in ngOnDestroy.
I modified it to be this, and it looks like it resolved that issue.
export abstract class BaseComponent implements OnDestroy {
protected componentDestroyed$: Subject<boolean>;
constructor() {
this.componentDestroyed$ = new Subject<boolean>();
let f = this.ngOnDestroy;
this.ngOnDestroy = function() {
// without this I was getting an error if the subclass had
// this.blah() in ngOnDestroy
f.bind(this)();
this.componentDestroyed$.next(true);
this.componentDestroyed$.complete();
};
}
/// placeholder of ngOnDestroy. no need to do super() call of extended class.
ngOnDestroy() {}
}
Another short addition to the above mentioned situations is:
Always unsubscribe, when new values in the subscribed stream is no more required or don't matter, it will result in way less number of triggers and increase in performance in a few cases. Cases such as components where the subscribed data/event no more exists or a new subscription to an all new stream is required (refresh, etc.) is a good example for unsubscription.
In case unsubscribe is needed the following operator for observable pipe method can be used
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '#angular/core';
export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
const subjectPropertyName = '__takeUntilDestroySubject__';
const originalOnDestroy = componentInstance.ngOnDestroy;
const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();
componentInstance.ngOnDestroy = (...args) => {
originalOnDestroy.apply(componentInstance, args);
componentSubject.next(true);
componentSubject.complete();
};
return observable.pipe(takeUntil<T>(componentSubject));
};
it can be used like this:
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
#Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {
ngOnInit(): void {
const observable = Observable.create(observer => {
observer.next('Hello');
});
observable
.pipe(takeUntilDestroyed(this))
.subscribe(val => console.log(val));
}
ngOnDestroy(): void {
}
}
The operator wraps ngOnDestroy method of component.
Important: the operator should be the last one in observable pipe.
in SPA application at ngOnDestroy function (angular lifeCycle) For each subscribe you need to unsubscribe it. advantage => to prevent the state from becoming too heavy.
for example:
in component1 :
import {UserService} from './user.service';
private user = {name: 'test', id: 1}
constructor(public userService: UserService) {
this.userService.onUserChange.next(this.user);
}
in service:
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});
in component2:
import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';
private onUserChange: Subscription;
constructor(public userService: UserService) {
this.onUserChange = this.userService.onUserChange.subscribe(user => {
console.log(user);
});
}
public ngOnDestroy(): void {
// note: Here you have to be sure to unsubscribe to the subscribe item!
this.onUserChange.unsubscribe();
}
For handling subscription I use a "Unsubscriber" class.
Here is the Unsubscriber Class.
export class Unsubscriber implements OnDestroy {
private subscriptions: Subscription[] = [];
addSubscription(subscription: Subscription | Subscription[]) {
if (Array.isArray(subscription)) {
this.subscriptions.push(...subscription);
} else {
this.subscriptions.push(subscription);
}
}
unsubscribe() {
this.subscriptions
.filter(subscription => subscription)
.forEach(subscription => {
subscription.unsubscribe();
});
}
ngOnDestroy() {
this.unsubscribe();
}
}
And You can use this class in any component / Service / Effect etc.
Example:
class SampleComponent extends Unsubscriber {
constructor () {
super();
}
this.addSubscription(subscription);
}
The SubSink package, an easy and consistent solution for unsubscribing
As nobody else has mentioned it, I want to recommend the Subsink package created by Ward Bell: https://github.com/wardbell/subsink#readme.
I have been using it on a project were we are several developers all using it. It helps a lot to have a consistent way that works in every situation.
Here is my take on this issue, keeping my life simple I have chosen the manual way of unsubscribing the subscription when the component get destroyed.
For this I have created a class named Subscriptor, which mostly contains static members namely:
A private variable subscriptions - which holds all the supplied subscriptions
A subscription setter - which push every new subscription to subscriptions array
An unsubscribe method - which unsubscribe every subscriptions the subscriptions array contains if defined, and empty out the subscriptions array
subscriptor.ts
import { Subscription } from "rxjs";
export class Subscriptor {
private static subscriptions: Subscription[] = [];
static set subscription(subscription: Subscription) {
Subscriptor.subscriptions.push(subscription);
}
static unsubscribe() {
Subscriptor.subscriptions.forEach(subscription => subscription ? subscription.unsubscribe() : 0);
Subscriptor.subscriptions = [];
}
}
Usage inside a component is as follows:
When you want to subscribe any service, simply put the subscription to the Subscriptor's setter.
ngOnInit(): void {
Subscriptor.subscription = this.userService.getAll().subscribe(users => this.users = users);
Subscriptor.subscription = this.categoryService.getAll().subscribe(categories => this.categories = categories);
Subscriptor.subscription = this.postService.getAll().subscribe(posts => this.posts = posts);
}
When you want to unsubscribe any service, simply call the unsubscribe method of Subscriptor.
ngOnDestroy(): void {
Subscriptor.unsubscribe();
}
DisposeBag
The idea was inspired by RxSwift's DisposeBag, so I decided to develop a similar yet simple structure.
DisposeBag is a data structure that holds a reference to all open subscriptions. It facilitates the disposal of the subscription in our components while providing us with APIs to track the state of open subscriptions.
Advantages
Very simple API, makes your code look simple and small.
Provides API for tracking the status of open subscriptions (allows you to show indeterminate progress bar)
No dependency injections/packages.
Usage
In component:
#Component({
selector: 'some-component',
templateUrl: './some-component.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SomeComponent implements OnInit, OnDestroy {
public bag = new DisposeBag()
constructor(private _change: ChangeDetectorRef) {
}
ngOnInit(): void {
// an observable that takes some time to finish such as an api call.
const aSimpleObservable = of(0).pipe(delay(5000))
// this identifier allows us to track the progress for this specific subscription (very useful in template)
this.bag.subscribe("submission", aSimpleObservable, () => {
this._change.markForCheck() // trigger UI change
})
}
ngOnDestroy(): void {
// never forget to add this line.
this.bag.destroy()
}
}
In template:
<!-- will be shown as long as the submission subscription is open -->
<span *ngIf="bag.inProgress('submission')">submission in progress</span>
<!-- will be shown as long as there's an open subscription in the bag -->
<span *ngIf="bag.hasInProgress">some subscriptions are still in progress</span>
Implementation
import { Observable, Observer, Subject, Subscription, takeUntil } from "rxjs";
/**
* This class facilitates the disposal of the subscription in our components.
* instead of creating _unsubscribeAll and lots of boilerplates to create different variables for Subscriptions;
* you can just easily use subscribe(someStringIdentifier, observable, observer). then you can use bag.inProgress() with
* the same someStringIdentifier on you html or elsewhere to determine the state of the ongoing subscription.
*
* don't forget to add onDestroy() { this.bag.destroy() }
*
* Author: Hamidreza Vakilian (hvakilian1#gmail.com)
* #export
* #class DisposeBag
*/
export class DisposeBag {
private _unsubscribeAll: Subject<any> = new Subject<any>();
private subscriptions = new Map<string, Subscription>()
/**
* this method automatically adds takeUntil to your observable, adds it to a private map.
* this method enables inProgress to work. don't forget to add onDestroy() { this.bag.destroy() }
*
* #template T
* #param {string} id
* #param {Observable<T>} obs
* #param {Partial<Observer<T>>} observer
* #return {*} {Subscription}
* #memberof DisposeBag
*/
public subscribe<T>(id: string, obs: Observable<T>, observer: Partial<Observer<T>> | ((value: T) => void)): Subscription {
if (id.isEmpty()) {
throw new Error('disposable.subscribe is called with invalid id')
}
if (!obs) {
throw new Error('disposable.subscribe is called with an invalid observable')
}
/* handle the observer */
let subs: Subscription
if (typeof observer === 'function') {
subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
} else if (typeof observer === 'object') {
subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
} else {
throw new Error('disposable.subscribe is called with an invalid observer')
}
/* unsubscribe from the last possible subscription if in progress. */
let possibleSubs = this.subscriptions.get(id)
if (possibleSubs && !possibleSubs.closed) {
console.info(`Disposebag: a subscription with id=${id} was disposed and replaced.`)
possibleSubs.unsubscribe()
}
/* store the reference in the map */
this.subscriptions.set(id, subs)
return subs
}
/**
* Returns true if any of the registered subscriptions is in progress.
*
* #readonly
* #type {boolean}
* #memberof DisposeBag
*/
public get hasInProgress(): boolean {
return Array.from(this.subscriptions.values()).reduce(
(prev, current: Subscription) => {
return prev || !current.closed }
, false)
}
/**
* call this from your template or elsewhere to determine the state of each subscription.
*
* #param {string} id
* #return {*}
* #memberof DisposeBag
*/
public inProgress(id: string) {
let possibleSubs = this.subscriptions.get(id)
if (possibleSubs) {
return !possibleSubs.closed
} else {
return false
}
}
/**
* Never forget to call this method in your onDestroy() method of your components.
*
* #memberof DisposeBag
*/
public destroy() {
this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();
}
}
You can use latest Subscription class to unsubscribe for the Observable with not so messy code.
We can do this with normal variable but it will be override the last subscription on every new subscribe so avoid that, and this approach is very much useful when you are dealing with more number of Obseravables, and type of Obeservables like BehavoiurSubject and Subject
Subscription
Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.
you can use this in two ways,
you can directly push the subscription to Subscription Array
subscriptions:Subscription[] = [];
ngOnInit(): void {
this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
//...
}));
this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
//...
}));
}
ngOnDestroy(){
// prevent memory leak when component destroyed
this.subscriptions.forEach(s => s.unsubscribe());
}
using add() of Subscription
subscriptions = new Subscription();
this.subscriptions.add(subscribeOne);
this.subscriptions.add(subscribeTwo);
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
A Subscription can hold child subscriptions and safely unsubscribe them all. This method handles possible errors (e.g. if any child subscriptions are null).
Hope this helps.. :)
--- Update Angular 9 and Rxjs 6 Solution
Using unsubscribe at ngDestroy lifecycle of Angular Component
class SampleComponent implements OnInit, OnDestroy {
private subscriptions: Subscription;
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$.subscribe( ... );
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
}
Using takeUntil in Rxjs
class SampleComponent implements OnInit, OnDestroy {
private unsubscribe$: new Subject<void>;
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$
.pipe(takeUntil(this.unsubscribe$))
.subscribe( ... );
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
}
for some action that you call at ngOnInit that just happen only one time when component init.
class SampleComponent implements OnInit {
private sampleObservable$: Observable<any>;
constructor () {}
ngOnInit(){
this.subscriptions = this.sampleObservable$
.pipe(take(1))
.subscribe( ... );
}
}
We also have async pipe. But, this one use on the template (not in Angular component).
In my case I am using a variation of the solution proposed by #seanwright:
https://github.com/NetanelBasal/ngx-take-until-destroy
It's a file used in the ngx-rocket / starter-kit project. You can access it here until-destroyed.ts
The component would look like so
/**
* RxJS operator that unsubscribe from observables on destory.
* Code forked from https://github.com/NetanelBasal/ngx-take-until-destroy
*
* IMPORTANT: Add the `untilDestroyed` operator as the last one to
* prevent leaks with intermediate observables in the
* operator chain.
*
* #param instance The parent Angular component or object instance.
* #param destroyMethodName The method to hook on (default: 'ngOnDestroy').
*/
import { untilDestroyed } from '../../core/until-destroyed';
#Component({
selector: 'app-example',
templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit, OnDestroy {
ngOnInit() {
interval(1000)
.pipe(untilDestroyed(this))
.subscribe(val => console.log(val));
// ...
}
// This method must be present, even if empty.
ngOnDestroy() {
// To protect you, an error will be thrown if it doesn't exist.
}
}
A lot of great answers here...
Let me add another alternative:
import { interval } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { Component } from "#angular/core";
import { Destroyable } from "#bespunky/angular-zen/core";
#Component({
selector: 'app-no-leak-demo',
template: 'πŸ‘ Destroyable component rendered. Unload me and watch me cleanup...'
})
export class NoLeakComponent extends Destroyable
{
constructor()
{
super();
this.subscribeToInterval();
}
private subscribeToInterval(): void
{
const value = interval(1000);
const observer = {
next : value => console.log(`πŸ‘ Destroyable: ${value}`),
complete: () => console.log('πŸ‘ Observable completed.')
};
// ==== Comment one and uncomment the other to see the difference ====
// Subscribe using the inherited subscribe method
this.subscribe(value, observer);
// ... or pipe-in the inherited destroyed subject
//value.pipe(takeUntil(this.destroyed)).subscribe(observer);
}
}
Live Example
What's happening here
The component/service extends Destroyable (which comes from a library called #bespunky/angular-zen).
The class can now simply use this.subscribe() or takeUntil(this.destroyed) without any additional boilerplate code.
To install the library use:
> npm install #bespunky/angular-zen

Angular: Many components listen to key events - refactor?

In a couple of my angular components I listen to key events
#HostListener('window:keydown', ['$event']) keyInput(event: KeyboardEvent) {
if (this.isActive) {
if (event.keyCode === 27) {
// do something
}
}
}
In this case it is just one key, but I have them with more too
Anyway, I see duplicate code in my project here. So, is this acceptable or should I refactor this ? If so, what would be the preferred way?
I would create a service that other components can subscribe to when the event occurs. For example:
#Injectable()
public class KeyEventService {
private keydown$: Observable;
constructor() {
this.keydown$ = Observable.fromEvent(window, "keydown");
}
public getEscapeKeyListener() {
return this.keydown$.filter((event: KeyboardEvent) => event.keyCode === 27);
}
}
This allows you to setup the listener for the event once and then filter it to the appropriate keypress. The public methods return observables that filter on specific keys or multiple keys without having to setup a new observable. Then in your component you would use it like this:
#Component({})
public class MyComponent implements OnInit, OnDestroy {
private keyListenerSub: Subscription;
constructor(private keyEventSvc: KeyEventService) { }
ngOnInit() {
this.keyListenerSub = this.keyEventSvc.getEscapeKeyListener().subscribe(() => {
/* respond to keypress */
});
}
ngOnDestroy() {
this.keyListenerSub.unsubscribe();
}
}
This allows you to set up the listener only once and then in your components you can just take the appropriate action when the event you want occurs.

Angular 2: Function as an #Input-Property

You can see my Plunk here.
In this very easy example I am passing a function
_ => {
console.log(number);
}
to a child component with the #Input property. My parent component looks like that:
#Component({
selector: 'my-app',
template: `
<child [func]="getFunction(3)">
</child>
<button type="button" (click)="startChangeDetection()">
Start change detection
</button>
`,
directives : [Child],
styles:[`
.titles {
color:#0099FF
}
.child-style {
background-color:#00ffff
}
` ],
})
export class CarComponent {
startChangeDetection()
{
}
getFunction(number)
{
return _ => {
console.log(number);
}
}
}
The button does nothing else than trigger another round of change detection (there is no implementation in the callback function.
However, my change detection always recognizes my input as a change, nevertheless it never changes.
This is my child component:
#Component({
selector: 'child',
template: `
<h2>Child Component</h2>
`,
inputs: ['func']
})
export class Child {
private func;
ngOnChanges(changes)
{
console.log(changes.func.previousValue.toString());
console.log(changes.func.currentValue.toString());
}
}
You can see, that in ngOnChanges I log my function to the console. But the logged value (obviously) does never change, so the output is always:
function (_) {
console.log(number);
}
function (_) {
console.log(number);
}
Why the heck does Angular even call ngOnChanges? And why does it think there is any change?
This method returns a different function instance every time getFunction is called.
getFunction(number)
{
return _ => {
console.log(number);
}
}
because of <child [func]="getFunction(3)">, getFunction is called every time change detection is run.
Binding to functions is usually not the best idea. If you move out the function creation this way, the same function instance is returned every time and Angular change detection won't recognize it as change:
myCallback = _ => this.myCallBack(number) {
console.log(number);
}
getFunction(number)
{
return this.myCallback;
}
I don't see anything abnormal there. The change detection is getting called twice.
First time because child component is rendered inside car component. The component tree is changed.
Second time because the function getFunction is getting called when you are passing it as getFunction(3) to the input. Thats equivalent to change in input value and hence triggering change detection cycle.

how can I listen to changes in code in angular 2?

I'm using angular 2. I have a component with an input.
I want to be able to write some code when the input value changes.
The binding is working, and if the data is changed (from outside the component) I can see that there is change in the dom.
#Component({
selector: 'test'
})
#View({
template: `
<div>data.somevalue={{data.somevalue}}</div>`
})
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
dataChagedListener(param) {
// listen to changes of _data object and do something...
}
}
You could use the lifecycle hook ngOnChanges:
export class MyComponent {
_data: Data;
#Input()
set data(value: Data) {
this.data = value;
}
get data() {
return this._data;
}
constructor() {
}
ngOnChanges([propName: string]: SimpleChange) {
// listen to changes of _data object and do something...
}
}
This hook is triggered when:
if any bindings have changed
See these links for more details:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html
As mentioned in the comments of Thierry Templier's answer, ngOnChanges lifecycle hook can only detect changes to primitives. I found that by using ngDoCheck instead, you are able to check the state of the object manually to determine if the object's members have changed:
A full Plunker can be found here. But here's the important part:
import { Component, Input } from '#angular/core';
#Component({
selector: 'listener',
template: `
<div style="background-color:#f2f2f2">
<h3>Listener</h3>
<p>{{primitive}}</p>
<p>{{objectOne.foo}}</p>
<p>{{objectTwo.foo.bar}}</p>
<ul>
<li *ngFor="let item of log">{{item}}</li>
</ul>
</div>
`
})
export class ListenerComponent {
#Input() protected primitive;
#Input() protected objectOne;
#Input() protected objectTwo;
protected currentPrimitive;
protected currentObjectOne;
protected currentObjectTwo;
protected log = ['Started'];
ngOnInit() {
this.getCurrentObjectState();
}
getCurrentObjectState() {
this.currentPrimitive = this.primitive;
this.currentObjectOne = _.clone(this.objectOne);
this.currentObjectTwoJSON = JSON.stringify(this.objectTwo);
}
ngOnChanges() {
this.log.push('OnChages Fired.')
}
ngDoCheck() {
this.log.push('DoCheck Fired.');
if (!_.isEqual(this.currentPrimitive, this.primitive)){
this.log.push('A change in Primitive\'s state has occurred:');
this.log.push('Primitive\'s new value:' + this.primitive);
}
if(!_.isEqual(this.currentObjectOne, this.objectOne)){
this.log.push('A change in objectOne\'s state has occurred:');
this.log.push('objectOne.foo\'s new value:' + this.objectOne.foo);
}
if(this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)){
this.log.push('A change in objectTwo\'s state has occurred:');
this.log.push('objectTwo.foo.bar\'s new value:' + this.objectTwo.foo.bar);
}
if(!_.isEqual(this.currentPrimitive, this.primitive) || !_.isEqual(this.currentObjectOne, this.objectOne) || this.currentObjectTwoJSON != JSON.stringify(this.objectTwo)) {
this.getCurrentObjectState();
}
}
It should be noted that the Angular documentation provides this caution about using ngDoCheck:
While the ngDoCheck hook can detect when the hero's name has changed,
it has a frightful cost. This hook is called with enormous frequency β€”
after every change detection cycle no matter where the change
occurred. It's called over twenty times in this example before the
user can do anything.
Most of these initial checks are triggered by Angular's first
rendering of unrelated data elsewhere on the page. Mere mousing into
another input box triggers a call. Relatively few calls reveal actual
changes to pertinent data. Clearly our implementation must be very
lightweight or the user experience will suffer.

Categories

Resources