We have a number of components in our Angular application that need to regularly display new values every second that are unique to each component (countdowns, timestamps, elapsed time, etc). The most natural way to is to create observables that use the RxJS timer and interval factory functions. However, these trigger Angular change detection at every interval for the entire app as many times as the interval function was called. If we have dozens of components on the page, this triggers change detection for the entire app dozens of times every second or time period, creating a large performance overhead.
So far there are two ways I've attempted to solve the problem. A good answer to either would be very helpful - ideally both. I want to avoid manual trigger of change detection and instead rely on new values emitted from Observables and have the async pipe/OnPush change detection strategy be responsible for triggering change detection. If this isn't possible, I'd like to understand why.
Is there any way to disable or prevent RxJS timer or interval functions from triggering Angular change detection? Using NgZone zone.runOutsideAngular(() => this.interval$ = interval(1000) ... ) does not appear to do this. StackBlitz example: https://stackblitz.com/edit/angular-zo5h39
Alternatively, if I create an Observable stream using an RxJS Subject combined with setInterval called inside zone.runOutsideAngular, why isn't change detection triggered for the child component when a new value is emitted from the subject? StackBlitz example: https://stackblitz.com/edit/angular-yxdjgd
Is there any way to disable or prevent RxJS timer or interval functions from triggering Angular change detection? Using NgZone
zone.runOutsideAngular(() => this.interval$ = interval(1000) ... )
does not appear to do this.
It's because observables are cold and the value producer (setInterval) is only executed when a subscription happens. Read more here. Although this code is executed outside of Angular zone:
() => this.interval$ = interval(1000) ...
it doesn't actually immediately invoke the value producer inside interval operator. It will be later invoked when AsyncPipe in the IntervalInnerComponent subscribes. And this happens in inside Angular zone:
Using subject in the next alternative you make it hot and subscribe immediately outside of Angular zone.
Alternatively, if I create an Observable stream using an RxJS Subject combined with setInterval called inside zone.runOutsideAngular... setInterval values are being created (they can be subscribed to
manually), but the async pipe does not appear to be triggering change
detection
Because Async Pipe doesn't trigger change detection. It simply marks a component and all its ancestors for check during the following change detection whenever it will be. See this answer or this article for more details. Zone.js triggers change detection. But since you're running setInterval outside Angular zone, it doesn't trigger it. Under this setup you have a few options:
inject ChangeDetectorRef and manually trigger change detection for the component and its ancestors
inject ApplicationRef and manually trigger change detection for the entire application
Related
The offical RFC
There is a example for effect
function createSharedComposable(composable) {
let subscribers = 0
let state, scope
const dispose = () => {
if (scope && --subscribers <= 0) {
scope.stop()
state = scope = null
}
}
return (...args) => {
subscribers++
if (!state) {
scope = effectScope(true)
state = scope.run(() => composable(...args))
}
onScopeDispose(dispose)
return state
}
}
I know what it will do, it will force all components to calculate only once when we use useMouse API
But I can't understand the concept of effect, and how does it work?
Espeically some APIs for effect like getCurrentScope. I tried to see the return values of getCurrentScope, but i have gained nothing.
Please help me!
effect is a common term used in reactive frameworks (both VueJS and React) to refer to (I believe) side effect. If you are familiar with functional programming, you probably already know that it is called side effect because it is not "pure function", because it mutates shared or global state.
Ignore the academic terminology, effect in these systems merely refers to any application defined method that does something bespoke, like
const foo = () => {
// I do something bespoke
}
The meaning of effect is really that broad. What your method actually does in its body does not matter to the framework. All that the framework knows is foo does some unstructured things. What VueJS does in extra, is to monitor, through its reactivity system, if your effect depends on any reactive data. And if it does, VueJS will re-run your effect every time the data it depends on changes.
effect (or side effect) isn't something bad or special or advanced. In fact, your application is all about making effects/side effects. For example, the commonest effect in a VueJS application is DOM manipulation. It is so common that VueJS extracts it into a different abstraction: template. Behind the scene, templates are compiled to render functions - which look a lot like the foo above - that get re-evaluated every time some dependent reactive data changes. That is how VueJS keeps your UI up to date.
Another extreme of common effects are those really bespoke ones, e.g. you just want to do some old fashion imperative things (like the jQuery style) whenever your data changes. And VueJS let you do it through watchEffect: you give VueJS a lambda, and VueJS will blindly call it every time its dependency changes without asking what it is doing.
VueJS discovers your dependency on reactive data by running your effect. As long as your effect accesses any reactive data (say, yourState.bar) during its execution, VueJS will notice that and record a dependency of your effect on yourState.bar
In its essence, the reactivity system is just the modern version of the good-old observable/subscriber pattern. Reactive states are the observables, and effects are the subscribers/observers. If you look beyond the magic layer and think of VueJS in the form of a subscriber pattern, there is one issue it cannot avoid: whenever you have subscribe, you will have to deal with unsubscribe, otherwise you will have memory or resource leaks simply because you keep holding on to subscribers (they in turn hold on to other things) and nothing can be freed. This unsubscribe part is what the RFC calls "effect dispose".
Typically you will have two challenges when dealing with this unsubscribing/disposing/cleaning up/cancelling business:
deciding when to unsubscribe/dispose
knowing how to unsubscribe/dispose
In a typical reactive framework, both of the above are application's responsibility. As the application dev, you are the only one who knows when a subscription is no longer needed and how to reverse the additional resource allocation (if any) you made at the time of creating the subscription.
But in a typical VueJS app you rarely need to manually deal with any kind of cleanup (stopping the DOM patching, watch, or computed etc). That is because VueJS takes care of it automatically. The reactive effects established within a component's setup method will be automatically disposed (whatever needed for a proper clean up) when the component is unmounted. How does that happen? Let's just say some other magic exists inside VueJS to associate all your effects with the life cycle of the corresponding component. Technically, as the RFC says, that magic is effectScope.
Conceptually, each component creates an effectScope. All your effects defined inside component setup method will be associated with that scope. When the component destroys, VueJS automatically destroys the scope, which will clean up the associated effects.
The RFC proposes to make effectScope a public api so that people can use it without using a VueJS component. This is possible because Vue3 is built with modularization. You can use Vue's reactivity module without using the entire VueJS. But without the underlying effectScope, you then have to manually dispose all your effects.
What would making a coffee look like in code?
snowingfox.getCupsOutOfCupboard();
snowingfox.getCoffeeOffShelf();
snowingfox.getMilkOutOfFridge();
snowingfox.boilingWater();
// ...
Now imagine each morning I wake up and have a coffee. You could say I'm making
a coffee in reaction to waking up. How would I run this code repeatedly in
response to an isMorning variable becoming true?
This is what effect solves in Vue 3. It wraps around a chunk of
code that should be executed in response to reactive data being changed. In practise you most likely won't use effect directly, and instead rely on
things like computed and watchEffect (which use effect in their
implementations).
In short: effect is one part of Vue's reactivity system and is Vue's way of
marking and locating code that should be re-run in response to data updates.
Docs: https://v3.vuejs.org/guide/reactivity.html
Course: https://www.vuemastery.com/courses/vue-3-reactivity/vue3-reactivity/
Here's how the initial code could be implemented to be reactive:
import { ref, watchEffect } from 'vue';
const isMorning = ref(false);
watchEffect(() => {
if (!isMorning.value) return;
snowingfox.getCupsOutOfCupboard();
snowingfox.getCoffeeOffShelf();
snowingfox.getMilkOutOfFridge();
snowingfox.boilingWater();
});
TL;DR:
I am rendering a BioDigital HumanAPI anatomical model in my Angular 5 app within an iFrame. I instantiate API object using:
this.human = new HumanAPI(iFrameSrc);
There's an API function human.on(...) that can be used to register click events from within iFrame (like picking objects from the model, etc.). I need this function to be able to listen to the events at all times. I do the object instantiation and put this function within ngOnInit() and it works, but when I change the source of iFrame to render a different model, this function stops working. Where should I put this listening function so that its logic is available at all times?
Longer version:
I am developing an Angular app using BioDigital HumanAPI. The basic idea here is that HumanAPI provides several anatomical models which can be rendered in a web-app using an iFrame (an example here). The src of this iFrame is a link, something like:
https://human.biodigital.com/widget?m=congestive_heart_failure
Since I want the user of my Angular app to be able to view several of such models, I have a list of these URLs, and based on user's selection, I update the src of iFrame, using a function updateFrameSrc which has following code:
iframeSrc: SafeUrl;
this.iframeSrc = this.sanitizer.bypassSecurityTrustResourceUrl(newUrl);
Finally (the question is coming, please stay with me), in order to manipulate and register different click events and user interactions with the model within the iFrame itself, we make a HumanAPI object like this:
this.human = new HumanAPI(iFrameID);
This lets us use API event listener functions like human.on('scene.picked') to register and save click events (like shown in the example I referenced above). All of this is working fine.
The problem is that since I initialize the human object in the ngOnInit() function and also put the human.on('scene.picked') function there, I cannot register the click events after the iFrame source is changed. As I understand it, ngOnInit() is only called once when the component is first initialized, so may be the listening logic of human.on is not available after updating the iFrame source? I have tried placing the logic in different life-cycle hooks but its doesn't work.
My current work-around is to re-call the ngOnInit() function after updating the iFrame source, and it works that way, but I believe this is against the standard life-cycle management practices.
My questions are:
It is acceptable to re-call the ngOnInit() function from within the component logic?
If not, where should I place a JavaScript API function that listens to click events from an iFrame at all times, even after the source of that iFrame has been changed?
As suggested in an earlier comment, you can just move the code in ngOnInit() to a separate function and call that function from both ngOnInit() as well as your update function.
Don't forget to re-initialize the human object of HumanAPI in that function as well, when updating the iFrame source.
Re-calling ngOnInit() should be avoided as it circumvents the acceptable functionality of lifecycle hooks, as mentioned by #iHazCode.
If you are looking for near real time you will want this to occur in the NgOnChanges life-cycle hook. Be advised this is expensive.
If slightly less "near real time" is acceptable, I would advise wiring up a rapid delay subject Observable.Interval(500) (also, but slightly less expensive) at the time of Component initialization NgOnInit.
Please DO NOT circumvent the hooks by re-calling ngOnInit.
If you have additional questions let me know.
I saw the following today in my code base and am trying to wrap my head around what it could be doing:
public ngOnInit(): void {
this.siteTitle = this.modalService.site ? this.modalService.site.siteTitle : null;
setTimeout(() => {
if (!this.modalService.site) {
this.ngZone.run(() => {
this.modalService.close();
});
}
}, 0);
}
I've been reading some articles such as this one but still would like some clarification. I know that because the setTimeout parameter is 0 it will still be placed into the event queue and will execute once all other non-JS pieces are finished.
Thanks
I think the blog which you have pointed out is the most appropriate that you could get.
NgZone enables us to explicitly run certain code outside Angular’s
Zone, preventing Angular to run any change detection. So basically,
handlers will still be executed, but since they won’t run inside
Angular’s Zone, Angular won’t get notified that a task is done and
therefore no change detection will be performed. We only want to run
change detection once we release the box we are dragging.
As you have already pointed out that you know why you are using setTimeout the confusion should be solves by reading these lines again once more.
The reason he is trying to use setTimeOut is because he wants to avoid getting the error
ExpressionChangedAfterItHasBeenCheckedError
which occurs when you try and change the value of the variable before Angular change detection completes
credits - https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
https://blog.thoughtram.io/angular/2017/02/21/using-zones-in-angular-for-better-performance.html
I don't have all the information to write a definitive answer but I use ngZone.run sometimes to force the refresh of the component.
Now using this in a setTimeout leads me to believe that something occurs elsewhere changing the state of the view but somehow requires a manual refresh to redraw the changes. This is usually a js-only library make changes that angular doesn't know about.
In Angular 1.x we use to have digest cycle which triggers the watchers and update the view whenever there is a change in the binded property.
In Angular 2 we see that there is interpolation(one way binding) of properties in view when there is a change so how does this one way binding works under the hood.
Can someone please explain this?
Thanks
I found few pointers which suggested that there is something called Zone.js which helped getting away with digest cycle and $apply in angular 2.
Angular 1
When you write an expression ({{aModel}}), behind the scenes Angular sets up a watcher on the scope model, which in turn updates the view whenever the model changes
$scope.$watch('aModel', function(newValue, oldValue) {
//update the DOM with newValue
});
The second argument passed to $watch() is known as a listener function, and is called whenever the value of aModel changes. It is easy for us to grasp that when the value of aModel changes this listener is called, updating the expression in HTML.
$digest cycle is responsible for firing the watchers.The $digest cycle starts as a result of a call to $scope.$digest().
Angular doesn’t directly call $digest(). Instead, it calls $scope.$apply(), which in turn calls $rootScope.$digest(). As a result of this, a digest cycle starts at the $rootScope, and subsequently visits all the child scopes calling the watchers along the way.
for more detail plz visit : Angular Digist cycle
Angular 2
Basically application state change can be caused by three things
1.Events - click, submit, …
2.XHR - Fetching data from a remote server
3.Timers - setTimeout(), setInterval()
They are all asynchronous. Which brings us to the conclusion that, basically whenever some asynchronous operation has been performed, our application state might have changed. This is when someone needs to tell Angular to update the view.
Angular allows us to use native APIs directly. There are no interceptor methods we have to call so Angular gets notified to update the DOM.
Zones take care of all these things. In fact, Angular comes with its own zone called NgZone
The short version is, that somewhere in Angular’s source code, there’s this thing called ApplicationRef, which listens to NgZones onTurnDone event. Whenever this event is fired, it executes a tick() function which essentially performs change detection.
for more please visit : Angular 2 change detection
Basically, AngularJS was all about Two-way databinding, that ended up being very costly in terms of ressource usage with the $scope and the $watchers. You could always do One-way databinding, but that was not well made for that and it became hard to work with AngularJS.
With Angular2, since it's a different Framework, you can do both pretty easily.
Here is a good link that explains the different databindings possibilities :
http://tutlane.com/tutorial/angularjs/angularjs-data-bindings-one-way-two-way-with-examples
There are even more differences between those two Frameworks that you should learn about.
Here are some cool links that I discovered a few months ago that are quite helpful to understand why they went from AngularJS to Angular2 and contain all the answers you are looking for :
http://blog.mgechev.com/2015/04/06/angular2-first-impressions/
http://angularjs.blogspot.fr/2015/09/angular-2-survey-results.html
https://dzone.com/articles/typed-front-end-with-angular-2
Zone.js is something else :
https://github.com/angular/zone.js/
Have fun reading !
I'm working on an electron application, whose client side is written in Angular2.
I'm having the classic problem that a lot of people have encountered (eg. here and here), namely that I'm updating my component's data, but the view doesn't get updated because Angular doesn't know that the data has changed. The solutions people suggest (see above and also here) is to manually run change detection, either on the whole component tree, or part of it (similar to Angular 1's $rootScope.$digest and $scope.$digest).
However, I'd like to avoid wrapping all my data changes in zone.run() calls, as it slightly defeats the purpose of using Angular.
I'm pretty sure I know why this happens: I'm creating Bluebird Promises outside of Angular, in electron code (ie, the main process), and these aren't Zone aware, so they don't notify Angular of the changes.
I don't know how to solve it, though. What can I do to create Zone aware promises in my electron code, to avoid having to manually run change detection all the time? Can I convert my Bluebird Promises to Zone aware Promises somehow?
EDIT: I think I was wrong, Bluebird promises aren't Zone aware even if created within angular. They're just not Zone aware in general.
EDIT 2: I was completely wrong in the previous edit. Bluebird promises work just fine with zones. However, in the context of an electron application, creating a promise in the main electron process and using it in the renderer process (where Angular lives), doesn't work, as the returned promise isn't zone-aware. Creating the promise from Angular code worked.
Promises don't make Angular run change detection with ChangeDetectionStrategy.OnPush except when you use the async pipe ... | async.
When code is initialized outside Angular2 it runs outside Angulars zone. What you can do is to move initialization inside your Angular2 code or use ´zone.run(...)to move execution inside Angulars zone. There is nothing bad aboutzone.run(...)`.
If the code executed only changes local properties of a component you can use ChangeDetectorRef.detectChanges() to run change detection for this component.
If the code causes changes in other components (like for example this.router.navigate(...), then detectChanges() is not sufficient. For this case zone.run() should be used.
setTimeout(...) triggers change detection for the whole application and should be avoided as a way to invoke change detection manually.
As explained in EDIT #2, the problem was that creating a promise in the main electron process made the promise non-zone-aware. Creating the promise from Angular solved it.