$scope is not updated after ngResource POSTed new item successfully - angularjs

I have run into this issue in my app several times. I have a fun ngResource resources that I attach to $scope as $scope.jogasok = Jogas.query();, and I create a new resource in the same scope by calling something like
$scope.addJogas = function (jogas) {
$scope.isDisabled = true;
var j = new Jogas(jogas);
j.$save(function (value) {
$scope.isDisabled = false;
}, httpErrorHandler);
};
where Jogas is an ngResource
.factory('Jogas', function ($resource) {
var Jogas = $resource('/jogasok/:id/:action', {
'id': '#_id',
'action': '#action'
}, {
'ujBerlet': {'method': 'POST', 'params': {'action': 'ujberlet'}}
});
return Jogas;
})
The POST call is successful, thus reloading the page shows the new item in $scope.jogasok, but the new item is not shown if I don't reload.
Have can I get $scope.jogasok reloaded/extended automatically when $save has finished?

I've figured out my own dumbness concerning this question.
$scope.jogasok is an Array, thus its value will not change without changing it.
The most basic solution is to reload $scope.jogasok in the success handler:
$scope.addJogas = function (jogas) {
$scope.isDisabled = true;
var j = new Jogas(jogas);
j.$save(function (value) {
$scope.jogasok = Jogas.query();
$scope.isDisabled = false;
}, httpErrorHandler);
};

Related

Angular string binding not working with ControllerAs vm

I've been trying to do a two-way bind to a string variable on the Controller. When the controller changes the string, it isn't updated right away. I have already run the debugger on it and I know that the variable vm.overlay.file is changed. But it isn't updated on the View... it only updates the next time the user clicks the button that fires the selectOverlayFile() and then it presents the previous value of vm.overlay.file
Here goes the code:
(function () {
angular
.module("myapp.settings")
.controller("SettingsController", SettingsController);
SettingsController.$inject = [];
function SettingsController() {
var vm = this;
vm.overlay = {
file: undefined,
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.DATA_URL
}
};
vm.errorMessages = [];
vm.selectOverlayFile = selectOverlayFile;
vm.appMode = "photo";
vm.appModes = ["gif-HD", "gif-video", "photo"];
activate();
function activate() {
}
function selectOverlayFile() {
navigator.camera.getPicture(successOverlay, errorOverlay, vm.overlay.options);
}
function successOverlay(imageUrl) {
//If user has successfully selected a file
vm.overlay.file = "data:image/jpeg;base64," + imageUrl;
}
function errorOverlay(message) {
//If user couldn't select a file
vm.errorMessages.push(message);
}
}
})();
Thanks!
After a couple of hours searching for the issue and testing various solutions. I finally found it. The issue was that when the navigator.camera.getPicture(successOverlay, errorOverlay, vm.overlay.options) calls the callback function, it is out of AngularJS scope. So we need to notify Angular to update binding from within these callbacks using $scope.$apply():
(function () {
angular
.module("myapp.settings")
.controller("SettingsController", SettingsController);
SettingsController.$inject = ["$scope"];
function SettingsController($scope) {
var vm = this;
vm.overlay = {
file: undefined,
options: {
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
destinationType: Camera.DestinationType.DATA_URL
}
};
vm.errorMessages = [];
vm.selectOverlayFile = selectOverlayFile;
vm.appMode = "photo";
vm.appModes = ["gif-HD", "gif-video", "photo"];
activate();
///////////////////
function activate() {
}
function selectOverlayFile() {
navigator.camera.getPicture(successOverlay, errorOverlay, vm.overlay.options);
}
function successOverlay(imageUrl) {
//If user has successfully selected a file
vm.overlay.file = "data:image/jpeg;base64," + imageUrl;
$scope.$apply();
}
function errorOverlay(message) {
//If user couldn't select a file
vm.errorMessages.push(message);
$scope.$apply();
}
}
})();

AngularJS v1.3 breaks translations filter

In Angular v1.2 I was using the following code for serving up localised strings in the application:
var i18n = angular.module('i18n', []);
i18n.service('i18n', function ($http, $timeout) {
/**
A dictionary of translations keyed on culture
*/
this.translations = {},
/**
The current culture
*/
this.currentCulture = null,
/**
Sets the current culture, loading the associated translations file if not already loaded
*/
this.setCurrentCulture = function (culture) {
var self = this;
if (self.translations[culture]) {
$timeout(function () {
self.currentCulture = culture;
});
} else {
$http({ method: 'GET', url: 'i18n/' + culture + '/translations.json?' + Date.now() })
.success(function (data) {
// $timeout is used here to defer the $scope update to the next $digest cycle
$timeout(function () {
self.translations[culture] = data;
self.currentCulture = culture;
});
});
}
};
this.getTranslation = function (key) {
if (this.currentCulture) {
return this.translations[this.currentCulture][key] || key;
} else {
return key;
}
},
// Initialize the default culture
this.setCurrentCulture(config.defaultCulture);
});
i18n.filter('i18n', function (i18n) {
return function (key) {
return i18n.getTranslation(key);
};
});
In the template it is then used as follows:
<p>{{ 'HelloWorld' | i18n }}</p>
For some reason that I can't fathom, upgrading to v1.3 of AngularJS has broken this functionality. Either the $timeout isn't triggering a digest cycle, or the filter isn't updating. I can see that the $timeout code is running, but the filter code never gets hit.
Any ideas why this might be broken in v1.3?
Thanks!
In angular 1.3 the filtering was changed so that they are no longer "stateful". You can see more info in this question: What is stateful filtering in AngularJS?
The end result is that filter will no longer re-evaluate unless the input changes. To fix this you can add the line:
i18n.filter('i18n', function (i18n) {
var filter = function (key) {
return i18n.getTranslation(key);
};
filter.$stateful = true; ///add this line
return filter;
});
Or else implement your filter some other way.

AngularJS local storage - initialize app retrieving local-stored data

I'm pretty new to angular and I'm trying to avoid losing items added on a simple cart application when the user refreshes the page.
I'm using angularLocalStorage (https://github.com/agrublev/angularLocalStorage) but don't know how to retrieve it back the content.
My lines:
var myApp = angular.module('ionicApp', ['ionic','angularLocalStorage']);
myApp.factory('prodottiData', function($http) {
return {
getFooOldSchool: function(callback) {
$http.get('http://192.168.1.128/hongkongapp/?json=get_recent_posts&post_type=product&custom_fields=all').success(callback);
}
}
});
myApp.factory('DataService', function() {
var myCart = new shoppingCart("AngularStore");
return {
cart : myCart
};
});
myApp.controller('MyController', function MyController ($scope, storage, $ionicSideMenuDelegate, prodottiData, DataService, $sce) {
$scope.toggleLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mainMenu').toggleLeft();
};
$scope.toggleMySecondMenuLeft = function() {
$ionicSideMenuDelegate.$getByHandle('mySecondMenu').toggleLeft();
};
//adding menu data to the scope object
prodottiData.getFooOldSchool(function(data) {
$scope.menu = data;
});
//adding the cart to the scope object
$scope.cart = DataService.cart;
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
images = $scope.menu;
$scope.showloader = function(){
$scope.shownImage = this.post.thumbnail_images.full.url;
$scope.itemDesc = this.post.content;
$scope.itemPrice = this.post.custom_fields._price[0];
$scope.productName = this.post.title;
$scope.skuProdotto = this.post.id;
}
});
Now, if I check local storage on the console I can see something is really stored, but I miss the way to re-populate the cart at startup.
Any help would be great!
why not just using browser local storage ?
you can add it to your services.js as a new service and just used that.
var storeService = myAppServices.factory('storeService', function() {
var service =
{
setClientData:function(client_details)
{
window.localStorage.setItem( "client_data", JSON.stringify(client_details) );
client_data = client_details;
},
getClientData:function()
{
if (client_data == null)
{
client_data = JSON.parse(window.localStorage.getItem("client_data"));
}
return client_data;
}
}
var client_data = null;
return service;
});
From the documentation, to retrieve, it's storage.get('key')
So, to check after refresh:
if (storage.get('someKey')){
$scope.retrieved_value = storage.get('someKey');
}else{
// whatever
}
You can use localStorage instead windows.localStorage.
if(typeof(Storage)!=="undefined")
{
// Code for localStorage/sessionStorage.
var hello = "Hello World!!";
localStorage.setItem("hello",hello);
// get string
console.log(localStorage.getItem("hello")); // will return 'Hello World!!'
var me = {name:'abel',age:26,gender:'male'};
localStorage.setItem("user", JSON.stringify(me));
//fetch object
console.log(localStorage.getItem("user")); // will return {"name":"myname","age":99,"gender":"myGender"}
var objetos = JSON.parse(localStorage.getItem("user"));
console.log(objetos.name);
}
else
{
// Sorry! No Web Storage support..
}

How Angular "$watchs" arrays but wont watch properties?

I have two controllers communicating via a service (factory).
OfficerCtrl and NotificationCtrl they communicate via the notification service.
Here's the NotificationCtrl and the notification service.
angular.module('transimat.notification', [])
.controller('NotificationsCtrl', function($scope, notification) {
$scope.msgs = notification.getMsgs();
$scope.showNotification = false;
$scope.$watchCollection('msgs', function() {
//put break point here: It enters
$scope.showNotification = $scope.msgs.length > 0;
});
$scope.close = function(index) {
notification.close(index);
};
$scope.showModal = false;
$scope.modalMsg = notification.getModalMsg();
$scope.$watch('modalMsg', function() {
//put break point here: Wont enter
$scope.showModal = $scope.modalMsg !== null;
},
true);
})
.factory('notification', function($interval) {
var severity = ['success','info','warning','danger','default'];
var msgs = [];
var modalMsg = null;
var notification = {
showSuccess: function(summary, detail, delay) {
showMsg(severity[0], summary, detail, delay);
},
//...
close: function(index) {
msgs.splice(index,1);
return msgs;
},
getMsgs: function() {
return msgs;
},
// MODALS
getModalMsg: function() {
return modalMsg;
},
showModalMsg: function(summary, detail, uri) {
modalMsg = {
summary: summary,
detail: detail,
uri: uri
};
},
closeModal: function() {
modalMsg = null;
}
};
var showMsg = function(severity, summary, detail, delay) {
var msg = {
severity: severity,
summary: summary,
detail: detail
};
msgs.push(msg);
if (delay > 0) {
$interval(function() {
msgs.splice(msgs.length - 1, 1);
}, delay, 1);
}
};
return notification;
});
That's my notification ctrl/service.
Now in my OfficerCtrl I "push" notifications via the notification service.
angular.module('transimat.officers', [])
.controller('RegisterOfficerCtrl', function (
$scope,
notification) {
// business logic
// this WONT work
$scope.showModal = function(){
notification.showModalMsg('NOW','BLAH','officer/1234');
}
// this works
$scope.showNotification = function() {
notification.showSuccess('Success', 'Blah blah.', 5000);
};
})
It will watch arrays but wont watch "normal" vars.
So I have to questions:
The showNotification() works, but the showModal() wont work. It has to do with some pointer thing? The Arrays have a "strong" pointer and the normal vars have "weak" pointers and get ignored/lost by the $scope.$watch expression?
How do I solve this?
The first watch watches the array of messages msgs of your controller scope, which is the array returned by the service getMsgs() function. So, each time the content of this array changes, the callback function is called. When showSuccess() is called, a new message is pushed to the array, and the callback is thus called.
The second watch watches the field modalMsg of your controller. So, each time a new value is assigned to $scope.modalMsg, or each time it's not equal to its previous value, the callback function is called. But a new value is never assigned to this variable. It's only assigned once, before the watch is created. The showModalMsg() function of the service assigns a new value to its own, private, modalMsg variable, but doesn't assign any new value to the controller's modalMsg variable, which still references the old notification modalMsg object:
Before showModalMsg():
$scope.modalMsg -----------------> object
^
|
notification modalMsg ---------------|
After showModalMsg():
$scope.modalMsg -----------------> object
notification modalMsg -----------> other object

AngularJS - Append to a model with a $resource

I have this service:
factory('Post', function($resource) {
return $resource('/api/post.json', {},
{
query: {method:'GET', isArray: false}
}
);
})
And I have this controller:
function PostsCtrl($scope, Post) {
// init
$scope.page = 0;
$scope.page_has_next = true;
$scope.loadMore = function() {
if($scope.page_has_next) {
$scope.posts = Post.query({page: ++$scope.page},
function(data) {
$scope.page_has_next = data.has_next;
}
);
}
}
$scope.loadMore();
}
This works just fine, each time loadMore() is executed the model gets updated with the next page until there are no more pages. However, I want to append the new set of posts to the current model instead of replacing it, how can I do that?
Assuming that posts is a array:
$scope.posts = $scope.posts.concat(Post.query({page: ++$scope.page})
This will only work if the new posts has no duplicates with the old posts. If there are duplicates, you have to traverse the array and push only new posts.

Resources