ngModel and resetting value - angularjs

So have two directives that need to share data. Both are under the same controller, so set up the variable $scope.selection to store the selection, it gets a default value in the controller.
knowledge.controller('industryController', function($scope, mwFactory){
$scope.menudata={sections: [
{group: 'FMP', name: 'Finance'},
{group: 'FinTech', name: 'Financial Technology'},
]
}
if ($scope.selection) {
console.log("This is " + $scope.selection)
} else {
$scope.selection = 'Main_Page'
}
})
I then send that to a menu where you can make selection
<nav-circle group="section.group" ng-model="selection"></nav-circle>
I then set it as follows inside the directive
function nodeclick(d){
//console.log("Name is " + d.url);
console.log("Old model is " + ngModel.$modelValue)
ngModel.$modelValue = d.url;
ngModel.$viewValue = d.url;
console.log("New model is " + ngModel.$modelValue)
}
However, it does not seem like it is updating, or potentially even weirder, that something is resetting ngModel.
Got a call further up,
ngModel.$render = function () {
console.log("ngRender got called " + ngModel.$modelValue);
};
And this kicks off every minute or so, and always returns the value to the original value. What am I missing

OK, this may not be the best way, but it works, and sometimes that is enough:)
Instead of sending a variable I sent an object, and I think (someone who knows angular and javascript better may be able to explain/confirm) this means that the directive will be writing to a memory address rather than to a variable. Which also means that the ng-model will not change (the memory address is the same, the data stored at that memory address is changed).
Might be wrong, but the solution works as if that was the case.
So, define the scope variable:
$scope.selection = {};
$scope.selection.default = 'Main_Page'
$scope.selection.current = '';
Then send the following to the directives:
<nav-circle group="section.group" ng-model="selection.current"></nav-circle>
And to change the scope variable in the directive you just set viewValue
ngModel.$setViewValue(d.url);
As I mentioned, I am not certain if this is the right way, or if my understanding is correct, but it works:)

Related

Angular filtered result not updating in the UI

I have this filter for moment:
function momentFilter() {
return function(input, format) {
if (!input) return null;
if (format == 'fromNow') {
var fn = moment(input).fromNow();
console.log('fromNow called with: ' + input + ' giving result: ' + fn);
return fn;
}
};
}
And I have this html:
<div>{{timestamp | moment: 'fromNow'}}</div>
I see the filter getting called on the digest cycle, and the relative time that gets logged in the console looks great. I.E. it goes from 1 minute ago, 2 minutes ago, etc... However the UI never updates to what the filter returns.
The first call is reflected on the UI, but after that the UI never updates.
What am I doing wrong?
EDIT:
That html is inside an ng-repeat that tracks by id. I am assuming that because nothing in the object actually changed (current time just moved), that angular never detects anything for that object.
Still not sure how to work around this.
did you try by changing object hash? normally if you change object hash code framework will understand object is modified and rendered updated object.
Filters are assumed to be idempotent, that is the same input always leads to the same output. Since timestamp doesn't change there's no reason for Angular to do anything.
If your filter is stateful and the output can change with every call then you have to mark it accordingly.
function momentFilter() {
function filter(input, format) {
if (!input) return null;
if (format == 'fromNow') {
var fn = moment(input).fromNow();
console.log('fromNow called with: ' + input + ' giving result: ' + fn);
return fn;
}
};
filter.$stateful = true;
return filter;
}

AngularJS : Why the data is not displayed in view may I use $scope.apply?

I am getting data from service and display on view using ng-repeat .Actually I am getting event when user scroll to bottom mean when user reached to bottom I will do something.When It reached to bottom I am changing the contend of my array .I am getting the correct contend in ng-repeat array (display array) but it is not reflect on view why ? May I use $scope.apply() or $scope.digest()
Here is my code
http://plnkr.co/edit/XgOxJnPXZk4DneJonlKV?p=preview
Here I am changing the contend of my display array which is not reflect on view
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if(scope.var<scope.arrays.length)
scope.display=[];
var nextvar =++counter;
var increment=counter+1
console.log("nextvar:"+nextvar+"increment:"+increment)
scope.display=scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
}
As #Claies mentioned you should use apply(). Though the digest() would probably have worked as well.apply() calls digest() internally. He also mentioned that your variable that seems to be storing the page number gets reset to 0 each time you scroll. You should store that in a scope variable outside that handler.
I tried to fix with minimum change
http://plnkr.co/edit/izV3Dd7raviCt4j7C8wu?p=preview
.directive("scrollable", function() {
return function(scope, element, attrs) {
var container = angular.element(element);
container.bind("scroll", function(evt) {
console.log('scroll called'+container[0].scrollTop);
var counter = scope.page;
if (container[0].scrollTop <= 0) {
if (scope.var > 0)
scope.display = scope.arrays[--scope.var].concat(scope.arrays[scope.var+1]);
}
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if (scope.var < scope.arrays.length)
scope.display = [];
var nextvar = ++counter;
var increment = counter + 1
console.log("nextvar:" + nextvar + "increment:" + increment)
scope.display = scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
scope.page = counter;
}
scope.$apply();
});
};
})
generally I would have implemented this differently. For example by having a spinning wheel on the bottom of the list that when displayed you get the rest of data.
It is difficult to give you a full working plunker. Probably you should have multiple JSON files in the plunker, each containing the data for one page so that we can add the data to the bottom of the display list.
After you modify display array you just have to call scope.$apply() so that it runs the $digest cycle and updates the view. Also you need the initialize scope.var either in your controller or the directive and modify it conditionally.
I dont if this is what you want. I have modified the plunker take a look.
http://plnkr.co/edit/J89VDMQGIXvFnK86JUxx?p=preview

angular: strange behavior with ng-options

I stumbled across a strange problem with ng-options. Consider a simple select:
<select ng-model="x" ng-options="n for n in [1,2,3]"></select>
where the model value gets assigned in the controller:
$scope.x = 3;
This works fine; the select element shows 3. However, if the assignment is delayed,
$timeout(function() { $scope.x = 3 }, 50);
The select shows up blank. But only if the model value is the last option in the list! In other words, this works fine:
$timeout(function() { $scope.x = 2 }, 50);
Then the select element shows 2. Same deal with 1. But 3 shows up blank.
What the?
Here's a plunk that demonstrates.
It's a problem that shows up with angular 1.2.26. Earlier versions are okay (you can see this for yourself by changing the angular version used by the plunk).
But my project has all kinds of dependencies and bower keeps pushing my angular to 1.2.29. Anybody got any idea how I might work around this issue?
I had the same problem and eventually solved it by telling css not to display these options:
option[value^=\?]{
display: none;
}
This has the effect of telling CSS not to display any option with a value attribute value starting with "?" (Note that the question mark must be escaped.)
This appears to be a bug in version 1.2.26 / 1.2.28, but works for 1.3.*.
I've submitted this as an issue to Angular:
https://github.com/angular/angular.js/issues/10718
To work around this issue, initialize the scope variables in your ngController (outside of $timeout):
function ctrl($scope, $timeout) {
$scope.p_immediate = 3;
$scope.s_immediate = "third";
$scope.p_delayed = 0;
$scope.s_delayed = "first";
$timeout(function() {
$scope.p_delayed = 3;
$scope.p_not_last = 2;
$scope.s_delayed = "third";
$scope.s_not_last = "second";
}, 50);
}
Demo

rootScope is upating on scope variable update

I have created a rootScope variable like
$rootScope.globalData = data;
$rootScope.globalData.chillerConditions.HeatSource.Value = "ST"; //Default Value
$scope.chillerConditions.HeatSource.Value = 1; //Default Value
where data is my returning value from api. Also create a scope variable which is a object contains a list of items.
$scope.chillerAttributes = data.ObjCandidateListChillerAttributes;
$scope.chillerConditions = data.ObjCandidateListConditions;
On HTML I have:
<select ng-model="chillerConditions.HeatSource.Value" style="width:53%;" ng-options="item.Id as item.Description for item in ValidRatingHeatSource" ng-change="heatSourceChanged()" id="ddRatingHeatSource" class="form-control search-select designComboboxHeight" data-container="body"></select>
Here ValidRatingHeatSource is
$scope.ValidRatingHeatSource = \*list of items*\
On change of Drop Down I have written an function. In that
if($scope.chillerConditions.HeatSource.Value == 2)
{
$rootScope.globalData.chillerConditions.HeatSource.Value = "HW";
}
else
{
$rootScope.globalData.chillerConditions.HeatSource.Value = "ST";
}
Till now was the my current code.
Issue is :
When the above function is called then whenever current $rootScope varible i.e. $rootScope.globalData.chillerConditions.HeatSource.Value is changed to "HW" or "ST" it also changing $scope.chillerConditions.HeatSource.Value to "HW" or "ST".
Why so?
Is there any inbuilt functionality in angularjs?
Please suggest if I am making any mistake? New suggestion are also welcome.
This behavior is the way JavaScript works and has nothing to do with AngularJS. JavaScript is an object-oriented (prototype-based) language where objects are addressed by reference and not by value. E.g. assign car2 to car1 and both of them will reference the same object (JSFiddle)
var car1 = {make: "Audi"}
var car2 = car1;
car2.make = "Toyota";
So in your case, $rootScope.globalData.chillerConditions.HeatSource and $scope.chillerConditions.HeatSource are the same object.
Rather, it seems like you want to create a copy. You can do so with angular.Copy
$scope.chillerAttributes = angular.copy(data.ObjCandidateListChillerAttributes);
$scope.chillerConditions = angular.copy(data.ObjCandidateListConditions);
In your example u have both ng-model and ng-change, so:
1. User change value in select.
2. $scope.chillerConditions.HeatSource.Value changes (ng-model)
3. heatSourceChanged starts (ng-change) -> $rootScope.globalData.chillerConditions.HeatSource.Value changes
So everything works as should...

Is there any way to optimize / speed up the sending of data to a UI with Protractor?

I have code similar to this:
ExamPage.prototype.enterDetailsInputData = function (modifier) {
page.sendKeys(this.modalExamName, 'Test Exam ' + modifier);
page.sendKeys(this.modalExamVersionId, 'Test exam version ' + modifier);
page.sendKeys(this.modalExamProductVersionId, 'Test exam product version ' + modifier);
page.sendKeys(this.modalExamAudienceId, 'Test exam audience ' + modifier);
page.sendKeys(this.modalExamPublishedId, '2014-06-1' + modifier);
page.sendKeys(this.modalExamPriceId, '100' + modifier);
page.sendKeys(this.modalExamDurationId, '6' + modifier);
};
Here's the page.sendKeys function. Note that currently this is not doing any return of promises or anything like that. If the function is not coded well then I welcome comments:
// page.sendkeys function
sendKeys(id: string, text: string) {
element(by.id(id)).sendKeys(text);
}
I watch as it slowly fills out each field on my screen and then repeats it again and again in more tests that follow.
Is there any way that this could be optimized or do I have to wait for one field after the other to fill and have to live with tests that take a long time to run?
I assume sendKeys is promise based. Could I for example use AngularJS $q to issue all the sendKeys at the same time and then use $q to wait for them to complete?
Potential Solution I think at least a little hackery is required no matter how you optimize it - protractor doesn't give you this out of the box. However would a small helper function like this suit your needs? What else do you need to speed up sides text inputs with ng-models?
function setNgModelToString(element, value) {
return element.getAttribute('ng-model').then(function (ngModel) {
element.evaluate('$eval("' + ngModel + ' = \'' + value + '\'") && $digest()');
});
}
Solution Example:
describe('angularjs homepage', function() {
it('should have a title', function() {
browser.get('http://juliemr.github.io/protractor-demo/');
var inputString1 = '';
var inputString2 = '';
for (var i = 0; i < 1000; i++) {
inputString1 += '1';
inputString2 += '2';
}
/* Uncomment this to see it runs much much slower when you enter each key. */
//element(by.model('second')).sendKeys(inputString1);
setNgModelToString(element(by.model('second')), inputString2);
expect(element(by.model('second')).getAttribute('value')).toEqual(inputString2);
});
});
Why does the solution work?
You need to use $eval to wrap the assignment and not just assignment as evaluate does not evaluate side effects (a nested evaluation, though... heh). Assuming that's truthy in angular expressions then $digest() is called from the &&; this causes a digest to happen, which you need to update everything since you set a value from outside the digest cycle.
Thoughts about the solution:
The whole idea behind an E2E test is to "emulate" an end-user using your app. This arguably doesn't do that as well as sending the keys one-by-one, or copy-and-paste (since pasting into elements is a valid way of entering input; it's just hard to set up due to flash, etc., see below).
Other Potential Solutions:
Copy and Paste: Create an element, enter text, copy it, paste the text sending Ctrl + V to the target element. This may require doing a bunch of fancy footwork, like using Flash (exposing the system clipboard is a security risk) and having "copy it" click an invisible flash player. See executeScript to evaluate functions on the target so that you have access to variables like window if you need that.
Parallelizing your tests. Read the official doc here and search for "shard" and then "multiple". If you're mainly worried about the duration of your entire test collection and not individual tests, scaling out your browser count is probably the way to go. However there's a good chance you are TDD-ing or something, hence needing each test to run faster.
You can do following -
ExamPage.prototype.enterDetailsInputData = function (modifier) {
var arr=[
{id:this.modalExamName, text:'Test Exam ' + modifier},
{id:this.modalExamVersionId, text:'Test exam version ' + modifier },
{id:this.modalExamProductVersionId, text:'Test exam product version ' + modifier},
{id:this.modalExamAudienceId,text:'Test exam audience ' + modifier},
{id:this.modalExamPublishedId, text:'2014-06-1' + modifier},
{id:this.modalExamPriceId, text:'100' + modifier},
{this.modalExamDurationId, text:'6' + modifier}
];
var Q = require('q'),
promises = [];
for (var i = 0; i < arr.length; i++) {
promises.push(page.sendKeys(arr[i].id, arr[i].text));
}
Q.all(promises).done(function () {
//do here whatever you want
});
};
sendKeys returns promise by default. See here -https://github.com/angular/protractor/blob/master/docs/api.md#api-webdriver-webelement-prototype-sendkeys
sendKeys(id: string, text: string) {
return element(by.id(id)).sendKeys(text);
}
If you really want to speed up the process of manipulating DOM in any way (including filling up data forms) one option to consider is to use: browser.executeScript or browser.executeAsyncScript. In such a case the webdriver let the browser execute the script on its own -- the only overhead is to send the script body to the browser, so I do not think there may be anything faster.
From what I see, you identify DOM elements by ids so it should smoothly work with the approach I propose.
Here is a scaffold of it -- tested it and it works fine:
browser.get('someUrlToBeTested');
browser.waitForAngular();
browser.executeScript(function(param1, param2, param3){
// form doc: arguments may be a boolean, number, string, or webdriver.WebElement
// here all are strings: param1 = "someClass", param2 = "someId", param3 = "someModifier"
var el1 = document.getElementsByClassName(param1)[0];
var el2 = document.getElementById(param2);
el1.setAttribute('value', 'yoohoo ' + param3);
el2.setAttribute('value', 'hooyoo ' + param3);
// depending on your context it will probably
// be needed to manually digest the changes
window.angular.element(el1).scope().$apply();
},'someClass', 'someId', 'someModifier')
small remark: If you pass webdriver.WebElement as one of your argument it will be cast down to the corresponding DOM element.
I also used browser.executeScript. But I didn't need to use $apply waitForAngular
I add a script when E2E tests are running, and put a function on global scope, don't worry it's only during E2E tests, and you can namespace if you want to.
'use strict';
function fastSendKeys(testId, value) {
//test id is just an attribute we add in our elements
//you can use whatever selector you want, test-id is just easy and repeatable
var element = jQuery('[test-id=' + '"' + testId + '"' + ']');
element.val(value);
element.trigger('input');
}
Then in your protractor test something like this (in a page object):
this.enterText = function (testId, value) {
var call = 'fastSendKeys(' + '"' + testId + '"' + ',' + '"' + value + '"' + ')';
console.log('call is ', call);
browser.executeScript(call);
};
The following worked for me when testing an angular 2 application
await browser.executeScript("arguments[0].value='" + inputText + "';", await element.by.css("#cssID"))
inspired by https://stackoverflow.com/a/43404924/6018757

Resources