In my Angular controller I have a http call to a REST service that returns data in a database. This data are shown in a table in the partial view.
It happens random that the render of the html view is done before to get the callback with data, so I see a void table.
Here the code in the controller (I use services for some business logic and to implement the http call):
commonServices.find(vm.modelUri, null, vm.filter, function (err, msg, data) {
if (err || !data.length) {
$scope.noResults = true;
return;
}
$scope.docs = data; //docs is bind in the view
return;
});
Here the service for the http call:
function _commonServices(config, $http, $rootScope, $cookies) {
return {
find: function _find(modelUri, id, filter, callback) {
var url = config.servicesUri + '/' + modelUri;
if (id) {
url += '/' + id;
}
if (filter) {
if (typeof filter !== 'string') {
filter = JSON.stringify(filter);
}
url += '?filter=' + filter;
if (document.cookie) {
url += '&' + accessToken;
}
} else {
if (document.cookie) {
url += '?' + accessToken;
}
}
$http.get(url)
.then(function (data) {
//success
return callback(null, null, data.data);
},
function (data) {
//error
var err = true;
return callback(err, data.data.error.message);
});
}
}
}
The find service is used in other controllers and it seems it works good. I would know if it is possible to defer the render of the html table until the data are ready to be shown.
I would suggest the use of Angular's ng-cloak. It is a directive to prevent the html from being displayed until your angular app is finished loading. Check out the documentation here: ng-cloak
Related
I am trying to post a simple binary value (using $http) to a URL which si defined by a value in my HTML.
I have successfully got the "card.id" being passed through (can see it in console log)
<td-card ng-repeat="card in cards"
on-destroy="cardDestroyed($index)"
on-swipe="cardSwiped($index)"
on-swipe-right="$parent.cardSwiped(card.id)"
on-swipe-left="$parent.cardSwiped(card.id)" >
The data I want to post needs to be to a URL which has the card.id in it.
How to I tell it what to post and how to trigger?
.controller('CardsCtrl', ['$scope', 'TDCardDelegate', 'cardsApi', '$http',
function($scope, TDCardDelegate, cardsApi, $http) {
console.log('CARDS CTRL');
$scope.cards = [];
$scope.onSwipeRight=function(product_id){console.log(product_id)}
//Post for swipe right {'like':1, 'uid':21}
$scope.onSwipeLeft=function(product_id){console.log(product_id)}
//Post for swipe left {'like':0, 'uid':21}
for(var i = 0; i < 7; i++) {
cardsApi.getApiData()
.then(function (result) {
$scope.cards.unshift(result.data);
$scope.product_id = result.data.product_id;
})
.catch(function (err) {
$log.error(err);
});
}
$scope.$watchCollection('cards', function (newVal, oldVal) {
if(newVal < oldVal) {
cardsApi.getApiData()
.then(function (result) {
$scope.cards.unshift(result.data);
})
.catch(function (err) {
console.log(err);
});
}
});
$scope.cardSwiped = function(card) {
console.log('here');
console.log(card);
};
//Removes card from top of stack
$scope.cardDestroyed = function(index) {
$scope.cards.splice(index, 1);
};
$scope.addCard = function() {
var newCard = $scope.cards[$scope.cards.length];
//newCard.id = Math.random();
$scope.cards.push(angular.extend({}, newCard));
};
$scope.postRecordLikes = function(product_id){
console.log(product_id)
$http.post('http://test.com/analytic/' + product_id)
.then(function successCallback(product_id) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
};
}
])
postRecordLikes is being defined but never used. You can call it in your html by different ways. If you want to call it when you click it for example, just use ng-click.
By the way, calling $http inside your controller is not a good practice, have a look at this post: https://toddmotto.com/resolve-promises-in-angular-routes/
EDIT
I have forked your plunkr. Take a look at it: http://plnkr.co/edit/uvooKeCtFagAnFjVYnhS?p=preview
You should call postRecordLikes when your event is fired. Modify it as you want.
I have a provider for my REST-Services named MyRestServices:
app.provider('MyRestServices', function() {
this.baseUrl = null;
this.setBaseUrl = function(_baseUrl) {
this.baseUrl = _baseUrl;
};
this.$get = ['$http', function($http) {
var _baseUrl = this.baseUrl;
function getMyData() {
return $http.get(_baseUrl + 'data1/?token=' + token + '&key=' + key);
}
function preGetTokenAndKey() {
return $http.get(_baseUrl + 'keyAndToken/');
}
return {
getMyData: getMyData,
preGetTokenAndKey: preGetTokenAndKey
};
}];
});
I configure it before calling the first REST service.
app.config(function(MyRestServicesProvider) {
MyRestServicesProvider.setBaseUrl('https://www.test.com/rest/');
});
And then I have a HeadCtrl controller which should call preGetTokenAndKey to get key and token which is needed for some other REST calls like getMyData.
app.controller('HeadCtrl', function (MyRestServices) {
MyRestServices.preGetTokenAndKey().success(function(data) {
var key = data.dataSection.key;
var token = data.dataSection.token;
});
});
My problem is I want to call getMyData from another controller, but I need key and token to make this call.
So I need to wait until preGetTokenAndKey was successful and I have to provide the two values to the MyRestServices provider.
How can I solve these problems?
It sounds like a better solution would be to chain them within your service itself. You'd setup your own promise within preGetTokenAndKey, which gets resolved by the $http call. Subsequent calls to preGetTokenAndKey() would just return the already resolved data w/o making additional $http calls.
So something along the lines of the following should get you started:
app.provider('MyRestServices', function() {
this.baseUrl = null;
this.setBaseUrl = function(_baseUrl) {
this.baseUrl = _baseUrl;
};
this.$get = ['$http', function($http) {
var _baseUrl = this.baseUrl;
var _tokenAndKey = {};
function getMyData() {
return preGetTokenAndKey().then(function (data) {
return $http.get(_baseUrl + 'data1/?token=' + data.dataSection.token + '&key=' + data.dataSection.key);
});
}
function preGetTokenAndKey() {
if(!_tokenAndKey.set) {
_tokenAndKey.deferred = $http.get(_baseUrl + 'keyAndToken/').then(function(data) {
_tokenAndKey.set = true;
return data;
});
}
return _tokenAndKey.deferred.promise;
}
return {
getMyData: getMyData,
preGetTokenAndKey: preGetTokenAndKey
};
}];
});
My problem is I want to call getMyData from another controller,
If so, you can use $broadcast to notify other controller that async call resolved and you have key/token
app.controller('HeadCtrl', function($rootScope, MyRestServices) {
MyRestServices.preGetTokenAndKey().success(function(data) {
var key = data.dataSection.key;
var token = data.dataSection.token;
$rootScope.$broadcast("getMyDataTrigger", {key: key,token: token});
});
});
In other controller implement listener:
$rootScope.$on("getMyDataTrigger", function(event, data){
if(data){
MyRestServices.getMyData(data.key, data.token);
// ...
}
});
Just override getMyData:
function getMyData(key, token) {
return $http.get(_baseUrl + 'data1/?token=' + token + '&key=' + key);
}
I'm really struggling with this because it should be very simple. I have a route with a controller defined called login. In my template I have the following data binding {{error}} which is defined in my controller after executing a method from a custom service, and resolving the returned promise.
Controller
app.controller("login", ['$scope','XMLMC', 'ManageSettings', function ($scope,api,ManageSettings) {
$scope.error = 'test';
$scope.login = function() {
var params = {
selfServiceInstance: "selfservice",
customerId: $scope.username,
password: $scope.password
};
var authenticated = api.request("session","selfServiceLogon",params).then(function(response) {
ManageSettings.set("session",response, $scope);
if(response.status === "ok") {
window.location.href = 'portal';
} else {
$scope.error = response["ERROR"];
console.log($scope.error);
}
});
};
}]);
The console shows Customer not registered. Showing that $scope.error has been updated appropriately, but the view never gets updated. My service is below, and please note that I am doing nothing "outside" of angular and so I should not have to $apply() anything manually.
app.factory("XMLMC", ['$http', '$q', function ($http, $q) {
function XMLMC($http, $q) {
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
var that= this;
this.prepareForPost = function(pkg) {
return JSON.stringify(pkg);
};
this.request = function(service, request, params, host, newsession) {
var def = $q.defer();
var P = def.promise;
if(request === "analystLogon") {
newsession = true;
}
var call = {
service: service,
method: request,
params: params
};
if(host) {
call.host = host;
} else {
call.host = "localhost";
}
if(newsession) {
call.newsession = "true";
}
var pkg = {
contents: this.prepareForPost(call)
};
$http.post('php/XMLMC/api.php', jQuery.param(pkg)).success(function (response,status) {
that.consume(response, def);
}).error(function (response,status) {
def.reject(response,status);
});
return P;
};
this.consume = function(response, defer) {
console.log(response);
var resp = response[0],
digested = {},
i;
digested.status = resp["attrs"]["STATUS"];
var params = resp["children"][0]["children"];
for(i=0; i < params.length; i++) {
var key = params[i]["name"];
var val = params[i]["tagData"];
digested[key] = val;
}
defer.resolve(digested);
//return digested;
};
}
return new XMLMC($http, $q);
}]);
I've created a plunk here with the code exactly as it is on my test server. The routes and etc aren't working for obvious reasons, but you can at least see the code and how it works together
http://plnkr.co/edit/AodFJfCijsp2VWxWpbR8?p=preview
And here is a further simplified plunk where everything has one scope and one controller and no routes. For some reason, this works in the plunk but the $http method fails in my server
http://plnkr.co/edit/nU4drGtpwQwFoBYBfuw8?p=preview
EDIT
Even this fails to update
var authenticated = api.request("session","selfServiceLogon",params).then(function(response) {
ManageSettings.set("session",response, $scope);
$scope.error = "foo!";
if(response.status === "ok") {
window.location.href = 'portal';
}
});
It appears that $scope.$apply is indeed needed. See AngularJS - why is $apply required to properly resolve a $q promise?
To quote #pkozlowski.opensource:
In AngularJS the results of promise resolution are propagated asynchronously, inside a $digest cycle. So, callbacks registered with then() will only be called upon entering a $digest cycle.
I'm new to AngularJS and am still trying to wrap my head around using services to pull data into my application.
I am looking for a way to cache the result of a $http.get() which will be a JSON array. In this case, it is a static list of events:
[{ id: 1, name: "First Event"}, { id: 2, name: "Second Event"},...]
I have a service that I am trying to use to cache these results:
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) {
if (ignoreCache || !eventListCache) {
eventListCache = $http.get("/events.json", {cache: true});
}
return eventListCache;
}
});
Now from what I can understand I am returning a "promise" from the $http.get function, which in my controller I add in a success callback:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
eventListService.get().success(function (data) { $scope.events = data; });
}
]);
This is working fine for me. What I'd like to do is add an event to the eventListService to pull out a specific event object from eventListCache.
appServices.service("eventListService", function($http) {
var eventListCache;
this.get = function (ignoreCache) { ... }
//added
this.getEvent = function (id) {
//TODO: add some sort of call to this.get() in order to make sure the
//eventListCache is there... stumped
}
});
I do not know if this is the best way to approach caching or if this is a stupid thing to do, but I am trying to get a single object from an array that may or may not be cached. OR maybe I'm supposed to call the original event and pull the object out of the resulting array in the controller.
You're on the right track. Services in Angularjs are singeltons, so using it to cache your $http request is fine. If you want to expose several functions in your service I would do something like this. I used the $q promise/deferred service implementation in Angularjs to handle the asynchronus http request.
appServices.service("eventListService", function($http, $q) {
var eventListCache;
var get = function (callback) {
$http({method: "GET", url: "/events.json"}).
success(function(data, status) {
eventListCache = data;
return callback(eventListCache);
}).
}
}
return {
getEventList : function(callback) {
if(eventListCache.length > 0) {
return callback(eventListCache);
} else {
var deferred = $q.defer();
get(function(data) {
deferred.resolve(data);
}
deferred.promise.then(function(res) {
return callback(res);
});
}
},
getSpecificEvent: function(id, callback) {
// Same as in getEventList(), but with a filter or sorting of the array
// ...
// return callback(....);
}
}
});
Now, in your controller, all you have to do is this;
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
// First time your controller runs, it will send http-request, second time it
// will use the cached variable
eventListService.getEventList(function(eventlist) {
$scope.myEventList = eventlist;
});
eventListService.getSpecificEvent($scope.someEventID, function(event) {
// This one is cached, and fetched from local variable in service
$scope.mySpecificEvent = event;
});
}
]);
You are on the right track. Here's a little help:
appServices.service("eventListService", function($http, $q) {
var eventListCache = [];
function getList(forceReload) {
var defObj = $q.defer(), listHolder;
if (eventListCache.length || forceReload) {
listHolder= $http.get("/events.json", {cache: true});
listHolder.then(function(data){
eventListCache = data;
defObj.resolve(eventListCache);
});
} else {
defObj.resolve(eventListCache);
}
return defObj.promise;
}
function getDetails(eventId){
var defObj = $q.defer();
if(eventId === undefined){
throw new Error('Event Id is Required.');
}
if(eventListCache.length === 0){
defObj.reject('No Events Loaded.');
} else {
defObj.resolve(eventListCache[eventId]);
}
return defObj.promise;
}
return {
eventList:getList,
eventDetails:getDetails
};
});
Then, in your controller, you handle it like this:
appControllers.controller("EventListCtrl", ["$scope", "eventListService",
function ($scope, eventListService) {
var eventList = eventListService.getList();
eventList.then(function(data){
$scope.events = data;
});
$scope.getEventsList = function(reloadList){
eventList = eventListService.getList(reloadList);
eventList.then(function(data){
$scope.events = data;
});
};
$scope.getEventDetails = function(eventID){
var detailsPromise = eventListService.getDetails(eventID);
detailsPromise.then(function(data){
$scope.eventDetails = data;
}, function(reason){
window.alert(reason);
});
}
}
]);
This way, your events are loaded when the controller first loads, and then you have the option to request a new list by simply passing in a boolean. Getting event details is also handled by an internal promise to give you some error handling without throwing a disruptive error.
On my web application, there are two kinds of users: guests & logged. The main page loads the same content for each.
My goal :
When a registered user clicks the link, 2 ajax requests ($http) retrieve
the data of another page and load them in a model.
If the user is a guest, another model appears saying that he has to register.
My link :
<h4 ng-click="guestAction($event, showOne($event,card.id));">click me</h4>
GuestAction :
$scope.guestAction = function($event, callbackB) {
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1)
{
alert('guest spotted !');
return false;
}
else
{
alert('user');
console.log(callbackB);
eval('$scope.'+callbackB);
}
});
}
This way, if a guest is spotted, we return false and stop the execution. If it's a regular user, we execute the function showOne. As I want to do 2 asynchronous requests one after the other, I chose to use the callback trick.
The problem is that showOne() is executed directly when ng-click is launched. I tried to pass showOne() as a string, and eval() the string in GuestAction, but the parameters become undefined...
Any idea how to solve this problem? I want to use a generic method which fires a function only if the user is logged.
I would recommend using a service and promises, see this AngularJS $q
You don't have to use a service for $http requests but that is just my preference, it makes your controller a lot cleaner
Here is the service with the promise:
app.factory('myService', function ($http, $q) {
var service = {};
service.guestAction = function () {
var deferred = $q.defer();
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
}).error(function (data) {
deferred.reject('Error checking server.');
});
return deferred.promise;
};
return service;
});
And then in our controller we would call it something like so:
app.controller('myController', function ($scope, myService) {
$scope.guestAction = function($event, card) {
myService.guestAction().then(function (data) {
if (data) {
alert('guest spotted !');
} else {
alert('user');
// Then run your showOne
// If this is also async I would use another promise
$scope.showOne($event, card.id);
}
}, function (error) {
console.error('ERROR: ' + error);
})
};
});
Now obviously you may have to change things here and there to get it working for your needs but what promises do is allow you to execute code and once the promise is returned then continue, I believe something like this is what you are looking for.
You have to pass functions as parameters without the parenthesis and pass in the parameters separately:
<h4 ng-click="guestAction($event,card.id, showOne);">click me</h4>
and
$scope.guestAction = function($event,id, callbackB) {
$http.get('/guest/is-guest/').success(function(data) {
console.log("isGuest retrieved : " + data);
if (data == 1)
{
alert('guest spotted !');
return false;
}
else
{
alert('user');
callbackB($event,id);
}
});
}