I'm trying to use ng-grid with a sliding window of 100 records. The data is coming in realtime via signalR and every message trigger the following method:
onNewTrades(records) {
console.log("onNewRecord", records);
if (connectionStopped) return;
for (var i = 0; i < records.length; i++) {
if ($scope.recordsData.length > maxRecordsInTable)
$scope.recordsData.pop();
$scope.recordsData.unshift({
t: new Date(records[i][0]),
p: records[i][1],
a: records[i][2]
});
}
}
I have a threshold of 100 maxRecordsInTable before I start popping items off the end (before adding the new message to the front)
However, when it reaches my threshold the table simple stops updating. Strangely though, if I set a breakpoint on unshift(), the table does update with every "continue".
I suspect it's some kind of angular timing issue? I tried using $timeout()
Or may when I pop() and unshift() at the same time it doesn't pick up a change in the array? I tried using $apply() (error already in digest cycle)
There are a few things that could be happening here.
First of all, if onNewTrade is using an external, non-angular, library making xhr requests outside of angular's framework (i.e. not using $http or $resource), you have to call $scope.$apply(function(){ }) around the code you want the scope's digest to know about. That part's not clear from what you've provided. edit: Read more about when to use $scope.$apply
Second, angular's digest phase does a minimum of two passes (first to make changes, second to make sure there are no more changes). It does at most 10 passes by default. If angular evaluates the scope 10 times and it is not consistent, it gives up. see documentation. It does this because you can have multiple watch functions where one watch affects the scope higher in the hierarchy, which makes changes and affects the same watch.. basically causing an evented infinite loop. Do you see a console error about '10 $digest iterations, aborting!' or something similar?
There are a couple of other questionable things:
is onNewRecord asynchronous? If so, I would doubt connectionStopped is being done correctly. You could be returning early. Because you say a breakpoint shows values on unshift, its probably not the cause of this issue (and most likely missing $scope.$apply is the problem), but I'd rethink this code.
Your function is onNewTrade(records), but you log onNewRecord(record). If you have nested variables here, make sure you haven't excluded code that may contain typos (e.g. record instead of records). You might be working on an unexpected object.
Related
I have an array of observables. Each of them will make a http call to a REST endpoint and return a result so I can update the UI.
I am using zip to run them all like this:
Observable.zip(allTransactions).subscribe(result=> {blab});
In subscribe, I update a page-level collection, so UI gets updated via 2-way binding (angular).
however, there are a few problems:
1) when I construct each observable in the array, I added .delay(1000) to it, so I expect each run will delay at least 1 second to the previous one. In fact, that's not true. Based on my log, it seems all those transactions were fired at the same time. But the subscribe was delayed on second. I really need them run in sequence, as the order I setup the array, because I have some dependency in those transactions. Running all together won't work for me.
2) zip doesn't seem to guarantee to bring back my results in ordered. So my UI is totally in random ordered. Because I was doing this.items.push(result), where items is a variable being bound to the UI.
I am currently trying to merge all the transactions and add an empty observable with delay between every 2 transactions (still working on it).
Can anyone provide any suggestion what other alternatives I can do? or a better way I can try?
Thanks
1) You are correct that adding .delay(1000) to all observables will not make them wait for the previous one. The delay operator will delay the execution from the moment you subscribe to them, and since you subscribe to them all at the same time and delay them for the same amount of time, they will all execute at the same time.
If you want to execute the observable in sequence, and wait for one to finish before proceeding to the next then use the flatMap operator:
obs1.get()
// First call
.flatMap(response1 => {
return obs2.get(response1.something);
})
.subscribe(response2 => {
// Result from second call
});
2) Looking at the zip documentation the result should return in an ordered list:
Merges the specified observable sequences or Promises into one
observable sequence by using the selector function whenever all of the
observable sequences have produced an element at a corresponding
index. If the result selector function is omitted, a list with the
elements of the observable sequences at corresponding indexes will be
yielded.
But as you have noted: the calls are all executed simultaneously and one observable does not wait for the next one before starting.
So the call:
Observable.zip(obs1, obs2, obs3).subscribe(result => console.log(result));
Will log:
[response1, response2, response3]
UPDATE
I've tried to make a standalone version here: https://codepen.io/neezer/pen/pPRJar
It doesn't work quite like my local copy, but I'm hoping it similar enough that you can see where I'm trying to go.
I'm not getting quite the same behavior as well because I changed the listener target to document, which seemed to help some.
Also, I'm using RxJS v5 and the latest version of React.
Still getting the hang of RxJS...
I have two Observables: one subscribed to mouseover x coordinates on a table to show a resize column, and the other to allow the user to drag on that column.
Roughly speaking, the first one looks like this (all of the below defined in a componentDidUpdate lifecycle method in a React component):
Rx.DOM.mouseover(tableEl)
.map(/* some complicated x coordinate checking */)
.distinctUntilChanged()
.subscribe(/* setState call */)
That works great, and gives me this:
So now I want to provide the actual "drag" behavior, and I tried setting up a new Observable like so
// `resizerEl` is the black element that appears on hover
// from the previous observable; it's just a div that gets
// repositioned and conditionally created
Rx.DOM.mousedown(resizerEl)
.flatMap(md => {
md.preventDefault()
return Rx.DOM.mousemove(tableEl)
.map(mm => mm.x - md.x)
.takeUntil(Rx.DOM.mouseup(document))
})
.subscribe(/* do column resizing stuff */)
There are three problems with that:
Once I've done my first "drag", I can't do any more. My understanding is that takeUntil completes the Observable, and I'm not sure how I can "restart" it.
The mousemove from the first observable is still active while I'm dragging, so my black div will disappear once my x position changes enough to trigger that behavior.
The binding on the second Observable doesn't always seem to trigger (it's unreliable). I think there might be a race condition or something happening here because sometimes I'll refresh the page and I'll get the drag once (from #1), and other times I won't get it at all.
Note at first after a clean refresh I can't drag the handle (#3), then I refresh, and I can't drag the handle past the bounds setup from the first Observable--and the black resizer bar disappears and reappears as my mouse's x coordinate enters and leaves that envelope (#2).
I've been head-banging on this for quite some time now and would really appreciate any insight as to what I'm doing wrong here. In short, I want
the first Observable to "pause" when I'm dragging, then resume when I'm done dragging
the second Observable to not "complete" (or "restart") once a drag is done
the second Observable to reliably work
As I mentioned earlier, I currently have this logic setup in a React component's componentDidUpdate lifecycle method, the shape of which looks roughly like this:
componentWillUpdate() {
// bail if we don't have the ref to our table
if (!tableEl) {
return;
}
// try not to have a new Observable defined on each component update
if (!this.resizerDrag$ && this.resizer) {
this.resizerDrag$ = // second Observable from above
}
// try not to have a new Observable defined on each component update
if (!this.resizerPos$) {
this.resizerPos$ = // first Observable from above
}
}
I've played around with this a bit now, I don't think this answer will be complete, but I'd like to share my insights. Hopefully a more advanced RxJS mind will chime in, and we can all work together to figure it out :).
I recreated a "lite-er" version of this in CodePen, using some light jQuery manipulation as opposed to React. Here's what I have so far:
"the first Observable to "pause" when I'm dragging, then resume when I'm done dragging"
Solving the first point helps with the other two. Based on what I had to do to get my resizerEl, I get the feeling it is rendered in the render method of the component based on something in this.state. If this is true, that means that when the first observable still has the ability to create and destroy resizerEl even while the second observable is listening. This means that resizerEl will no longer be able to generate any events, even though the observable doesn't complete until you've moused up.
In my case, I noticed that if you moved the mouse fast enough to go outside of width of what you were trying to drag, it would eliminate resizerEl, which is of what we want, but not while we're trying to drag something!
My solution: I introduced another variable to the "state" of the "component". This would set to true when we moused down on resizerEl, and then false when we moused up again.
Then we use switchMap.
Rx.DOM.mousemove(tableEl)
.switchMap(function(event) {
return this.state.mouseIsDown ? Rx.Observable.never() : Rx.Observable.of(event);
})
.map(...
There's probably a better way to do it rather than just sticking event back in an Observable, but this was the last part of it I worked on and my brain is kind of fried hehe. The key here is switching to Observable.never while the mouse is down, that way we don't go any further down the operator chain.
Actually, one nice thing is that this may not even need to be put in this.state, since that would cause a re-render. You can probably just use an instance variable, since the variable is only essential to the Observables functionality, and not any rendering. So, using this.mouseIsDown would be just as good.
How do we handle the mouse being down or up?
Part 1:
...
Rx.DOM.mousedown(resizerEl)
.do(() => this.mouseIsDown = true)
Better to abstract this to a function of course, but this is the gist of what it does.
Part 2:
...
return Rx.DOM.mousemove(tableEl)
.map(mm => mm.x - md.x)
.takeUntil(Rx.DOM.mouseup(document))
.doOnCompleted(() => this.mouseIsDown = false)
Here we take advantage of doOnComplete to perform this side-effect once the observable has completed, which in this case, would be on mouseup.
"the second Observable to not "complete" (or "restart") once a drag is done"
Now here's the tricky one, I never ran into this problem. You see, every time Rx.DOM.mousedown(resizerEl) emits an event, inside of flatMap, a new Observable is created each time with return Rx.DOM.mousemove(tableEl).... I used RxJS 4.1 when making this, so it's possible that there could be behavioral differences, but I found that just because the inner observable completed didn't mean the outer one would complete as well.
So what could be happening? Well, I'm thinking that since you're using React, that resizerEl is being created/destroyed respectively when the component is rendering. I haven't seen the rest of your code of course, but please correct me if I'm wrong about this assumption.
This wasn't a problem for me because, for the sake of simplicity, I simply re-used the same element as a dragger, only hiding it when I wasn't hovering over a draggable element.
So the important question is: how is resizerEl being defined and used in your component? I'm assuming the actual reference to it is made using, well, a ref. But if it that DOM element is ever destroyed or recreated, then the Rx.Dom binding needs to be repeated all over again.
I see you're doing this with componentDidUpdate. However, the Rx.Dom.mousedown event may still be bound to an old copy of the ref to resizerEl. Even if the component destroys the resizer in the DOM, and sets the ref (I assume that is this.resizer) to null or undefined, that does not destroy the Observable that is bound to that element. In fact, I don't even think it removes it from memory, even if it's removed from the DOM! That means that this.resizerDrag$ will never evaluate to false/null, and it will still be listening to an element that is no longer in the DOM.
If that is the case, something like this in componentWillUpdate might help:
if (!this.resizerDrag$ && this.resizer) {
this.resizerDrag$ = // second Observable from above
}
else if (!this.resizer && this.resizerDrag$) {
this.resizerDrag$ = null;
}
This will remove the Observable if the resizer object ceases to exist, that way we can properly reinitialise it upon it's return. There's a better to way to do with Subjects, keeping the subscription to one subject and just subscribing the subject to different mousedown streams once they become available, but let's keep this simple :).
This is something where we'd have to see the rest of your code (for this component) tell what's going on, and figure how to address it. But, my hypothesis is that you'd need to intentionally destroy the Observable if this.resizer is ever removed.
the second Observable to reliably work
Pretty sure that once the above two issues work, this one goes away. Nice and easy!
The CodePen
Here is the very naive mockup I made of this problem:
https://codepen.io/anon/pen/KmapYZ
Drag the blue circles back and forth along the X axis. (Has some little problems and bugs unrelated to the scope of this question, so I'm not worried about them.)
I made some slight changes of course just to keep it in-step with the more dumb downed approach I used. But all the concepts are there, as well as most of the code you wrote, modified to match this approach.
As I mentioned before, I didn't encounter the problem of the dragging only working once, so this better demonstrates the solution to pausing the first Observable. I re-use the dragging element, which I assume is why I didn't run into the 'drag-only-once' problem.
I hope that you or anyone else can comment with some improvements to this approach, or just show us a better (potentially more idiomatic) approach.
I have multiply functions like count() getTotal(). All those calculation functions have to run if something changes my cart/object.
Currently I'm using them in the view like {{count()}}. So all those functions are running multiply times.
I think those functions are called multiply times, because of the dirty checking. Is this correct?
Now I read about the $watch function and that you should try to avoid it, because of performance issues. Anyway I wanted to test it, and $watchedmy cart. I found out it only logs a single time so wouldn't it be faster to $watch my cart and call all the calculations functions in there and then just bind to the result of the calculations?
I'm a bit confused know, cause i thought it would have the same behaviour. Because the $watch will also go through the digest loop.
I know there are already a lot of similiar questions, but having somebody explaining it directly for my case in a not so hard english, would make it a lot easier.
Example:
What has better performance? and why? Does both variants go through the digest cycle? Why then Variant 2 logs 10 times and Variant 1 only 1 time?
Variant 1
// bind these to scope and show like {{totalGross}}
cartService.totalGross = null;
cartService.totalNet = null;
cartService.totalItems = null;
// watch the cart here and update all variables
$rootScope.$watch(function () {
return cartService.cart;
}, function(){
cartService.totalGross = cartService.getCartTotalGross();
cartService.totalNet = cartService.getCartTotalNet();
cartService.totalItems = cartService.getTotalItems();
}, true);
Variant 2
// bind these to scope and show like {{getCartTotalGross()}}
cartService.getCartTotalGross();
cartService.getCartTotalNet();
cartService.getTotalItems();
Trying to answer my own question, altough i'm not shure if it's correct.
Variant 1 you have 1 more watcher, but we just watch values instead of functions and the 1 more because we watch the cart manually with $watch. But the watchers are less heavy to compute.
Variant 2 we are watching the value that functions are returning, so the functions have to calculate the same value multiply times, witch is more heavy. In each digest Cycle the UI get updated
Is this correct? So Variant 1 is better for performance?
It's generally considered bad practice to use $watch not (only) because it has performance issues, but because there are, most of the time, better alternatives (both clearer, and more efficient).
For example, instead of watching a model value that changes when the user enters something in an input, in order to compute something that depends on this value, you can use ng-change on the input and call the computation from there. This is faster, and the intent is clearer.
Now, to answer your question:
Every time an event is handled by angular, the event handler can modify anything in the scope. Angular has no way to know what has been modified. So it has to call, for example, getTotalItems() because an event handler might have changed something that makes the returned value of getTotalItems() change.
Additionally, changing any value that is watched causes a watcher function to be executed, and this watcher function can, in turn, change other values, that can be watched by other watchers, etc. etc. So angular needs to evaluate all the watched expressions in a loop, until it can be sure that the last evaluation leads to the same results as the previous one. That's what is known as the digest loop.
So, in short, having {{ getTotalItems() }} in the view is not a big deal if that function just returns something simple, like the length of an array, or the sum of a few values. But if it computes the meaning of life, it's a really bad idea, and you should instead compute the meaning of life only when it needs to be computed, and store the result in a variable that is displayed in the view.
You can do that with a watcher, but that should be the last resort. As I said already, there are often better alternatives.
I am trying to implement a virtual scrolling tree directive in Angular using this guide as a reference. However, when I start scrolling, the $watcher count explodes to 17k-20k+ watchers (which crashes the page), scrolling is consistently slow, and nothing I have tried seems to help.
Plunker with my current code: HERE
(Note, above not showing up for me in Firefox, but is working in Chrome). If you have any thoughts of what else I can try so the scrolling is not a disaster, I am open for ideas. Been working on this for way too long...
Other methods I have tried:
$compile(element.components())(scope.$new())
Was called in onScroll(). Result: The list no longer displayed at all/still lagged badly and I got continual "Cannot call method 'insertBefore' of null on $compile" errors until the page crashed.
<li ng-repeat="node in nodeList" vs-node="node"></li>
Aka, I tried to give each element an isolated scope of its own in hopes that its scope and any watchers associated with it would be destroyed when the list was updated. Result was no difference with the watcher issue of above.
function clearVisibleProvider(nodeList){
for(var i=nodeList.length-1; i >= 0; i--){
nodeList[i] = null;
}
return nodeList;
function clearNode(node){
if(node.nodes){
for(var j=node.nodes.length-1; j >= 0; j--){
clearNode(node.nodes[j]);
}
}
nodeList[i] = null;
}
}
An attempt to clear old list elements before replacing them. Again, no difference. It was called within updateDisplayList() before the main list was updated.
Achieved my goal by redesigning to use a regular list view instead. Displayed nodes are flattened into a list, manual indexing is used to keep track of things, and there is no nesting. Any elements that would be nested have padding calculated based on their level of depth in the tree.
There was also a bug where I was getting too many nodes at once as well. Instead of 50, I'd get 200-500. Fixing that did not solve the watch count exploding however, so I don't consider it the source of the original problem.
jsFiddle : http://jsfiddle.net/ADukg/4766/
I've written some basic angular code which actually works properly, and I get the desired result and ouput. However, I get some errors in my console which keep looping and eventually crash the browser. I've read some similar problems here, but can't seem to get a solution to work with my code.
Can anyone figure out what is going wrong and how to fix?
Error: 10 $digest() iterations reached. Aborting!
From your function, you must return a stable object (or near stable). Because you var new objects in the getBreakdown function, angular thinks they're new and puts them in the scope with new hashkeys.
Angular then runs this $digest again, to check to make sure nothing has changed... but it sees new objects and assumes that the model is not stabalized. It runs it again... and gets new objects again... and again... and again.
The moral of the story is you shouldn't create new model inside a function assigned to scope.
If you don't need live-binding, just transform this var into a new $scope var just once, don't bind to the function. If you need live binding, I think the solution for you is to use a filter.
btw... add a console.log(breakdown) right before your function returns, inspect each object inside the array and you'll see it outputs 10 times, each $$hashkey value is different. Angular uses the $$hashkey to keep track of objects. I think it'll be easier to understand how you're confusing it.
The example I use to teach people this concept is a function that returns a random number. If you try to bind to that function... angular will digest it 10 times each time getting a new random number and never stablizing. Angular won't know it's done digesting... ever. So thats why they limit it to 10.