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.
Related
I'm working with the Ext JS framework. We have a fairly complex ViewModel with many formulas bound to other formulas. I'd like to be able to run some code only once the ViewModel has finished its long cascade of events triggering events. I'm trying to verify that, after all the ViewModel formulas have calculated, whether or not the data model remains identical to the expected values for the calculated fields returned from our API -- basically a deferred model.isDirty() check.
I've tried using setTimeout to add my event to the end of the event queue, but it sometimes ends up firing in the middle of the formula-updating cascade. I could simply set a longer timeout, but that feels kludgey, would force the user to wait longer, and can't be guaranteed to work on a particularly slow browser.
Is there a good way to run code as soon as the ViewModel is done calculating, and not a moment sooner?
Is there a way to check whether or not the ViewModel is done?
Is there a way to defer an event similarly to setTimeout, but to run only after the event queue becomes entirely empty?
I've solved my own problem.
In Ext JS version 7.3.1, you can inspect the scheduledCount member of the ViewModel's Scheduler, to find out whether any ViewModel formulae are waiting to run. If the value is zero, you can be sure the ViewModel has finished calculating.
const scheduler = this.getViewModel().getScheduler();
setTimeout(awaitViewModelFinished, 0);
function awaitViewModelFinished() {
if (scheduler.scheduledCount > 0) {
setTimeout(awaitViewModelFinished, 0);
return;
}
// Payload code goes here.
}
I couldn't find a supported feature of the framework to do this. Per the sencha docs, "[Scheduler] is a private utility class for internal use by the framework. Don't rely on its existence." If you're using another Ext JS version, you may need to find a similar but different solution for this.
Inside the controller you can use:
// come viewModel options might not be resolved
this.getViewModel().notify();
// all viewModel options are solved
!!Careful: if a binding is null the bound formula won't run.
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
I use protractor to test an angular app and I have a weird problem. Occasionally or since recently, protractor has been stalling/slowing down considerably.
I narrowed down to the issue and can see that it's taking a long time for a simple someElement.getText().then(...) to resolve; so the .then(...) part never executes; however, putting an allScriptTimeOut: 500 000 waits untils that promise eventually resolves which takes about 6mins to resolve (very inconvenient!).
Again, waiting with allScriptTimeOut: 500 000 will eventually work but takes too long.
Another solution to this extreme slowdown is to tell protractor hey Protractor, don't wait for Angular to finish fufilling all promises and asynchronous background tasks and just go on, don't wait for angular and this would work with browser.ignoreSynchronization = true;
However setting this boolean to true is problematic because it basically treats your whole angular app as a NON-angular app and never waits for angular and thus causing all sort of issues with Protractor. Another complication is that our app is NOT entirely angular, some pages are non-angular; anyways; here's my question:
Is there a way to hook into the controlflow queue and basically poll or query the queue by asking hey controlflow queue tell me whenever any action that was enqueued is taking longer than 11sec? and if yes, set browser.ignoreSynchronization = true; a sudo-like code would look like this:
protractor.controlflow(function(delay){
if(delay > 11 sec){
browser.ignoreSynchronization = true;
}else{
browser.ignoreSynchronization = false;
}
});
This might not make sense syntactically but i'm more interested in the concept than properly written javascript;
Thanks again hopefully that's clear enough to understand my problem
UPDATES:
I checked again and basically, Protractor is able to click on most button, but then it gets to another button which was at the bottom of the screen, but I scrolled first to bring button into view, then tried to click on it and it's just taking 6mins then it finally clicks on that button.
Why is it taking this long before it finally successfully clicks on that button?
With respect to your question about changing ignoreSynchronization within a test, this is currently not possible (up through protractor 3.3.0) because the built-in waitForAngular function does not implement that variable within the context of the control flow (browser.ts:399 would have to be wrapped in control flow so that it would recognize when the variable is being changed). Hopefully that kind of changed can be patched into a future release, but it would introduce a breaking change from the existing behavior.
So I have this single page application that does a lot of computation every time the user does an action like click a button. As javascript is a not threaded, the lengthy calc blocks the UI updates and creates a bad user experience:
$('update-btn').click() {
updateDomWithAnimation();
doLenghtyCalc();
}
After reading perhaps one too many articles on how to handle this, I find that wrapping some of the function calls with window.setTimeout does help. So armed with this knowledge I have started wrapping them up and it seems to bring some responsiveness back to the browser.
However my question is, are there any adverse side effects of having too many timeout statements even if the delay is only 0 or 1? From a business logic perspective I am making sure only independent standalone functions are wrapped in setTimeout. I wanted to check from a technical viewpoint. Can any JS gurus share some insight?
P.S: I had taken a look at Web Workers, but my code is built using Jquery and depends heavily on DOM states etc so implementing web workers atm would not be possible which is why I am using timeouts
Much appreciated!
While technically it's ok to have several timeouts running it's generally advisable to not have too many.
One thing we did was to have a single timeout/setInterval each that when fired runs a set of functions that can be added or removed at anytime.
///Somewhere
var runnableFunctions = []
var runningIntervalID = window.setInterval(function() {
runnableFunctions.forEach(function(func) {
if(typeof func === 'function') {
func.call(null);
}
}, 1);
/// Elsewhere
$(domElem).on(event, function() {
runnableFucntions.push(function() {
//Do something on interval
// slice out this function if it only needs to run once.
});
});
This is just a dirty example but you get the idea where you can shove functions into an array and have them run in a single timeout/interval vs setting up many timeouts/intervals and then remembering to stop them later.
I've got some UI tests that are attempting to test that a click on an element makes something else appear. There's an existing check for all tests that looks to see if the DOM is Ready, however there's a small amount of time between that even firing and the app.controller() calls all completing where my test could jump in and wrongly determine that the click handler has not been added.
I can use angular.element('[ng-controller=myController]').scope() to determine if the scope is defined, however there is still a very small window where the test could run before the click handler is bound (a very, very small window).
<div ng-controller="myController">
<div ng-click="doWork()"></div>
</div>
Can anyone see a way to tell that the click handler has been added?
PS: There's an event that fires within a controller when the controller has loaded:$scope.$on('$viewContentLoaded', function(){ }); But that doesn't really help me unless I subscribe to it in the controller and flag a variable somewhere that I can check via Selenium.
PPS: A lot of these have classes that change when scope changes and they can be used to trigger the test, but many do not.
There is a specialized tool for testing AngularJS application - protractor. It is basically a wrapper around WebDriverJS - selenium javascript webdriver.
The main benefit of using protractor is that it knows when Angular is settled down and ready. It makes your tests flow in a natural way without having to use Explicit Waits:
You no longer need to add waits and sleeps to your test. Protractor
can automatically execute the next step in your test the moment the
webpage finishes pending tasks, so you don’t have to worry about
waiting for your test and webpage to sync.
It also provides several unique AngularJS-specific locators, like by.model, by.binding etc. And, in general, it provides a very convenient and well-designed API for end-to-end testing.
There are two issues to overcome here:
How do we know when Angular is done (with the sub issue of "what does done mean?"
How do we get that information to Selenium
Angular provides a method called "getTestability" that can be called with any element (assuming you've included it, it is optional). Usage:
angular.getTestability(angular.element('body')).whenStable(function(){/*Do things*/})
That seems to solve the first problem...
But, now what does Done mean in this case. Done means that anything that uses $browser.defer will have been executed. What does that mean? No idea, but in practice it at least verifies that there are no http requests in play when the callback is called.
Ok, now Selenium... You can ask it to execute JavaScript on the client and use the code above to set a variable. .whenStable(function(){window.someVar=true}) and then poll in the test until that variable is set.
Will this catch all cases of "Done"? Probably not, but it made my tests pass more consistently. As long as it works I'm not going to think any harder on the issue.
That said, I'm not marking this as the answer. It feels like a dirty solution.