Trouble locating Angular element for Protractor UI Nav test - angularjs

I'm trying to select the third option in a dropdown list. Below are some details about the element:
Outer HTML:
<md-option _ngcontent-c6="" role="option" ng-reflect-value="last90Days" tabindex="0" id="md-option-2" aria-selected="false" aria-disabled="false" class="mat-option"><!--bindings={
"ng-reflect-ng-if": "false"
}--> Last 90 Days <!--bindings={
"ng-reflect-ng-if": "true"
}--><div class="mat-option-ripple mat-ripple" md-ripple="" ng-reflect-trigger="[object HTMLElement]"> </div> </md-option>
CSS Selector:
#md-option-37 > div:nth-child(1)
Sorry for the terrible formatting. If anyone has any suggestions about how to select the "Last 90 Days" dropdown item then I'd be very grateful.

Based on what I understand out of your info an the AngularJS md-select you need to do the following
// Open the md-select, use the correct selector, this is a demo
$('md-select').click();
// There is an animation, I don't know how long, you need to wait for the animation to be finished.
// Quick and dirty is a browser.sleep(), but it's better to find a more stable way because if the animation will take longer in the future your test will break
browser.sleep(500);
// Click on you option, this can be done in several ways
// By index
$$('md-option').get(1).click();
// By text
element(by.cssContainingText('md-option', 'your text')).click();
// Wait for the menu to close, this is also an animation
browser.sleep(500);
For element(by.css('selector')); I always use the shorthand notation, for the rest of the selectors see the docs of Protractor.
I would advice to use a better way to wait for the animation to be done. Here I used a browser.sleep(), but it's not future proof. You don't want your script to rely on sleeps.
Hope it helps.

Related

Slow performance with angular material md-select and ng-repeat

I'm writing an enterprise application using angular and angular material and have problem with the performance of a medium sized (in my opinion) form. Especially in IE.
(Working demo, see https://codepen.io/tkarls/pen/vGrqWv . Klick on the card title and it pauses slightly before opens. Especially using IE and mobile. Desktop chrome works pretty well.)
The worst offenders in the form seem to be some md-selects with ng-repeat on them.
<md-select ng-model="form.subchannelId" ng-disabled="vm.readOnly">
<md-option ng-repeat="id in subchannelIds" value="{{::id}}">{{::id}}</md-option>
</md-select>
<md-select ng-model="form.serviceReference" ng-disabled="vm.readOnly">
<md-option ng-repeat="id in serviceReferences" value="{{::id}}">{{::countryId}}{{::id}}</md-option>
</md-select>
<md-select ng-model="form.audioCodec" ng-disabled="vm.readOnly">
<md-option ng-repeat="audioCodec in audioCodecs | orderBy:'toString()'" value="{{audioCodec}}">{{::systemVariables.encoders.aac[audioCodec].displayName}}</md-option>
</md-select>
<md-select ng-model="form.audioSource" ng-disabled="vm.readOnly">
<md-option ng-repeat="audioSource in audioSources | orderBy:'toString()'" value="{{audioSource}}">{{audioSource}}</md-option>
</md-select>
<md-select ng-model="form.padSource" ng-disabled="vm.readOnly">
<md-option ng-repeat="padSource in padSources | orderBy:'toString()'" value="{{::padSource}}">{{::padSource}}</md-option>
</md-select>
<md-select ng-model="form.lang" ng-disabled="!form.generateStaticPty || vm.readOnly">
<md-option ng-repeat="langKey in langKeys | orderBy:'toString()'" value="{{::langs[langKey]}}">{{::langKey}}</md-option>
</md-select>
<md-select ng-model="form.pty" ng-disabled="!form.generateStaticPty || vm.readOnly">
<md-option ng-repeat="ptyKey in ptyKeys | orderBy:'toString()'" value="{{::ptys[ptyKey]}}">{{::ptyKey}}</md-option>
</md-select>
The data model looks like:
$scope.subchannelIds = [0, 1, 2]; //up to 63 in real life
$scope.serviceReferences = ["000", "001", "002"]; //up to 999 in real life
$scope.ptys = {
"No programme type": 0,
"News": 1,
"Current Affairs": 2}; //Up to ~30 in real life
$scope.ptyKeys = Object.keys($scope.ptys);
$scope.langs = {
"Unknown": "00",
"Albanian": "01",
"Breton": "02"}; //Up to ~100 in real life
$scope.langKeys = Object.keys($scope.langs);
The other ng-repeats are small with 3-5 items each. I think that a modern browser should handle datasets of this size and render it very quickly. So hopefully I'm doing something wildly wrong with my HTML code. The data is fetched from the server in real life but I do pre-fetch it so once the form is ready to be displayed it is already in the $scope.
I tried to pre-generate HTML after I fetched the data using normal js loops. And then insert just the html snippet like:
{{::preGeneratedHtmlHere}}
But then angular would not treat it as html but text...
Any help on how to optimize this is appreciated!
Angular material has very poor performance, because the objects pinned to the scope are huge, which makes the digest cycle very long and inperformant.
You should try it first with the default select and ng-options (DOCS HERE). If this works better for you, I'd suggest using plain html and then use MaterializeCSS to get the look and feel of Material Design.
Yes, making it all plain old html will speed it up, however then you lose all the eye candy. To have the good parts from both of the worlds you can do some basic optimizations.
Do you really need to watch the collection - are the collections
going to change and if so can't you trigger a digest then? As you did
with the id you can also one-way bind the repeated collection as
well.
ng-repeat="id in ::serviceReferences"
You don't really need
all the options preloaded, right? Since you're using
angular-material, the default drop-down will be exchanged with
multiple elements, to emulate the drop-down behavior. I did just
remove the options list, replaced it with the actually selected
element and populate the list only when the control has gained
focus. See documentation.
Still I agree the angular-material has a poor performance. It simply does not scale well. 1-2 controls work but if you have more then 10 it starts to fail.
PS.: Don't cook the $scope soup!
For a big amount of items in ng-repeat will cause some issues. When angular use ng-repeat to create nested list , a single watcher will be created for each item. Hundreds of watchers will slow down the performance obviously on moible (and IE probably). We used have this issue with ng-repeat, so the best practice is avoid using ng-repeat if you could, create and attach the watcher when you really need to.
So I think the possible solution is, try to use normal for loop instead of ng-repeat.

ng-repeater executing every digest cycle

I am trying to understand how angular 1 digest cycles work and how they impact the existing scope. I have 2 controllers one of them is using angular material with a repeater. The second controller is a simple button click event. Both events print to the console to see what is happening.
What i am seeing is that every time i click the button on the second controller the repeater re-runs its entire calculation?
Is this how angular is intended to work? Please see attached the following codepen - when the button is clicked the repeater re-runs on the first controller every single time. I assume this is going to happen every single time any operations occurs on any controller - which just seems like madness.
the repeater code is as follows:
<div flex="50" ng-repeat="item in items">
<md-checkbox ng-checked="exists(item, selected)" ng-click="toggle(item, selected)">
{{ item }} <span ng-if="exists(item, selected)">selected</span>
</md-checkbox>
</div>
At first i thought there was something wrong in my angular but it seems like this happens anywhere full codepen bellow.
Codepen Example
If you don't want ng-repeat to rerun on change then use "track by $index" in ng-repeat
yes this is exactly how it is supposed to work. That is the nature of two-way binding, you constantly check whether one of both values changed.
If you want to turn off that feature and use a one-time binding you can use the :: syntax.
see in the documentation: https://docs.angularjs.org/guide/expression (you need to scroll down to One-time binding. Sadly there are no anchors :D)

AngularJS Animations ng-switch ng-repeat

I am trying to do what looks like a simple process: to display a list of items received from an HTTP request with animation.
First of all, here is my way of doing it ( I am open to any suggestions to do it in a better angular way ):
I define a scope variable state that I initialize to loading in my controller and that I change to loaded when I receive data from the HTTP request.
I initialize a scope variable items with the received data.
In my view, I use ng-switch for the states, and ng-repeat with the items.
I define an animation with css on ng-repeat.
Here is a plunkr ( with a $timeout instead of the request ).
I cannot understand why the animation does not work.
Any help will be appreciated. Thanks.
The reason it is happening is because your ng-when. The same thing happens with ng-if, but would work fine if you used ng-show.
The problem is that when your ng-when condition returns true, the ng-when first renders it's content in a detatched dom (so animations do not happen). This dom is then attached to the dom tree (this step is animated but you would have to put your animation class on the ng-when).
When using something like ng-show or ng-hide things work as expected because the dom is always attached (it is simply shown/hidden).
This might be considered either a bug or a limitation of ng-animate, you might want to post a github issue and see if the angular guys have any thoughts.
It seems to be a "feature" of angular that it won't add .ng-enter to repeat items inside ng-switch-when block. You can remove ng-switch-when="loaded" and it will work (You don't really need it as ng-repeat won't do anything if there is no items)
<div ng-switch="state">
<div ng-switch-when="loading">
<p>Please wait...</p>
</div>
<div >
<ul ng-repeat="item in items" class="animate-items">
<li>{{item}}</li>
</ul>
</div>
</div>
http://plnkr.co/edit/ocEj7BSQPSeIdnnfAOIE?p=preview

ngRepeat breaks the Foundation Switch CSS. How can I fix it?

I'm trying to use the Switch component from Zurb's Foundation.
It works great until you put it inside an ng-repeat. Then, all the switches except the last one are broken--they don't display the labels until you click them.
Here's a JSBin documenting the issue. Anyone know what's up?
You need ng-model on your radios. It works fine in your JSBin if you include an ng-model on each radio button.
According to the docs, value is also required.
Edit:
Okay look at this version, I finally got it to work with both ng-model and ng-value, which I've learned is preferable to value. What do you think, does that work for you?
Apparently ng-repeat created a child scope for each iteration. Using $parent seems to be a way around that.
Found I had to use "ng-checked" in the following fashion for it to work as expected within an "ng-repeat". As long as the ng-clicked var is unique per switch, should work as expected. Make the toggle function something that toggles the visible value between true and false.
<div class="switch" ng-click="toggle()">
<input type="radio" ng-checked="!visible">
<label>Off</label>
<input type="radio" ng-checked="visible">
<label>On</label>
<span></span>
</div>
Hope that helps someone.

Dynamic data-binding in AngularJS

I'm building an AngularJS app and I have ran into an issue. I have been playing with the framework for a while and I have yet to see documentation for something like this or any examples. I'm not sure which path to go down, Directive, Module, or something that I haven't heard of yet...
Problem:
Basically my app allows the user to add objects, we will say spans for this example, that have certain attribute's that are editable: height and an associated label. Rather than every span have its own dedicated input fields for height and label manipulation I would like to use one set of input fields that are able to control all iterations of our span object.
So my approx. working code is something like this:
<span ng-repeat="widget in chart.object">
<label>{{widget.label}}</label>
<span id="obj-js" class="obj" style="height:{{widget.amt}}px"></span>
</span>
<button ng-click="addObject()" class="add">ADD</button>
<input type="text" class="builder-input" ng-model="chart.object[0]['label']"/>
<input type="range" class="slider" ng-model="chart.object[0]['amt']"/>
The above code will let users add new objects, but the UI is obviously hardcoded to the first object in the array.
Desired Functionality:
When a user clicks on an object it updates the value of the input's ng-model to bind to the object clicked. So if "object_2" is clicked the input's ng-model updates to sync with the object_2's value. If the user clicks on "object_4" it updates the input's ng-model, you get the idea. Smart UI, essentially.
I've thought about writing a directive attribute called "sync" that could push the ng-model status to the bound UI. I've though about completely creating a new tag called <object> and construct these in the controller. And I've thought about using ng-click="someFn()" that updates the input fields. All of these are 'possibilities' that have their own pros and cons, but I thought before I either spin out on something or go down the wrong road I would ask the community.
Has anyone done this before (if so, examples)? If not, what would be the cleanest, AngularJS way to perform this? Cheers.
I don't think you need to use a custom directive specifically for this situation - although that may be helpful in your app once your controls are more involved.
Take as look at this possible solution, with a bit of formatting added:
http://jsfiddle.net/tLfYt/
I think the simplest way to solve this requires:
- Store 'selected' index in scope
- Bind ng-click to each repeated span, and use this to update the index.
From there, you can do exactly as you proposed: update the model on your inputs. This way of declarative thinking is something I love about Angular - your application can flow the way you would logically think about the problem.
In your controller:
$scope.selectedObjectIndex = null;
$scope.selectObject = function($index) {
$scope.selectedObjectIndex = $index;
}
In your ng-repeat:
<span ng-repeat="widget in chart.object" ng-click="selectObject($index)">
Your inputs:
<input type="text" class="builder-input" ng-model="chart.object[selectedObjectIndex]['label']"/>
<input type="range" class="slider" ng-model="chart.object[selectedObjectIndex]['amt']"/>

Resources