In our application, we have an search input field. Typically a request is sent while the user types (a la Google Instant) and the results are displayed.
Obviously, the following can happen:
User types, which results in ajaxRequest1
User continues typing, resulting in ajaxRequest2
results2 corresponding to ajaxRequest2 are received and displayed
After this, results1 corresponding to ajaxRequest1 are received. Obviously, since ajaxRequest2 was sent after ajaxRequest1, we only care about results2, not results1.
EDIT: The obvious answer here is "Use debounce". For reasons of confidentiality and brevity, I'll just say here that it won't work in our particular scenario. I know what debounce does and I have considered it.
In pseudo-code, we used to handle it like this:
$scope.onInput = function() {
var inputText = getInput();
SearchService.search(inputText).then(function(results) {
// only display if input hasn't changed since request was sent
if(inputText === getInput()) {
displayResults(results);
}
});
};
Since this involves a lot of boilerplate and looks ugly, we moved to a pattern where the SearchService manages things a bit better
$scope.onInput = function() {
var inputText = getInput();
SearchService.search(inputText).then(function(results) {
displayResults(results);
});
}
function SearchService() {
var cachedSearchDeferred;
this.search = function(inputText) {
if(cachedSearchDeferred) {
//means there's an unresolved promise corresponding to an older request
cachedSearchDeferred.reject();
}
var deferred = $q.deferred();
$http.post(...).then(function(response) {
// saves us having to check the deferred's state outside
cachedSearchDeferred = null;
deferred.resolve(response.data);
});
cachedSearchDeferred = deferred;
return deferred.promise;
}
}
This works fine. The SearchService creates a deferred containing the promise corresponding to the most recent call to SearchService.search. If another call is made to SearchService.search the old deferred is rejected and a new deferred is created corresponding to the new call.
Two questions:
Is this a good pattern to do what we need - essentially request locking? We want to ensure that only the most recent request's promise resolves successfully
If we had other SearchService methods that needed to behave similarly, then this deferred boilerplate needs to be inside every method. Is there a better way?
#Jayraj depends how sophisticated you want to make your http api. You can go very deep, but if I understand your question you are looking for a http timeout interceptor. Using Angular $httpProvider you can register a custom interceptor which needs to return a response and request.
I should note I've frankensteined this from pieces of different code bases so I don't take credit for code, but it is early morning and would need to go find the source in my libraries, but to help best practice directionally here goes.
ANGULAR.JS EXAMPLE
angular team give this example
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'request': function(config) {
// same as above
},
'response': function(response) {
// same as above
}
};
});
create a factory object that holds you http endpoint configuration i.e a config file that with a server component and an endpoint that identified the UID for the endpoint i.e. where does it go and who is sending it
(function() {
'use strict';
var config = {
server: {
url: null
},
endpoint: {
url: null,
uuid: null,
}
};
return angular.module('matrixme.config', [
]).constant('config', config);
})();
for brevity sake I will leave out the service provider code, but you will need to build an REST api service provider, which you then inject into all relevant classes. The provider will effectively configure your config object e.g. user, articles and will serve as home for api calls.
You create your own interceptor and inject as such:
(function() {
'use strict';
angular.module('matrixme.api', ['matrixme.config'])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('timeoutInterceptor');
}]);
})();
Build the injector before you inject :) I have not tested this but really answering your question of best practice. So this is directional, but you would then create your request and response. You can build multiple custom interceptors e.g. uuid, auth timeout, etc.
(function() {
'use strict';
TimeoutInterceptor.$inject = ['$timeout', '$q', '$rootScope', 'request'];
function TimeoutInterceptor($timeout, $q, $rootScope, request) {
return {
request: function(config) {
if ((config.url)) {
config._ttl = config._ttl ? Math.min(2000, config._ttl * 2) : 2000;
config.timeout = $timeout(function() {
config._isTimeout = true;
}, config._ttl);
}
return config;
},
response: function(response) {
if (response.config.timeout) {
$timeout.cancel(response.config.timeout);
$rootScope.serverStatus = 0;
}
return response;
},
};
}
angular.module('matrixme.api')
.factory('timeoutInterceptor', TimeoutInterceptor);
})();
It turns out there already exists a solution for this: RxJS. The example in their README is almost this exact scenario.
const $input = $('#input');
/* Only get the value from each key up */
var keyups = Rx.Observable.fromEvent($input, 'keyup')
.pluck('target', 'value')
.filter(text => text.length > 2 );
/* Now debounce the input for 500ms */
var debounced = keyups
.debounce(500 /* ms */);
/* Now get only distinct values, so we eliminate
the arrows and other control characters */
var distinct = debounced
.distinctUntilChanged();
/* Once that is created, we can tie together the
distinct throttled input and query the service.
In this case, we'll call flatMapLatest to get
the value and ensure we're not introducing any
out of order sequence calls. */
const suggestions = distinct
.flatMapLatest(() => {
// Do XHR and return a promise
// flatMapLatest will always use the latest one
});
There's also RxJS for Angular which adds things to the $scope object.
Related
I am working with interceptors, and they intercept all requests but I have certain routes where I don't want to interfere with the request/response
app.service('WizardService', ['$http', function($http) {
var base_url = '/api';
var service = {};
service.postStep1 = function (){
return $http.post(base_url+'/step-1');
};
service.postStep2 = function (data){
return $http.post(base_url+'/step-2', data);
};
service.postStep3 = function (data){
return $http.post(base_url+'/step-3', data);
};
return service;
}]);
For step 1 and 2, I want to use InteceptorA and Step 3 I want to use InterceptorB. What is the cleanest way to do this?
What Cloves answered is good but if there are multiple routes then it will be hard to assign multiple if conditions based on the URL. Also, if the URL changes, you need to modify in the interceptor as well.
I guess the cleanest way of achieving this is the config. Let's start with your service:
app.service('WizardService', ['$http', function($http) {
var base_url = '/api';
var service = {};
service.postStep1 = function (){
return $http.post(base_url + '/step-3', null, {interceptMe: 'A'});
};
service.postStep2 = function (data){
return $http.post(base_url + '/step-2', null, {interceptMe: 'A'});
};
service.postStep3 = function (data){
return $http.post(base_url + '/step-3', null, {interceptMe: 'B'});
};
service.postStep4 = function (data) {
// no interception
return $http.post(base_url + '/step-3');
};
return service;
}]);
Now, register the interceptor (I'm just showing you the main logic of interceptor):
$httpProvider.interceptors.push(['$rootScope', function($rootScope, $q) {
return {
'request': function (config) {
if (config.interceptMe) {
if (config.interceptMe === 'A') {
// do something for interceptor type A
} else if (config.interceptMe === 'B') {
// do for type B
}
}
return config;
}
}
});
Implement a single interceptor, as shown in the documentation for the $http service.
If you need to intercept requests, in the 'requests' callback, inspect the config.url and use something like regex matching. Example:
// register the interceptor as a service
$provide.factory('myInterceptor', function() {
return {
'request': function(config) {
if (/step-1$/.test(config.url)) doSomething1();
if (/step-2$/.test(config.url)) doSomething2();
if (/step-3$/.test(config.url)) doSomething3();
return config;
}
}
}
First off, there's nothing in the $http service and its use of the $httpProvider.interceptors property to help accomplish what you're looking for.
Referring to v1.5.6 source.
.interceptors is a plan array (line 375):
var interceptorFactories = this.interceptors = [];
And they're ultimately just pushed (or unshifted) into the promise chain, depending on whether it's a request or response.
Line 986:
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});
So, other than rolling your own alternative to the $http service (not something I'd recommend in this case), you'll have to work within the confines of what you can do within a http interceptor itself. In other words, the interceptor (or interceptors) will have to manage filtering routes itself.
I see 3 slightly different ways you could go about this.
A single interceptor to act as a 'marshal' to call logic based on the route.
Multiple interceptors, each with their own route filtering logic.
Multiple interceptors with configured / injected rout filtering logic.
Which of these methods works best for you will depend on a few factors, like:
Quantity of interceptors
Complexity of route filtering (your example could be a simplified one)
Likelihood that filtering rules will change independently of interceptor logic
A note on singletons
Keep in mind that services are singletons. They're created once, and re-used. Per Angular doco:
Each component dependent on a service gets a reference to the single instance generated by the service factory.
So, while there's undoubtedly some overhead in $http going through a long promise chain if you've got multiple interceptors registered, it's not as taxing as it might first seem. That said, you've mentioned in comments that you might be registering as many as 100 interceptors, and I cannot comment on the specific performance impact that may or may not have.
1 - Single Interceptor
Cloves answer already provided an example for this, as did Shashank's. Something like this:
function SingleInterceptor() {
var service = {
request: request
};
return service;
function request(config) {
if (config.url === base_url + '/step-1') {
step1Logic();
}
if (config.url === base_url + '/step-2') {
step2Logic();
}
// etc...
return config;
}
}
This is neat and simple if you've not got a lot of different interceptor logic to deal with, but could become a bit clumsy to maintain in higher volumes.
2 - Multiple Interceptors (with their own filtering)
Each interceptor has its own logic for determining whether the route applies. Like:
function InterceptorStep1() {
var service = {
request: request
};
return service;
function request(config) {
if (config.url === base_url + '/step-1') {
// Step 1 logic here
}
return config;
}
}
function InterceptorStep1() {
var service = {
request: request
};
return service;
function request(config) {
if (config.url === base_url + '/step-2') {
// Step 2 logic here
}
return config;
}
}
This would be more maintainable in high volumes, but becomes awkward if you want to start changing the filtering rules, or the interceptors don't have an obvious one-to-one mapping with URL's. Hypothesising here, maybe they're not all as obvious as "Step 1", "Step 2", etc.
3 - Multiple Interceptors (config based filtering)
Similar to #2, but separate the route filtering logic from the interceptors themselves by using a provider for each.
function InterceptorB(filterUrl) {
var service = {
request: request
};
return service;
function request(config) {
if (!filterUrl || filterUrl(config.url)) {
// Logic here
}
return config;
}
}
angular
.module('app')
.provider('InterceptorB', function InterceptorBProvider() {
var filterUrlInner;
this.setFilterUrlCallback = function (fn) {
filterUrlInner = fn;
};
this.$get = [function InterceptorBFactory($log) {
return new InterceptorB(filterUrlInner);
}];
});
There's more work in getting this one set up in the first place, but is in my view the most flexible and easily maintained once you've got more than just a few interceptors.
I need to prevent sending the same request repeatedly to API before the previous request will give the response. I have found out some solutions. But, I don't want to disable the button while waiting for response because I have more API calls in my app.
I really need to do something in my $provider .config() .I found a way here(http://blog.codebrag.com/post/57412530001/preventing-duplicated-requests-in-angularjs).
But I need more clarification code. Any kind of reference about this is welcome.
Lets say you have $http in your controller.js file.
Many request to server
$http.get('/link/to/file.php');
Just one request to server, no matter how many times you will call this method:
$http.get('/link/to/file.php', {cache: true});
Example:
(function() {
'use strict';
angular
.module('yourModuleName')
.controller('DashboardCtrl', DashboardCtrl);
DashboardCtrl.$inject = ['$scope'];
function DashboardCtrl($scope) {
$scope.get = function () {
$http.get('/link/to/file.php', {cache: true}).then(function(response) {
// it will do GET request just once
// later you will get the same response from cacheFactory
})
}
}
}());
I would like to complement #VikasChauhan answer, however I do not have enough reputation to comment on his answer.
His code works great for me, except the part where he returns null. That causes Angular to throw a bunch of errors all over.
Instead of null, I simply reject the request:
return $q.reject(request);
Here's my function:
$httpProvider.interceptors.push(['$injector', '$q', function interceptors($injector, $q) {
return {
// preventing duplicate requests
request: function request(config) {
var $http = $injector.get('$http');
var _config = angular.copy(config);
delete _config.headers;
function isConfigEqual(pendingRequestConfig) {
var _pendingRequestConfig = angular.copy(pendingRequestConfig);
delete _pendingRequestConfig.headers;
return angular.equals(_config, _pendingRequestConfig);
}
if ($http.pendingRequests.some(isConfigEqual)) {
return $q.reject(request);
}
return config;
}
};
}
]);
Hope this helps other people.
You can create a function to cancel the first http request, when calling the another one on the button click.
Here is a reference that uses $q.defer() function that helped me on a similar issue:
http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx
In my project, i was facing this problem. I found a very useful working solution here
And implemented it inside the config:
function config($routeProvider, $httpProvider) {
$httpProvider.interceptors.push(['$injector', function interceptors($injector) {
// Manually injecting dependencies to avoid circular dependency problem
return {
// preventing duplicate requests
'request': function request(config) {
var $http = $injector.get('$http'),
copiedConfig = angular.copy(config);
delete copiedConfig.headers;
function configsAreEqual(pendingRequestConfig) {
var copiedPendingRequestConfig = angular.copy(pendingRequestConfig);
delete copiedPendingRequestConfig.headers;
return angular.equals(copiedConfig, copiedPendingRequestConfig);
}
if ($http.pendingRequests.some(configsAreEqual)) {
debugger;
return null;
}
return config;
}
}
}
]);
}
My question is that given the power of interceptors, does it make sense to wrap $http in a service so that all my other code just calls that wrapper. Now basic tasks like header/exception handling can easily be done by interceptors. So though I cant think of a valid usecase now, but lets say just to shield any future api changes etc to $http? Or maybe later migrate to $resource?
Also please note that here I am talking of a basic wrapper service around $http’s methods, not a client service like DataService with methods sendData, receiveData that wraps $http calls.
Please find below sample code -
angular.module(‘myapp’).factory(‘myhttpwrapper’, ['$http', function (http) {
return {
myGet: function (getUrl) {
return http.get(getUrl);
},
myPost: function (postUrl, data) {
return http.post(postUrl, data);
},
// other $http wrappers
};
}]);
Now all other code will use myhttpwrapper’s myGet, myPost methods instead of $http’s get, post methods. Hope it makes sense!
[EDIT] What use-cases we'll definitely have is to intercept requests for adding headers, logging, and responses for logging, exception handling, etc. But I am sure these can be handled by interceptors. Later moving from $http to $resource is not known at this point.
Thanks.
For the specific case you describe, I'd advise against wrapping $http. There is no real gain in doing it.
A case where this could come into play would be where you'd want a more 'speaking' API. Lets say you have User(s) in a system, and Address(es). You described it as a data based service DataService:
var app = angular.module("users", []);
app.service("User", ['$http', '$q', function(http, q) {
return {
getAddress: function(user) {
var address = q.defer();
http.get("/user/" + user.id + "/address").then(function(data) {
address.resolve(data);
}, function(err) {
address.reject(err);
});
return address.promise;
},
getUser: function() {
var user = = q.defer();
http.get("/user/address").then(function(data) {
user.resolve(data);
}, function(err) {
user.reject(err);
});
return user.promise;
}
}
}]);
This allows you to use parameters for your call. Whenever you'd have to change routes, you would have only one place to change them (this would be really awful if, say, you had a dozen controllers making $http requests).
You can also make use of $resource here if you which (and you actually have compatible resources). The decision making factor(s) here should be readability, reusability and ease of change later.
So, if you only ever have on place where you make these calls and the routes never change, go ahead and use the $http directly, without a wrapper. This I'd consider unlikely in a growing application.
I needed my 'get' generalized so did it like this:
app.factory('myHttp', ['$http', '$q', function($http, $q) {
return {
get: function(url) {
var deferred = $q.defer();
$http.get(url).then(
function(response) {
//console.log("response good");
deferred.resolve(response)
},
function(response) {
//console.log("response bad");
deferred.reject(response)
})
return deferred.promise;
}
};
}]);
You can now chain an additional "then" to the result of the get. You can have core logic for handling error or success (my use case being to log out the user if credentials expired) but still allow for additional context specific logic for the get response (like setting $scope etc...).
I asked the wrong question yesterday (and got a goodanswer that worked), but am realizing it's not what I needed. I need to be able to retrieve JSON data (preferably once), store it, and access it throughout my service. The challenge I'm having is that all the examples I can find talk about using JSON and passing to the app/controller, whereas in this case I need to get it, check it, and then it dictates what my module/service does.
For instance, I have my App and Controller, and then I have a module such as (this is psuedo-code, not meant to run):
angular.module("myModule")
.service("myService1", function($q, myService2, $http) {
this.getModel = function() {
return {
title: "My Title",
desc: "My Desc"
options: function () {
if (condition A)
return "option1";
else
return "option2";
}
};
};
})
.service("myService2", function($q, $http) {
this.getCfgInfo = function () {
var defer = $q.defer();
$http.get("my/json/url").then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
})
In this example, I'm wanting to get the JSON, and use it within myService1 for both literal values (title, desc) as well as for conditions (condition A within the if).
I know I can do something like this (thanks to Joel for helping yesterday):
service("myService1", function($q, myService2, $http) {
// get a promise object for the configuration info
var cfgProm = rtDataMapper.getCfgInfo()
this.getModel = function() {
return {
title: cfgProm.then(function(response) {
return response.JSON_NAME;
}),
and it works fine as I've got the title mapped back into my model and there is a watch(), but I'm stumped as to how I get, store, and use the JSON within the service itself as a conditional (i.e. if (condition A) where condition A is coming from the JSON. Trying to wrap these in .then() doesn't seem to make sense, or at least I can't figure out how to do it.
I'm new to Angular and am attempting to modify some code that was left to us. I'm guessing I don't need the myService2 just to get the JSON. Can anyone help point me in the right direction? I've spent several hours online but can't seem to find a relevant reference/example.
Thanks
Live demo (click).
I'm having the service immediately get the data when it is injected (that code will only run once no matter how many times you inject it). That's nice because you won't have to call a function to get the data - it's called for when creating the service.
Your service method that returns that data will need to return the promise of the data, of course, since you aren't guaranteed that it will have come through when you ask for it. You can pass arguments to that method to use to determine your conditions. All you need to do for that is use promise.then in the method and resolve the promise with the modified data. Since that method is returning the promise already, the modification will be updated on the resolve. See all of this below and in the demo.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.getData(15).then(function(data) {
$scope.myData = data;
});
});
app.factory('myService', function($q, $timeout) {
//this code only runs once when you first inject the service
//get data immediately
var deferred = $q.defer();
$timeout(function() { //simulate ajax call
var data = { //ajax response data
foo: 15,
bar: 'Some data!'
};
data = modifyData(data, 1);
deferred.resolve(data);
}, 500);
function modifyData(data, fooVal) {
if (data.foo === fooVal) {
data.baz = 'Conditional data!';
}
return data;
}
var myService = {
//data can be modified when it comes from the server,
//or any time you call this function
getData: function(fooVal) {
if (fooVal) { //if you want to modify the data
deferred.promise.then(function(data) {
data = modifyData(data, fooVal);
deferred.resolve(data);
});
}
return deferred.promise;
}
};
return myService;
});
I have defined two AngularJS services ... one is for the YouTube Player API, and other for the YouTube iFrame Data API. They look like this:
angular.module('myApp.services',[]).run(function() {
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
})
.factory('YtPlayerApi', ['$window', '$rootScope', function ($window, $rootScope) {
var ytplayer = {"playerId":null,
"playerObj":null,
"videoId":null,
"height":390,
"width":640};
$window.onYouTubeIframeAPIReady = function () {
$rootScope.$broadcast('loadedApi');
};
ytplayer.setPlayerId = function(elemId) {
this.playerId=elemId;
};
ytplayer.loadPlayer = function () {
this.playerObj = new YT.Player(this.playerId, {
height: this.height,
width: this.width,
videoId: this.videoId
});
};
return ytplayer;
}])
.factory('YtDataApi', ['appConfig','$http', function(cfg,$http){
var _params = {
key: cfg.youtubeKey
};
var api="https://www.googleapis.com/youtube/v3/";
var yt_resource = {"api":api};
yt_resource.search = function(query, parameters) {
var config = {
params: angular.extend(angular.copy(_params),
{maxResults: 10,
part: "snippet"}, parameters)
};
return $http.get(api + "search?q=" + query, config);
};
return yt_resource;
}]);
(also note that the 'setPlayerId' function of my player service is called by a custom directive ... but that's not important for my question).
So, here's the issue. I need to ensure that the Player API code is loaded before I set the video id and create the player, which is why I have it broadcasting the 'loadedApi' message. And this works great, if I then in my controller pass a hard-coded video id, like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytplayer.videoId='voNEBqRZmBc';
ytplayer.loadPlayer();
});
}
However, my video IDs won't be determined until I make an API call with the data api service, so I ALSO have to ensure that the results of that call have come back. And that's where I'm running into problems ... if I do something like this:
$scope.$on('loadedApi',function () {
ytdataapi.search("Mad Men", {'topicId':$routeParams.topicId,
'type':'video',
'order':'viewCount'})
.success(function(apiresults) { // <-- this never gets triggered
console.log(apiresults); // <-- likewise, this obviously doesn't either
});
});
Then the interaction with the data service never happens for some reason. I know the data service works just fine, for when I un-nest it from the $on statement, it returns the api results. But sometimes latency makes it so that the results don't come back fast enough to use them in the player service. Any thoughts on what I can do to make the data search after receiving the message that the player API is ready, but still keep the two services as two separate services (because other controllers only use one or the other, so I don't want them dependent on each other at the service level)?
Figured it out; I had to call $scope.$apply(), like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytdataapi.search("",{'topicId':$routeParams.topicId,'type':'video','maxResults':1,'order':'viewCount'}).success(function(apiresults) {
ytplayer.videoId=apiresults.items[0].id.videoId;
ytplayer.loadPlayer();
});
$scope.$apply();
});
}
Is there anyone who could shed light on why this works, though? $scope.$digest() also works ... but I thought those methods were only used when you need to update bindings because of some javascript code that Angular isn't aware of. Is the nesting I've got here doing that (I wouldn't think it should, as my ytdataapi service is using $http)?