I'm creating an hybrid app with Ionic that will load some JSON files that are stored on the device. Since the same data will be used in several different states, I thought it would make sense to store the response to the JSON request and reuse it, rather than re-reading the JSON file over and over.
This question seems to address that scenario, but I can't seem to get it to work. Although the template works when I used a simpler $http.get().success() request, it never fills in since I started trying to use this service.
app.factory('localJsonService', function($http, $q) {
var localJsonService = {};
localJsonService.returnLegislators = function() {
if (this.legislators) {
return $q.when(this.legislators);
}
return $http.get('/data/legislators.json').then(function(response) {
this.legislators = response.data;
return this.legislators;
});
}
return localJsonService;
});
//old malfunctioning controller
app.controller('profileController', function($scope, $stateParams, localJsonService) {
$scope.legislators = localJsonService.returnLegislators();
$scope.legislator = $scope.legislators[$stateParams.seq_no-1];
console.log($scope.legislator); //displays undefined
});
//EDIT: newer, working controller (but still loads JSON file on each new state)
app.controller('profileController2', function($scope, $stateParams, localJsonService) {
localJsonService.getLegislators().then(function(legislators){
$scope.legislator = legislators[$stateParams.seq_no-1];
});
});
Is it just a simple change to the service that I'm missing? Or am I going about this the wrong way entirely? I'm running AngularJS v1.3.13, but I'm not opposed to a different version, if that will help.
Thanks for any help you can give me.
Use a promise callback and assign your variables in that callback:
localJsonService.returnLegislators().then(function(legislators){
$scope.legislators = legislators;
$scope.legislator = legislators[$stateParams.seq_no-1];
console.log($scope.legislator);
});
If the service data response is not changing, I'd rather user localStorage to cache your response. I'll suggest you ngStorage, that makes it really easy to use localStorage and sessionStorage.
P.S: if datas are changing, then use sessionStorage, that is persistant upon session, but cleaned after app restart.
Example after injecting $localStorage:
Set a default value :
var jsonDefaultVariable = {};
jsonDefaultVariable["myDatas"] = false;
$localStorage.$default(jsonDefaultVariable);
Check for cache :
if($localStorage["myDatas"] !== false){
factory.myDatas = $localStorage.myDatas;
}else{
$http(....).success(function(data){
$localStorage.myDatas = data;
factory.myDatas = data;
});
Related
This is my task: Get search selections from server when route to a search page or it child pages (eg: #/search/option1). The problem is how to share the selections to all search relative pages and don't request server twice and don't expose selections to root scope?
I don't know weather I describe clearly, not good at it. Thanks for your reading. Appreciate any tip, any.
You could get the result from the server and then reuse the result throughout you application.
Create a factory (or service) that retrieves and stores the values from the server:
app.factory('DataService', function($http) {
var values;
var requestValues = function() {
$http.get("/api/getValues").then(
function(results){
values = results;
});
};
var getValues = function() {
return values;
};
return {
requestValues : requestValues, // this will make a http request and store the result
getValues: getValues // this will call the stored result (without making a http request)
}
});
Now you have two functions in your factory.
requestValues() to make the http request and save the result locally
getValues() to get the locally saved values without making a http request.
Once requestValues() has been called, you should be able to call getValues() from anywhere to get the values without making a new http request.
myApp.controller('MyController', function ($scope, DataService) {
var init = function (){
DataService.requestValues(); // this will make the http request and store the result
$scope.items = DataService.getValues(); // this will get the result
};
var justGetValues = function(){
$scope.items = DataService.getValues(); // this will get the result (without making a http request)
};
});
Now you simply have to call DataService.getValues() whenever you need the values. (You might want to wrap these in a promise. I have refrained from doing this due to simplicity)
I am using AngularJS, ui-router and $resource for RESTful webservices.
A button in html view is clicked that calls below function i.e. $scope.login(). Consequently a REST service (through $resource) is called and returns a user in case user/pass are correct,
$scope.login = function() {
myfactory.get({
email: $scope.user.email,
password: $scope.user.password
},function(user) {
accessmgr.grantAccess(user); //Line of interest - loi1
$state.go('app.dashboard-v1'); //Line of interest2 - loi2
}, function(x) {
if (x.status == 401)
$scope.authError = 'Email or Password not right';
else
$scope.authError = 'Server Error! Are you connected to internet?';
});
}
in case above successfully executes, another factory function (loi1 above) is called to store user instance in $localStorage as below;
myapp.factory('accessmgr', function($localStorage) {
//var User = {};
return {grantAccess: function(usr) {
$localStorage.user = usr;
}
}});
and ui-router $scope.go(...) takes the user to dashboard.
Problem:
Sometimes $state.go(...) executes before accessmgr.grantAccess(...) causing exceptions as the new state reads user from $localStorage that is not yet written. Reload the page manually solves the problem.
Any help would be really appreciated.
localStorage itself works in synchronous manner, but ngStorage's $localstorage doesn't. The latter is intended to be used in conjunction with scope and is tied to Angular digest cycles. My guess is that
myapp.factory('accessmgr', function($localStorage) {
return {grantAccess: function(usr) {
$localStorage.user = usr;
$localStorage.$apply();
}
}});
may help. ngStorage doesn't really shine when being used like this, probably JS generic library like store.js applies better.
A good alternative is to use model that acts as single source of truth and dumps the data to localStorage under the hood. Depending on the scale of the project, js-data-angular can be considered a solid solution for that.
ngStorage's $localStorage cannot be referred directly without using watchers (not recommended as per here, alternatively it can to be passed as a reference to hook to $scope as mentioned as recommended approach here.
For me, I was using $localStorage through a factory and I tied it to rootScope as below;
$rootScope.$storage = $localStorage;
and consequently
myapp.factory('accessmgr', function($localStorage) {
$rootScope.$storage = $localStorage;
return {
grantAccess: function(usr) {
$rootScope.$storage.user = usr;
},
getUser: function() {
return $rootScope.$storage.user;
}
}});
I'm trying to take the response of an $http request and save it to a custom cache. I want to then use that cache to display data into the view. I thought the cache would be checked automatically on each request before fetching new data, but that doesn't seem to be working for me.
The problem I'm having is I can't seem to save the data. The following function needs to make 2 requests: articles and images.
getImages: function() {
var cache = $cacheFactory('articlesCache');
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
});
});
return cache;
}
This creates the custom cache, but there's no data in the returned object. I know now that I can't save the data this way, but I don't know why or how I would go about doing it.
Can anyone explain how storing response data works? Where, if at all, does using promises come in here?
Your return statement executes before the code in your then function does. If you want to return the cache you'll want to run everything through the $q service and then return the resolved promise.
This is probably not the best way to use $cacheFactory. Typically you'd expose your cache as a service at a higher level and then access the cache via the service where needed.
So on your main module you'd have something like this to create the cache.
.factory('cache', function ($cacheFactory) {
var results = $cacheFactory('articleCache');
return results;
})
Then where ever you need the cache you inject it into the controller and use cache.get to retrieve the data from it.
If you want to use $q to implement this, your code would look something like the code below. (Disclaimer: I've never used $q with $cacheFactory like this, so without all of your components, I can't really test it, but this should be close.)
var imageService = function ($http, $q,$cacheFactory) {
var imageFactory = {};
imageService.cache = $cacheFactory('articlesCache');
imageFactory.getImages = function () {
var images = $q.defer();
$http.get(posts)
.then(function (data) {
var articles = data;
angular.forEach(articles, function (article) {
var imageId = {id: article.image_id};
$http.post(images, imageId)
.then(function (response) {
article.image = response;
cache.put(article.url, article);
});
images.resolve(cache.get('articlesCache'))
});
});
return images.promise
app.factory('ImageService', ['$http', '$q', '$cacheFactory', imageService]);
});
I adapted the code from this answer: How to get data by service and $cacheFactory by one method
That answer is just doing a straight $http.get though. If I understand what you're doing, you already have the data, you are posting it to your server and you want to avoid making get call to retrieve the list, since you have it locally.
In my app, there is some data will be used by many views or controllers. You can think they are some data dictionary, like status map definition.
Now my solution is to get them in AppContoller, and put them into $scope or $ rootScope. (the $scope in AppController is the parent scope of all controllers, so the data is accessible in all controllers.)
But the problem is, it will be initiated with $resource asynchronously, so maybe they are not ready to be used. Because all data will be got with $resource. Sometime the other data is got before the necessary global data.
Now in the controllers using the global data, I have to check the global data, and if it is not ready, call the initializing function later with timeout.
So, my question is, is there a better solution to initialize the necessary data used by all app?
Well, as far as i understand your problem, you need to ensure that when your app starts, you have some data fetched from server that can be used globally throughout your app.
My suggestion would be to go by the following approach,
Create a service to hold your global data.
var app = angular.module('myApp', []);
app.service('constantService', function() {
// All your constants declared here.
});
Now in your app.run method, make an ajax call or whatever you want to do, and initialize all the constants inside the service.
app.run(function($rootScope, $http, constantService) {
/*
Make an ajax call here and fetch all the Constants from your server.
var request = {};
request.method = 'POST';
request.data = {};
request.url = url;
var promise = $http(request);
var service = this;
promise.success(function(data, status, header, config) {
if (showLoader === true || showLoader === undefined) {
service.hideModal();
}
successCallback(data);
});
promise.error(function(data, status, header, config) {
});
*/
});
You can show a loading message while these constants are being loaded, to avoid user intervention during the call.
I have arrays stored in Firebase, one of which I need to retrieve when a user logs in. Each user has their own array which requires authentication for read. (It would be inconvenient to switch to another data structure). Since $firebase() always returns an object, as per the docs, I'm using the orderByPriority filter. However, if I do simply
$scope.songs = $filter('orderByPriority')($firebase(myref));
that doesn't work as songs always get an empty array.
I don't understand why this happens, but what I've done to solve it is use the $firebase().$on('loaded',cb) form and applied the filter in the callback. Is this a good solution?
The drawback is that I cannot do $scope.songs.$save()
Here's my controller, including this solution:
.controller('songListController', function($scope, $rootScope, $firebase, $filter, $firebaseSimpleLogin){
var authRef = new Firebase('https://my-firebase.firebaseio.com/users'),
dataRef;
$scope.loginObj = $firebaseSimpleLogin(authRef);
$scope.songs = [];
$rootScope.$on("$firebaseSimpleLogin:login", function(event, user) {
// user authenticated with Firebase
dataRef = $firebase(authRef.child(user.id));
dataRef.$on('loaded', function(data){
$scope.songs = $filter('orderByPriority')(data);
});
});
//other controller methods go here
$scope.save = function(){
if (!$scope.loginObj.user)
{
alert('not logged in. login or join.');
return;
}
//Was hoping to do this
//$scope.songs.$save().then(function(error) {
//but having to do this instead:
dataRef.$set($scope.songs).then(function(error) {
if (error) {
alert('Data could not be saved.' + error);
} else {
alert('Data saved successfully.');
}
});
};
});
---Edit in response to Kato's answer---
This part of my app uses Firebase as a simple CRUD json store without any realtime aspects. I use $set to store changes, so I think I'm okay to use arrays. (I'm using jQueryUI's Sortable so that an HTML UL can be re-ordered with drag and drop, which seems to need an array).
I don't need realtime synchronisation with the server for this part of the app. I have a save button, which triggers the use of the $scope.save method above.
The problem with the approach above is that orderByPriority makes a single copy of the data. It's empty because $firebase hasn't finished retrieving results from the server yet.
If you were to wait for the loaded event, it would contain data:
var data = $firebase(myref);
data.$on('loaded', function() {
$scope.songs = $filter('orderByPriority')(data);
});
However, it's still not going to be synchronized. You'll need to watch for changes and update it after each change event (this happens automagically when you use orderByPriority as part of the DOM/view).
var data = $firebase(myref);
data.$on('change', function() {
$scope.songs = $filter('orderByPriority')(data);
});
Note that the 0.8 release will have a $asArray() which will work closer to what you want here. Additionally, you should avoid arrays most of the time.