angularJS data not available due to async call - javascript

This is my controller:
function TestCtrl ($scope, sharedProperties, $resource){
var userDataProfile = sharedProperties.getUserDataProfile();
var userCreatedBoard = $resource('/boards?owner=:owner');
userCreatedBoard.query({'owner':userDataProfile.id}, function (result) {
console.log(result);
});
}
Now problem is sharedProperties.setUserDataProfile() is called after calling a 3rd party service and its async. Hence, when the TestCtrl is bound, userDataProfile is effectively null. How do I handle this situation so that after the sharedProperties.setUserDataProfile() is called and variable has been assigned a value from 3rd party service, then only my controller should get bound?

I think you want to look at resolve:
http://www.youtube.com/watch?v=Kr1qZ8Ik9G8
This allows you to load all your data before instantiating your controller and firing a routeChangeSuccess event.
In the angular docs.

It does not make sense to pass variables into an Angular.js controller like that, that is what the Scope variable is meant for.
Try this
window.activeTestCtrls = [];
window.sharedProperties = [];
function TestCtrl ($scope, $resource){
if (window.sharedProperties) this.createUserData(window.sharedProperties)
window.activeTestCtrls.push[this];
function createUserData(sharedProperties) {
var userDataProfile = sharedProperties.getUserDataProfile();
var userCreatedBoard = $resource('/boards?owner=:owner');
userCreatedBoard.query({'owner':userDataProfile.id}, function (result) {
console.log(result);
}
});
// and when you get your sharedproperties
function gotSharedProperties(sharedProperties) {
$.each(activeTestControllers, function(index,value) {
activeTestControllers[index].createUserData(sharedProperties)})
}
}

Related

Loading view configuration

I would like to do something like this:
app.config(function($routeProvider){
$routeProvider.when('products/list', {
controller: 'ProductListCtrl',
templateUrl : 'products/list/view.html',
resolve : { data : function(){
...
},
loadingTemplateUrl : 'general/loader.html'
}
});
I would like to have the loading page in a different view.
This would make the code in the view and controller of every page cleaner, (no <...ng-include ng-show="loading"...>). This would also mean that I don't have to $scope.$watch the data for changes. Is there a clean solution to do something similar (not necessarily in the .config method) or an alternative library to do this?
Assuming you want to show some general template for all state transitions while the data is resolved, my suggestion is to listen to the events fired by the routing library. This allows to use one central point to handle all state transitions instead of polluting the routing config (which I think will not be that easy to do).
Please see the docs for $routeChangeStart, $routeChangeSuccess and of course $routeChangeError at the angular router docs
Maybe someone could be interested in what I did: I created a new service and a new view directive. It could seem like a lot of work, but doing this was much easier than I had expected. The new service enables me to separate the main view from the loading view, that I could reuse in all pages of the application. I also provided the possibility to configure an error template url and error controller, for when the loading failed.
The Angular $injector, $templateRequest and $controller services do most of the work. I just had to connect a directive, that depends on these services, to the right event ($locationChangeSuccess), and to the promise, retrieved (using $q.all) from the resolve object's functions. This connection was done in the route service. The service selects the right template url and comtroller, and passes it on for the directive to handle.
A shortened version (with the getCurrentConfig method left out):
RouteService:
(function () {
'use strict';
// provider:
angular.module('pikcachu')
.provider('pikaRouteService', [function () {
var routeConfigArray;
var otherwiseRouteConfig;
//configuration methods
this.when = function (url, routeConfig){
routeConfigArray.push({url: url, routeConfig: routeConfig});
return this;
}
this.otherwise = function(routeConfig){
otherwiseRouteConfig = routeConfig;
return this;
}
// service factory:
this.$get = ['$rootScope', '$location', '$q', '$injector', '$templateRequest',
function ($rootScope, $location, $q, $injector, $templateRequest) {
function RouteService() {
this.setViewDirectiveUpdateFn = function(){ /*...*/ }
function init(){
$rootScope.$on('$locationChangeSuccess', onLocationChangeSuccess);
}
function onLocationChangeSuccess(){
// get the configuration based on the current url
// getCurrentConfig is a long function, because it involves parsing the templateUrl string parameters, so it's left out for brevity
var currentConfig = getCurrentConfig($location.url());
if(currentConfig.resolve !== undefined){
// update view directive to display loading view
viewDirectiveUpdateFn(currentConfig.loadingTemplateUrl, currentConfig.loadingController);
// resolve
var promises = [];
var resolveKeys = [];
for(var resolveKey in currentConfig.resolve){
resolveKeys.push(resolveKey);
promises.push($injector.invoke(resolve[resolveKey]));
}
$q.all(promises).then(resolveSuccess, resolveError);
function resolveSucces(resolutionArray){
// put resolve results in an object
var resolutionObject = {};
for(var i = 0; i< promises.length;++i){
resolved[resolveKeys[i]] = resolutionArray[i];
}
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
function resolveError(){
viewDirectiveUpdateFn(currentConfig.errorTemplateUrl, currentConfig.errorController);
}
}
}
init();
}
return new RouteService();
}]
})();
View directive
(function () {
'use strict';
angular.module('pikachu')
.directive('pikaView', ['$templateRequest', '$compile', '$controller', 'pikaRouteService', function ($templateRequest, $compile, $controller, pikaRouteService) {
return function (scope, jQdirective, attrs) {
var viewScope;
function init() {
pikaRouteService.listen(updateView);
}
function updateView(templateUrl, controllerName, resolved) {
if(viewScope!== undefined){
viewScope.$destroy();
}
viewScope = scope.$new();
viewScope.resolved = resolved;
var controller = $controller(controllerName, { $scope: viewScope });
$templateRequest(templateUrl).then(onTemplateLoaded);
function onTemplateLoaded(template, newScope) {
jQdirective.empty();
var compiledTemplate = $compile(template)(newScope);
jQdirective.append(compiledTemplate);
}
}
init();
};
}
]);
})();

How do I add result to my scope ng-click?

This is a relatively simple piece of code that calls a service and returns some data. I need to set the $scope with the result of the data. Is there an easy way to set this data to the scope without resorting to to binding the scope to the function in the then clause?
Angular Code
(function () {
var app = angular.module('reports', []);
var reportService = function($http, $q) {
var service = {};
service.getMenuData = function() {
var deffered = $q.defer();
$http.get('/Report/MenuData').success(function(data) {
deffered.resolve(data);
}).error(function(data) {
deferred.reject("Error getting data");
});
return deffered.promise;
}
return service;
};
reportService.$inject = ['$http', '$q'];
app.factory('reportService', reportService);
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = function(e) {
reportService.getMenuData().then(function(data) {
// Need to set the $scope in here
// However, the '$scope' is out of scope
});
}
};
reportMenuController.$inject = ['$scope', '$http', 'reportService'];
app.controller('ReportMenuController', reportMenuController);
})();
Markup
<div>
<div ng-controller="ReportMenuController">
<button ng-click="getMenuData()">Load Data</button>
</div>
</div>
There is absolutely no problem to set the $scope from within the function passed to then(). The variable is available from the enclosing scope and you can set your menu data to one of its fields.
By the way: You should consider to use then() instead of success() for your http request. The code looks much nicer because then() returns a promise:
service.getMenuData = function() {
return $http.get('/Report/MenuData').then(function(response) {
return response.data;
}, function(response) {
deferred.reject("Error getting data");
});
}
success() is deprecated by now.
I didn't notice the small detail missing in the plunker where my code was different.
(function () {
...
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = getMenuData;
function getMenuData(e) {
reportService.getMenuData().then(function(data) {
// Now I have access to $scope
});
}
};
...
})();
Notice the changes to the two lines as below:
$scope.getMenuData = getMenuData;
function getMenuData(e) {
This also begs a small question which is, "Why is it okay to set getMenuData to the $scope before it is declared?

value returned from ajax call not getting set to variable in controller in angular js

HTML
<div id="SalaryDiv">
<div ng-controller="SalaryController as sal">
<div ng-repeat="salaryDetail in sal.data">
<p>{{salaryDetail.Name}}</p>
<p>{{salaryDetail.Salary}}</p>
</div>
</div>
</div>
JS
(function () {
var salary = angular.module("SalaryDetails", []);
salary.controller("SalaryController", function () {
var newData = getAssociateData();
alert(newData); //here it is alerting as undefined
this.data = salaryDetails;
});
function getAssociateData() {
var returnData;
$.ajax({
url: "https://gist.githubusercontent.com/vigneshvdm/862ec5a97bbbe2021b79/raw/d7155b9c7fd533597c912fc386682e5baee1487a/associate.json",
type: "GET",
success: getDetails
});
function getDetails(data) {
salaryDetails = data;
return data;
};
};
}());
Here the success function is getting called,
but value is not getting set
1) The return value from getAssociateData is whatever the return value is of $.ajax - which is NOT the data that you return. $.ajax is an async call.
2) You should use Angular's $http rather than jQuery's $.ajax - this would apply the changes to the View automatically (i.e. it will call $scope.$apply() on your behalf).
You also seem to be expecting this.data to be an array (I gather from the use of ng-repeat="salaryDetail in sal.data", so push instead of assigning.
salary.controller("SalaryController", function ($http) {
var url = "https://gist.gith....json";
var vm = this; // your "Controller As" ViewModel
vm.data = [];
$http.get(url).then(function(salaryDetail){
vm.data.push(salaryDetail);
}
}
This is by way of illustration. I agree with other suggestions here that HTTP calls should be abstracted away in a Service, rather than keeping them in the controller. But, one step at a time...
You should use the $http service.
salary.controller("SalaryController", function ($scope, $http) {
$scope.salaryDetails = null;
var url = 'https://gist.githubusercontent.com/vigneshvdm/862ec5a97bbbe2021b79/raw/d7155b9c7fd533597c912fc386682e5baee1487a/associate.json';
$http.get(url).
success(function (data) {
$scope.salaryDetails = data;
});
});
Note: I put it in the controller here, but as a best practice, you should only inject an $http dependency into your custom service.
Whenever you trying to call an ajax call inside the controller, it will not fire the angular digest and hence the controller variables wont get updated.
The ideal way to go about it to have this in a factory and then call the factory method from the controller.
Code example:
appModule.factory('Search', function($rootScope, $http, $q) {
return {
var deferred = $q.defer();
$http.get('https://gist.githubusercontent.com/vigneshvdm/862ec5a97bbbe2021b79/raw/d7155b9c7fd533597c912fc386682e5baee1487a/associate.json').success(function(response) {
deferred.resolve({
data: response)};
});
return deferred.promise;
}
});
This code is using $q service inorder to make it more cleaner.Now just inject this factory (Search) into your controller and use it as is.

Catch response from callback-function or pass variable into callback function?

Using the facebook api, you can add a listener that triggers a function whenever someone clicks a like button:
FB.Event.subscribe('edge.create', page_like_callback);
This is what my function looks like, where I listen:
function($scope, $window) {
$scope.liking = $window.liking;
FB.Event.subscribe('edge.create', page_like_callback);
}
I want to set $scope.liking = true; for whenever someone clicks like:
var page_like_callback = function(url, html_element) {
$scope.liking = true;
};
But the $scope variable isn't accessible inside the callback. I either want to pass $scope to the callback or get a return from the callback and use that to set the like. What's the best approach for this and how should I do it?
You could have a function return your callback taking $scope as an argument so that $scope is now captured by the callback function
var page_like_callback = function($scope) {
return function(url, html_element) {
$scope.liking = true;
};
}
FB.Event.subscribe('edge.create', page_like_callback($scope));
That's because the $scope variable is only defined within your function($scope, $window), so you need to make sure that page_like_callback has access to it. One way to do this is:
function($scope, $window) {
var page_like_callback = function(url, html_element) {
$scope.liking = true;
};
$scope.liking = $window.liking;
FB.Event.subscribe('edge.create', page_like_callback);
}
That way, page_like_scope has access to $scope.
This is a bit spaghetti-ish, so you might need to provide more of your code if you want a better solution.

Unset object property

I have a provider:
AdviceList.provider('$adviceList',function(){
this.$get = function ($rootScope,$document,$compile,$http,$purr){
function AdviceList(){
$http.post('../sys/core/fetchTreatments.php').success(function(data,status){
this.treatments = data;
console.log(this.treatments); // the correct object
});
this.adviceCategories = [
// available in the controller
];
}
return{
AdviceList: function(){
return new AdviceList();
}
}
}
});
Further, i have this controller:
AdviceList.controller('AdviceListCtrl',function($scope,$adviceList){
var adv = $adviceList.AdviceList();
$scope.treatments = adv.treatments; // undefined
});
Why is it, that the controller's $scope.treatments stays undefined, this.treatments inside the provider however, is filled correctly? Also, adviceCategories is available in my controller.
The call you get teatment is async in nature so the results may not have been populated when you try to assign them.
So here
var adv = $adviceList.AdviceList();
$scope.treatments = adv.treatments; //The treatments would only get filled after the server call is over.
You need to rewrite the code in a way that you assign it to your scope property on the success callback.
I will recommend you to simplify your code
1) Use simple factory method of angular instead of provider
2) return a promise to avoid using callbacks
AdviceList.service('adviceList', function ($http) {
return {
adviceList: function () {
return $http.post('../sys/core/fetchTreatments.php');
}
}
});
AdviceList.controller('AdviceListCtrl', function ($scope, $adviceList) {
adviceList.AdviceList().then(function (data) {
$scope.treatments = data //set value to data when data is recieved from server
});
});

Categories

Resources