manage factory JSON $http data with 2 controllers - javascript

I'm trying to get a factory JSON response, save it in a variable, in order to be ready to be called from 2 different controllers.
Here bellow I paste the code I'm using:
storyFactory.js
var story = angular.module('story.services', []);
story.factory('storyAudio', [ '$http', function ($http) {
var json = {};
function getJSON(story_id, callback) {
$http({
url: 'https://api.domain.co/get/' + story_id,
method: "GET"
}).success(function (data) {
json = data;
callback(data);
});
};
return {
getSubaudios: function(story_id, callback) {
getJSON(story_id, function(result) {
callback(result);
});
},
getTopbar: function(callback) {
callback(json);
}
};
}]);
StoryCtrl.js
var storyCtrl = angular.module('story', ['story.services']);
storyCtrl.controller('storyCtrl', [ 'CONFIG', '$stateParams', 'storyAudio', function(CONFIG, $stateParams, storyAudio) {
var data = this;
data.story = {};
storyAudio.getSubvideos($stateParams.story_id, function(response) {
data.story = response;
});
}]);
TopbarCtrl.js
var topbarCtrl = angular.module('topbar', ['story.services']);
topbarCtrl.controller('topbarCtrl', [ 'CONFIG', '$stateParams', 'storyAudio', function(CONFIG, $stateParams, storyAudio) {
var data2 = this;
data2.story = {};
storyAudio.getTopbar(function(response) {
data2.story = response;
});
}]);
The problem is in my TopbarCtrl response I'm receiving an empty data2.story when I call it in the HTML.
The reason is because it doesn't have a callback of the $http response, so it prints the var json with the actual status, that is an empty object.
How could I load the second controller when the variable has content?
Thanks in advice.

I think the best you can do in this case is load the data via getSubaudios and provide a reference to the data for other controllers to use. Something like this...
story.factory('storyAudio', function($http) {
var factory = {
story: {}
};
factory.getSubaudios = function(story_id) {
return $http.get('https://api.domain.co/get/' + story_id).then(function(response) {
return angular.extend(factory.story, response.data);
});
};
return factory;
})
Using angular.extend() instead of directly assigning a value to the factory's story property maintains any references that may be established before the data is loaded.
Then you can load the data via
storyCtrl.controller('storyCtrl', function(storyAudio) {
var data = this;
storyAudio.getSubaudios($stateParams.story_id).then(function(story) {
data.story = story;
});
})
and directly reference the story data by reference in your controller
topbarCtrl.controller('topbarCtrl', function(storyAudio) {
this.story = storyAudio.story;
})

I think I'm understanding correctly, but let me know if not.
There are two issues I'm seeing. The first is that there is a typo in your StoryCtrl.js file. You are calling "storyAudio.getSubvideos" but the function is called "getSubaudios" in your factory.
Even with that typo fixed, the issue could still technically happen. It all really depends on how quickly the promise returns from the first call. Unfortunately, promises are asynchronous, so there is no guarantee that the "json" variable will get set before the second controller tries to get it.
In order to resolve this, you need to ensure that the first call is finished before trying to access the "json" variable you have on the service. There are probably a few different ways to do this, but one that comes to mind is to actually return and store the promise in the service like so...
var dataPromise;
function getSubaudios(story_id){
if(!dataPromise){
dataPromise = $http({
url: 'https://api.domain.co/get/' + story_id,
method: "GET"
});
}
return dataPromise;
}
return {
getSubaudios: getSubAudios
};
Then in your controllers, you can just call the service and use .then to get the data out of the promise when it returns...
storyAudio.getSubaudios($stateParams.story_id).then(function(response){
data.story = response; //or data2.story = response;
});
Here is a plunkr example. I've used the $q library to simulate a promise being returned from an $http request, but it should illustrate the idea.

Similar to Phil's answer. (Angular extend, or angular copy keeps the references the same in both controllers. If you don't want to put watchers in both controllers to keep track if the value changes.) Several methods here:
Share data between AngularJS controllers.
You could also bind the object you are returning directly to the update-function. That way the references stay intact.
storyServices.factory('storyAudio', ['$http', function($http) {
return {
data: { json: '' },
getSubaudios: function(story_id) {
$http.get('http://jsonplaceholder.typicode.com/posts/' + story_id)
.then(function(response) {
this.data.json = response.data.body;
}.bind(this));
}
};
}]);
var storyCtrl = angular.module('story').controller('storyCtrl', ['$scope', 'storyAudio', function($scope, storyAudio) {
$scope.data = storyAudio.data;
storyAudio.getSubaudios(2);
}]);
var topbarCtrl = angular.module('story').controller('topbarCtrl', ['$scope', 'storyAudio', function($scope, storyAudio) {
$scope.data2 = storyAudio.data;
}]);
Plunk here: http://plnkr.co/edit/auTd6bmPBRCVwI3IwKQJ?p=preview
I added some scopes to show what happens.
Sidenote:
I think it's straight out dishonest to name your non-controller "storyCtrl" and then assign it a controller of its own:
var storyCtrl = angular.module(...); // Nooo, this is not a controller.
storyCtrl.controller(...); // This is a controller! Aaaah!
Another sidenote:
.success() is the old way of doing things. Change to .then(successCallback) today! I dare to say it's the standard convention for promises.
https://docs.angularjs.org/api/ng/service/$http#deprecation-notice

Related

AngularJS objects should work independently

I have 2 array objects both initialized with $http response but when I try to add(push) in one array, it gets added into both.
I tried below code:
Controller:
myApp.controller("abc", function($scope, lastday_data){
$scope.objectiveData = [];
$scope.doneData = [];
// call service & get data from server
lastday_data.getData().then(function(success){
$scope.objectiveData = success;
$scope.doneData = success;
$scope.$digest(); // *---> $digest() used*
},function(error){
$scope.objectiveData = null;
$scope.doneData = null;
});
// add task done
$scope.addTaskDone = function() {
var p = {"id": 101, "name": "testadd", "check": true};
$scope.doneData.push(p);
$scope.textDone = "";
}
});
Service: -- get data from the server
myApp.service("lastday_data", function($http){
this.getData = function() {
return new Promise(function(resolve, reject){
$http({
method: 'GET',
url: 'http://localhost/task/index.php/v1/example/users'
}).then(function (response) {
if(response.status)
resolve(response.data);
else
reject();
},function (error) {
reject();
});
});
}
});
Problem: when I try to call controller's addTaskDone() method, this method add one object in doneData array but this object get added in objectiveData also.
Basically the problem is objectiveData & doneData $scope variables are holding same memory location. So changing any of the value would make changes in all of three value success, objectiveData and doneData.
So basically you should make sure that while assigning a one variable with multiple values, create a clone of that success variable and keep then assign that variable to desired variable.
In angularjs there is angular.copy method exists, which will help you to create clone of an object with new memory location. That will ensure that the new variable will point to different memory location.
Controller:
$scope.objectiveData = angular.copy(success);
$scope.doneData = angular.copy(success);
Bonus: It clearly seems that you have wrong implementation of service, where you're creating an promise explicitly that's the reason why you had to call $digest inside your .then success callback. It means that you're creating an cases where you have to run digest cycle manually since code will be running outside angularjs context. Rather you should return the existing $http promise like below, and remove $scope.$digest() from you code which isn't needed at all.
Service
myApp.service("lastday_data", function($http) {
this.getData = function() {
return $http({
method: 'GET',
url: 'http://localhost/task/index.php/v1/example/users'
}).then(function(response) {
if (response.status)
return response.data;
else
return $q.reject('Problem retrieving data');
}, function(error) {
return $q.reject(error);
});
}
});
The problem
Both $scope.objectiveData and $scope.doneData are referencing the same variable success, so if you change one, the other one is changed too.
Solution
Make $scope.objectiveData and $scope.doneData reference independent variables by getting independent copies of success. You can use for this
Plain JavaScript
Array.prototype.slice: $scope.doneData = success.slice();
Array.prototype.concat: $scope.doneData = [].concat(success);
Array.from: $scope.doneData = Array.from(success);
Object.assign: $scope.doneData = Object.assign([], success);
AngularJS builtin functions
angular.copy: $scope.doneData = angular.copy(success);
angular.extend: $scope.doneData = angular.extend([], success);
angular.merge (Deprecated since 1.6.5, see known issues): $scope.doneData = angular.merge([], success);
Other tricks
JSON.parse/JSON.stringify [1]:
$scope.doneData = JSON.parse(JSON.stringify(success));
So instead of
$scope.objectiveData = success;
$scope.doneData = success;
Do (or any other of the previous alternatives)
$scope.objectiveData = success.slice(); // get a copy of success
$scope.doneData = success.slice(); // get a copy of success

how to make a function reusable between two or more controllers in angularjs

I have a question as I am new to angularjs. I have searched alot but unable to understand the concept. I have a basic concept that there should be one controller for one view but we can have multiple controllers for a single view. I have below two controllers in a single view and there is one function let say for example addComma function which I have to use in both controllers but I want to write it one time to make it reusable between all controllers let say 5,6 controllers on the same view. So basically the question is how to make a function in a controller global between all controllers of same view or other views so that I can use it anywhere in my application. Appologies if it is a dumb question I am having a hard time understanding the concepts of angularjs.
app.controller("GetChargesController", function ($scope, GetService, $rootScope) {
$scope.Title = "Charges Details List";
$rootScope.loading = true;
// Calling Serivce Method here
$scope.GetChargesDetails = GetService.GetAll("CommonApi", "GetChargesDetails").then(function (d) {
$scope.ChargesDetails = d;
$rootScope.loading = false;
});
// add comman function goes here
$scope.addComma = function(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
});
app.controller("GetPaymentsController", function ($scope, GetService, $rootScope) {
$scope.Title = "Payments Details List";
$rootScope.loading = true;
// Calling Serivce Method here
$scope.GetPaymentsDetails = GetService.GetAll("CommonApi", "GetPaymentsDetails").then(function (d) {
$scope.PaymentsDetails = d;
$rootScope.loading = false;
});
// add comman function goes here
$scope.addComma = function (x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
});
Below is the generic service I've written to get any kind of data from database(using asp.net web api). As I've read that angular services can hold data and we don't need to call the database again and again when we are moving back and forth on links let's say for example I have these 3 links on a page. Home Link, PaymentCharges Link, Orders Link. so Home view will open by default. And when I click on PaymentCharges Link the call will go get data from database and render its view but when I click back on Home Link there should be no call going to database to get the data for the home page or when I click forward again on PaymentCharges Link the second time there should be no call going to database but what I see on firebug console option it is calling the function and going to database to get the data.I think we need to use cache to hold the data to not send the call to database.
app.factory("GetService", function ($http) {
var thisService = {};
// get all data from database
thisService.GetAll = function (controllername, methodName) {
var promise = $http({
method: 'GET',
url: '/api/'+controllername + '/' + methodName
})
.then(function (response) {
return response.data;
},
function (response) {
return response.data;
});
return promise;
};
});
Create an utils service and add the addComma function there. Inject the utils service and reuse the addComma in the controllers
app.factory('utils', function() {
return {
addComma: function() {}
}
});
app.controller("GetChargesController", function ($scope, utils) {
$scope.addComma = utils.addComma;
});
app.controller("GetPaymentsController", function ($scope, utils) {
$scope.addComma = utils.addComma;
});
Several ways to manage the service issue:
Re-use the promises by storing them in the service:
// create an object to store promises
var promises = {};
thisService.GetAll = function(controllername, methodName) {
promises[controllername] = promises[controllername] || {};
// if the particular promise doesn't already exist create it as a property of above object
if (!promises[controllername][methodName]) {
promises[controllername][methodName] = $http({...}).then(...;
}
// now return the specific stored promise
return promises[controllername][methodName];
};
or store the data and use $q to return a different promise that resolves with the stored data when it already exists. Make sure to inject $q for this approach
var data = {};
thisService.GetAll = function(controllername, methodName) {
data[controllername] = data[controllername] || {};
if (data[controllername][methodName]) {
// data exists for this controller and method, no need for new request
return $q.resolve(data[controllername][methodName]);
} else {
return $http({...}).then(function(response) {
// store the data for next use
var newData = response.data;
data[controllername][methodName] = newData;
return newData;
});
}
};
Why don't you just store the data in the service?
You could do:
.then(function (resp) {
thisService.controllerName.data = resp.data
}
Then you can reference the variable in your code using GetService.controllerName.data
Instead I would suggest you to use something like this which might make it little more generic. Check the below code
appResources-Factory will contain all the resources that are needed to be exposed.
indexServices-Factory will contain all your services corresponding to your index controller.
(function () {
use strict';
var commonServices = angular.module('commonServices', ['ngResource']);
commonServices.factory('appResources', ['$resource',
function ($resource) {
var indexResource=$resource('/api/index/:id');
return {
indexResource:indexResource
}
}]);
commonServices.factory('indexService',['appResources',
function (appResources){
var getAllIndexes =function(sucesscallback){
appResources.indexResource.query({}).$promise.then(
//success
function( value ){/*success events and Data is present in the 'value'*/
sucesscallback(value);
},
//error
function( error ){/*failure events*/}
)
}
}
]);
});
Controller Method calling the service and getting the value in $scope variables like
(function () {
'use strict';
angular.module('saniehhaApp')
.controller('indexController', indexController);
indexController.$inject = ['$location', 'indexService'];
function indexController($location, index,indexService) {
/* jshint validthis:true */
indexService.getAllIndexes(function(sucesscallback){
$scope.value=sucesscallback;
})
//$locaton.qwets=....
}
})();

Angular Promise not working

I try to get some important things like: companyid,employeeid etc. with every request that a user makes. So this has to be received before everything else is done.
After that the user receives information based on his companyid that he sets with every request (get/company/{companyid}).
The problem that I have is that the response for receiving the companyid takes to long and angular already tries to make a request to (get/company/{companyid}) obviously there is no companyid yet.
I've tried to fix this whit promise but it's not working.
Here I try to receive some important information about the user(that I do with every request) :
Service
(function () {
angular.module('employeeApp')
.service('authenticationservice', authenticationservice);
function authenticationservice($http,$location,authenticationFactory,$q,GLOBALS,$cookies) {
this.validateUser = function () {
var vm = this;
vm.deferred = $q.defer();
data = {"api_token": api_token};
return $http.post(GLOBALS.url+'show/employee/' + $cookies.get('employeeid'),data)
.success(function(response)
{
vm.deferred.resolve(response);
})
.error(function(err,response)
{
vm.deferred.reject(err);
});
return vm.deferred.promise;
}
}
})();
Routes file
(In my routes file I use the authenticationservice to set all important users variables.)
employeeAppModule.run([
'authenticationservice',
'constants',
function(authenticationservice,constants) {
authenticationservice.validateUser()
.then(function(response)
{
constants.companyid = response.result.Employee;
constants.role = response.result.Role;
constants.name = response.result.FirstName;
console.log('test');
},
function(response){
console.log('error');
});
}
]);
So the problem is that the user information is set to late and angular already goes to my homeController where he uses the companyId that is not being set yet.
Thankyou
The problem in your current code is return $http.post are having two return statement in your validateUser method. Which is returning $http.get before returning return vm.deferred.promise; & that why customly created promise doesn't get returned from your method. Though by removing first return from $http.get will fix your problem, I'd not suggest to go for such fix, because it is considered as bad pattern to implement.
Rather I'd say, you should utilize promise return by $http method, & use .then to return data to chain promise mechanism.
Code
function authenticationservice($http, $location, authenticationFactory, $q, GLOBALS, $cookies) {
this.validateUser = function() {
var vm = this;
data = {
"api_token": api_token
};
return $http.post(GLOBALS.url + 'show/employee/' + $cookies.get('employeeid'), data)
.then(function(response) {
var data = response.data;
retrun data;
}, function(err) {
return $q.reject(err);
});
}
}
To make sure that $ http return a $ promise object you need to check that the action in the controller returns a value and it is not a void action.

Execute function after another AngularJS

I need to execute a function which fetches data after a kind of login function who provides the sessionId. This sessionId is necessary for the second function.
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID = loginService.getSessionID(); //Login function
var self = this;
this.items = [];
this.constructor = function() {
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
};
this.constructor(); //get the data
return this;
});
I tried several variations like:
loginService.getSessionID().then(function(sessionID){
this.constructor(); //also with just constructor();
});
But I always receive errors (in the case above: Illegal constructor).
So how can I manage to execute one function after another ? Maybe a callback structure would help here but I have no clue how to realize it.
EDIT
Here is the code for the login:
app.service('loginService', function($http, $q) {
this.getSessionID = function()
{
return $http({
method: "GET",
url: "http://localhost:8080/someRequestDoneHere"
}).then(function(response)
{
return response.data.sessionId; // for example rYBmh53xbVIo0yE1qdtAwg
});
};
return this;
});
Does your getSessionID() function return a promise? If so you want code like this:
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID;
var vm = this;
vm.items = [];
loginService.getSessionID()
.then(function(sid) {
sessionID = sid;
return dbObjectsDAO.getAll(sessionID);
})
.then(function(arrObjItems){
vm.items = arrObjItems;
});
});
So your login service returns a promise which resolves to the session id. You can save that in a variable for use elsewhere, and also use it to trigger fetching the items.
I also changed your self to vm as that naming is an Angular convention, and stored the items in vm.items rather than directly in the scope.
Edit:
Your login code already returns a promise, not a session id. return inside a then is simply going to return a new promise that resolves to the value you are returning.
There are several ways to chain multiple $http requests. If they are independent of each other just fire off a bunch of requests and use $q.all to handle when they have all completed.
var promise1 = $http(something)
.then(function(response) { vm.data1 = response.data; return vm.data1; });
var promise2 = $http(something)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
$q.all([promise1, promise2], function(values) {
// here can safely use vm.data1 or values[0] as equivalent
// and vm.data2 or values[1].
});
If one request depends on the result of another you could even do this:
var promise1 = $http(something)
.then(function(response) {
vm.data1 = response.data;
return { method:'GET', url: response.data.link}
});
var promise2 = promise1.then($http)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
Your template needs to declare the controller using the 'controller as something' syntax:
<div ng-controller="TestController as test" ng-bind="test.items"></div>
Have you tried to nest the second function, like this ? without the constructor call ?
loginService.getSessionID().then(function(sessionID){
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
});
Mb you have wrong scope in
..then(function(sessionID){...}) ?
you can try some this like this:
var vm=this;
loginService.getSessionID().then(function(sessionID){
vm.constructor();
});

Is it possible to pass GET response data back to same factory?

Question:
From any controller, how can I call the getPages function, return the data back to the controller and replace the empty Page.details.refobject with the GET response data?
is it possible for this all to happen within the factory regardless of which controller calls the function?
app.factory('Pages', function($http, ENV){
var Pages = {};
Pages.details =
{
pages:
{
length: 0,
offsets: []
},
ref:
{
//data goes here on success
},
getPages: function($scope) {
return $http.get(ENV.apiEndpoint + '/' + $scope.storeSlug + '/pages.json?code=' + $scope.promoCode)
.success(function(data){
// I want this Pages.details.ref to be replaced on success of getPages
Pages.details.ref = data;
$scope.handlePagesSuccess(data);
return data;
})
.error(function(data, status){
// console.log('error:' + status);
});
}
}
return Pages;
});
Controllers:
this controller calls the init request
app.controller('RandomCtrl', function($scope, Pages){
var handleSuccess = function (data) {
$scope.data = data;
}
Pages.details.getPages($scope).success(handleSuccess);
})
Controller #2:
this controller just consumes a temp version of the request no relationship between the RandomCtrl. e.g this controller is typically a directive level controller where the theres no bubbling between a parent ctrl
app.controller('OtherCtrl', function($scope, Pages){
$scope.tempPage = Pages.details.ref;
})
it shouldnt matter where getPages is called from. I want ref to be replaced everytime getPages is called.
It seems like you are trying to manage state inside your factory, which probably is not a good idea. Also it is not a good idea to pass around $scope in factories. They should be limited to its own controller. You could instead cache the promise for the previous call made and based on a flag you could either return the cached promise or make the actual service call.
app.factory('Pages', function($http, ENV, $q){
var Pages = {};
var cachedPromise = {};
Pages.details =
{
pages:
{
length: 0,
offsets: []
},
getPages: function(request) {
//Get a request key to make sure you are returning right promise incase multiple product calls are made at the same time.
var reqKey = request.storeSlug + request.promoCode;
//if a call has already been made and there is a promise return it
if(cachedPromise[reqKey]) return cachedPromise[reqKey];
//Store the promise in the cache for lastCall retrieval
return cachedPromise[reqKey] = $http.get(ENV.apiEndpoint + '/' + request.storeSlug + '/pages.json?code=' + request.promoCode)
.then(function(result){
return result.data; //You can alter data and send as well
}, function(data, status){
return $q.reject('some error'); //or return some data
}).finally(function(){
//remove the cache from the map, once promise is resolved.
delete cachedPromise[reqKey];
});
}
}
return Pages;
});
In your first controller do:-
app.controller('RandomCtrl', function($scope, Pages){
//Build your request.
Pages.details.getPages(request).then(function (data) {
$scope.data = data;
});
});
In your second controller just do the same:-
app.controller('OtherCtrl', function($scope, Pages){
//Pass the flag to get the cached data.
Pages.details.getPages(request).then(function (data) {
$scope.tempPage = data;
});
});

Categories

Resources