AngularJS Data One Step Behind - javascript

I'm tracking map coordinates using angularJS to update the data, however I've run into an odd issue where the data you see on the screen does not match the console statement.
zombie.controller("move", function($scope) {
io.on("location", function(data) {
console.log(data);
$scope.location = data.loc;
})
$scope.move = function(direction) {
$scope.title = ": Traveling";
io.emit("move", {direction:direction});
}
});
The console will log something like: Object {loc: "(59,30)"}
Let's assume the previous data was Object {loc: "(60,31)"}. My page will print (60,31) when the console is logging (59,30).
Also, when the page loads, the initial click will not display anything, but the console will log the correct data.
I have tried moving io.on('location') around inside the angular function, but if it's inside move() it will go bonkers and log like 15 times in a row and lag out. Outside the function is fine except for this issue. Any thoughts?

The code inside the io.on("location") is initiated by socket.io and Angular doesn't know about it, so its changes to the scope are not reflected until the next digest cycle. That is likely why the screen updates are always one step behind. Use $scope.$apply() to force a digest...
io.on("location", function(data) {
console.log(data);
$scope.location = data.loc;
$scope.$apply();
})

I agree with above answer but still in some cases like on input of text box,
you need to give timer to digest it properly.
setTimeout(function(){
$scope.apply();
}, 50);
This solves my problem.

Related

Protractor shows variable value as "undefined"

We are defining variables from the elements on one page of the website, clicking on the edit button, which is opening the next page. In this page we need to assert that the data captured on the earlier page matches the data shown on the 2nd page. Our problem is, once the test moves to the 2nd page, it fails to recall the variables that we defined on the 1st page. below is our code snippets:
it ('Student ID Validation', function(){
// get rows
var rows = tableData_Dashboard.all(by.tagName("tr"));
// get cell values
var cells = rows.all(by.tagName("td"));
var Student_ID = cells.get(0).getText().then(function(SID){
console.log(SID);
});
Edit_Button_1.click();
browser.sleep(2000);
expect(Student_ID_on_Reg_Page.getAttribute('value')).toEqual(Student_ID);
after execution, we get the following error
Message:
Expected '123456' to equal undefined.
We were suspecting that it may be due to asynchronization, but that is not the case. the test moves to page 2 after it stores the variable from page 1, so we are at a loss why this is happening. How can we fix this and use the variables for assertion purpose?
The problem is that you've specified the then() callback where you just log the value but don't return it:
var Student_ID = cells.get(0).getText().then(function(SID){
console.log(SID);
});
As nothing is returned, Student_ID would become a promise which would resolve into undefined.
You either need a return:
var Student_ID = cells.get(0).getText().then(function(SID){
console.log(SID);
return SID;
});
Or, remove the custom callback completely:
var Student_ID = cells.get(0).getText();
actually, the following part is causing the problem. Once we removed this part, the test is working fine.
.then(function(SID){
console.log(SID);
});

Angularjs click row if the data is available

I'm new to JavaScript so I am not sure what is possible. I am using AngularJS as my frontend application.
I've a clickable table(rows) its pretty much a table inside a table (collapisble table)
I'm trying to click the first row of the table if the data is available so I wrote this function
$scope.clicker = function(){
if (!$scope.first || !$scope.second){
setTimeout($scope.clicker, 500)
}
$(".clickableRow").first().click()
}
This pretty much checks if the values first and second is not null, if not then click the first row. This WOULD work sometimes but almost every time I get this error '
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.3/$rootScope/inprog?p0=%24digest
I am not sure what this means. Any help would be nice.
Using $q.all you can pass in multiple $http requests in an array.
You can use .then on the q.all() method and get a callback when they're all done.
$scope.$watch(
function() {
return $route.current;
},
function(newValue) {
if (newValue) {
$scope.url = newValue.$$route.originalPath;
if($scope.url == '/loadTestForm') return;
$scope.neustaridParam = newValue.params.neustarid;
$q.all([
$http.get('/corecase/browserKPI/'+$scope.neustaridParam).success(function(response){
$scope.browserKPI = response;
}),
$http.get('/corecase/serverKPI/'+$scope.neustaridParam).success(function(response){
$scope.serverKPI = response;
}),
$http.get('/corecase/info/'+$scope.neustaridParam).success(function(response){
$scope.corecaseinfo = response;
})
]).then(function(){
$scope.selectTableRow(0, 1000); //I'd advise not hardcoding the first row's data here. You can do something like this instead: $scope.storeDataModel.storedata[0].id, scope.storeDataModel.storedata[0].storeId
});
}
}
);
If the only purpose behind the click event is to show or hide then you can potentially skip the click event all together. Put a "ng-show" in your tr (or any other element you want to show) and simply set a scoped variable equal to whatever is referencing your data. If you have data then your ng-show will be true and it will show. This is also assuming that your data is undefined or null until it is available, if not just set it to false.
In view:
<tr ng-show="checkData"></tr>
In controller:
$scope.checkData = data;

Ionic Angular - Resetting scope variables

I have a flow of few pages/views starting from first page to last page. This is pretty much based on the Ionic tutorial.
I update a factory "Info" with some data as I proceed with the flow. In the final page, after displaying the "summary info" to the user, I use $state.go('firstPage') to navigate back to the first page.
When I make a different selection in the first page this time, it doesn't seem to take effect in the view.
I tried the suggestion from here but that didn't help me. I tried resetting the variables again, but that doesn't help either.
angular.module('my.services', [])
.factory("Info", function(){
// All user data
var infoData = {
type: "",
level: 0
};
var originalInfoData = angular.copy(infoData);
// Reset data
infoData.resetUserDetails = function() {
userData = angular.copy(originalInfoData);
};
Final Page Controller
$scope.finish = function() {
UseInfo.resetUserDetails();
$state.go('firstPage');
}
This takes me back to first page but even though I select something different this time, the pages seem to remember what I did in my first run.
The question is - how do I clear things up so the user can do something else after getting back to the first page without remembering previous selections.

How to get notified when grid changed in AngularJS ui-grid?

If there is a way in ui-grid that I can know a grid is finish updating the rows?(like a filter is being applied etc)? I want to run some function after the grid view changes.
I tried the following method:
$scope.filteredRows = $scope.gridApi.core.getVisibleRows($scope.gridApi.grid);
$scope.$watch('filteredRows', function(){console.log('view updated');});
The above approach works when the grid just finish initiating, after that, it won't work anymore.
I also tried using the filterChanged api:
$scope.gridApi.core.on.filterChanged($scope, function() {
console.log('filter changed');
foo();
});
The problem with this method is that although I can get notified when the filter is changed, but if the grid is very large, it needs some time to finish updating the view, and before that, the function foo() is being called before the grid update is finished.
Any idea will be appreciated.
I've seen use of $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue ); in ui-grid-footer-cell.js. I'm not sure exactly when rowsRendered fires, but given it's being used to calculate aggregations and aggregations require knowledge whenever the rows are changed, and must run after the rowsProcessors finish running, there's a good chance that it's what you want.
EDIT: the framework to use it would be:
Define a function that you want to call when the visible rows have changed
var myFunction = function() {
do some stuff
};
Set this function to be called whenever rows are rendered
$scope.gridApi.core.on.rowsRendered( $scope, myFunction );
Well, I found a workaround, in order to call the function after the grid is updated, which takes some time, I added a delay in filterChanged event:
$scope.gridApi.core.on.filterChanged($scope, function() {
console.log('filter changed');
$timeout(foo(),800);
});
To use the $timeout, you need to add that to your controller first.

How to refresh an angular tag

I was brought in to fix a website that was on fire a couple months back. I've got most things under control and I'm down to fixing various wish-list items. One of them involved some angular code that I just can't seem to get to do what I want. On some pages there are videos followed by a short quiz. I need to update the user's scores after each event. So far, this proved to be easy enough for the total score which looked like this:
<a id="updateafterscore" href="~/user/leaderboard/" class="fill-div">
{{ profile.currentScore }}
</a>
And that got updated with this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
So far, so good. However other elements on the page have, thus far, proved impossible to update. Here's what's on the page:
I added the "id="refreshvideo" myself so I could try to alter the tag. Finally, here's the angular module for simple-circle (I've left out the actual drawing code since it's not really relevant):
angular.module('thrive.shared').directive('simpleCircle', function() {
return{
replace: true,
template: '<canvas width="60" height="60" style="margin: -10px 0 0 -15px;"></canvas>',
restrict: 'E',
scope: {
value: '#',
color: '#',
bgColor: '#',
forecolor: '#',
radius: '#'
},
link: function (scope, elem, attrs) {
var multiplyLength = 1;
var canvasElem = elem[0];
var inMotion = false;
if (scope.value <= 2) {
multiplyLength = 5;
}
scope.$watch('value', function() {
drawCircle(canvasElem, scope.color, scope.value * multiplyLength, scope.value, scope.name);
});
function drawCircle(canvas, color, calculatedPoints, displayPoints, name) {
So, to the question: how the heck do I update the number that's displayed? I tried various things:
document.getElementById('refreshvideo').setAttribute('value', data.Data.VideoWatchedCount);
document.getElementById('refreshvideo').setAttribute('data-value', data.Data.VideoWatchedCount);
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
None of these things worked. I inspected the canvas element in the source in the browser and I could see the value and data-value tags change to whatever I set them, but the image remained unchanged. Am I setting the wrong thing? (Perhaps whatever $watch is watching) Do I have to force some kind of re-paint of a canvas element?
#charlietfl means your solution is not actually using AngularJS - you're completely bypassing it. Angular provides two-way data binding between Javascript data and the HTML DOM. All you do is tell it where to draw data, and it will do that for you automatically, keeping it up to date from then on as the data changes.
In Angular, you never call getElementById and certain never set innerHTML because then you block Angular from doing its thing - in many cases you actually break it. Every one of those instances introduces a new bug while "patching" another.
Go back to your example template line:
<a ..attributes...>{{ profile.currentScore }}</a>
When it sees this, Angular will create what it calls a "watcher" on profile.currentScore. If its value right now is '1', it will render this as <a ...>1</a>.
Every digest cycle, that watcher will tell it to look at profile.currentScore to see if it changed. This line of code is pretty typical in JS:
profile.currentScore = 42;
Angular will "see" this happen through that watcher, and will automatically update the rendered template. You do nothing else - and if you ever feel that you need to, it almost always means something else is wrong.
If you're running into this a lot, try the "standard quick-fix". We see this a lot with people who didn't architect an application properly, and they're doing data model updates outside Angular's digest cycle where it can't "see" them. Try wrapping your update code in an $apply() call:
$scope.$apply(function() {
profile.currentScore = 42;
});
If you have a LOT of updates to make and you don't want to nest the call, you can also cheat, like this:
// Lots of stuff...
profile.currentScore = 42;
// Lots more stuff...
$scope.$apply();
You will know right away if you need to do this. If it works, you need to do it. :) If you get an error message in your console saying you're already in a digest cycle, you do NOT need to do it (it's something else).
I mentioned that I thought perhaps I was modifying the wrong profile variable and so it wasn't refreshing. So I looked back a little bit in the code that is supplying the numbers:
angular.module('episodes').controller('episodeCtrl', ['$scope', '$rootScope', '$window', 'episode', 'relatedCourses', 'Video', 'episodeItems', 'profile', 'Profile',
function ($scope, $rootScope, $window, episode, relatedCourses, Video, episodeItems, profile, Profile) {
// stuff skipped....
onComplete: function () {
Video.complete({ videoId: item.item.id }).$promise.then(function () {
item.progress = "Completed";
$scope.loadNextItem();
$scope.profile = Profile.get(); // <<-- gotten from somewhere
$.ajaxSetup({ cache: false });
$.get('/user/getCurrentUserPointsModel', function (data) {
if (data == "")
return;
$scope.profile.currentScore = data.Data.CurrentScore;
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
$scope.profile.testTakenAndCorrectAnswerCount = data.Data.TestTakenAndCorrectAnswerCount;
Profile.save(); // <-- added
The value in $scope.profile is pulled from Profile, but I don't fully get how that gets where it is. I suppose I will need to figure that out because there's another place where these updates have to happen that lack that Profile information. Anyways I added the last 4 lines in place of this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
... and all worked according to plan. I guess I tackle the other part later when I figure out how the data gets to the controller.
You can't do this that way. It's not Angular way of dealing with data.
Read the documentation before https://docs.angularjs.org/tutorial/step_04
If you need to modify your DOM using document.. probably sth wrong is with your code.
BTW. Stop using globals like:
document.getElementById('updateafterscore')

Categories