$resource callback with saved value - javascript

I've just started using AngularJS and I love it.
However - I have a need to save an item to my database using $resource and then get back and object containing the values of the newly created item in the database (especially the database-assigned ID).
I've found a few articles describing this - but none of them seems to work for me :(
I have a very simple setup:
var app = angular.module("todoApp", ['ngResource', 'ngAnimate']);
app.factory("TodoFactory", function ($resource) {
return $resource('.../api/todo/:id', { id: '#id' }, { update: { method: 'PUT' }});
});
var todoController = app.controller("TodoController", function ($scope, TodoFactory) {
$scope.todos = [];
init();
function init() {
$scope.todos = TodoFactory.query();
}
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
But when I call TodoFactory.save, the $scope.item does not contain the Id-property from the database - only the values it had upon calling save.
How can I get my setup to return the updated object with all the database-generated values?
If somebody could point me in the right direction it would be much appreciated :)
Update: I just went over the source for the API I've been supplied - the save-method doesn't update the object that's inserted.
After I fixed this "minor" issue, peaceman's example worked like a charm.
Sorry for the inconvenience everybody - but thank you very much for the responses! :)

The save method of the TodoFactory won't update the $scope.item, but instead calls the callback function with the saved object as a parameter, that contains the new id.
So you have to replace
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
with
$scope.addTodo = function () {
TodoFactory.save($scope.item, function (savedTodo) {
// Success
console.log(savedTodo);
$scope.todos.push(savedTodo);
$scope.item = {};
},
function () {
// Error
});
};
This behaviour is documented at ngResource.$resource

This should solve the problem.
$scope.addTodo = function () {
// ADD IN A VARIABLE FOR THE RESPONSE DATA AS THE PARAMETER TO YOUR SUCCESS CALLBACK FN
TodoFactory.save($scope.item, function (responseItem) {
// Success
console.dir(responseItem); // <--- AND THEN USE IT
$scope.todos.push(responseItem);
$scope.item = {};
},
function () {
// Error
});
};

Related

How to access Array value from outside scope with Angular JS

Okay so I know this is a really stupid question. I looked everywhere before making this thread and I am just completely unable to figure it out. This is the code I had. My issue is that between passing the value from getJSON to the angular controller, my array has lost its values. What is the correct way to do what I'm trying to do here?
function getJSON(json) {
data = [];
$.each(json, function (index, value) {
data.push(value);
});
console.log(data); // Accurately logs array data
return data;
}
function passValue() {
return getJSON();
}
app.controller('ExampleController', function($scope) {
x = passValue()
$scope.title = x[0]; // Throws error
console.log(x); // Only returns an empty array
}
And this I have in a script on my html file. It works 100%.
$(document).ready(function() {
$.getJSON("{{ url_for('static', filename='movies.json') }}?id={{ movie_id }}", function(json) {
getJSON(json);
});
For example, this works.
function getJSON(json) {
data = [];
$.each(json, function (index, value) {
data.push(value);
});
console.log(data) // Acurrately logs array data
document.getElementById('exampleDiv').innerHTML = data[0] // Accurately appends array data (0 being title)
}
I found a solution that fixes my problem. If anyone has a similar issue, I hope this helps you as well.
function getJSON(json) {
data = [];
$.each(json, function (index, value) {
data.push(value);
});
console.log(data)
update(data)
}
function update(data) {
var $scope = angular.element(document.querySelector('#movie')).scope()
$scope.$apply(function(){
$scope.title = data[0];
});
}
app.controller('MovieController', function($scope) {
});
You can use the window to store and access data between angular and any component. Angular has a wrapper called $window to access it too.
In javascript:
function foo(value){
window.data = value;
}
In angular:
app.controller('ctrl', ['$scope', '$window', function($scope, $window) {
$scope.getData= function() {
//using $window
alert($window.data);
//Or
alert(window.data);
};
}]);

How to pass a callback function to once().then() in angularjs?

I have a separate model and a controller for a teachers list.
My teacherModel.js is:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// I'm trying to pass this callback from the controller:
callback;
});
}
};
return Teacher;
}]);
Now from my controller I call the getAllTeachers() method with a callback function:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers(function() {
$scope.teachers = teacher;
console.log($scope.teachers);
});
}]);
Problem is console.log($scope.teachers); is not logging anything to the console. I don't think the callback is being executed at all.
Can someone help me to figure out what I'm doing wrong or suggest a better way to add functionality to the model data from controller after the data is asynchronously retrieved from firebase? Thanks.
You can leverage the fact that once returns a firebase promise so you can alter your code to the following:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
return ref.once('value').then(function(snapshot) {
return snapshot.val();
});
}
};
return Teacher;
}]);
This would behave similarly to any $http request where it returns a promise. Now, in your controller, you can then call your getAllTeachers() like so:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers().then(function (snapshotValues) {
// What you returned in the promise above is populated in snapshotValues here
$scope.teachers = snapshotValues;
});
}]);
Update
If you want to use the $q service for your particular scenario, you can do the following:
app.factory('Teacher', ['$q', function($q) {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var defer = $q.defer();
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
var val = snapshot.val();
// Transform your data any way you want.
// Whatever you pass into resolve() will be available as a parameter in the subsequent then()
defer.resolve(val);
});
return defer.promise;
}
};
return Teacher;
}]);
Using the method would still be the same. You simply just call then()
teacher.getAllTeachers()
.then(function (whatYouPassedInResolve) {
});
Another thing to note is that in the getAllTeachers method inside of your factory, I did not handle any error cases. That would be achieved by rejecting the promise with defer.reject(objectToSendBack). You pass in any data you want accessible when you deem that call a failure.
Just pass in a function for the second parameter to the `then(successCallback, errorCallback) to handle any rejected promises.
I think you are not calling the callback actually, use callback()
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// Try this
callback();
});
}
};
return Teacher;
}]);

PubsubJs and *this* is undefined when subscribing to a message

I have the following code:
someClass1 = function () {
this.doStuff = function () {
PubSub.publish('topic1', { id: 1 });
}
}
someClass2 = function () {
this.forename = 'bob2';
PubSub.subscribe("topic1", function (msg, data) {
log(msg, data, this.forename);
});
}
function log() {
console.log(arguments);
}
var c1 = new someClass1();
var c2 = new someClass2();
c1.doStuff();
and I am using the pubsubjs library (https://github.com/federico-lox/pubsub.js)
The code is simple - publish a message and handle it in another class (someClass2) using PubSub
My question is that when I publish a message and handle it in someClass2, this is undefined. This occurs at the line: log(msg, data, this.forename);
This means I cant access any of the someClass2 properties/functions. What do I need to do to get the this to not be undefined? Is this possible? Are they other libraries that will help? Am I doing it wrong....
All help apprenticed! Thanks
You're passing an unbound function to subscribe. Such a function has "no idea" about this. You have to bind it:
PubSub.subscribe("topic1", (function (msg, data) {
log(msg, data, this.forename);
}).bind(this));
this is not what you expect inside the callback, just cache a copy of this as another variable outside:
someClass2 = function () {
this.forename = 'bob2';
var that = this;
PubSub.subscribe("topic1", function (msg, data) {
log(msg, data, that.forename);
});
}

Angular two way data binding not working when $scope is updated

I have a really serious problem, I'm updating, editing, deleting data, and the two-way data binding is not working.
This is one of my controllers:
'use strict';
var EventController = function($timeout, $scope, $state, EventModel) {
this.$timeout = $timeout;
this.$scope = $scope;
this.$state = $state;
this.EventModel = EventModel;
/**
* When the page is requested, retrieve all the data.
*
*/
this.retrieve();
};
EventController.prototype = {
create: function(event) {
var that = this;
this.EventModel.Model.insert(event)
.then(function() {
that.refresh();
});
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.$scope.events = result;
});
},
one: function(id) {
var that = this;
this.EventModel.Model.one(id)
.then(function(result) {
that.$scope.event = result;
});
},
update: function(id, event, state) {
if (state !== undefined) {
event.is_active = state;
}
var that = this;
this.EventModel.Model.update(id, event)
.then(function() {
that.refresh();
});
},
delete: function(id) {
var check = $('[data-controller-input]:checked');
var that = this;
$.each(check, function() {
var target = $(this);
var id = target.prop('id');
that.EventModel.Model.remove(id)
.then(function() {
that.refresh();
});
});
},
clear: function() {
this.$scope.event = angular.copy(this.$scope.initial);
},
refresh: function() {
this.$state.go(this.$state.current, {}, {reload: true});
}
};
angular
.module('adminApp')
.controller('EventController',
[
'$timeout',
'$scope',
'$state',
'EventModel',
EventController
]
);
In the create, update and delete methods I need to update the HTML without refreshing the page, I already tried using, $scope.apply, $scope.digest, $timeout after the result came, but not happens in the HTML.
If I try $scope.apply and $scope.digest the error will be:
Prevent error $digest already in progress when calling $scope.$apply()
So I was trying to wrap the $scope.$apply or $digest with the $timeout, same result, nothing happens.
Thanks.
First of all, your refresh method will never update your controller.it will simply fail just because this.$state.current won't be able to resolve any url ,template or controller.
And this is the main reason you are not able to see updated data ,just check your console you might be getting Error: Cannot transition to abstract state '[object Object]' error.
Update : I have create a plnkr.as i don't have access to event model code i simply removed it and try to create the same scenario.
http://plnkr.co/edit/RsI3TgKwcjGEXcTMKoQR?p=preview
see if this can help you
I am not sure, but try using the following function which checks the current phase before executing your function. It may solve the issue.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Usage:
$scope.safeApply(function() {
//Your lines
});

Calling a service from within another service in AngularJS

I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).

Categories