I have a list of 'cards' that when a filter is selected on the left side of the screen, the cards shown in the middle of the screen will update accordingly. Here is a snippet of the js:
$scope.isReady = false;
// This is an async method to retrieve from DB
AssignmentManagerService.getAssignmentsForClass(teacherId, classId).success(function(response) {
var instances = response.instances;
$scope.assignmentListData = instances;
$scope.isReady = true;
});
And here is a piece of the template:
<div flex class="item-list" ng-if="isReady">
<md-card ng-repeat="assign in assignmentListData track by assign.instanceId"
ng-click="selectCard(assign)"
class="item-list-item"
style="font-size:16px;"
ng-class="getCardClass(assign)"
ng-style="getCardStyle(assign)">
<div>
<span style='font-weight:500'>Title</span>: {{assign.title}}
</div>
<div style="margin-top:10px;">
<span style='font-weight:500' ng-if="assign.availableDate">Available Date:</span> {{assign.availableDate | date:'fullDate'}}
<span style='font-weight:500' ng-if="assign.createDate">Create Date:</span> {{assign.createDate | date:'fullDate'}}
</div>
</md-card>
</div>
One of two things will happen: I will either get two sections of cards on top of each other, one section with the old selection and one section with the new selection, for a half second before the old selection is removed OR the cards will all be integrated before the old cards are removed. All of this happens within the time span of a second but looks very unprofessional and ugly.
What is supposed to occur is the user makes a filter selection, the cards immediately hide, the new data is retrieved, and the new cards are shown. No overlapping, no multiple groups.
Here's what I have done:
Wrapped the async method/success into a $timeout function: Didn't work. Same results.
Wrapped the async method/success into a $scope.$$postDigest function: Didn't work. Same results.
Added a 400ms thread sleep in the back-end: While it does work, I'm not inserting a back-end fix for a front-end problem.
Tried various combinations of ng-if/ng-show on the div and the md-card: Didn't work. Same results.
The template was originally ~500 lines. I have stripped out all the extraneous markup except for this card bit to see if it was the digest cycle taking too long. Didn't work. Same results.
Any help would be greatly appreciated. Thanks!
Wow. My coworker and I just found some really old and buried CSS animations that were affecting the list in question. After removing them, everything works as it should. Doh! I'd like to thank #The.Bear and #JBNizet for pointing me in the right direction on this!
Related
I am trying to click a button that is buried in div classes in the code via protractor.
I am pioneering a protractor project for my work and have reached a point where I no longer know what to do. I have a button that is buried in div classes and is not allowing me to click. I have tried using mouseMove to get over to the coordinates of the button, I have tried using the className of the specific button, etc. The button does not have an id. The id is not the issue as I have tried clicking a different button, equally buried in divs, by it's id. I need to know how to get through the layers of divs in order to click the button because the rest of the tests will be dependent on it.
APPLICATION CODE:
::before
<dashboard-label>
<div class="att-topic-analysis-tabs">
<div class="att-button-group">
<button class="btn btn-default btn-lg att-close-topic ng-scope"
role="presentation" tabindex="-1"
ng-click="removeTopic(currentTopic.id)" translate>
Close Topic
</button>
</div>
</div>
PROTRACTOR TEST:
it('Closes Topic Successfully', function(){
//opens the first available topic
openTopic.click();
//checks that the URL contains 'topics' after 5 seconds
browser.wait(proExpect.urlContains('topics'), 5000);
var closeTopic = element(by.className('att-close-topic'));
//browser.wait(proExpect.elementToBeClickable(closeTopicButton), 5000);
console.log(closeTopic);
closeTopic.click();
browser.wait(proExpect.urlContains('home'), 5000);
});
As you can see, the Close Topic button is kind of buried in div classes and the standard click isn't working. Any info would be greatly appreciated
If the closeTopic locator is finding the element, but failing to click it, check to make sure there's only one matching element in the DOM, and that it's visible. My favorite way to check the DOM is just ctrl-F in Chrome inspector and paste the exact CSS that the test is using (.att-close-topic). And to check that what it's getting is visible, use
console.log(closeTopic.isDisplayed());
This can be a big gotcha in protractor, because it doesn't fail (only warns) when there are multiple matches on the page, and it defaults to the first match rather than the first visible match, which drives me nuts, because it's very rare that you want to do anything with a non-visible element on the page.
This will be partly opinion, but just to add a layer to the conversation...
Sometimes the solution to locating a troublesome element on the page is to go back to the developers and make the page more testable. I've seen testers spend hours or days crafting brilliant workarounds to access a stubborn element, and the end result was a fragile, complicated end-to-end test (and aren't they fragile enough already?).
Sometimes a 5-minute conversation with a developer can result in a quick change in the production code (e.g. add a unique ID) that avoids all that effort and yields a much better result, more stable, more simple. But this requires open conversation between the dev and test team, and a culture that values testing as a primary activity enough to make those testability changes to production code that is otherwise working just fine.
This is what you want to read to help you debug why your test doesn't work.
Also, you might want to start adopting await/async since the control flow will go away in the future.
http://www.protractortest.org/#/debugging
try this
var closebutton=element(by.css("[ng-click="removeTopic(currentTopic.id)"]"),
EC = protractor.ExpectedConditions;
Waits for the element to be clickable.checks for display and enable state of button
browser.wait(EC.elementToBeClickable(closebutton), 10000);
now use : closebutton.click();
I use rn-carousel with ng-repeat and I am able to swipe between all my items. However there is an infinite number of blank items appended to the list, meaning that I can swipe past my own items to the right an infinite number of time, it displays empty slides.
This happens even with a very basic markup:
<ul rn-carousel rn-carousel-controls class="swiper-container">
<li ng-repeat="p in UseCaseCtrl.getProjects()">
<img ng-src="{{p.picture}}">
</li>
</ul>
and regardless of the device.
Also the next '>' control link is never displayed on any slide, which is quite misleading when the user lands on the first slide and does not even know there are others slides on the right.
Any guess on what could be the source of the problem? Or a way to debug?
In case someone faces the same issue, the root cause was my implementation of UseCaseCtrl.getProjects() which used to return an object. Internally, rn-carousel will use .length to determine the number of slides. Since it tried to make .length of an object with nested items, it could not work. The problem was solved by returning an array instead.
So, I have made some custom directive which draws kind of a data-grid, based on floated divs (because nested flex implementation in FF sucks - but it's not the point).
How it works :
I pass some data collection to the directive via something like <the-grid data-list="parentController.displayedRows">
Inside this first directive, I have columns via something like <a-grid-column data-value="row.value"></a-grid-column> with many attributes I won't precise here.
The data-value value can be a direct expression, bound to the row on which the the-grid directive controller is ng-repeating in order to display each columns, or a function which have to be $eval-uated in order to display the intended value from the parentController.
In my <the-grid> directive controller, I have the rendering template of my grid which make a nested ng-repeat div (first one on the rows of the data-collection, second one on the columns, created in the directive), it looks like :
<div data-ng-repeat="row in list">
<div data-ng-repeat="cell in theGridColumns"
data-ng-bind-html="renderCell(row, cell)">
</div>
</div>
I have some keyboard nav in order to quickly select a row or navigate within pagination or many tabs, which does nothing more than applying some class on the selected row, in addition to update some "selectedRowIndex".
I'm using angular-vs-repeat in order to have the minimum of row divs in my DOM (because the app is running on slow computers). This works well.
The problem is that every time I'm hitting some "up" or "down" key on my keyboard, Angular is "redrawing" EVERY cells of the list.
So, let's suppose I've 200 rows in my data list, and 7 columns for each rows. First load of the page, it passes ~3000 times in the renderCell() function. Ok , let's accept that (don't really understand why, but ok).
I hit the down key in order to go to the second line of my list. It passes ~1100 times in the renderCell() function.
So yes, the result is very slow (imagine if I let the down arrow key pressed in order to quick navigate within my rows)... I can't accept that. If someone could explain that to me... Any help would be greatly accepted :)
If I make the same thing without a directive (direct DOM manipulation, with columns made by hand and not in a ng-repeat within a ng-repeat), every thing is smooth and clean.
Yes, I've look into every angular grid on the market. No one is satisfying me for my purpose, that's why I've decided to create my own one.
And no, I really can't give you some JSFiddle or anything for the moment. The whole app is really tentacular, isolating this is some kind of complicated).
Try to use bind once (angular 1.3+)
<div data-ng-repeat="row in ::list">
<div data-ng-repeat="cell in ::theGridColumns"
data-ng-bind-html="::(renderCell(row, cell))">
</div>
</div>
Today's bogglement! I have two sidebars and a main content area. When I click on a button to expand the page, the sidebars should go away and the main content area spreads to 100%. (I'm trying to do something like what quandl.com does in its api displays, which is totally nifty.)
This should be really simple! I just add a class of 'apponly', which set the main-content to 100% and the sidebars to display:none. In my controller, I've got:
$scope.toggle = function() {
$scope.displayed = !$scope.displayed;
}
and in my html, I've got basically:
<div class="sidebar left" ng-class="{'apponly' : displayed }">
--something something--
</div>
<div class="main-content" ng-class="{'apponly' : displayed }">
<button class="btn btn-info device" ng-click="toggle()">[icon]</button>
--something something--
</div>
<div class="sidebar right" ng-class="{'apponly' : displayed }">
--something something--
</div>
...but it consistently only applies the class 'apponly' to the first sidebar and main content, and leaves the second sidebar intact (wrapped around to the next line, but still right there, w/no class of 'apponly' applied).
I tried to set up a plnk (http://plnkr.co/edit/ER4TPGexGnZ8knb4ThXQ?p=preview) but it won't work at all [okay that was a stupid oversight, now it does work except now I'm even more confused]. What really simple obvious thing am I totally missing here?
many thanks in advance!
AS PSL said, juste update your plunkr to <body ng-app="app" ng-controller="mainController"> and it will work. It actually does what you were expecting, so the issue is probably somewhere else.
Welp, I might as well answer since I (sort of) figured it out, and you never know, I might not be the only one baffled by such behavior.
It's tied to the animation. If you take the animation away, then everything behaves. You add it back in, and things get wacky again.
Why is this? I have no idea. But when I took the animation off the sidebars (so they simply disappear immediately), then the right-hand sidebar doesn't get pushed to the next line and end up remaining despite the added-class that should make it disappear. From behavior at least, it seems that the browser is trying to render animation at the same time the css-class is trying to make the section disappear. Which doesn't make any sense, b/c this works fine in other places, but all I know is that if you leave the animation on the main (middle) part, and turn it off on the sidebars, then everything behaves properly.
I'm not counting this as an answer, though, since it's more of a "well, that fixed it, if inadequately," which is less of an answer and more of a workaround left here for posterity, the enlightenment of any confused devs behind me, and the entertainment of any senior devs wandering through.
I have situations where I need to prepend an item to a list that is initially generated using ng-repeat. How do I do this?
<div ng-click="prependItem()>Click Here</div>
<div ng-repeat="item in items">
<div class="someClass">Item name: {{item.name}}</div>
<div class="anotherClass">Item type: {{item.type}}</div>
</div>
If I click on prependItem() I want want the new item to be added to the top of the list. Obviously, I don't want to regenerate the entire ng-repeat. I've been unable to find any documentation that would explain how to do this. Thank ahead of time for any help!
scope.prependItem = function (newItem) {
items.unshift(newItem);
};
AngularJS is smart enough to know the addition, and only create html element for it
http://plnkr.co/edit/qzIfzSP6buiQ49rDreNk?p=preview
You can see from console that only the newly added item will log messages
ng-repeat will rebuild the list if you add an item to the front of your array. If you add it to the end it's smarter in that it will only update the DOM to reflect the change for the one item you've added in.
Because you've added an item to the front, it has to move everything in the DOM so I think it just rebuilds it as it's easier to do than moving (don't quote me on that though lol). It isn't necessarily a bad thing to rebuild that list (unless it's huge, you won't even notice the refresh, if it is very big, I'd recommend showing a spinner whilst it rebuilds the html, that's the approach we've taken at work; since the user has clicked the button they're expecting the interaction so the spinner seemed like the best compromise whilst angular rerendered).