Make response from API call accessible from all controllers - angular - angularjs

I have a controller that takes the user input and post's it to my API and captures the response which happens to be the user data. That's great however I need to access the user data on the dashboard which has a different controller. I can't figure out how to abstract the API call into a service and fetch the response from the dashboard controller. Is there an easier way? The login functionality works perfect however the res.data.user appears inaccessible. My question is how do I make the response accessible from a diffefrent controller?
Controller (signup):
angular.module('app').controller('Signup', function ($scope, $http, toastr, $location, $auth) {
// Submit request to Sails.
$http.post('/api/user/email', {
email: $scope.signupForm.email.toLowerCase(),
password: $scope.signupForm.password
})
.then(function onSuccess(res){
$auth.setToken(res.data.token)
$location.path('/dashboard')
$scope.user = res.data.user;
})
})
HTML (dashboard):
<div class="container" ng-show="isAuthenticated()" ng-controller="Dashboard">
<p>License: {{user.license}}</p>
</div>

Have a factory or service that stores user data to pass around. Inject it in the original
.factory('storeData', function () {
var userData = {}; //it's empty, thats ok
function dataAll () {
return userData;
};
return dataAll;
}
.controller('Signup', function ($scope, $http, toastr, $location, $auth, ***storeData***) {
//remove *** obviously
var importedData = storeData();
// or storeData.dataAll() for some reason OP said this didn't work but it works in my code
$http.post('/api/user/email', {
email: $scope.signupForm.email.toLowerCase(),
password: $scope.signupForm.password
})
.then(function onSuccess(res){
$auth.setToken(res.data.token)
$location.path('/dashboard')
will modify factory object that was returned to contain res.data.user
instead of blank. javascript does not make copies, but instead links
back to the original factory object. So changes take place on the
factory object.
importedData = res.data.user
$scope.user = res.data.user;
})
})
new controller example
.controller('test', function ($scope, storeData) {
console.log(storeData() + 'see if data shows up');
}

Related

Data from service arent updated

I'm very new to AngularJS and programming aswell, so it can be easy question for you but I'm struggling with it for plenty of hours and can't get my thinking straight.
So, my goal is simple, to have facebook login on my app (webpage), I'm using Ciul angular-facebook module, this actually works but not in the way I want it. When user loggs I want to show his name and photo, but now I have to manually reload page, then it shows, until then it won't. Also I'm storing logged user into localStorage (works ok).
The problem is that after logging in the data aren't updated. In my html code I tried to call them from controller or service, but both had old data, and I don't know how to update it without reloading the page. The most interesting part is when I try it with simple variable and it works like a charm.
Here is my Service
app.factory('mainService', ['$window', '$location', '$route', '$filter', 'Facebook', function (win, $location, $route, $filter, Facebook) {
var scope = {
fbLogin: false,
fbUid: 0,
fbAccessToken: 0,
vkLogin: false
};
var user = {};
if(localStorage.getItem('user') != null) {
user = JSON.parse(localStorage.getItem('user'));
} else {
user = null;
}
return {
scope : scope,
user : user,
fbLogin : function () {
Facebook.login(function (response) {
scope.fbLogin = response.status;
scope.fbAccessToken = response.authResponse.accessToken;
scope.Uid = response.authResponse.userID;
Facebook.api('/me?fields=id,name,email,picture', function (response) {
localStorage.setItem('user', JSON.stringify(response));
});
});
console.log('setting user');
user = JSON.parse(localStorage.getItem('user'));
},
fbLogout : function () {
Facebook.logout(function (response) {
});
user = null;
localStorage.removeItem('user');
},
removeAuth : function () {
Facebook.api({
method: 'Auth.revokeAuthorization'
}, function (response) {
Facebook.getLoginStatus(function (response) {
scope.fbLogin = response.status;
});
});
}
};
}]);
Here is my Controller
app.controller('IndexController', ['$scope', '$location', '$http', 'mainService', function ($scope, $location, $http, mainService) {
$scope.ms = mainService;
$scope.user = mainService.user;
$http({
method: 'GET',
url: 'backend/api/v1/getItems.php',
headers: { "Content-Type": 'text/json; charset="utf-8"' }
})
.success(function(data, status) {
//alert("status is : "+status);
if(status === 200) {
$scope.items = data;
}
})
.error(function(data, status) {
$scope.items = [];
});
}]);
Calling login function < li >< a href="#/" ng-click="ms.fbLogin()">Login using Facebook
Calling data in html {{user.name}} or {{ms.user.name}}
Well, the same problem is also with logging out.
Hey so it's hard for me to say this is the exact problem but from the looks of it everything you have so far is actually pretty good.
There's only one problem I spot and it just boils down to understanding objects are bound to variables by reference. If you're not sure what that means I would say do some research on that first since it's a fundamental part of javascript.
What seems to be the problem here is that you are setting 'user' to an empty object in your service. In your controller you are then assigning $scope.user to that same object empty.
However in your login function you are assigning user to the new JSON from the storage. First, this should also be inside the success callback, right after you set the localstorage.
I'm not familiar with that module but I'm going to assume that those are async functions. Therefore you're grabbing that local storage data before it's even been set.
Also, by setting 'user' to a new object you have updated it's value in the service but not in the controller.
Because it's bound by reference, 'user' in the service now points to a new object, while $scope.user is still pointing to that original empty object.
To solve this problem you can do two things:
Handle the callback differently so that you reassign $scope.user to the new data.
Or you can take advantage of object reference.
You can keep most of your code the same, but in your service, instead of assigning the data to 'user', assign it to a property on user.
Facebook.api('/me?fields=id,name,email,picture', function (response) {
localStorage.setItem('user', JSON.stringify(response));
user.data = response;
});
Since both the service and the controller are referencing the same object, you will have access to that new data property on $scope.user.
HTML:
<span>{{user.data.name}}</span>

angularjs Transfer data between pages

My English is very bad,at first Controller,I post data form The server,and i got a $rootScope.YD for Transfer data .but when i use the YD in the second page,it does't work.can you help me ?
.controller('yd_homeCtrl',function($rootScope, $scope, $state, $http,$ionicActionSheet, $ionicModal)
{
$scope.getReadList = function ()
{
var url = $rootScope.rootUrl + "/read.php";
var data = {
"func":"getReadList",
"unionid":$rootScope.userinfo.unionid,
"fr":1
};
encode(data);
$rootScope.LoadingShow;
$http.post(url, data).success(function (response)
{
$rootScope.LoadingHide();
$rootScope.YD=response.data.result[0];
$state.go('yd_improve')
}).error(function (response, status)
{
$rootScope.LoadingHide();
$rootScope.Alert('连接失败![' + response + status + ']');
return;
});
}
})
.controller("yd_improveCtrl",function($rootScope, $scope, $state, $http, $ionicActionSheet, $ionicModal, $stateParams, $interval, $sce, $ionicHistory,$ionicSlideBoxDelegate)
{
$scope.text="";
angular.forEach($rootScope.YD,function(value,key){
if(key==0)
{
//alert(1111111);
//alert(value.text);
$scope.text=value.text;
alert($scope.text);
}
});
});
there is app.js state:
.state('yd_improve', {
cache: false,
url: '/yd_improve/:id',
onExit: function ()
{
var v = document.getElementById("audio");
v.pause();
v.src = "";
},
templateUrl: 'templates/yd_improve.html',
controller: 'yd_improveCtrl'
})
Use $broadcast service. It's the most reliable solution for broadcasting events and passing parameters between controllers.
Don't rely on storing data in service because on page refresh, service also gets refreshed and you will lose the data stored in service's variable. So maintaining state in service is highly unrecommended. Use $broadcast instead.
You have a couple of options. Create a service which stores the information. In first controller set the data. And in second controller get the data.
Or you can use $broadcast. This publishes an event with a name you give it and the data you parse through. You do this on first controller. And on second controller listen for the event using $rootScope.on.
But I'd recommend using a service its best solution in terms of memory and performance.

AngularJS service with HTTP request, offline status

I have a AngularJS app with a service that load a HTTP request, that works all fine, but...
When my app is offline at the beginning, i show a "offline" page with a retry button, when i click the button and the APP is online I need the service to load the data.
The problem is that nothing happen when I click the button, the online check works fine but the web service is never called :(
my service:
.service('webService', function ($http, $q){
var defferer = $q.defer()
var webservice_url = "http://mywebservice_url/";
$http.get(webservice_url+"?action=get_settings").success(function(data) {
defferer.resolve(data);
});
return defferer.promise;
})
my controller:
.controller('NetworkCtrl', function($scope, $location, $timeout, webService) {
$("#tryagain-button").click(function(e) {
e.preventDefault();
if(checkifonline()) {
webService.then(function(data) {
$scope.data = data;
});
}
});
})
First of all, using jQuery within an controller is absolutely not how angularjs works. Use the ng-click directive. Even if your service would be implemented correctly, it wouldn't work since AngularJS will not get notified about model changes in the scope (you would need to use $apply, but just stick to the ng-click directive)
Second, the service function takes a constructor function. Within this constructor you're making an http request (while you're offline) and the promise is resolved. You will never make a new request, since the service always retuns the same promise. Your service should look like:
.service('webService', function ($http){
var webservice_url = "http://mywebservice_url/";
this.getSettings = function(){
return $http.get(webservice_url+"?action=get_settings");
};
})
and your controller:
.controller('NetworkCtrl', function($scope, $location, $timeout, webService) {
$scope.onClick = function(){
webService.getSettings().success(function(data){
$scope.data = data;
}
}
})
and your HTML button:
<button ng-click="onClick()">reload</button>

Execute scope function from service parameter

I am trying to create a service when I can set my formSubmit.
For example. In controller A I call "service.setFormSubmit(doThis(obj))" and in controller B I call "service.getFormSubmit()". Where it will execute the function doThis(obj) in controller B.
UPDATE - Re-formulated question.
I have 1 view where I want to edit or create a category. This means I need a dynamic ng-submit. I want to to this in the controller. So like this:
$scope.editCategory = function(obj) {
$scope.formSubmit = 'editCategory'
}
And on the create I want to change the formSubmit var to createCategory of course.
So I can make a difference between creating and editing the category.
Is this possible? Would be really nice if someone has a way to do this..!
Thanks in advance!
Instead of passing around strings which need to be eval'ed, use the service to share functionality directly between controllers.
The service can be dirt-simple:
.factory('MyService', function(){
var service = {};
return service;
});
Once injected and assigned to scope variables in both controllers you have an intermediary unit which can act as a modifiable channel for cross-controller collaboration.
.controller('FirstController', function($scope, MyService){
$scope.service = MyService;
})
.controller('SecondController', function($scope, MyService){
$scope.service = MyService;
$scope.service.create = function(obj){
console.log('Creating');
}
$scope.service.edit = function(obj){
console.log('Editing');
}
})
From the scope of FirstController, you can now call the function also available on the scope of SecondController:
<div ng-controller="FirstController">
<input type="checkbox" ng-model="button.type"> Toggle create/edit<br/>
<button ng-if="button.type" ng-click="service.create(obj)">Create</button>
<button ng-if="!button.type" ng-click="service.edit(obj)">Edit</button>
</div>
Demo
If you aren't reloading the page you can create an encapsulated variable in your service. Your set call would assign the value passed to that variable and your get call would return that variable to the caller.
One way I have achieved passing the data is to submit the form using the service and return a Json result to the service. Store the Json object in the encapsulated variable on the return and then pass a success or failure to the controller. When successful, let the controller redirect the view which will redirect using angular routing and ng-view. Once the new view, along with the new controller is loaded into the page, you can call the variable in your service to retrieve the data on the next controller.
Example Code:
app.factory('service', function ($q, $http) {
var savedData;
return {
loadData: function() {
return data;
},
search: function (parameters) {
var searchURL = '/MVCController/Search?parameter1=' + parameters.one +
'&parameter2=' + parameters.two;
var deferred = $q.defer();
$http.get(searchURL).success(function (data) {
savedData = data;
deferred.resolve(true);
}).error(function(data) {
data = 'An error occurred while searching: ' + data;
savedData = data //(if you want to save the error)
deferred.reject(data);
});
return deferred.promise;
}
}
});

How would I create a factory that only queries the database once, but can be used by many controllers?

I have a 'messages' factory that will query my database for a list of messages.
I'm using the list of messages in two different places. Once to add a message count indicator, and then once to show a list of messages. Since I'm injecting the service into two different controllers, it seems like it's creating two instances of my factory, and hitting the database twice for the list.
How would I set things up to only ask for the list once, and use the list for both display and count purposes in both controllers?
My factory looks like this:
myApp.factory('messagesService', [
'$rootScope',
function($rootScope) {
var messages = [];
function query() {
// Would actually hit the database asyncronously
messages = ['one', 'two', 'three', 'four'];
console.log('query');
$rootScope.$emit('messages.update');
}
function all() {
return messages;
}
return {
query: query,
all: all
}
}
]);
My controllers are using blocks like this to watch for changes:
$rootScope.$on('messages.update', function() {
$scope.messagesCount = messagesService.all().length;
});
But it means i need a messagesService.query(); in each controller for things to be reliable.
So here are a few jsFiddle examples of it as I have things now:
Doesn't work (only updates the header): http://jsfiddle.net/TSLfc/1/
Works but would break if I didn't load the dashboard controller:
http://jsfiddle.net/TSLfc/2/
Works every time, but queries the server twice:
http://jsfiddle.net/TSLfc/3/
Is there a better way to organize my code? Should I build out the messages factory into it's own full module?
Here (Plunkr) is how I would do it:
I have gone back and modified my previous answer, updating with what we discussed in the comments below as well as using promises instead of the timeout as an asynchronous simulation I was showing before (see revision history for reference).
I also removed every variable/function that didn't need to be returned to the controller from the service object, if it doesn't need to be accessed via the controller than it doesn't need to be included on the returned object.
var myApp = angular.module('myApp', []);
myApp.factory('messagesService', [
'$q',
'$rootScope',
'$http',
function ($q, $rootScope, $http) {
var mService = {};
mService.messages = [];
var queryInit = false;
// We don't need to access this function in the controller
// So I am not going to attach to the returned object
var getMessages = function () {
// Stops each controller from getting messages when loaded
if (!queryInit) {
queryInit = true;
// Using the $q promise library we use 'then()' to handle
// What happens after the async call is returned
// The first function parameter is the success/resolve callback
// The second function parameter is the error/reject callback
mService.query().then(function (successResults) {
// Tell all of the controllers that the data has changed
$rootScope.$broadcast('messages.update');
}, function (errorResults) {
console.error(errorResults);
});
}
};
// Used to force an update from the controller if needed.
mService.query = function () {
var deferred = $q.defer();
$http.get('path/to/file.php')
.success(function (data, status, headers, config) {
// assign the returned values appropriately
mService.messages = data;
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(data);
});
return deferred.promise;
};
mService.getCount = function () {
return mService.messages.length;
};
mService.all = function () {
return mService.messages;
};
// Initialize the messages
// so we don't need to get the messages in each controller
getMessages();
return mService;
}]);
In your html, on your first controller setup an init function (ng-init="init()") that instantiates the factory:
<div ng-app="myApp">
<div ng-controller="HeaderCtrl" class="header" ng-init="init()">
Messages Count: {{ messageCount }}
</div>
<div ng-controller="DashboardCtrl" class="dashboard">
<ul ng-repeat="message in messages">
<li>{{ message }}</li>
</ul>
<button ng-click="getMessages()">Check for new messages.</button>
</div>
</div>
And in your controllers you just have the $rootScope.$on('messages.update' fn) and you can call manually by calling the services query() function which returns the promise:
myApp.controller('HeaderCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messageCount = messagesService.getCount();
});
// Manual call, if needed
$scope.getMessageCount = function () {
messagesService.query().then(function (successCallback) {
$scope.messageCount = messagesService.getCount();
});
};
}]);
myApp.controller('DashboardCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messages = messagesService.all();
});
// Manual call, if needed
$scope.getMessages = function () {
messagesService.query().then(function (successCallback) {
$scope.messages = messagesService.all();
$rootScope.$broadcast('messages.update');
});
}
}]);
You can set cache:true on a $http request. There are numerous ways to data bind within angular without needing to use the $broadcast approach you are using. Also note, $broadcast from a scope will be receievd by all descendent scopes, so no need to inject $rootSCope just for that purpose, can listen on $scope.
Here's one approach that controllers use promise of $http to retrieve data. I used a button click to retrive data for DashControl so can see that request does get cached
myApp.factory('messagesService',function($http) {
return{
query:function query(callback) {
/* return promise of the request*/
return $http.get('messages.json',{ cache:true}).then(function(res){
/* resolve what data to return, can set additional properties of the service here if desired*/
return res.data
}).then(callback);
}
}
});
myApp.controller('HeaderCtrl',function($scope, messagesService) {
messagesService.query(function(messages){
$scope.messagesCount = messages.length;
});
});
myApp.controller('DashboardCtrl', function($scope, messagesService) {
/* use button click to load same data, note in console no http request made*/
$scope.getMessages=function(){
messagesService.query(function(messages){
$scope.messages = messages;
})
}
});
Essentially in this scenario, whatever controller calls the factory service first will generate the data cache
DEMO
I would do it like that:
myApp.factory('messagesService', function() {
var expose = {
messages: []
};
expose.query = function () {
// Would actually hit the database asyncronously
expose.messages = ['one', 'two', 'three', 'four'];
console.log('query');
};
// Initialization
expose.query();
return expose;
}
);
And in your controllers:
$scope.messagesCount = messagesService.messages.length;
Model with broadcasting and pre-hitting database looks heavy for me.
So here is code, that can be embedded in service:
var sv = this;
var deferred = sv.$q.defer();
if (sv._running) {
return sv._running;
}
sv._running = deferred;
It based on reusing promise. To make it query database once - just don't set sv._running to false and it will always return first obtained result.

Resources