I have a function which is going to make a REST call, but it cannot do this until an auth token has been fetched.
So I wrapped the REST call in the 'then()' of the auth token call's promise, like so:
var RESTCall = function() {
return authTokenPromise.then(function() {
return $http.get('userService' {
userId: 1234,
someFlag: true
});
});
};
This has the result of waiting to fire off the call to the userService until the authToken promise (from the service that gets the auth token) has resolved.
The problem comes when I try to remove the hard-coded params that set userId and someFlag. What I want to do is this:
var RESTCall = function(params) {
return authTokenPromise.then(function() {
return $http.get('userService' {
userId: params.userId, // params is undefined
someFlag: params.flag // params is undefined
});
});
};
How do I pass params into the anonymous function scope created by then(function() {...})?
Related
I currently have VueJS components that makes an ajax call to github like so:
(Child) component
Vue.http.get('user/repos').then((response) => {
console.log(response);
}, (response) => {
console.log(response);
});
The problem is that I first need to get an access token before I can make this ajax call. This access token is stored in the database so my main Vue component is making the ajax call to set a common header to all ajax calls:
Main Vue instance
Vue.http.headers.common['Authorization'] = `token ${this.token}`;
const app = new Vue({
el: '#app',
data: {
token: ''
},
created() {
Vue.http.get('/token').then((response) => {
this.token = response.data.token;
}, () => {
console.log('failed to retrieve the access token for the logged in user.');
})
}
});
How can I be sure that before running the ajax call from my component that the ajax call to set the 'Authorization' header has been successful?
Adding this for anyone else who could benefit.
Get the token from the API call, add it up in a vuex state variable.
Access the same using a getter in the child component, as a computed property or you can pass it on as props or via an event bus but both the ways are not as powerful as using vuex.
watch over the property, and perform your required action when the token is obtained.
// Add this up in the child component
computed: {
...mapGetters({
token: <name-of-the-getter> // token becomes the alias for the computed
}) // property.
},
watch: {
token () {
if(this.token) this.someAPICall()// or some other applicable condition
}
},
methods: {
...mapActions({
someAPICall: <name-of-the-action>
})
}
// ----------------------------------------------
Watch requires the value to change, I have noticed that commits made in an action cause the watch to trigger. So if for some reason the token is lost, or expires you will naturally not be able to make the subsequent requests.
EDIT
import store from 'path/to/store'
axios.interceptors.response.use(function (response) {
// extract the token from the response object
// save the token to the store for access during subsequent
// requests.
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
axios.interceptors.request.use(function (config) {
// use store getters to access token
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
You can replace/proxyfiy Vue.http.get function by your own function that will request token first and then do your request, rough idea:
!function()
{
var vue_http_get = Vue.http.get;
var token = null;
// patching/proxying Vue.http.get function
Vue.http.get = function get() {
vue_http_get.apply(Vue.http,"path/to/get/token/").then(function(resp){
token = resp;
Vue.http.headers.common['Authorization'] = ...;
// putting back original Vue.http
Vue.http = vue_http_get;
return Vue.http.get(arguments[0]);
});
};
}();
I want two different controllers to run different functions after some promises are resolved in a service (i dont want this service to make an http request each time a controller needs the data, I only want one http request).
I have a service that makes a request and gets a promise. I want controller1 to see this resolution and then run some code. I then want controller2 to also see that this promise resolves and run some code (basically multiple then() methods that run on the same promise but from different files). How can I go about doing this?
All the examples I have seen have one controller running code after a certain promise resolves, but not multiple controllers listening for the same promise to resolve.
here is some code im borrowing from this article (ill add a 'mother controller' to illustrate my example, I dont want the son service to ever make his http call twice): http://andyshora.com/promises-angularjs-explained-as-cartoon.html
son service
app.factory('SonService', function ($http, $q) {
return {
getWeather: function() {
// the $http API is based on the deferred/promise APIs exposed by the $q service
// so it returns a promise for us by default
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function(response) {
// something went wrong
return $q.reject(response.data);
});
}
};
});
father Controller:
// function somewhere in father-controller.js
var makePromiseWithSon = function() {
// This service's function returns a promise, but we'll deal with that shortly
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
prepareFishingTrip();
} else {
prepareSundayRoastDinner();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
prepareSundayRoastDinner();
});
};
Mother Controller:
var makePromiseWithSon = function() {
SonService.getWeather()
// then() called when son gets back
.then(function(data) {
// promise fulfilled
if (data.forecast==='good') {
workInTheGarden();
} else {
sweepTheHouse();
}
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
sweepTheHouse();
});
};
To have your factory service only get the url once, store the httpPromise in your factory service.
app.factory('SonService', function ($http) {
var weatherPromise;
function getWeather() {
return $http.get('http://fishing-weather-api.com/sunday/afternoon')
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
throw response;
}
}, function(response) {
// something went wrong
throw response;
});
}
function sonService() {
if (!weatherPromise) {
//save the httpPromise
weatherPromise = getWeather();
}
return weatherPromise;
}
return sonService;
});
The simple answer, in a non-angular-specific (but really easy to apply to Angular) way, is to create a service which caches ON-OUTBOUND-REQUEST (rather than caching return values, like most systems would).
function SearchService (fetch) {
var cache = { };
return {
getSpecificThing: function (uri) {
var cachedSearch = cache[uri];
if (!cachedSearch) {
cachedSearch = fetch(uri).then(prepareData);
cache[uri] = cachedSearch;
}
return cachedSearch;
}
};
}
function A (searchService) {
var a = this;
Object.assign(a, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(a.init.bind(a));
},
init: function (data) { /* ... */ }
});
}
function B (searchService) {
var b = this;
Object.assign(b, {
load: function ( ) {
searchService.getSpecificThing("/abc").then(b.init.bind(b));
},
init: function (data) { /* ... */ }
});
}
var searchService = SearchService(fetch);
var a = new A(searchService);
var b = new B(searchService);
a.load().then(/* is initialized */);
b.load().then(/* is initialized */);
They're sharing the same promise, because the service they were talking to cached and returned the same promise.
If you wanted to be safe, you could cache a promise and then return new instances of promises which resolve (or reject) based on the cached promise.
// instead of
return cachedSearch;
// replace it with
return Promise.resolve(cachedSearch);
Each user is now getting a new instance, every time you make a request, but each instance is also passing or failing based on the original cached call.
And of course you can take it further, and put a time-limit on the cache, or have hooks to invalidate the cache, or whatever...
Converting this to Angular is also a snap
SearchService is a service
A and B are controllers
use $http instead of fetch (though fetch is really pretty)
in fetch( ).then(prepareData) you'd be converting data from JSON on success;
in $http, you'd be returning response.data because your users don't want to have to do that
either way, you're performing that operation exactly once, per outbound call, so cache it, too
use $q (and q methods) instead of native Promise
use angular.extend, instead of Object.assign
You're done; you've now ported that whole concept into Angular AND VanillaJS
In an app I have the following url structure for the api:
// public
public/url-xyz
// private
dashboard/url-xyz
Knowing that, and trying to save unnecessary requests: What would be the best way to cancel a request? What I've tried so far is:
angular.module('mymod').factory('httpInterceptors', function ($injector, $q, httpBuffer)
{
return {
request: function (config)
{
var url = config.url;
if (url.match('/dashboard/')) {
// immediately cancel request
var canceler = $q.defer();
config.timeout = canceler.promise;
canceler.reject(config);
// logout and go to login-page immediately
// ...
}
// request config or an empty promise
return config || $q.when(config);
}
};
});
But this can lead to problems with $ressource as it expects an array and gets an object as a response, if its request is canceled like that.
You should be able to return $q.reject([reason])
angular.module('mymod').factory('httpInterceptors', function ($injector, $q, httpBuffer)
{
return {
request: function (config)
{
var url = config.url;
if (url.match('/dashboard/')) {
// immediately cancel request
return $q.reject(config);
// logout and go to login-page immediately
// ...
}
// request config or an empty promise
return config || $q.when(config);
}
};
});
regarding the question Passing data between controllers in Angular JS? I ran into the situation that my ProductService is executing some $http(RestFUL Service) and returns NULL because the callback function isn't completed.
Because the p.getProducts() function is evaluted and the callback function which fetches the data from the RestFUL service, is'nt complete the function returns always null.
app.service('productService', function() {
p = this
p.productList = [];
var addProduct = function(newObj) {
productList.push(newObj);
}
p.getProducts = function(){
return $http(RestFUL Service,function(data){p.productList.push(data)});
}
return {
addProduct: addProduct,
getProducts: return p.getProducts();
};
});
How can I solve this problem?
If you change your service to look more like this
return {
addProduct: addProduct,
getProducts: p.getProducts
}
then in controller you can make it work like that
app.controller('myCtrl', function ($scope, productService) {
var products = null
productService.getProducts().then(function(response){
//do something with data returned
})
})
your getProducts return $http which itself returns promise, that's why the I used then in controller
You must play with callback's. The http is an async operation, and for that reason you canĀ“t return a valid result right away. How invoke getProducts must set as parameter a function (callback) that will be invoked when http is completed - when data is available.
app.service('productService', function() {
p = this
p.productList = [];
var addProduct = function(newObj) {
productList.push(newObj);
}
p.getProducts = function(callback){
$http(RestFUL Service,function(data){
p.productList.push(data)
callback(data);//do something with data
});
}
return {
addProduct: addProduct,
getProducts: p.getProducts
};
}
//invoking
getProducts(function(products){
//do something with products
});
I am trying to get data from a function in a node module, that returns a json object, and show the json object in a router in my server.js file.
I am trying to export it like this:
// Export the function
exports.getTweets = function() {
var tweetResult = {};
twitter.get(
'/statuses/user_timeline.json',
{ screen_name: 'twitter', count: 5},
function (error, tweets) {
tweetResult = tweets;
}
);
return tweetResult;
};
And here is my server.js file, with the route that should return the json object
tweets = require("./twitter_stream.js"),
// set up the route for the json object
app.get("/tweets.json", function (req, res) {
console.log(tweets.getTweets());
});
I haven't been able to get anything but an empty object.
You need to pass a callback to the getTwitter function to receive the twitter result. The variable tweetResult is null when the code return tweetResult; is invoked.
Please check the code below to see how to use the callback in your code:
exports.getTweets = function(callback) {
twitter.get(
'/statuses/user_timeline.json',
{ screen_name: 'casperpschultz', count: 5},
function (error, tweets) {
//invoke the callback function passing the tweets
callback(tweets);
}
);
};
Code to invoke the getTweets function passing a callback function:
app.get("/tweets.json", function (req, res) {
//invoke getTweets passing a callback function by parameter
tweets.getTweets(function(tweets){
console.log(tweets);
});
});
Node.js works in an asynchronous way with callback, here is a good reference that explains it.