WebSQL query causing $digest reached 10 iterations error - angularjs

I'm using Angular and WebSQL in a Cordova project, and I've started a new list controller that will list the results of a table from the WebSQL database.
I have a sqlSvc that queries the database like so:
service.upc = function(newUpc) {
var deferred = $q.defer();
var resolveResults = function (tx, results) {
deferred.resolve(results.rows);
}
var selectUpcs = function() {
var queryString = "SELECT * FROM UPC";
service.db.transaction(function (tx) {
tx.executeSql(queryString, [], resolveResults, rejectWithError);
});
}
deferUntilInit(function () {
if (newUpc) {
insertOrReplaceAndSelect(newUpc); //omitted
} else {
selectUpcs();
}
});
return deferred.promise;
}
All the controller does is this:
var listCtrl = function($scope, sqlSvc) {
sqlSvc.upc().then(function(result) {
$scope.list = result;
});
}
angular.module("RDb").controller("listCtrl", ["$scope", "sqlSvc", listCtrl]);
And it's binding to a simple UI view template:
<div id="scanList">
<ul class="list-unstyled">
<li ng-repeat="scan in list">
<div>{{scan.upc}} ({{scan.datetime}})</div>
</li>
</ul>
</div>
This is giving me the 10 $digest iterations reached error, and it seems to be caused by the way WebSQL is return its results. The error goes away if I deep copy the data, changing resolveResults to:
var data = JSON.parse(JSON.stringify(results.rows));
deferred.resolve(results.rows);
I would like to be able to get this service to work without having to deep copy every results set that it gets. Can anyone help me understand why this is happening?

Related

AngularJS : data binding not working as expected using a service

I'm having trouble with services in AngularJS.
Being a newbie it's probably something crucial I'm missing here.
The title {{p01g.visiteTitel}} isn't magically refreshing but keeps displaying "sometitle".
The ng-repeat is working as expected.
(dataFactory is a service that connects to a remote server using $resource)
My service :
myApp.service('p00Service', ['dataFactory', function(dataFactory) {
var service = this;
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum) {
dataFactory.get({verb: "search", q: datum}, function (data) {
angular.copy(data.visites, service.visites);
service.visiteAantal = service.visites.length;
if (service.visiteAantal === 0) {
service.visiteTitel = "geen visites op " + datum
} else if (service.visiteAantal === 1) {
service.visiteTitel = "1 visite op " + datum
} else {
service.visiteTitel = service.visiteAantal + " visites op " + datum
}
});
};
}]);
My controller :
myApp.controller('p01gCtrl', ['p00Service', function (p00Service) {
var vm = this;
var datum = moment(); //I'm using moment.js -> moment() is date of today
p00Service.findVisites(datum);
vm.visites = p00Service.visites;
vm.visiteTitel = p00Service.visiteTitel;
}]);
My HTML :
<div class="p01g" ng-controller="p01gCtrl as p01g">
<div class="well_grey" style="min-height:40px;max-height:40px;max-width:330px">
<p style="font-size:20px;text-align:center;cursor:pointer;">
{{p01g.visiteTitel}}
</p>
</div>
<div class="well" style="min-height:190px;max-height:190px;max-width:330px">
<table style="width:100%;line-height:40px">
<tbody ng-repeat="visite in p01g.visites">
<tr>
<td style="width:20%;line-height:40px;padding-left:7px"><span style="font-size:16px">{{visite.t133datum | date:"dd/MM/yy"}}</span></td>
<td style="width:60%;line-height:40px;text-align:center"><span style="font-size:16px">{{visite.t133achternaam}}</span></td>
<td style="width:20%;line-height:40px;padding-left:30px"><span style="font-size:16px">{{visite.t133classificatie}}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
the dataFactory looks like this :
myApp.factory("dataFactory", ['$resource', function ($resource) {
return $resource("/vf/rest/visites/:verb", {}, {
get: {method: "GET", isArray: false, cache: false}
});
}]);
I copied your code exactly and created a plunkr and added my own dataFactory to return sample data and there are no errors and the data is getting bound fine, so I believe the issue is in your implementation of dataFactory, or the way you are calling it (make sure it's expecting a callback function, since that is what you are passing it).
Edit: Here is a new plunkr with updated code that I believe reproduces your issue. So the reason why the title is not getting updated is because
vm.visiteTitel = p00Service.visiteTitel;
is setting vm.visiteTitel to the value of the string in p00Service.visiteTitel, but it is NOT getting a reference to p00Service.visiteTitel, so if you update visiteTitel in your p00Service after this assignment (which is happening in this case because the $resource callback is async), then it has no effect on vm.visiteTitel.
One way to make this work would be to pass a callback to p00Service to update your controller values like so:
p00Service.findVisites(datum, function(visites, visiteTitel) {
vm.visites = visites;
vm.visiteTitel = visiteTitel;
});
and then update your service to call this callback after the data is loaded:
service.visites = [];
service.visiteAantal = 0;
service.visiteTitel = "sometitle";
service.findVisites = function (datum, callback) {
dataFactory.get({}, function (data) {
...
if(callback) {
callback(service.visites, service.visiteTitel);
}
});
};
This code can be cleaned up a bit, but here is a workable plunkr demonstrating vm.visites and vm.visiteTitel getting updated correctly.

How to Change Color of background in Angularjs Dynamically

I have a Poller that I have setup that has 2 files which are being queried. When new data has been found I am trying to set the color of my text background in the view but its just not happening.
If someone can solve this issue that would be great I am also welcome to suggestions to improving the structure of the code.
Service:
function Poller($http, $timeout) {
var projectcache = { response: [], calls: 0 };
var msgcache = { response: [], calls: 0 };
var newdata = false;
var msgdata = false;
var msgcolor = {};
var projectcolor = {};
var poller = function () {
$timeout(poller, 10000);
console.log("Begin Poller!");
$http.get('http://localhost/app/controllers/php/getProjects.php')
.then(function(r) {
if (r.data.projects.length > projectcache.response.length) {
newdata = true;
projectcolor = 'green';
} else {
newdata = false;
projectcolor = 'green';
};
angular.copy(r.data.projects, projectcache.response);
console.log("New Data Found: " + newdata);
});
$http.get('http://localhost/app/controllers/php/getMessages.php')
.then(function(m) {
if (m.data.messages.length > msgcache.response.length) {
msgdata = true;
msgcolor = 'green';
} else {
msgdata = false;
msgcolor = 'green';
};
angular.copy(m.data.messages, msgcache.response);
console.log("New Msg Found: " + msgdata);
});
};
poller();
return {
projects: projectcache.response,
messages: msgcache.response,
newdata: newdata,
msgdata: msgdata,
msgcolor: msgcolor,
projectcolor: projectcolor
};
};
View:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Controller:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller',
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller) {
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}]);
My first thought is to use ng-class for this. I see you already have ng-class handling the display of your 'active' class.
If you'd like to try this approach out, I would:
1. Create css clases for each state/color you want to change to. (Can do this in external css file or between tags you create at the beginning of your page.
.successBackground {
background-color:green;
}
.errorBackground {
background-color:red;
}
Modify your ng-class attributes. Here I am assuming that success means that msgdata=true and error means that msgdata=false
Current html:
<li ng-class="{active: selectTab=='inbox'}" style="background-color:{{msgcolor}};" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects'}" style="background-color:{{projectcolor}};" ng-click="selectTab='projects'">Projects</li>
Updated html:
<li ng-class="{active: selectTab=='inbox', successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='inbox'">Inbox</li>
<li ng-class="{active: selectTab=='projects',successBackground:msgdata===true, errorBackground:msgdata===false}" ng-click="selectTab='projects'">Projects</li>
Now when your msgdata is updated, the successBackground and errorBackground are automatically updated based on the latest msgdata value.
Hope this helps!
#Elevant, the comment option didn't allow me to format my code snippets, so I am replying to your latest comment in this answer post.
I'm not sure if the watcher can listen to just the Poller object or if it'll need to listen to each attribute separately (msgColor, projectColor). In my code snippet here, I'll assume we cannot and we'll need to listen to each individually.
Current code:
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
Updated with watchers:
$scope.$watch('Poller.msgcolor', function(newValue,oldValue) {
$scope.msgcolor = Poller.msgcolor;
});
$scope.$watch('Poller.projectcolor', function(newValue,oldValue) {
$scope.projectcolor = Poller.projectcolor;
);
Though if you still wanted to look into the option to move $timeout, I would make the following changes (not sure if this matches what you had tried).
In the Poller service definition remove $timeout. Updated snippet:
function Poller($http)
Still in Poller, remove this line:
$timeout(poller, 10000);
In the Controller add $timeout - updated snippet:
app.controller("taskbarController", ['$scope', 'authData', '$location', 'projectsModal', 'sendMessageModal', 'Poller','$timeout'
function ($scope, authData, $location, projectsModal, sendMessageModal, Poller,$timeout)
Then in the controller, you would add:
$timeout(function(Poller) {
Poller.poller();
$scope.msgcolor = Poller.msgcolor;
$scope.projectcolor = Poller.projectcolor;
}, 10000);
I hope this helps, I haven't had a chance to test the code, so you may have to tinker around with it a bit. Let me know how it goes!

AngularJS Service to Service with a delay

I am currently facing a problem with my project's design.
I am using angularjs framework and my task is to provide a translations for a webpage, but the translations need to be provided form the xml file o the BE side.
So since I#ve found out that angulars i18n is configurable on the FE side i had to use another strategy.
I've decided to make a service which fetches the data during a resolve period before everything else is loaded:
app.factory('dictionaryService', ['$http', '$rootScope', function ($http, $rootScope) {
return {
getDictionary: function (defaultLanguage) {
var chosenLanguage = null;
if (angular.isUndefined($rootScope.defaultLanguage) || $rootScope.defaultLanguage == null) {
chosenLanguage = defaultLanguage;
$rootScope.defaultLanguage = chosenLanguage;
} else {
chosenLanguage = $rootScope.defaultLanguage;
}
var translation = new Array();
translation[chosenLanguage] = new Array();
return $http.get('Translation/GetCurrentDictionary/', {
params: {
language: chosenLanguage
}
});
},
GetLanguagesSetup: function () {
return $http.get('Translation/GetLanguagesSetup/');
}
}
}]);
and then resolve it as follows:
$routeProvider.when("/diagnose", {
controller: "diagnoseCtrl",
templateUrl: "/app/views/diagnose.html",
resolve: {
startupData: function (dictionaryService, $q) {
var def = $q.defer();
var translation = new Array();
var startupData = new Array();
var defaultLanguage = "EN";
var dict = dictionaryService.getDictionary(defaultLanguage).then(function (JSONData) {
var keys = Object.keys(JSONData.data.data);
var chosenLanguage = JSONData.data.lang;
translation[chosenLanguage] = {};
for (i = 0; i < keys.length; i++) {
translation[keys[i]] = JSONData.data.data[keys[i]];
}
startupData['translations'] = translation;
def.resolve(startupData);
}).catch(function (e) {
console.log("Translation fetching exception, " + e);
return $q.reject(e);
});
return def.promise;
}
}
});
So as you can see I am storing my fetched translations in a startupData. Then in a controller which is using it I am assigning this data to the $rootScope. It seems already here as a not the best solution, but I could not come up with a different one
Then I have created a translation service which gets the direct translation text:
app.factory('translationService', ['$rootScope', '$http', function ($rootScope, $http) {
var translations = null;
return {
getText: function (key) {
if ($rootScope.cachedTranslations == undefined) {
return key;
}
var result = $rootScope.cachedTranslations[key];
if (result == null) {
return key;
} else {
return result;
}
}
}
}]);
The biggest problem with this solution is, that I am not using promises, but I do not want to make an http query to BE for each translation.
The other problem is with the html template provided by the designers:
<body ng-controller="mainController">
<loading-screen ng-show="!isDataLoaded"></loading-screen>
<div id="header" class="headerView" ng-controller="headerController" ng-show="isDataLoaded">
some header stuff
...
<button ng-bind="option1" ng-click="redirectTo('#subpage1')"></button>
<button ng-bind="option2" ng-click="redirectTo('#subpage2')"></button>
<button ng-bind="option3" ng-click="redirectTo('#subpage3')"></button>
<button ng-bind="language" ng-if="availableLanguages.length > 1" ng-repeat="language in availableLanguages" ng-click="setLanguage(language)"></button>
</div>
</div>
<
<div id="content" ng-view ng-show="isDataLoaded">
</div>
<footer id="footer" class="footer" ng-show="isDataLoaded">
<status-bar></status-bar>
</footer>
Resolve applies only for ng-views's controller, but header stuff needs to be translated as well, so I need to make a headerCtrl somehow wait before it tries to apply translations.
So I have made another unpopular decision to inform all controllers about the finished startup via a broadcast message and to wait until it is all done while showing the loading screen.
It looks fine and is pretty responsive (1sec per startup is acceptable at this point).
The problem is, that I see many design mistakes with this attempt and I just can not come up with the better design.
So my main question is:
How can I make it better? 1st service returns a whole array which is used by the 2nd service so I do not know how to combine it with promises?
I am afraid that with the development of the application I will find myself in a global variables and global events hell
Thanks in advance for your help!
It seems like you are looking for the angular-translate-loader-static-files extension for angular-translate. See the documentation here.
This together with proper configuration of $translateProvider will allow you to fetch json files with translations from the backend or even swap translations on demand - for example user changes language setting, controller reconfigures $translateProvider. Your job is done - everything will be fetched and updated automatically without a page reload.

$http.get method not working on ng-submit

I want $http.get method to work when a form is submitted.
Here is my code. The object $scope.questions is being set when the method is called but the data doesn't show up in the div. Moreover, when the $http.get method is outside the signIn() function it works just fine.
$scope.signIn = function(data) {
$location.path('/profile');
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
});
};
<div>
User Profile
<br/>Question Posted
<br/>
<input ng-model="query.title" id="value" type="text" placeholder="Search by Title..." ">
<div>
<ul>
<li ng-repeat="question in questions | filter: query ">
{{question.title}}
</li>
</ul>
</div>
<br/>
</div>
You need to move your $location.path('/profile') inside your http request. Remember that a http request is async call. You should redirect after getting the data not before.
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
$location.path('/profile');
});
};
If you're redirecting to another route with a completely separate scope you will lose any scope you're setting in the success handling.
From what I'm reading you're clicking a button to do an action. After that action you're redirecting to another page with a separate controller and trying to persist the data.
Unfortunately, Angular hasn't figured out a great way to do this. The easiest way to persist data through controllers and scope is to create a service that will store it in one controller and grab it in another controller.
For instance:
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$location.path('/profile');
storageService.store("question", questiondata)
});
};
Your new factory to persist data through:
angular.module('moduleName').factory('storageService', [
function () {
return {
store: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function(key) {
return JSON.parse(localStorage.getItem(key));
},
remove: function(key) {
localStorage.removeItem(key);
}
}
}
]);
Other controller to access data:
$scope.question = storageService.get("question");
// remove localstorage after you've grabbed it in the new controller
storageService.remove("question");
An alternative to doing the somewhat 'hacky' way of using localStorage to persist data through controllers is to use ui-router and have a resolve on the route you're redirecting to.
For instance:
$scope.signIn = function(data) {
$state.go('profile');
};
In your route file:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(res) {
return res.data;
});
}]
}
}
In your profile controller:
Inject your 'questions' resolve into your controller and assign `$scope.question = questions;
This will make the HTTP call as soon as you click the route, return the data if successful, then render the page. It will NOT render the page if the resolve does not return success. This will ensure your data will be loaded before you load the page that depends on that data.
I would highly recommend using services to hold your HTTP calls for specific parts of your application. If you have a GET questions, POST question, PUT question. I would create a questionService and make all my HTTP methods there so you don't have to clutter your routes. You would only have to call:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
return questionService.getQuestions(id).then(function(res) {
return res.data;
})
}]
}
}

Data from firebase not loading on route change, but does on refresh

I'm using angularFire with Angular to update some views but the strange thing is when I switch from view to view the data doesn't load, but when I refresh the page it does. What's going on?
WizardController:
/* initialize data */
var ref = new Firebase('https://dlwj.firebaseio.com/');
/* set data to automatically update on change */
$scope.currentLocation = $route.current.$$route.originalPath;
$scope.propertyRef = $firebase(ref);
$scope.propertyRef.$on('loaded', function(value) {
//value will be undefined when I switch views using hash routes.
//there is no issue when I just refresh on that page.
console.log(value);
$scope.propertyConfiguration = value.products;
var products = [];
for (var key in value.products) {
if (value.products.hasOwnProperty(key)) {
products.push(key);
}
}
$scope.productsArray = products;
});
console.log('Data retrieved');
Routes:
$routeProvider.when('/SharedProperties',
{
templateUrl: 'partials/SharedPropertiesPartial.html',
controller: 'WizardController'
});
$routeProvider.when('/Registration',
{
templateUrl: 'partials/Registration.html',
controller: 'WizardController'
});
$routeProvider.when('/Login',
{
templateUrl: 'partials/Login.html',
controller: 'WizardController'
});
There is no reason to download the data using a wrapper lib like $firebase (which takes care of synchronization and such) and then immediately pull that data out and put it into a different scope object.
Just declare your scope var:
$scope.products = $firebase(ref);
And to use it:
<ul>
<li ng-repeat="product in products | orderByPriority">{{product|json}}</li>
</ul>
If you need to iterate the data in a controller or service:
$scope.products = $firebase(ref);
// some time later, probably in $scope.products.$on('loaded')...
// note that $getIndex() is only useful here to get the keys in
// the order they appear in the database, otherwise, forEach($scope.products, ...)
// is sufficient
angular.forEach($scope.products.$getIndex(), function(key) {
console.log(key, $scope.products[key]);
});
If you want to use Firebase as a static database (which is quite baffling to a lover of all things real-time like myself) and not be notified each time there is a change, you can simply do the following:
angular.controller('MyController', function($timeout, $scope) {
new Firebase('<URL>').once('value', function(snap) {
$timeout(function() {
$scope.products = snap.val();
});
});
});
And then utilize it normally:
<ul>
<li ng-repeat="(key,product) in products">{{key}}: {{product|json}}</li>
</ul>

Resources