I'm working on an Angular project which uses Breeze and zStorage to manage data. When I try to refresh data, it pushes the new data to the storage without removing the old data.
function getAll(forceRemote, page, size, nameFilter) {
var self = this;
// Only return a page worth of devices
var take = size || 20;
var skip = page ? (page - 1) * size : 0;
if (self.zStorage.areItemsLoaded(entityName) && !forceRemote) {
// Get the page of Peripherals from local cache
return self.$q.when(getByPage());
}
// Load all devices to cache via remote query
return EntityQuery.from('Devices')
.orderBy(orderBy)
.toType(entityName)
.using(self.manager).execute()
.then(querySucceeded)
.catch(self._queryFailed);
function querySucceeded(data) {
self.zStorage.areItemsLoaded(entityName, true);
self.zStorage.save();
self.log('Retrieved [Devices] from remote data source', data.results.length, true);
return getByPage();
}
function getByPage() {
var predicate = null;
if (nameFilter) {
predicate = _devicePredicate(nameFilter);
}
var devices = EntityQuery.from(entityName)
.where(predicate)
.orderBy(orderBy)
.toType(entityName)
.take(take).skip(skip)
.using(self.manager)
.executeLocally();
return devices;
}
}
I don't know whether the problem is with my configuration or the zStorage.
It was my own mistake in producing new GUID in the breeze controller, breeze has become very powerful tool for entity management in the client side.
Related
I have service that pulls an object from an API. Some of that object may contain image URLs. The backend currently scans for these, and processes them, (in PHP) by get_file_contents() and translating them to inline data. This is heavily loading the throughput on my server. The reason I am doing this is because I want to cache the images for being offline later, but in a way that I can still just use regular angular to render the object.
I can't do the processing in Javascript in the browser with $http.get() because the site hosting the images is blocking the cross-site request. What I thought to do, then, was to create an <IMG> element in the browser, that called the service back once it was loaded so I can extract the data and process the object with it.
I can't control the service worker to store the get from inside the app, and the URL's are not known by the app at any time before it downloads the API object anyway.
I did think about redoing the service worker to store gets from off my site as well, but that seemed a little bit wrong, and I'm not sure how well it would work anyway, plus, while developing, I switch off the service worker as it means I have to let the entire site load twice for it to refresh completely.
Can anyone help me with a way to get image data via the browser into my service?
If I had found a CORS supportive image host in the first place I may not have needed this and could probably have just used the $http call.
A directive, service and controller are required, as well as a host that supports CORS (Imgur for example). I also used this base64 canvas code.
Here is the javascript code:
// Using this from https://stackoverflow.com/questions/934012/get-image-data-in-javascript
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to
// guess the original format, but be aware the using "image/jpg"
// will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
return dataURL;
// return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
// Used on the img tag to handle the DOM notification feeding into the service
app.directive('notifyimgsvc', function() {
return {restrict : 'A', link : function(scope, element, attrs) {
element.bind('load', function() {
console.log('imgSvc::notify() image is loaded');
console.log("imgSvc::notify(): " + this.src);
imgSvc.notifyLoad(this.src, getBase64Image(this));
});
element.bind('error', function() {
console.log('imgSvc::notify() image could not be loaded');
console.log("imgSvc::notify(): " + this.src);
});
}};
});
// A core service to handle the comms in both directions from requests to data
app.service('imgSvc', [function(netSvc) {
imgSvc = this; // to avoid ambiguoity in some inner function calls
imgSvc.images = {}; // a cache of images
imgSvc.requests = []; // the requests and their callbacks
imgSvc.handlers = []; // handlers that will render images
console.log("imgSvc::init()");
// Allows a controller to be notified of a request for an image and
// a callback to call when an image is added. There should only ever
// be one of these so an array is probaby not needed and any further
// requests should probably throw an error.
imgSvc.registerHandler = function(callback) {
console.log("imgSvc::registerHandler()");
if (imgSvc.requests.length) {
// Already have image requests, so tell the new handler about them
for ( var i in imgSvc.requests) {
callback(imgSvc.requests[i].url);
}
}
// Add the new handler to the stack
imgSvc.handlers.push(callback);
};
// The usage function from your code, provide a callback to get notified
// of the data when it loads.
imgSvc.getImg = function(url, callback) {
console.log("imgSvc::getImg('" + url + "')");
// If we have pre-cached it, send it back immediately.
if (imgSvc.images[url] != undefined) {
console.log("imgSvc::getImg('" + url + "'): Already have data for this one");
callback(url, imgSvc.images[url]);
return;
}
// push an object into the request queue so we can process returned data later.
// Doing it this way als means you can have multiple requests before any data
// is returned and they all get notified easily just by looping through the array.
var obj = {"url" : url, "callback" : callback};
if (imgSvc.handlers.length) {
console.log("imgSvc::getImg('" + url + "'): informing handler");
for ( var i in imgSvc.handlers) {
imgSvc.handlers[i](obj.url);
}
}
imgSvc.requests.push(obj);
};
// Notification of a successful load (or fail if src == null).
imgSvc.notifyLoad = function(url, src) {
console.log("imgSvc.notifyLoad()");
// Save the data to the cache so any further calls can be handled
// immediately without a request being created.
imgSvc.images[url] = src;
// Go though the requests list and call any callbacks that are registered.
if (imgSvc.requests.length) {
console.log("imgSvc.notifyLoadCallback('" + url + "'): scanning requests");
for (var i = 0; i < imgSvc.requests.length; i++) {
if (imgSvc.requests[i].url == url) {
console.log("imgSvc.notifyLoadCallback('" + url + "'): found request");
// found the request so remove it from the request list and call it
var req = imgSvc.requests.splice(i, 1)[0];
i = i - 1;
console.log("imgSvc.notifyLoadCallback('" + url + "')");
req.callback(url, src);
} else {
console.log("imgSvc.notifyLoadCallback('" + url + "'): skipping request for '" + imgSvc.requests[i].url + "'");
}
}
} else {
console.log("imgSvc.notifyLoadCallback('" + url + "'): No requests present??");
}
};
// The notifiy fail is just a logging wrapper around the failure.
imgSvc.notifyFail = function(url) {
console.log("imgSvc.notifyFail()");
imgSvc.notifyLoad(url, null);
};
}]);
// A simple controller to handle the browser loading of images.
// Could probably generate the HTML, but just doing simply here.
app.controller('ImageSvcCtrl', ["$scope", function($scope) {
$scope.images = [];
console.log("imgSvcCtrl::init()");
// Register this handler so as images are pushed to the service,
// this controller can render them using regular angular.
imgSvc.registerHandler(function(url) {
console.log("imgSvcCtrl::addUrlHandler('" + url + "')");
// Only add it if we don't hqve it already. The caching in the
// service will handle multiple request for the same URL, and
// all associated malarkey
if ($scope.images.indexOf(url) == -1) {
$scope.images.push(url);
}
});
}]);
The HTML you need for this is very simple:
<div data-ng-controller="ImageSvcCtrl" style="display:none;">
<img data-ng-repeat="img in images" data-ng-src="{{img}}" alt="loading image" crossorigin="anonymous" notifyimgsvc />
</div>
And you call it within your controllers like this:
var req_url = "https://i.imgur.com/lsRhmIp.jpg";
imgSvc.getImg(req_url, function(url, data) {
if(data) {
logger("MyCtrl.notify('" + url + "')");
} else {
logger("MyCtrl.notifyFailed('" + url + "')");
}
});
i am very new to pouchdb, meaning i have not yet been successfully able to implement an app that uses it.
This is my issue now, in my controller i have two functions:
var init = function() {
vm.getInvoicesRemote(); // Get Data from server and update pouchDB
vm.getInvoicesLocal(); // Get Data from pouchDB and load in view
}
init();
Basically in my app i have a view that shows customer invoices, now i want customers to be able to still see those invoices when they're offline. I have seen several examples of pouchdb and couchdb but all use the "todo" example which does not really give much information.
Now i'm just confused about what the point was in me spending hours understanding couchdb and installing it if in the end i'm just going to be retrieving the data from my server using my API.
Also when the data is returned how does pouchdb identify which records are new and which records are old when appending.
well, i m working on same kind..!this is how i m making it work..!
$scope.Lists = function () {
if(!$rootScope.connectionFlag){
OfflineService.getLocalOrdersList(function (localList) {
if(localList.length > 0) {
$scope.List = localList;
}
});
}else{
if(!$scope.user){
}else {
Common.callAPI("post", '***/*************', $scope.listParams, function (data) {
if (data !== null) {
$scope.List = data.result;
OfflineService.bulkOrdersAdd_updateLocalDB($scope.List);
}
});
}
}
};
so,$scope.List will be filled if online as well as offline based on connectionFlag
note : OfflineService and Common are services.
call method:
$ionicPlatform.ready(function () {
OfflineService.configDbsCallback(function(res) {
if(res) {
$scope.Lists();
}
});
});
u can try calling $scope.Lists(); directly..!
hope this helps u..!
I am trying to build an Angular project with Pusher using the angular-pusher wrapper. It's working well but I need to detect when the user loses internet briefly so that they can retrieve missed changes to data from my server.
It looks like the way to handle this is to reload the data on Pusher.connection.state('connected'...) but this does not seem to work with angular-pusher - I am receiving "Pusher.connection" is undefined.
Here is my code:
angular.module('respondersapp', ['doowb.angular-pusher']).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('Foooooooo')
.setOptions({});
}
]);
var ResponderController = function($scope, $http, Pusher) {
$scope.responders = [];
Pusher.subscribe('responders', 'status', function (item) {
// an item was updated. find it in our list and update it.
var found = false;
for (var i = 0; i < $scope.responders.length; i++) {
if ($scope.responders[i].id === item.id) {
found = true;
$scope.responders[i] = item;
break;
}
}
if (!found) {
$scope.responders.push(item);
}
});
Pusher.subscribe('responders', 'unavail', function(item) {
$scope.responders.splice($scope.responders.indexOf(item), 1);
});
var retrieveResponders = function () {
// get a list of responders from the api located at '/api/responders'
console.log('getting responders');
$http.get('/app/dashboard/avail-responders')
.success(function (responders) {
$scope.responders = responders;
});
};
$scope.updateItem = function (item) {
console.log('updating item');
$http.post('/api/responders', item);
};
// load the responders
retrieveResponders();
};
Under this setup how would I go about monitoring connection state? I'm basically trying to replicate the Firebase "catch up" functionality for spotty connections, Firebase was not working overall for me, too confusing trying to manage multiple data sets (not looking to replace back-end at all).
Thanks!
It looks like the Pusher dependency only exposes subscribe and unsubscribe. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L86
However, if you access the PusherService you get access to the Pusher instance (the one provided by the Pusher JS library) using PusherService.then. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L91
I'm not sure why the PusherService provides a level of abstraction and why it doesn't just return the pusher instance. It's probably so that it can add some of the Angular specific functionality ($rootScope.$broadcast and $rootScope.$digest).
Maybe you can set the PusherService as a dependency and access the pusher instance using the following?
PusherService.then(function (pusher) {
var state = pusher.connection.state;
});
To clarify #leggetters answer, you might do something like:
app.controller("MyController", function(PusherService) {
PusherService.then(function(pusher) {
pusher.connection.bind("state_change", function(states) {
console.log("Pusher's state changed from %o to %o", states.previous, states.current);
});
});
});
Also note that pusher-js (which angular-pusher uses) has activityTimeout and pongTimeout configuration to tweak the connection state detection.
From my limited experiments, connection states can't be relied on. With the default values, you can go offline for many seconds and then back online without them being any the wiser.
Even if you lower the configuration values, someone could probably drop offline for just a millisecond and miss a message if they're unlucky.
On the server side, I'm using Web API with the OData routing convention, which means that my route for getting a single entity looks something like this:
/api/v1/Products(1)
rather than:
/api/v1/Products/1
Normally, in Restangular, I'd be able to get a single entity with something like this:
Restangular.one('Product', 1);
But that doesn't work for my OData endpoint. I've looked at customGET, and setRequestInterceptor but I can't seem to find an example of or figure out how to change the route to match my endpoint. Preferably globally since all of my entities will have this same format.
Any help is greatly appreciated.
Restangular documentation details how to create a custom configuration, you could do the same by editing the source restangular.js but this extensibility point allows us to keep a clean implementation that should be compatible with most customisations or future versions of RestAngular as well as allowing side-by-side standard REST APIs and OData v4 APIs.
How to create a Restangular service with a different configuration from the global one
// Global configuration
app.config(function(RestangularProvider) {
RestangularProvider.setBaseUrl('http://localhost:16486');
RestangularProvider.setRestangularFields({ id: 'Id' });
});
// Restangular service targeting OData v4 on a the specified route
app.factory('ODataRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl(RestangularConfigurer.baseUrl + '/odata');
// OData v4 controller(key) Item Route convention
RestangularConfigurer.urlCreatorFactory.path.prototype.base = function(current) {
var __this = this;
return _.reduce(this.parentsArray(current), function(acum, elem) {
var elemUrl;
var elemSelfLink = RestangularConfigurer.getUrlFromElem(elem);
if (elemSelfLink) {
if (RestangularConfigurer.isAbsoluteUrl(elemSelfLink)) {
return elemSelfLink;
} else {
elemUrl = elemSelfLink;
}
} else {
elemUrl = elem[RestangularConfigurer.restangularFields.route];
if (elem[RestangularConfigurer.restangularFields.restangularCollection]) {
var ids = elem[RestangularConfigurer.restangularFields.ids];
if (ids) {
// Crude Implementation of 'several', don't try this with more than
// 60 Ids, performance degrades exponentially for large lists of ids.
elemUrl += '?$filter=((Id eq ' + ids.join(')or(Id eq ') + '))';
}
} else {
var elemId;
if (RestangularConfigurer.useCannonicalId) {
elemId = RestangularConfigurer.getCannonicalIdFromElem(elem);
} else {
elemId = RestangularConfigurer.getIdFromElem(elem);
}
if (RestangularConfigurer.isValidId(elemId) && !elem.singleOne) {
elemUrl += '(' + (RestangularConfigurer.encodeIds ? encodeURIComponent(elemId) : elemId) + ')';
}
}
}
acum = acum.replace(/\/$/, '') + '/' + elemUrl;
return __this.normalizeUrl(acum);
}, RestangularConfigurer.baseUrl);
};
// add a response interceptor for OData v4:
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var extractedData;
// Collection requests are 'getList' operations
if (operation === "getList") {
// return the value array
extractedData = data.value;
} else {
// return the first item in the array
if(data.value.length > 0)
extractedData = data.value[0];
}
// pass the metadata back
if(extractedData) {
extractedData.meta = { context: data['#odata.context'] };
if(data['#odata.count'])
extractedData.meta.count = data['#odata.count'];
}
return extractedData;
});
});
});
Implementation example:
// Controller for list route
function ListCtrl($scope, ODataRestangular) {
$scope.providers = ODataRestangular.all("providers").getList({ $count:true }).$object;
$scope.some = ODataRestangular.several("providers", 15,16,17,18).getList();
$scope.single = ODataRestangular.one("providers", 15).get();
}
Captured URLs from network Traffic:
http://localhost:16486/odata/providers?$count=true
http://localhost:16486/odata/providers?$filter=((Id eq 15)or(Id eq 16)or(Id eq 17)or(Id eq 18))
http://localhost:16486/odata/providers(15)
I struggled to try to write a custom service factory and to modify BreezeJS to work with OData v4 and only recently stumbled into Restangular, I can now really appreciate the extensible design that went into restangular, the general lack of documented client side framework support has been the Achilles heel that has prevented a wider adoption of OData v4. I hope this answer contributes to getting more developers onboard with version 4.
Restangular does not explicitly support OData APIs. You can make the basics work, but you would probably be better off using a library that does support querying an OData API, like breeze.js.
Last few days I am working to invoke REST services and track the response, error, callback etc. I have gone through most of the posting however due to my limited understanding on Angular seems like I am not able to understand it. Following is my problem and understanding I got so far.
I am using Project.$update() service which returns only "project_id". This server doesn't return complete data again. Following is line of few line of code to share here.
//create Project factory
app.factory('Project', function ($resource) {
return $resource('/api/projects/:projectid',
{projectid:'#id'},
{update: {method:'PUT', isArray:false}}
);
});
Following is code in directive I am using to update/create project.
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project._id = project.$save()._id;
}
else {
//Call server to update the project data
project.$update({ projectid: project._id });
}
};
Following is service response for both save() and update().
{"_id":"52223481e4b0c4d1a050c25e"}
Problem here is; "project" object value is replaced by new response returned by server having only project_id and other fields are replaced.
I was going through detailed documentation on $resource however I am not able to grasp it. It will be great to get some guidance here to write code to detect error, response, callback.
You can replace the original object by the one returned from the server in your success callback like this:
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project.$save(function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
}
else {
//Call server to update the project data
project.$update(function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
}
};
That will replace the original object by the one returned by the server as soon as the server response is received.
If your callback is identical for the $save and $update methods, you can further simplify your code like this:
//save project
scope.saveProject = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
var action = (angular.isDefined(project._id)) ? '$update' : '$save';
//call server to save the data
project[action](function(updatedProject, headers){
// Replace project by project returned by server
project = updatedProject;
});
};
Hope that helps!
As per suggestion made by jvandemo and BoxerBucks; I have used following approach for save/update by passing the callback method with copy of original data. However still I am looking for central approach to take care of error/success status. Please suggest.
//save project metadta
scope.saveProjectMetadta = function (project) {
//update modified by field
project.modifiedby = scope.user._id;
//change to view mode
scope.projectView = 1;
//keep original data to pass into callback
var originalProjectObject = angular.copy(project);
//call server to save the data
if (project._id == undefined || project._id == "") {
//Call server to create new and update projectID
project.$save(originalProjectObject, function (projectResponse) {
originalProjectObject._id = projectResponse._id;
//update scope
scope.project = originalProjectObject;
//invoke method to update controller project object state
scope.updateProjectScope(scope.project);
});
}
else {
//Call server to update the project data
project.$update({ projectid: project._id }, function (projectResponse) {
originalProjectObject._id = projectResponse._id;
//update scope
scope.project = originalProjectObject;
//invoke method to update controller project object state
scope.updateProjectScope(scope.project);
},originalProjectObject);
}
};