AngularJS : Render controller without waiting factory variable to be intialized first - angularjs

First, I'm new to angularjs. I've create a factory to handle most of my data named "store". Here is an example:
app.factory('store', function ($rootScope, $http, $q, api) {
var data = {};
return {
setData: function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: api.getData()
}).then(function successCallback(response) {
// handle data
$rootScope.broadcast('store:data', data);
deferred.resolve();
}, function errorCallback(reponse) {
// do stuff
deferred.reject();
});
},
getData: function () {
return data;
},
addData: function (newData) {
// do stuff
},
editData: function (newData) {
// do stuff
},
deleteData: function (newData) {
// do stuff
}
};
});
I'm initializing this data inside my app.run function. BUT, I don't want my app to wait my data to be initialized first to render the controller. I want it to be rendered first and wait for updating when the data is initialized.
store.setData()
.then(function (response) {
// do stuff
})
.catch(function (response) {
// do stuff
});
Here is how I'm getting the data updated inside my controller to be rendered
$scope.data = store.getData();
$rootScope.$on('store:data', function (event, data) {
$scope.data = data;
})
SO my problem is that I don't want to wait my data to be initialized to render my controller.
Is there a solution to this problem ?
Thanks a lot.
EDIT May 20 2021
Btw if what I'm doing is wrong and there is better things to do, I'm open to any suggestions ! Thnx
EDIT June 9 2021
Now I'm using $resource, but I don't know how can I get the new version of my list of data when I add new element to it.
agents: $resource(
api.getAgents(),
{},
{
get: {method: 'GET', isArray: false, cache: true},
add: {method: 'POST', url: api.addAgent(), hasBody: true},
edit: {method: 'PUT', url: api.editAgent(), params: {agentId: '#id'}, hasBody: true},
delete: {method: 'DELETE', url: api.deleteAgent(), params: {agentId: '#id'}},
}
),
Waiting for an answer. Thank you vm !

There are a couple options you can consider, but first a note on best practices in AngularJS and JavaScript: avoid the deferred antipattern. The $http service returns a promise. You should work with that rather than creating a new promise with $q.defer.
The first option is to change the getData method to return a promise instead of the actual data. It is a good idea to always design your data retrieval services to return promises, even when you intend to pre-retrieve and cache the data. This provides the cleanest way to ensure that the data is available before you try to use it. In your example, you should be able to internally cache the promise rather than the data. So your code would change to something like this:
app.factory('store', function ($rootScope, $http, api) {
var dataPromise;
return {
setData: function () {
dataPromise = $http({
method: 'GET',
url: api.getData()
}).then(function successCallback(response) {
// handle data
$rootScope.broadcast('store:data', data);
}, function errorCallback(reponse) {
// do stuff
});
},
getData: function () {
if (!dataPromise) {
this.setData();
}
return dataPromise;
},
// etc.
};
}
You will of course have to change the code that calls the getData method to work with the promise instead of working directly with the data.
Another option is to use an AngularJS Resource. This feature works very much like your original intent by returning an instance of an object that at some point will get populated with data. It takes advantage of the AngularJS change detection to render the data once it becomes available. Resources also have the ability to cache responses internally so that the call to the server is only made once. Rewriting your service as a resource would look something like this:
app.factory('store', function ($rootScope, $resource, api) {
return $resource(
api.getData(), // the base URL
{}, // parameter defaults
{ // actions
getData: {
method: 'GET',
cache: true
},
// etc.
}
);
}

Related

How to sort and match data with multiple $http when Consume Restful APIs with angularjs

So following my last question of how to consume multiple $http, I need also to sort the data and match them. It's mean, I have the Albums, Photos and users:
http://jsonplaceholder.typicode.com/albums
http://jsonplaceholder.typicode.com/photos
http://jsonplaceholder.typicode.com/users
For each Album I need to display the owner name and number of photos.
So I try this to get the Album by Id, but it gives me error:
$http.get('http://jsonplaceholder.typicode.com/albums/'+ $scope.id + '/photos')
Or I also try this:
$http.get('http://jsonplaceholder.typicode.com/albums/'+ albumUsers.id + '/photos')
But still get error.
The question is if there's a way to link / create dependency between them, so the second controller basically rely on the first one?
Thanks
So this is the controller to get the albums and the users
MyAlbum.controller('albumList', function($scope, $http, $q) {
$http.get('http://jsonplaceholder.typicode.com/albums').
then(function(response) {
$scope.albumDetails = response.data;
});
$http.get('http://jsonplaceholder.typicode.com/users').
then(function(response) {
$scope.albumUsers = response.data;
});
$q.all(['http://jsonplaceholder.typicode.com/albums','http://jsonplaceholder.typicode.com/users',]).
then(function(response){
$scope.albumDetails = response[0].data;
$scope.albumUsers= response[1].data;
});
});
And this the controller to get each album - for the example, I'm using a link to one specific album:
MyAlbum.controller('photoList', function($scope, $http) {
$http.get('http://jsonplaceholder.typicode.com/albums/1/photos')
.then(function(response) {
$scope.PhotoDetails = response.data;
});
});
The thing is that instead of albums/1 it should be albums/id
You can chained the $http calls together.Success function of the first $http request calls the second $http request.
function firstCall() { // It will return a promise.
return $http({
method: 'GET',
url: firstAPIURL
});
}
function dependantCall() { // It will return a promise
return $http({
method: 'GET',
url: secondAPIURL
});
}
$scope.onloadRequest = function() { // This function will execute on load the view/controller.
firstCall()
.then(function(result) {
$scope.value = result.data; // Now that the server has answered, you can assign the value to $scope.value, and then, call the second function.
dependantCall().then(function(dependentResult) {
// Your code comes here
}, function(dependentError){
// If an error happened during the dependent call, handle it here.
});
}, function(error){
// If an error happened during first call, handle it here.
});
};
$scope.onloadRequest();

Q.all.then doesn't wait for completion

I have a service in separate JS file, this service is like OOP class that holds 'methods' to load necessary data from the web.
I want call those 'methods' and get the data in my main JS file, actually I want to load data of three types and force JS flow to wait untill that data is retrieved, here's my code:
services.js
// My 'Class' to load data from the web server
myApp.factory("LoadData", ["_gl", function (_gl) {
return {
GetUsers: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetUsers" }
}).then(function successCallback(response) {
// Save the response JSON object to my global objects
_gl.myUsers = response.data;
}, function errorCallback(response) {
console.log("GetUsersError:" + response);
});
},
GetObGroups: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetObGroups" }
}).then(function successCallback(response) {
// Save the response JSON object to my global objects
// This code fills array because it iterates through it
angular.forEach(response.data, function (value, key) {
_gl.myObGroups.push(value)
});
}, function errorCallback(response) {
console.log("GetObGroups:" + response);
});
},
GetObjects: function ($http) {
$http({
method: 'POST',
url: 'http://localhost/dgis/ps/select.php',
data: { "action": "GetObjects" }
}).then(function successCallback(response) {
_gl.myObjects = response.data;
}, function errorCallback(response) {
console.log("GetObjectsError:" + response);
});
}
}
}]);
// My global variables
myApp.factory('_gl', function () {
return {
myUsers: [],
myOrganisations: [],
myObGroups: [],
myObjects: []
}
});
script.js
Q.all([LoadData.GetUsers($http), LoadData.GetObGroups($http), LoadData.GetObjects($http)]).then(function () {
console.log(_gl.myUsers);
console.log(_gl.myObGroups);
console.log(_gl.myObjects);
});
The problem is, the Q.all won't wait till all http request will get the data, it evaluates calls in then before it happens. Sure, I could use some timer and just wait for a second, but I want more proper way to do that, please share with your knowledge.
And one more thing, if I use forEach in then of my get methods then arrays filling all right, but other arrays are empty and I want to know why it happens.
Thank you.
You have to return the promises in GetUsers, GetObGroups and GetObjects, otherwise Q.all can't do its job.
Therefore, e.g.:
GetUsers: function ($http) {
return $http({
....
should do the trick.

Allowing variable length arguments to Angular's $resource?

Here's the issue, I have multiple API calls to make along the lines of:
www.domain.com/foo/bar?token=12345
There may be more subdirectories in-between, there may be less.
I'm currently using $resource
agentApp.factory('apiFactory', function($resource) {
return $resource('www.domain.com/v1/' + ':folder',
{
'query': {
method: 'GET',
isArray: true
}
});
Which gets called as follows:
apiFactory.query({folder: 'foo', token: '12345'}, function() {...})
I would like to make this more extensible as I have the need to occasionally change the isArray value to false, and the amount of subdirectories in the URL are unknown on occasion, so I'd prefer to not use $resource's :token structure, and rather just have one that takes a string.
Is there a service I could create that would allow me to make the call as follows:
apiService.query(urlStringAndQueries, booleanForIsArray).then(function(response) { ...do something with response });
So far I've attempted the following, which obviously doesn't give me what I want, I'm not sure how to get $resource to actually kick off the API call, I put this down to a fundamental misunderstanding of how $resource works:
agentApp.factory('apiService', ['$resource', '$q', function ($resource, $q) {
var factory = {
query: function (urlStringAndQueries, isArray) {
return $q(
function() {
$resource('www.domain.com/v1/' + ':location', { location: urlStringAndQueries }, {
'query': {
method: 'GET',
isArray: isArray
}
});
}
)
},
return factory;
}]);
Which I attempt to call as follows:
apiService.query('/foo/bar?token=12345', true)
.then(function(response) { ...do something with response });
Any help and/or advice is very much appreciated.
Thanks!
EDIT: Here's my solution until a more generic pattern comes along
I couldn't simply provide an extra entity to the base URL string externally, e.g. foo/bar?token=12345, due to $response inherently encoding URL, so the characters (/?=) get converted into their encoded counterparts. Hence the separating of strings in the pattern:
agentApp.factory('apiService', ['$resource', function($resource) {
var factory = {
resourceObj: function(isArray, urlStringAndQueries) {
/* urlStringAndQueries will be variadic parameters,
so we deconstruct them for use in the URL */
var location1, location2, location3, locations = [];
angular.forEach(arguments, function(path) {
locations.push(path);
});
return $resource(vapiURL + vapiVer + '/' + ':location1' + '/' + ':location2' + '/' + ':location3' + '/' + ':location4', {
location1: locations[1],
location2: locations[2],
location3: locations[3],
location4: locations[4],
}, {
'query': {
method: 'GET',
isArray: isArray
},
'save': {
method: 'POST',
isArray: isArray
}
})
}
};
return factory;
}]);
This solution still assumes I'll have a finite amount of paths, which isn't ideal but at least it's easy to reuse.
Still open to more and better suggestions :)
I don't understand why you are returning promise object from your service method again, while $resource return promise itself. Don't do that
Code
agentApp.factory('apiService', ['$resource', '$q', function($resource, $q) {
var factory = {
resourceObj: function(urlStringAndQueries, isArray) {
return $resource('www.domain.com/v1/' + ':location', {
location: urlStringAndQueries
}, {
'query': {
method: 'GET',
isArray: isArray
}
});
)
}
}]);
Call factory method then you'll get access to resource object then call its query of that resource object method
Controller
apiService.resourceObj('/foo/bar?token=12345', true).query()
.then(function(response) { ...do something with response });

Angularjs: a Service that serves multiple $resource urls / data sources?

I have an Angular service/provider that serves json data to my controller which works great:
angular.module('myApp.services', ['ngResource']).
factory("statesProvider", function($resource){
return $resource('../data/states.json', {}, {
query: {method: 'GET', params: {}, isArray: false}
});
});
But I also need to serve json data to the same controller from another file counties.json.
Where can I find out how to I write a service that serves both files to my controller?
You can update service to return a hash of resources, not a single one:
angular.module('myApp.services', ['ngResource']).
factory("geoProvider", function($resource) {
return {
states: $resource('../data/states.json', {}, {
query: { method: 'GET', params: {}, isArray: false }
}),
countries: $resource('../data/countries.json', {}, {
query: { method: 'GET', params: {}, isArray: false }
})
};
});
You will be able to use it adding .query() at the end your function name i.e. geoProvider.states.query() and geoProvider.countries.query() and myApp.services has to be injected into your controller, then inject geoProvider service into controller itself as well.
I'm assuming you want to execute some code when both files have loaded. Promises work really well for this. I don't think resources return promises, but you can use the $http service for simple ajax calls.
Here I define one service each for the two data files, and a third service that returns a promise that gets fulfilled when both files are done loading.
factory('states',function($http) {
return $http.get('../data/states.json');
}).
factory('countries',function($http) {
return $http.get('../data/countries.json');
}).
factory('statesAndCountries', function($q, states, countries) {
return $q.all([states, countries]);
});
Then in your controller:
statesAndCountries.then(function(data) {
var stateData = data[0];
var countryData = data[1];
// do stuff with stateData and countryData here
});

AngularJS - scope variable does not get updated from method

I'm totally new to AngularJs and I have this problem I do not understand. I have two methods. The first one takes some data from a webservice and puts in in a variable defined in the scope. But when I want to use that variable in the second method it is undefined. Can someone help me understand why this is happening and provide a solution?
var myApp= angular.module( "myApp", [] );
myApp.controller("myAppController",
function( $scope ) {
$scope.getAll = function(){
$.ajax({
type: "GET",
dataType: "jsonp",
contentType: "application/json; charset=utf-8",
url: ..something...,
success: function (parameters) {
$scope.profiles = angular.copy(parameters); <-- correct data is returned
$scope.$apply();
},
error: function () {
alert("Error calling the web service.");
}
});
}
$scope.getCategories = function(){
var all = $scope.profiles; <-- At this point profiles is empty
...
}
$scope.getAll();
$scope.getCategories();
}
Use the $http service and promises:
$scope.profiles = $http.jsonp(url).then(function(r){ return r.data; });
$scope.categories = $scope.profiles.then(function(profiles) {
var params = { }; // build url params
return $http.jsonp(url, { params: params }).then(function(r){ return r.data; });
});
When you call getCategories(), getAll() hasn't finished yet, which is why profiles is empty. There are several ways to solve this. The best way would be to use promises the built-in $http service.
If you prefer to use jQuery, you can add a watcher on the profiles variable and only when it's populated run the getCategories().
Something like this should work:
$scope.getAll = function(){
$.ajax({
type: "GET",
dataType: "jsonp",
contentType: "application/json; charset=utf-8",
url: ..something...,
success: function (parameters) {
$scope.profiles = angular.copy(parameters); <-- correct data is returned
$scope.$apply();
},
error: function () {
alert("Error calling the web service.");
}
});
}
$scope.getCategories = function(){
var all = $scope.profiles;
}
// Wait for the profiles to be loaded
$scope.watch('profiles', function() {
$scope.getCategories();
}
$scope.getAll();
There is no guarantee that getAll has completed before getCategories is invoked, since it is an asynchronous request. So if you want to sequentially invoke getAll and getCategories, you should invoke getCategories inside the success callback of getAll. You could also look into promises for a neater way of chaining asynchronous callbacks (I assume you're using jQuery since you're calling $.ajax).
...
<snipped some code>
success: function(parameters) {
// snipped more code
$scope.getCategories();
}
(and if you're using jQuery promises)
$.ajax(ajaxCallOneOpts).then($.ajax(ajaxCallTwoOpts));
Neither are very "Angularish" though, so you might want to look into some of the provided services for working with http/rest resources instead of using jQuery.
Why are you using a jQuery ajax request in angular? If you write jQuery style code and wrap it angular, you're going to have a bad time...
Here is an angularised version:
myApp.controller("myAppController",
function( $scope, $q, $http ) {
$scope.getAll = function(){
var deferred = $q.defer();
$scope.profiles = deferred.promise;
$http.jsonp('your url').then(function(data) {
deferred.resolve(data);
});
});
$scope.getCategories = function(){
$q.when($scope.profiles).then(function(profiles) {
... <-- At this point profiles is populated
});
}
$scope.getAll();
$scope.getCategories();
}

Resources