Angular - view not updating after model changed after http request - angularjs

I am writing an app with Angular 1.5, Ionic 1.3, and Cordova. Right now I am working on a part where the user will push a button, the app will store the time and geolocation in the localStorage and then post the data back to the server and update the view. The issue I am having is that in one situation the view is not updating after a model change.
I have tried a few things: $timeout, $rootScope, $q and nothing seems to fix the issue. It only happens on iOS and not on Android. I am also using this library to help with the geolocation process: https://github.com/dpa99c/cordova-diagnostic-plugin
I am aware of the 3rd party library issue where it may not be a part of the Angular digest cycle but I have wrapped it with $q and no luck: https://www.searchenginepeople.com/blog/top-5-technical-issues-large-sites-angularjs.html
I am posting the pseudo code here.
view.html
<ion-view cache-view="false">
<ion-content>
<span>{{ data.text }}</span>
<span>{{ data.date }}</span>
<span>{{ data.isSync }}</span>
<button ng-click="showPopup()">Click</button>
</ion-content>
</ion-view>
controller.js
(function () {
'use strict';
angular
.module('myApp')
.controller('ViewController', ViewController);
ViewController.$inject = ['$scope', 'GeoDataModelService'];
function ViewController($scope, GeoDataModelService) {
$scope.data = GeoDataModelService.value;
$scope.showPopup = GeoDataModelService.showPopup();
}
})();
service.js
(function () {
'use strict';
angular
.module('myApp')
.factory('GeoDataModelService', GeoDataModelService);
GeoDataModelService.$inject = [
...
];
function GeoDataModelService(
...
) {
//data model
var dataModel = {
isSync: true,
text: null,
date: null
};
return {
value: dataModel,
showPopup: showPopup
};
function showPopup() { //gets called
$ionicPopup.confirm({
title: 'clock now ?',
buttons: [{
text: 'CONTINUE',
type: 'button-positive',
onTap: function () {
geoData();
}
}, {
text: 'CANCEL',
type: 'button-default'
}]
});
};
function geoData() {
getLocationServicesStatus()
.then(geoServiceSuccessful)
.catch(function(err) {});
};
function geoServiceSuccessful() { //gets called
DataModelService.createRecord();
sendDataToServerAfterGeoData();
}
function getLocationServicesStatus() {
console.log(' getLocationServicesStatus');
var deferred = $q.defer();
//this is outside of angular
cordova.plugins.diagnostic.isLocationAvailable(
function (available) {
if (available) {
deferred.resolve(true);
} else {
deferred.reject(false);
}
}, function (error) {
deferred.reject(false);
}
);
return deferred.promise;
}
function updateDataModel(source) {
console.log('source ', source); //this one is not null
if (source != null) {
dataModel.text = source.text;
dataModel.date = source.date;
console.log(JSON.stringify(dataModel)); //correct
}
}
function sendDataToServerAfterGeoData() {
//if offline just skip the server post
if (!navigator.onLine) {
// trigger digest cycle
$timeout(function () {
updateModelAfterRecord(); //this one works fine
}, 0);
return;
}
var clockins = DataModelService.load(); //load from local storage
console.log(' * * * * * HERE WE GO * * * * * ');
//this service returns an http promise
DataModelService
.sendLocalDataToService(clockins)
.then(sendDataToServerAfterGeoDataSuccess)
.then(getClockDataToServerAfterGeoSuccess)
.catch(handleSendDeviceDataToServerFail);
};
function sendDataToServerAfterGeoDataSuccess() {
console.log(' sendDataToServerAfterGeoDataSuccess ');
//this service returns an http promise
return DataModelService.getDataModelFromServer();
}
function getClockDataToServerAfterGeoSuccess(response) {
console.log(' getClockDataToServerAfterGeoSuccess ', response);
console.log('1 dataModel: ', dataModel);
// $timeout not working here
// $rootScope.asyncEval not working either
// $rootScope.$apply threw an error
console.log(' 2 dataModel: ', dataModel); //correct
dataModel.isSync = true;
updateDataModel(response); //goes through this code
console.log('3 dataModel: ', dataModel); //correct
console.log(' 4 dataModel: ', dataModel); //correct
console.log('5 dataModel: ', dataModel); //correct
return response; //tried to leave this out - no effect
}
function handleSendDeviceDataToServerFail(error) {
console.log('handleSendDeviceDataToServerFail ', error);
var clockins = DataModelService.load();
dataModel.isSync = false;
updateDataModel(clockins); //this works
}
function updateModelAfterRecord() {
dataModel.isSync = false;
var data = DataModelService.load();
updateDataModel(data);
}
}
})();
I added a watcher to see if the data is changing:
$scope.$watch('data.text', function(newVal, oldVal) {
console.log(' new val', newVal); //this is correct
});

Related

Creating new function in angularjs controller won't work

When creating new function in angularjs controller and assign it to button with ng-click and function is simple,
function toLogin() {
console.log("Entered function");
$state.go('login');
}
I tried with $scope and still does not work.
this is my controller:
'use strict';
angular.module('crudApp').controller('UserController',
['UserService', '$scope','$state', function( UserService, $scope, $state) {
var self = this;
self.user = {};
self.users=[];
self.user.enabled = false;
self.user.confirmationToken = '';
self.loggedUser = null;
self.submit = submit;
self.getAllUsers = getAllUsers;
self.createUser = createUser;
self.updateUser = updateUser;
self.removeUser = removeUser;
self.editUser = editUser;
self.reset = reset;
self.firstNamePattern=/^[A-Z][a-z]*\S$/;
self.lastNamePattern=/^[A-Z][a-z]*\S$/;
self.userNamePattern= /^\S*$/;
self.passwordPattern = /^\S*$/;
self.phonePattern = /^[0-9]+\S$/;
self.successMessage = '';
self.errorMessage = '';
self.done = false;
self.onlyIntegers = /^\d+$/;
self.onlyNumbers = /^\d+([,.]\d+)?$/;
function toLogin() {
console.log('Entered function');
// $state.go('login');
}
function submit() {
console.log('Submitting');
if (self.user.id === undefined || self.user.id === null) {
console.log('Saving New User', self.user);
createUser(self.user);
} else {
updateUser(self.user, self.user.id);
console.log('User updated with id ', self.user.id);
}
}
$scope.moje = function () {
console.log('Submitting');
if (self.user.id === undefined || self.user.id === null) {
console.log('Saving New User', self.user);
createUser(self.user);
} else {
updateUser(self.user, self.user.id);
console.log('User updated with id ', self.user.id);
}
}
function createUser(user) {
console.log('About to create user');
UserService.createUser(user)
.then(
function (response) {
console.log('User created successfully');
self.successMessage = 'User created successfully';
self.errorMessage='';
self.done = true;
self.user={};
$scope.registerForm.$setPristine();
$scope.registerForm.$setUntouched();
// $state.reload();
},
function (errResponse) {
console.error('Error while creating User');
self.errorMessage = 'Error while creating User: ' + errResponse.data.errorMessage;
self.successMessage='';
}
);
}
function updateUser(user, id){
console.log('About to update user');
UserService.updateUser(user, id)
.then(
function (response){
console.log('User updated successfully');
self.successMessage='User updated successfully';
self.errorMessage='';
self.done = true;
$scope.myForm.$setPristine();
},
function(errResponse){
console.error('Error while updating User');
self.errorMessage='Error while updating User '+errResponse.data;
self.successMessage='';
}
);
}
function removeUser(id){
console.log('About to remove User with id '+id);
UserService.removeUser(id)
.then(
function(){
console.log('User '+id + ' removed successfully');
},
function(errResponse){
console.error('Error while removing user '+id +', Error :'+errResponse.data);
}
);
}
function getAllUsers(){
return UserService.getAllUsers();
}
function editUser(id) {
self.successMessage='';
self.errorMessage='';
UserService.getUser(id).then(
function (user) {
self.user = user;
},
function (errResponse) {
console.error('Error while removing user ' + id + ', Error :' + errResponse.data);
}
);
}
function reset(){
self.successMessage='';
self.errorMessage='';
self.user={};
$scope.myForm.$setPristine(); //reset Form
}
}
]);
And and I using ui-routing and this is where I assign controller to my view:
}).state('success',{
url: '/success',
templateUrl: 'partials/successMessage',
controller: 'UserController',
controllerAs: 'sCtrl'
});
And the ftl part of code is here:
<button type="button" ng-click='sCtrl.toLogin()' class="btn btn-primary">Login</button>
It won't even print to console, but when assigning older function it works perfectly, although it is much more complicated. Ps. Sorry for bad clarification at first, I am new to community and still learning a proper way to ask question.
You're not registering the method to the controller, to do that.
Add this below in self.onlyNumbers
self.toLogin = toLogin;
You need to use $scope
$scope.toLogin = function(){
console.log("Entered function");
$state.go('login');
}

Angular Components does not work with Promise and Service

I have a really strange problem with Angular Components calling a service.
For example, i have a simple service with some mockup data as an array inside. The i add two methods, one synchron and one asynchron which returns a promise (if i correct understood).
Now a have a angular component which is well loaded in a example application.
On the frontend i have two buttons, one for each method in the service.
If i press now the button "btn1" the list is well loaded, all works fine.
If i now press the button "btn2" i see in the console that the service returns all data correctly, but the list in the frontend will not be loaded.
Service
var myItems = [
{id: 1, name: "item1"},
{id: 2, name: "item2"}
];
function ItemsService($q) { // inject the $q service
this.$q = $q;
}
ItemsService.prototype = {
loadItems: function () {
return myItems;
},
loadItemsAsync: function () {
var $qme = this.$q; // if i do not this, "this.$q" is undefined
return this.$q.resolve(myItems);
}
};
module.exports = {
ItemsService: ItemsService
}
Controller
function Foo(ItemsService) {
this.itemsService = ItemsService;
}
Foo.prototype = {
loadItems: function () {
this.items = this.itemsService.loadItems();
},
loadItemsAsync: function () {
this.itemsService.loadItemsAsync().then(
function (response) {
this.items = response;
console.log('data->' + this.items + ' -> ' + this.items[0].name);
},
function (error) {
console.log('error->' + error); // ignore
}
);
}
};
Component HTML
<section>
<button id="btn1" ng-click="$ctrl.loadItems()">Load</button>
<button id="btn2" ng-click="$ctrl.loadItemsAsync()">Load Async</button>
<ul>
<li ng-repeat="item in $ctrl.items">{{item.id}} // {{item.name}}</li>
</ul>
</section>
In future i want to replace the code in the service method of "loadItemsAsync" with a service call, but actually nothing works.
Future planned service method code
loadTreasuriesAsync: function () {
var $qme = this.$q;
return this.$http.get(this.url).then(
function (response) {
return $qme.resolve(response.data);
},
function (error) {
throw error;
}
);
}
I also tried this service call, but it also returns a promise object.
loadItems: function () {
return this.$http.get(this.url).then(function (response) {
return response.data;
});
},
Can anyone help me finding a solution?
this.items = response;
this does not exist anymore in the context. Try to save the context previously either by an outside variable (i.e. _this) or use arrow functions if you have those available.

Delay loading data in Angular JS

I have code like this
(function (app) {
app.controller('productListController', productListController)
productListController.$inject = ['$scope', 'apiService', 'notificationService', '$ngBootbox', '$filter'];
function productListController($scope, apiService, notificationService, $ngBootbox, $filter) {
$scope.products = [];
$scope.page = 0;
$scope.pagesCount = 0;
$scope.getProducts = getProducts;
$scope.keyword = '';
$scope.search = search;
$scope.deleteProduct = deleteProduct;
$scope.selectAll = selectAll;
$scope.deleteMultiple = deleteMultiple;
function deleteMultiple() {
var listId = [];
$.each($scope.selected, function (i, item) {
listId.push(item.ID);
});
var config = {
params: {
checkedProducts: JSON.stringify(listId)
}
}
apiService.del('/api/product/deletemulti', config, function (result) {
notificationService.displaySuccess('Deleted successfully ' + result.data + 'record(s).');
search();
}, function (error) {
notificationService.displayError('Can not delete product.');
});
}
$scope.isAll = false;
function selectAll() {
if ($scope.isAll === false) {
angular.forEach($scope.products, function (item) {
item.checked = true;
});
$scope.isAll = true;
} else {
angular.forEach($scope.products, function (item) {
item.checked = false;
});
$scope.isAll = false;
}
}
$scope.$watch("products", function (n, o) {
var checked = $filter("filter")(n, { checked: true });
if (checked.length) {
$scope.selected = checked;
$('#btnDelete').removeAttr('disabled');
} else {
$('#btnDelete').attr('disabled', 'disabled');
}
}, true);
function deleteProduct(id) {
$ngBootbox.confirm('Are you sure to detele?').then(function () {
var config = {
params: {
id: id
}
}
apiService.del('/api/product/delete', config, function () {
notificationService.displaySuccess('The product hase been deleted successfully!');
search();
}, function () {
notificationService.displayError('Can not delete product');
})
});
}
function search() {
getProducts();
}
function getProducts(page) {
page = page || 0;
var config = {
params: {
keyword: $scope.keyword,
page: page,
pageSize: 20
}
}
apiService.get('/api/product/getall', config, function (result) {
if (result.data.TotalCount == 0) {
notificationService.displayWarning('Can not find any record.');
}
$scope.products = result.data.Items;
$scope.page = result.data.Page;
$scope.pagesCount = result.data.TotalPages;
$scope.totalCount = result.data.TotalCount;
}, function () {
console.log('Load product failed.');
});
}
$scope.getProducts();
}
})(angular.module('THTCMS.products'));
So my problem is when i loading data the application take me some time to load data.
I need load data as soon as
Is the any solution for this?
Since you are loading data via api call, there will be a delay. To handle this delay, you should display a loading screen. Once the data is loaded, the loading screen gets hidden and your main screen is visible. You can achieve this using $http interceptors.
See : Showing Spinner GIF during $http request in angular
The api-call is almost certainly causing the delay. Data may be received slowly via the api-call so you could display any sort of loading text/image to notify the use that the data is being loaded.
If u want the data ready at the time when controller inits, u can add a resolve param and pass the api call as a $promise in the route configuration for this route.

How to dismiss an angularjs alert when user changes route/state

I am using this angular js service/directive github pageto display alerts. An issue has been opened relating to the question I am asking but it has not been addressed by the developer.
i want first alert shown when user logs in to disappear when the user clicks logout but the alerts are stacking up on top of each other.
Here is a fiddle although I could not replicate the issue but it shows the structure of my code. fiddle
html:
<body ng-app="app">
<mc-messages></mc-messages>
<button ng-click="login()">Login</button>
<button ng-click="logout()">Logout</button>
</body>
js:
/*jshint strict:false */
'use strict';
var app = angular.module('app', ['MessageCenterModule']);
app.controller(function ($scope, messageCenterService, $location) {
$scope.login = function () {
$location.path('/');
messageCenterService.add('success',
'You are now loggedin!', {
status: messageCenterService.status.next
});
};
$scope.logout = function () {
$location.path('login');
messageCenterService.add('success',
'You are now loggedout!', {
status: messageCenterService.status.next
}
};
});
// Create a new angular module.
var MessageCenterModule = angular.module('MessageCenterModule', []);
// Define a service to inject.
MessageCenterModule.
service('messageCenterService', ['$rootScope', '$sce', '$timeout',
function ($rootScope, $sce, $timeout) {
return {
mcMessages: this.mcMessages || [],
status: {
unseen: 'unseen',
shown: 'shown',
/** #var Odds are that you will show a message and right after that
* change your route/state. If that happens your message will only be
* seen for a fraction of a second. To avoid that use the "next"
* status, that will make the message available to the next page */
next: 'next',
/** #var Do not delete this message automatically. */
permanent: 'permanent'
},
add: function (type, message, options) {
var availableTypes = ['info', 'warning', 'danger', 'success'],
service = this;
options = options || {};
if (availableTypes.indexOf(type) === -1) {
throw "Invalid message type";
}
var messageObject = {
type: type,
status: options.status || this.status.unseen,
processed: false,
close: function () {
return service.remove(this);
}
};
messageObject.message = options.html ? $sce.trustAsHtml(message) : message;
messageObject.html = !! options.html;
if (angular.isDefined(options.timeout)) {
messageObject.timer = $timeout(function () {
messageObject.close();
}, options.timeout);
}
this.mcMessages.push(messageObject);
return messageObject;
},
remove: function (message) {
var index = this.mcMessages.indexOf(message);
this.mcMessages.splice(index, 1);
},
reset: function () {
this.mcMessages = [];
},
removeShown: function () {
for (var index = this.mcMessages.length - 1; index >= 0; index--) {
if (this.mcMessages[index].status == this.status.shown) {
this.remove(this.mcMessages[index]);
}
}
},
markShown: function () {
for (var index = this.mcMessages.length - 1; index >= 0; index--) {
if (!this.mcMessages[index].processed) {
if (this.mcMessages[index].status == this.status.unseen) {
this.mcMessages[index].status = this.status.shown;
} else if (this.mcMessages[index].status == this.status.next) {
this.mcMessages[index].status = this.status.unseen;
}
this.mcMessages[index].processed = true;
}
}
},
flush: function () {
$rootScope.mcMessages = this.mcMessages;
}
};
}]);
MessageCenterModule.
directive('mcMessages', ['$rootScope', 'messageCenterService', function ($rootScope, messageCenterService) {
/*jshint multistr: true */
var templateString = '\
<div id="mc-messages-wrapper">\
<div class="alert alert-{{ message.type }} {{ animation }}" ng-repeat="message in mcMessages">\
<a class="close" ng-click="message.close();" data-dismiss="alert" aria-hidden="true">×</a>\
<span ng-switch on="message.html">\
<span ng-switch-when="true">\
<span ng-bind-html="message.message"></span>\
</span>\
<span ng-switch-default>\
{{ message.message }}\
</span>\
</div>\
</div>\
';
return {
restrict: 'EA',
template: templateString,
link: function (scope, element, attrs) {
// Bind the messages from the service to the root scope.
messageCenterService.flush();
var changeReaction = function (event, to, from) {
// Update 'unseen' messages to be marked as 'shown'.
messageCenterService.markShown();
// Remove the messages that have been shown.
messageCenterService.removeShown();
$rootScope.mcMessages = messageCenterService.mcMessages;
messageCenterService.flush();
};
$rootScope.$on('$locationChangeStart', changeReaction);
scope.animation = attrs.animation || 'fade in';
}
};
}]);
Hope this is clear enough for someone to help me. If not let me know and I can try to clarify.

AngularJS Chart Directive - Data loaded in async service not updating chart

I am having one chart directive created, and I am bootstrpping the app after loading google api. In following code, a simple data table is working fine. But when I load data from server in async manner, chart is not being displayed.
Controller
'use strict';
myNetaInfoApp.controller('allCandidatesController', [
'$scope','allCandidates2009Svc', '$timeout',
function ($scope, allCandidates2009Svc, $timeout) {
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string", "Party");
$scope.data1.dataTable.addColumn("number", "qty");
$scope.data1.dataTable.title = "ASDF";
$timeout( function (oldval, newval) {
allCandidates2009Svc.GetPartyCriminalCount().then(function(netasParty) {
var i = 0;
for (var key in netasParty) {
$scope.data1.dataTable.addRow([key.toString(), netasParty[key]]);
i++;
if (i > 20) break;
}
});
});
$scope.dataAll = $scope.data1;
//sample data
$scope.data2 = {};
$scope.data2.dataTable = new google.visualization.DataTable();
$scope.data2.dataTable.addColumn("string", "Name");
$scope.data2.dataTable.addColumn("number", "Qty");
$scope.data2.dataTable.addRow(["Test", 1]);
$scope.data2.dataTable.addRow(["Test2", 2]);
$scope.data2.dataTable.addRow(["Test3", 3]);
}
]);
Service
'use strict';
myNetaInfoApp.factory('allCandidates2009Svc', ['$http', '$q',
function ($http, $q) {
var netas;
return {
GetPartyCriminalCount: function () {
var deferred = $q.defer();
$http.get('../../data/AllCandidates2009.json')
.then(function (res) {
netas = res;
if (netas) {
var finalObj = {};
_.each(netas.data, function(neta) {
finalObj[neta.pty] = finalObj[neta.pty] ? finalObj[neta.pty] + 1 : 1;
});
deferred.resolve(finalObj);
}
});
return deferred.promise;
}
};
}]);
Directive
"use strict";
var googleChart = googleChart || angular.module("googleChart", []);
googleChart.directive("googleChart", function () {
return {
restrict: "A",
link: function ($scope, $elem, $attr) {
var dt = $scope[$attr.ngModel].dataTable;
var options = {};
if ($scope[$attr.ngModel].title)
options.title = $scope[$attr.ngModel].title;
var googleChart = new google.visualization[$attr.googleChart]($elem[0]);
$scope.$watch($attr.ngModel, function (oldval, newval) {
googleChart.draw(dt, options);
});
}
};
});
HTML
<div ng-controller="allCandidatesController">
<div class="col-lg-6">
<h2>Parties and Candidates with Criminal Charges</h2>
<div google-chart="PieChart" ng-model="dataAll" class="bigGraph"></div>
<!--<p><a class="btn btn-primary" href="#" role="button">View details »</a></p>-->
</div>
<div class="col-lg-6">
<h2>Heading</h2>
<div google-chart="BarChart" ng-model="data2" class="bigGraph"></div>
</div>
</div>
I think you need to wrap your function body in allCandidates2009Svc factory with scope.$apply(). But the return deferred.resolve() will be outside scope.$apply().
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// since this fn executes async in a future turn of the event loop, we need to wrap
// our code into an $apply call so that the model changes are properly observed.
scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
Read the docs here
http://docs.angularjs.org/api/ng.$q

Resources