How to manage data offline and online in ionic - angularjs

I am working on blogging app . i want to save article list on SqlLite as well. I need to fetch all blogs in once. ( having more than 2000) blogs .
following is my controller code.
var promise= userService.getArticles();
promise.then(function(data) {
$scope.articles = data;
}, function(error) {
console.log('Failed for some reason:' + error);
});
and factory code is
angular.module('starter.controllers')
.factory('userService', function($http,$q) {
var articleList = [];
return {
getArticles : function() {
var deferred = $q.defer();
$http({
url: "https://www.website.com/getallinfo?access_token=94529e5d",
data: { starLimit: 0, endLimit: 150,created_date: 0 },
method: 'POST',
withCredentials: true,
}).success(function (data, status, headers, config) {
deferred.resolve(data);
}).error(function (err) {
deferred.reject(error); //
})
return deferred.promise;
},
}
which is returing result.
I need to save that data in sqllite as well. Also i want to show data as offline.
I am not sure how to proceed this. Kindly help.
Thanks

Most offline applications use the local storage as a cache and updating that if there's a connection available.
An easy way to do this is to:
Grab the articles from the local storage and assign it to a $scope variable (this can be undefined)
Request the articles as normal from the server
On a successful callback overwrite the local storage and reassign the scope variable.
Exampe code:
// The article service
myApp.factory('articles', function($q, $timeout, $window, loremIpsum) {
// Initialize by grabbing the articles from the local cache (if any)
var items = angular.fromJson($window.localStorage.getItem('myArticles'));
// Construct our service
var service = {
items: items,
refresh: function() {
// Sync with the server
var defer = $q.defer();
// For this demo I'm using a timeout (this also allows for some artificial lag).
// Replace this with your $http calls.
$timeout(function() {
// Here I'm generating some total random items that we're resolving
// Also not needed in your app
var c = 100, result = [];
while (c--) {
result.push({
title: loremIpsum.getRandomLine(2),
description: loremIpsum.getRandomLine(15)
});
}
service.items = result;
defer.resolve(result);
// Store the returned result from the server in the local storage
$window.localStorage.setItem('myArticles', angular.toJson(result));
}, 500);
return defer.promise;
}
};
return service;
});
Plunker example can be found here.

Related

Angular JS saving information from factory AJAX request

I have a factory where I'm getting data from a server using the $http methods:
.factory('$factory', function ($q, $http, $timeout, $state, $ionicHistory, $localstorage) {
var obj = [];
var functions = {
getData: function () {
var dfd = $q.defer();
if(!obj){
$http({
url: remoteUrl+'/getdata',
method: 'POST',
data: {}
}).then(function(response) {
$timeout(function(){
obj = response.data;
dfd.resolve(response.data);
}, 2000)
}, function(response) {
}
}else{
return obj;
}
return dfd.promise;
}
}
}
So this gets the data and puts it in an object. Correct me if I'm wrong, but this method of using a factory for this type of action is so it's not tied to a controller, and can be used anywhere in my application.
With this in mind, I wish to make it so I can get the data anywhere in my app without having to query the server each time. i.e. once it's queried the server the factory saves the response to an object (like I'm doing now). But I'm having trouble accessing the data afterwards in another controller for example.
I've started to make what I think it should look like by using the if(!obj) line, but in the else statement I cant seem to just return the obj object. It throws errors as it's not returning a promise like it's expected too.
I'm not sure if I'm even along the right lines with this.
Thanks
You're returning a promise so you need to always return a promise even on the cached response, you can do it relatively easy at this level by wrapping it in a $q.when (https://docs.angularjs.org/api/ng/service/$q) which will return a promise immediately resolved.
return $q.when(obj);
Although $http service has built in caching, so you may want to take a look under the Cache section.
https://docs.angularjs.org/api/ng/service/$http
https://www.ng-book.com/p/Caching/
This should work:
Insteada assigning obj = [] assign as a null. Usually I prefer callback. You can try this code:
.factory('$factory', function($q, $http, $timeout, $state, $ionicHistory, $localstorage) {
var obj = [];
var functions = {
getData: function(cb) {
// instead of checking !obj you have to check for length or you have to set obj as null
if (obj && obj.length == 0) {
$http({
url: remoteUrl + '/getdata',
method: 'POST',
data: {}
}).then(function(response) {
obj = response.data;
cb(response.data)
}, function(response) {
}
}
else {
cb(obj)
}
}
}
}
})
// You can use callback by following code
$factory.getData(function(response){
// response will come here
})

ANgularJS sync server data with local data on request

There is one thing I can't understand in Angular - how better to sync API data with local data.
My structure object is very big, I just give you simple example.
I have 4 api links.
api_links: {
allCars: '/cars/',
cabrio: '/cars/cabrio',
sedan: '/cars/sedan',
mercedes: 'cars/sedan/mercedes'
}
And this is my local object to keep data locally
$rootScope.apiData = {
cars{
cabrio:{},
sedan: {}
}
}
In my single page app I want to reduce count of requests.
So on common page I'm recieving all Cars and put data to $rootScope.apiData
When I go to sedans page, I check if there any data in $rootScope.apiData.cars.sedan. If it's exist, I don't send request.
But if I start from sedans page, I recieve only sedans. Then when I go to common page - HOW can I check am I need to load more categories.
It's quick abstract example. But I try to find any good solutions or plugins for this and cannot.
Please help me!
UPDATE:
Here is my factory:
var api = angular.module('app.api', []);
api.factory('GetData', ['$http', '$q', '$rootScope', '$location'
function($http, $q, $rootScope, $location) {
var apiUrl = '/api/js/';
var apiMixes = {
dashBoard: 'api/dashboard',
accounts: 'api/accounts',
top: 'top',
logout: 'client/logout',
...
}
var methods = {};
methods.getApi = function(u, params) {
var url = apiMixes[u] + '?' + $.param( params );
var deferred = $q.defer();
// Here I need to check if I have cached data
cachedData(data) = ___TODO___;
if( cachedData(data) ){
deferred.resolve(cachedData(data));
// Turn watching on
$rootScope.$emit("receivedApiUpdate", cachedData(data));
return deferred.promise;
}
// If no - get gata from server and put to cache
$http.get(url, {cache: true}).success(function (res, status, headers, config) {
if(res && res.data){
// Turn watching on
$rootScope.$emit("receivedApiUpdate", res.data);
//LocalData.put(res.data);
deferred.resolve(res.data);
}else{
deferred.reject(status);
}
}).error(function (res, status, headers, config) {
(status==401) && $location.path('login/lock');
deferred.reject(status);
});
return deferred.promise;
};
methods.sendData = function(u, data, o) {
data = data || {};
o = o || {};
var deferred = $q.defer();
var url = '/api/js/'+ apiMixes[u];
$http.post(url, JSON.stringify(data)).success(function(res, status) {
if(res.success && res.data){
// Here I need to update my cache with some new value
o.localUpdate && ___PUT_TO_CACHE_TODO___;
deferred.resolve(res.data);
}
});
return deferred.promise;
};
return methods;
}]);
Here is my Controller
app.controller('MyAccountsCtrl', ["$rootScope", "$scope",
function ($rootScope, $scope) {
// Watcher for api updates
$rootScope.$on('receivedApiUpdate',function(event, response){
if(!response || !response.accounts) return;
renderData(response.accounts);
});
function renderData(accounts){
// This renders to template
$scope.accountList = accounts;
}
}]);
Here is mainCtrl, whick make common request, after it I need to update data in several templates.
app.controller('AppCtrl', ['$rootScope', '$scope', 'GetData',
function ($rootScope, $scope, GetData) {
// Here I fire different request for each page, I keep requests in router.
GetData.getApi( 'accounts' ).then(function(data){
// Show content when page is loaded
$('.main-content').removeClass('hidden');
},function(res){
log('GET DATA FAIL', res);
});
}]);
You need to create a service/factory for this, not use the $rootScope. Also, unless you need the data to be constantly updated, you can use the cache: true option inside your $http call.
angular
.module('app')
.factory('carFactory', carFactory);
function carFactory() {
var factory = {
getData: getData
}
return factory;
function getData(callback) {
$http({
method: 'GET',
url: '/cars',
cache: true
}).success(callback);
}
}
And then inside your controller/directives just inject the carFactory and use carFactory.getData(function(cars) { ... }) when you need the data. If the data doesn't exist, it will $http call for it and afterwards execute the callback function. If it does exist, it will return the data directly to the callback, without an $http call.

ionic framework with SQL lite

I am using ionic framework .
I am implementing following steps
1) As my app runs . it will create db and table
2) As soon as use login, i am saving data into mysql and go to Dashboard page
dbQuery.insertCategory();
$state.go('app.dashboard');
dbQuery is factory .
.factory('dbQuery', function ($http, $q, $cordovaSQLite, localstorage) {
return {
insertCategory: function () {
$http({
url: "http://mydoman.comcategory",
method: 'GET',
withCredentials: true,
}).success((function (result) {
var user_id = localstorage.get('user_id');
var query = "INSERT OR IGNORE INTO categories (category_id, user_id, category_name,category_type) VALUES (?,?,?,?)";
var data = '';
result.forEach(function (category) {
$cordovaSQLite.execute(db, query, [category.id,user_id, category.category_name, category.category_type]).then(function (res) {
console.log("insertId");
}, function (err) {
console.dir(err);
});
});
}))
},
Which is working fine
On Dashboard i am showing categories list and i found nothing .
I did debugging and found insertion of categories taking time to insert.
Is there is any way to do two way data binding
Thanks
Why not use a promise and only redirect to app.dashboard once the success promise triggers?
Example:
dbQuery.insertCategory().then(function(result) {
$state.go('app.dashboard');
}, function(error) {
console.error(error);
});
Then in your factory you can have something like this:
.factory('dbQuery', function ($http, $q, $cordovaSQLite, localstorage) {
return {
insertCategory: function () {
var deferred = $q.defer();
$http({
url: "http://mydoman.comcategory",
method: 'GET',
withCredentials: true,
}).success((function (result) {
var user_id = localstorage.get('user_id');
var query = "INSERT OR IGNORE INTO categories (category_id, user_id, category_name,category_type) VALUES (?,?,?,?)";
var data = '';
result.forEach(function (category) {
isWaiting = true;
$cordovaSQLite.execute(db, query, [category.id,user_id, category.category_name, category.category_type]).then(function (res) {
console.log("insertId");
isWaiting = false;
}, function (err) {
console.dir(err);
isWaiting = false;
});
while(isWaiting) {}
});
deferred.resolve("done");
}))
return deferred.promise;
},
A lot of what I did is just theory, but it should give you an idea. I created a boolean to make the SQL commands synchronous. Since they are synchronous, when the loop ends, you can return your success promise.
There are probably a few other ways you could do it as well. Like maybe move the SQL stuff into its own function and recursively enter it.
Let me know if any of this works for you.

Use $http once from Angular Service

I need to grab some data from my db through an API and make it accessible throughout my Angular app. I understand that Services are good for storing data to be accessed from multiple controllers. However, in the following code I end up with a new $hhtp.get() each time just to get the same data.
Service:
.factory('Playlist', ['$http', function($http) {
var playlist = {};
playlist.getPlaylist = function() {
return $http.get('api/playlist.php')
.then(function (response) {
var data = response.data;
return data;
})
}
return playlist;
}])
Controllers:
.controller('ScheduleCtrl', ['$http', 'Playlist', function($http, Playlist) {
var self = this;
Playlist.getPlaylist()
.success(function(playlist) {
self.playlist_id = playlist.id;
fetchItems();
})
var fetchScheduleItems = function() {
return $http.get('api/schedule.php/'+self.playlist_id).then(
function(response) {
if (response.data === "null") {
console.log("No items");
} else {
self.items = response.data;
}
}, function(errResponse) {
console.error('Error while fetching schedule');
});
};
}])
.controller('PlaylistItemCtrl', ['$http', 'Playlist', function($http, Playlist) {
var self = this;
Playlist.getPlaylist()
.success(function(playlist) {
self.playlist_id = playlist.id;
fetchItems();
})
var fetchPlaylistItems = function() {
return $http.get('api/schedule.php/'+self.playlist_id).then(
function(response) {
if (response.data === "null") {
console.log("No items");
} else {
self.items = response.data;
}
}, function(errResponse) {
console.error('Error while fetching schedule');
});
};
}])
Is there a way to store the Playlist ID without pinging 'api/playlist.php' from every controller?
Update
Here's a Plunkr based on Abhi's answer: http://plnkr.co/edit/or9kc4MDC2x3GzG2dNeK?p=preview
As you can see in the console, it's still hitting the server several times. I've tried nesting CachedData.save() differently, but it doesn't seem to apply.
I would say store your data locally (CachedData factory - rename it to something that makes sense) and inside your getPlaylist method, before doing http call, check CachedData to see if your data is present and if not, then do the http call.
The code will be something like the below. I have just written it free-hand, so there may be some errors, but you get the picture.
.factory('Playlist', ['$http', 'CachedData', function($http, CachedData) {
var playlist = {};
playlist.getPlaylist = function() {
if (CachedData.data) {
// return cached data as a resolved promise
} else
return $http.get('api/playlist.php')
.then(function (response) {
var data = response.data;
cachedData.save(data);
return data;
})
}
return playlist;
}])
// CachedData factory
.factory('CachedData', function() {
var _data;
var cachedData = {
data: _data,
save: function(newData) {
_data = newData;
}
};
return cachedData;
})
EDIT: Also Remove fetchPlaylistItems from the controller and put it in a factory. The controller is just a glue between your viewmodel and view. Put all your business logic, http calls in a service.
EDIT: I have setup a plunk for you here. I hope it helps.
EDIT: John, the reason you are seeing two server calls is because they are from different controllers ManiCtrl1 and MainCtrl2. By the time, the getPlaylist method from MainCtrl2 is called, the first http request didn't get the chance to finish and save the data. If you add a timeout to MainCtrl2 before calling your service method, you will see that the data is retrieved from cache. See this updated plunk for a demo.
This will be more useful in an app with multiple views, where you don't want to reload data when navigating back to a view. (Ideally, depending on the type of data you are caching, you will have some expiry time after which you would want to reload your data).
You could do some pre validation when calling that method.
var playlist = {};
playlist.playlist = [];
playlist.getPlaylist = function () {
if (playlist.playlist.length <= 0) { //or some lodash _.isEmpty()
$http.get('api/playlist.php')
.then(function (response) {
playlist.playlist = response.data;
})
}
return playlist.playlist;
};
Hope it helps!

Angular js $http factory patterns

I've a factory named as 'userService'.
.factory('userService', function($http) {
var users = [];
return {
getUsers: function(){
return $http.get("https://www.yoursite.com/users").then(function(response){
users = response;
return users;
});
},
getUser: function(index){
return users[i];
}
}
})
In the first page, On button click I want to call getUsers function and it will return the 'users' array.
I want to use 'users' array in the second page. How can I do it?
P.s: I'm using getters and setters to store the response in first page and access the same in second page. Is this the way everyone doing?
1). getUsers. For consistence sake I would still use the same service method on the second page but I would also add data caching logic:
.factory('userService', function($q, $http) {
var users;
return {
getUsers: function() {
return users ? $q.when(users) : $http.get("https://www.yoursite.com/users").then(function(response) {
users = response;
return users;
});
},
getUser: function(index){
return users[i];
}
};
});
Now on the second page usage is the same as it was on the first:
userService.getUsers().then(function(users) {
$scope.users = users;
});
but this promise will resolve immediately because users are already loaded and available.
2). getUser. Also it makes sense to turn getUser method into asynchronous as well:
getUser: function(index){
return this.getUsers().then(function(users) {
return users[i];
});
}
and use it this way in controller:
userService.getUser(123).then(function(user) {
console.log(user);
});
Here is the pattern i have followed in my own project. You can see the code below
.factory('userService', function($http) {
return {
serviceCall : function(urls, successCallBack) {
$http({
method : 'post',
url : url,
timeout : timeout
}).success(function(data, status, headers, config) {
commonDataFactory.setResponse(data);
}).error(function(data, status, headers, config) {
commonDataFactory.setResponse({"data":"undefined"});
alert("error");
});
}
},
};
After service call set the response data in common data factory so that it will be accessible throughout the app
In the above code I've used common data factory to store the response.

Resources