AngularJS: automatically chaining $resource actions - javascript

I've got an AngularJS resource implementation, which (in principle) works fine. Now the special circumstances are:
one of the fields (attributes) of the resource is the "last changed timestamp"
if it arrives at the server in an updating request, it is ignored. The sever sets the "last changed timestamp" always automatically
the updating methods on the server are all implemented in a way, that the response is empty (rather than containing the modified entity)
So under these circumstances, to get the "last changed timestamp" after an update operation from the server, I always have to do a get immediately following the update.
myEntity.$update( function(){ myEntity.$get(); } );
Now the question is: Does AngularJS offer a way to automatically chain actions:
to define in the MyEntity definition, that it needs to always do a $get after the $update.
and then, in application code just call
myEntitty.$update();

Thanks to #marck for pushing me in the right direction. The solution is basically
var MyEntity = $resource( ...,
{
get: {...},
update: {...}
}
);
// params is passed to $update.
// after successful $update, a parameterless $get is called
// success is called after successful $get
// error is called if $update OR $get failed
// returns the promise of the $update
MyEntity.prototype.$updateAndRefresh = function(params, success, error){
var item = this;
return item.$update(params, function(){
item.$get(success, error);
},
error);
}

Related

Asynchronous requests from angular

I have a method in service to get httprequest from another server and in the components console get null of value ollayer but data is not null
ollayer={};
service:
getLayer(): Observable<any> {
return this.http.get<olLayer>(
'http://localhost:3000/geomap/getlayer',
);
}
components:
this.olservice.getLayer().subscribe((data) => {
this.ollayer = data;
console.log(data)
});
console.log(this.ollayer)
That behaviour is fine, that's how asynchronous pipelines work.
For whatever reason you need the data, for example, to update a view on your web page, you have to do it in the subscribe scope. For example, you are updating this.ollayer inside the subscribe, so any view properly binded to that variable will update when the request is done.
Edit: it is ok for data being null on your second log because data may not have arrived yet. But it is not ok to be null on the log inside the subscribe.

Prevent previous, yet longer-running $http request from returning after most recent request

I just came across a weird situation and wasn't able to find an answer for after some searching.
I have a textbox that I'm using to allow a user to type keywords to filter table data. I have an ng-change directive on the input element which will fire this code:
return $http.get(url).then(function (response) {
return response.data;
});
All of this has worked great, until our tester just found some undesirable behavior in IE11. Here is an example of typing "M-A-T-T" into the textbox in IE10:
As you can see, each subsequent request takes longer than the first so the result of the fourth and final request is the one the controller receives.
Here is the same example of typing "M-A-T-T" into the textbox in IE11.
Inexplicably, the second request takes almost 2 seconds to complete which is after the fourth and final request completed. This results in the results from the "MA" request displaying when the user is expecting the results of the "MATT" request (which is what is currently in their textbox).
How can this be dealt with in Angular? Thanks in advance.
UPDATE
Based on frosty's response, I've implemented the following (in addition to bouncing) which works great:
var cancelDeferred;
var getUsers = function (criteria) {
if (cancelDeferred) {
cancelDeferred.resolve();
}
cancelDeferred = $q.defer();
return $http.get(url, { timeout: cancelDeferred.promise }).then(function (response) {
cancelDeferred = undefined;
return response.data;
});
};
The main challenge, actually, was handling errors when this method is called. The timeout returns an error just like a 500 would return an error. I want to ignore the timeouts and handle the actual errors. Here is my solution for that:
function onError(data, status) {
if (data.data !== null && data.status !== 0) {
//handle error
}
}
Now I'll try to figure out if there is a way to implement this promise-timing-out globally instead of having to alter a ton of $http.get() calls...
In the documentation for $http, it talks about a config object that you can pass as a second argument to the $http.get call, like this:
$http.get(url, config);
In that config, it talks about a "timeout" property that you can set. If you set that property as a Promise, then if you resolve the promise, it will abort the request. Check out the documentation for the description there.
Even if you use a debounce, you will want to use this "timeout" to abort the request.
https://docs.angularjs.org/api/ng/service/$http
So you would do something like this:
var cancelDeferred;
function makeRequest(){
if(cancelDeferred) cancelDeferred.resolve(); //when this gets resolved, the previous request will abort.
cancelDeferred = $q.defer();
return $http.get(url,{timeout:cancelDeferred.promise})
.then(function(res){
cancelDeferred = undefined;
return res;
});
}
So, you pass a promise to the .get request, inside the config object as the "timeout" property. If you resolve this deferred before the response comes back, it will abort the request.

The promise of a promise again (Angular JS)

Updated with HTTP and initial code based on requests/Please look at the bottom of the post:
I've been posting several questions on my AngularJS learning curve of late and the SO community has been fantastic. I've been a traditional C programmer when I used to program and have recently started writing my own ionic/Angular JS app. I'm struggling with the promise version of traditional async calls when it comes to converting a custom function to a promise. I don't think I really understood and I find various examples very contrived. I'd appreciate some help. I have some code which is not working, and I have some conceptual questions:
Let's take this simple function:
angular.module('zmApp.controllers').service('ZMDataModel', function() { return { getMonitors: function () { return monitors; } }
getMonitors is a simple function that basically returns an array of monitors. But here is the rub: When the app first starts, I call an http factory that does an http get and goes about populating this monitor list. This http factory is different from this service but invokes a setMonitor method in this service to populate the array. When the array is populated, a variable called 'monitorsLoaded' is set to 1. When this variable is set to 1, I know for sure monitors is loaded.
Now, I have a view with a controller called "MontageCtrl". I want to wait for the monitors to load before I show the view. In a previous post, one person suggested I use route resolve, but I had to first convert my getMonitors to a promise. So here is what I did:
angular.module('zmApp.controllers').service('ZMDataModel', function($q) {
getMonitors: function () {
var _deferred = $q.defer();
if (monitorsLoaded!=0)
{
console.log ("**** RETURNING MONITORS *****");
_deferred.resolve(monitors);
}
console.log ("*** RETURNING PROMISE ***");
return _deferred.promise;
},
Next up, in app.js I connected the route as follows:
.state('app.montage', {
data: {requireLogin:false},
resolve: {
message: function(ZMDataModel)
{
console.log ("Inside app.montage resolve");
return ZMDataModel.getMonitors();
}
},
Finally I modified my controller to grab the promise as such:
angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', function($scope,$rootScope, ZMHttpFactory, ZMDataModel,message) {
//var monsize =3;
console.log ("********* Inside Montage Ctrl");
It seems based on logs, I never go inside Montage Ctrl. Route resolve seems to be waiting for ever, whereas my logs are showing that after a while, monitorLoaded is being set to 1.
I have several conceptual questions:
a) In function getMonitors, which I crafted as per examples, why do people return a _deferred.promise but only assign a _deferred.resolve? (i.e. why not return it too?). Does it automatically return?
b) I noticed that if I moved var _deferred definition to my service and out of its sub function, it did work, but the next view that had the same route dependency did not. I'm very confused.
c) Finally I ready somewhere that there is a distinction between a service and a factory when it comes to route resolve as a service is only instantiated once. I am also very confused as in some route resolve examples people use when, and I am using .state.
At this stage, I'm deep into my own confusion. Can someone help clarify? All I really want is for various views to wait till monitorsLoaded is 1. And I want to do it via route resolves and promises, so I get the hang of promises once and for all.
Added: Here is the HTTP factory code as well as the app.run code that calls this when the app first starts. FYI, the http factory works well - the problems started when I crafted ZMDataModel - I wanted this to be a central data repository for all controllers to use -- so they did not have to call HTTP Factory each time to access data, and I could control when HTTP factory needs to be called
angular.module('zmApp.controllers').factory('ZMHttpFactory', ['$http', '$rootScope','$ionicLoading', '$ionicPopup','$timeout','ZMDataModel',
function($http, $rootScope, $ionicLoading, $ionicPopup, $timeout,ZMDataModel) {
return {
getMonitors: function() {
var monitors = [];
var apiurl = ZMDataModel.getLogin().apiurl;
var myurl = apiurl+"/monitors.json";
return $http({
url: myurl,
method: 'get'
}) //http
.then(function(response) {
var data = response.data;
//console.log("****YAY" + JSON.stringify(data));
// $rootScope.$broadcast ('handleZoneMinderMonitorsUpdate',monitors);
$ionicLoading.hide();
ZMDataModel.setMonitors(data.monitors);
ZMDataModel.setMonitorsLoaded(1);
//monitors = data.monitors;
return ZMDataModel.getMonitors();
},
function (result)
{
console.log ("**** Error in HTTP");
$ionicLoading.hide();
ZMDataModel.setMonitorsLoaded(1);
//$ionicPopup.alert ({title: "Error", template:"Error retrieving Monitors. \nPlease check if your Settings are correct. "});
return ZMDataModel.getMonitors();
}
); //then
}, //getMonitors
And here is the code in app.run that first calls this:
.run(function($ionicPlatform, $ionicPopup, $rootScope, $state,ZMDataModel, ZMHttpFactory)
{
ZMDataModel.init();
var loginData = ZMDataModel.getLogin();
if ( loginData.username && loginData.password && loginData.url && loginData.apiurl)
{
console.log ("VALID CREDENTIALS. Grabbing Monitors");
// this calls http factory getMonitors that eventually populated the ZMDataModel
// monitors array and sets monitorsLoaded to 1
ZMHttpFactory.getMonitors();
}
}
I finally solved all the problems. There were various issues with my initial attempts. My final resolved solution is here Am I returning this promise correctly?
The learnings:
a) Separating the HTTP get into a factory and the data model into another service was unnecessarily complicating life. But that separation was not the problem. Infact, the way the promise was coded above, on first run, if monitorsLoaded was 0, it would simply return the deferred promise and there was no ".success" or similar construct for me to get into the resolve code block again.
b) The biggest thing that was making me run around in loops was deferring or rejecting was simply setting a state. the return always has to be the promise - and it would return the state you set. I assumed return d.promise always means returning "in progress".

Angular route resolve for sessionStorage login variable

I've built a login system using Angular JS. When the user logs in, a session storage variable is set and they are redirected to a dashboard page (should only be accessible when logged in)"
$window.sessionStorage["isLoggedIn"] = true;
$location.path("/dashboard");
Now I want to use resolve on my any routes that required the user to be logged in. I find the documentation for this very confusing and can't understand it. If the user is not logged in and tries to access one of these pages, they need to be shown a message saying they can't access that page.
app.config(function($routeProvider) {
$routeProvider.when("/dashboard", {
templateUrl : "framework/views/dashboard.html",
controller : "DashboardCtrl",
title: "Dashboard",
resolve: {
//how does this work?!
}
});
app.factory("loginCheckService", function(){
//check sessionStorage and return?
});
You would rather listern for locationChangeStart event, perform validations (auth), prevent the route change (if required) and raise some events (unauthroized) to show the login form.
something like
app.run(function($rootScope,LoginService){
$rootScope.$on('$locationChangeStart',function(event){
if(!LoginService.isUserLoggedIn()){
event.preventDefault();
//LoginService.raiseUserNotLoggedIn(); OR
$rootScope.$broadcast('UserNotLoggedIn');
}
});
});
app.controller('LoginFormController',function($scope){
$scope.userLoggedIn=true;
$scope.on('UserNotLoggedIn',function(){
$scope.userLoggedIn=false;
});
});
Resolve allows you to define a set of tasks that will execute before the route is loaded. It is essential just a set of keys and functions, which allow you to do things like asynchronous http requests, run code snippets, set values, etc (really whatever you want), prior to page load.
So if you had a service that made a http get request and returned a promise to ensure a session exists on the server each time a route occurs, resolve guarantees that it will not load the page until the http request is fulfilled, and the promise is a success. In other words if the fulfilled promise fails the page will not load:
.config([ '$routeProvider', function( $routeProvide ) {
$routeProvider.when('/dashboard', {
templateUrl: 'framework/views/dashboard.html',
controller: 'DashboardCtrl',
controllerAs: 'dashCtrl',
resolve: {
DateOfBirth: ['Date', function( Date ) { // random example of other uses for resolve
return Date.getCurrentYear() - 37;
}],
AuthUser: ['$q', '$location', 'UserSession',
function( $q, $location, UserSession) {
return UserSession.getSession()
.then(function( success ) { // server response 200 = OK
// if you are in here the promise has succeeded and
// the page will load
// can do whatever you want
return success
}, function( error ) { // server response not 200 = Not OK
// if you are in here the promise has failed and
// the page will not load
// can do whatever you want
// if unauthenticated typically you'd:
$location.path('/login);
$location.replace();
// for this read up on promises, but promises can be
// chained, and it says move me to next error promise
// in the chain. if you don't use this it will assume
// the error was handled and pass you to the next
// success in chain. So good practice even if you're
// not chaining
return $q.reject( error );
});
}];
}
})
}]);
Another nice thing about resolve is the keys are injectable. So you can pass the result to your controller:
.controller('DashboardCtrl', ['AuthUser', 'UserSession', 'DateOfBirth'
function(AuthUser, UserSession, DateOfBirth) {
// show all the errors you want by accessing the AuthUser
// data contained in the server response, or just read the
// server response status
var self = this;
self.status = AuthUser.status;
self.response = AuthUser.data;
}]);
Then in your UI you can ngShow or ngBind blah blah on the result using dashCtrl.response or dashCtrl.status, or whatever you decide on doing with the resolved data, and show your errors knowing the page never loaded.
I'd suggest that on route you check the session instead of storing it on the client. Also, keep in mind the resolve only works on routes, but if you're making calls to the server that don't require routing you'll want to look up how to use interceptors. They allow you to peak at the outgoing and incoming server requests/responses unrelated to routing, so those which occur while you're currently on a specific page like /dashboard/home that don't trigger a route, and just simply update /home content.

Javascript Object appears then disappears Angular Js

I have created a factory which does a HTTP get to a php script.
The php script returns the proper data to the factory which then sends its data to the controller:
.controller('CatListController', ['$scope', 'Category', function ($scope, Category) {
$scope.categories = {};
var res = Category.list();
console.log(res); // <-- shows the proper object
console.log(res.data); //<-- shows nothing
if (res.error) {
$scope.error = true;
$scope.message = "http error";
}else{
if (res.data.error) {
$scope.error = true;
$scope.message = "database error";
}
$scope.categories = res.data;
}
}]);
the first console log displays the full object in its entirety, but the next console log display an empty object.
the object that is supposed to shown is (and does from the console log res):
{
data: { name: "" },
error: false
}
why is this happening?
I guess it is all right! Your first console.log prints an object. The browser keeps the output up to date with the state of the object. Keep in mind list() ist an asynchronous call, so the result will arrive later. The second console.log output prints a property of the object. The console did not update this if the data are arriving.
From the angular $ressource documentation:
It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most cases one never has to write a callback function for the action methods.

Categories