Update AngularJS scope variables in the view - angularjs

I have the following AngularJS controller function which makes a JSONP request to a webservice, gets the data as a JSON file and assigns the values to scope variables which I show in the UI by AngularJS directives such as {{variableName}}. This function works fine when I run it on page load, however it does not update the view values when I call this as a function:
$scope.getData = function(id) {
$http.jsonp( webServices.getData+'?callback=JSON_CALLBACK', config ).then( function ( response ) {
$scope.data = response.data;
if($scope.data.status == "ok") {
$scope.data.receivedData = campaign.receivedData;
}
})
}
Can anybody give me a hint on how can I make this update the values in the view (form input controls) dynamically when the JSONP call returns the values? I read about $apply() but not sure where and how to use it.
Thanks.

Does the following solution help?
$http.jsonp( webServices.getData+'?callback=JSON_CALLBACK', config ).then( function ( response ) {
$scope.data = response.data;
if($scope.data.status == "ok") {
$scope.data.receivedData = campaign.receivedData;
$scope.$apply();
}
})

Scratch what I had earlier....
In your then promise handler you have:
$scope.data.receivedData = campaign.receivedData;
but you have not defined what campaign is, I assume that you want to set something on your $scope (what you're view will bind to) that is related to campaign that resides within the data coming back from the call to webServices.getData.

do not bind to a function... bind to a value.
and: return the promise
$scope.getData = function(id) {
// return the promise....
return $http.jsonp( webServices.getData+'?callback=JSON_CALLBACK', config ).then( function ( response ) {
$scope.data = response.data;
if($scope.data.status == "ok") {
$scope.data.receivedData = campaign.receivedData;
}
})
}
// fetch that data into a variable, that you bind to.
$scope.myActualData = $scope.getData();
if you later want to call the getData again, dont just call it, asign it again to the bound variable.
$scope.myActualData = $scope.getData();

Related

Get a reference to service data in controller from async request (AngularJS)

I'm having some trouble wrapping my head around data sharing between controllers. What I want to do is fetch data from a database (through a $http request), store it in a service variable, then share it between different controllers. From what I understand, that would allow my view to update automatically as the data is modified.
It seems quite easy with variables simply declared inside a service and accessed through a getter by the controllers. But the data I'm trying to share comes from an async operation, and I'm struggling to access it.
I came up with the following code, and I don't understand why I keep getting an "undefined" variable.
File: userController.js
function userController($scope, user) //user = userService
{
user.getChallengeList(25)
.then(function(defiList)
{
$scope.allDefis = defiList;
console.log($scope.allDefis); //ok
console.log(user.allDefis); //undefined
});
}
File: userService.js
function userService($http)
{
this.allDefis;
this.getChallengeList = function(id)
{
return $http
.post('http://localhost:3131/defi/defiList', {'id': id})
.then(function(response)
{
this.allDefis = response.data;
return this.allDefis;
});
}
}
From this piece of code, shouldn't the allDefis variable be accessible inside the controller?
Doesn't using .then in the controller "force" it to wait for the getChallengeList() method to be executed?
In that case, why is the user.allDefis variable undefined?
I think I could be solving this problem by using $rootscope, but I'd prefer not to, as it doesn't seem like a recommended solution.
Your issue lies in your implementation of getChallengeList:
this.getChallengeList = function(id)
{
return $http
.post('http://localhost:3131/defi/defiList', {'id': id})
.then(function(response)
{
this.allDefis = response.data; //<-- this is scoped here
console.log(this.allDefis);
return this.allDefis;
});
}
When you assign the web request response data to allDefis, the this is scoped to be within the anonymous function, rather than the overall function.
A technique to address this is to define a local variable that points to this, and use it instead.
You would adjust your implementation like this:
this.getChallengeList = function(id)
{
var _this = this; // a reference to "this"
return $http
.post('http://localhost:3131/defi/defiList', {'id': id})
.then(function(response)
{
_this.allDefis = response.data; // now references to correct scope
console.log(this.allDefis);
return this.allDefis;
});
}

How to use an angular scope variable outside the scope it is defined in

I have a service like below, which fetches the id of a student from a RESTful (Laravel) API and returns it.
.factory('Student', function($http)
{
return {
getId: function(adm_no) {
return $http.post('/api/student/getId',{adm_no:adm_no})
.then(function(response)
{
return response.data;
},
function(httpError)
{
Notifier.error(httpError.data.error.message,'Error ' + httpError.status + " Encountered");
});
}
}
}
)
Then i use it as follows in a controller.
$scope.adm_no = 98;
Student.getId($scope.adm_no)
.then(function(response)
{
$scope.id = response;
});
// probably i want to use the `$scope.id` when a particular event takes place (or even in another query to the server alltogether), but outside the above function scope e.g.
$scope.showId = function()
{
alert($scope.id);
};
Now, the question is how I can use the a scope variable declared in a 'local scope' outside the scope, for the usage above shows that $scope.id is undefined?
Your $scope.id is undefined in function $scope.showId() because when you call an alert function, your post request hasn't finished yet and so $scope.id hasn't been initialized (it is beeing executed asynchronously). Try this:
$scope.showId = function() {
if ($scope.id) {
alert($scope.id);
}
};
Anyway you don't have to use $rootScope in this case. Your property id from $scope is accesible from your whole controller. You have to wait for the ajax post request and than it is initialized.
In place of $scope you have to use $routescope variable to get id in other place as-
Student.getId($scope.adm_no)
.then(function(response)
{
$routescope.id = response;
});

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

Angular $resource - Data not being returned

I am quite beginner level with both JS and Angular, and I am trying to return data from an API and store it in $scope.
Once I've stored it, I want to loop over each item and output it into the page, pretty basic stuff.
Problem I am having is the API and data is there, but it seems to be returning after the loop is running, is there any way of making the loop wait?
Heres the code;
Service (Hit the endpoint and retrieve the data)
'use strict';
function RecruiterDashJobs($resource, API_URL) {
var dashJobs = {};
dashJobs.getJobs = function(uuid) {
return $resource(API_URL + 'recruiters/' + uuid + '/jobs').get();
}
return dashJobs;
}
angular
.module('app')
.service('RecruiterDashJobs', RecruiterDashJobs);
Controller (Call the service and store the data)
$scope.currentRecruiter = User.getUser();
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
$scope.recruiterJobs = $scope.getJobs($scope.currentRecruiter.uuid);
View (the Ng-repeat)
<div class="panel border-bottom pad-s-2x pad-e-1x" ng-repeat="job in recruiterJobs">
<div class="panel__body">
<aside class="valign">
<a class="icon--edit color--echo mar-r-2x" ui-sref="jobs/edit/{{job.uuid}"></a>
</aside>
<div class="valign">
<p>{{job.title}}</p>
<p class="color--charlie">Closing Date: {{job.closing_date}}</p>
</div>
</div>
</div>
EDIT: the "magical" approach below no longer works as of Angular 1.2
In your getJobs method, the return statements are inside child functions. You aren't returning the data from getJobs, you're returning it from the function you passed to then.
Two ways to fix it:
The magical Angular way for Angular less than 1.2
Angular views will work with promises for you, so you can just change your getJobs method to this:
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data.$promise.then(
function(res) {
return res.jobs
},
function(err) {
return err;
}
)
};
Added return data.$promise...
If you want this to still work in Angular 1.2, you need to call $parseProvider.unwrapPromises(true) somewhere in your code, typically on your main modules config block.
Less magical way or for Angular 1.2 and above
If you want to better understand what is going on, then you can do it this way
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
$scope.recruiterJobs = res.jobs
},
function(err) {
console.log(err);
}
)
};
A $resource call is asynchronous, but $resource immediately returns an empty object that you can embed in your page and will be later populated by response contents. If all goes well, angular should spot the change (because it comes from a $resource process that angular monitors) and update your view accordingly.
So, the behaviour you observe is normal : the very premise of a $promise is that it will be done at a later stage and the process should proceed anyway.
Solutions :
Simply try :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
return data;
};
If you don't need to post-process data, this should be all you need (except that you might need to call recruiterJobs.jobs in your view, if your response does indeed return an object containing a jobs array, and not the array itself). The page will display, with an initial empty div, then update when data are retrieved and ng-repeat discovers new data to add to the page.
If you do need some post-processing, you can still use your callback :
$scope.getJobs = function(uuid) {
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(
function(res) {
//do something
},
function(err) {
return err;
}
);
return data;
};
If you really need to wait for your data (e.g. because there are some downstream processes that you need them for that can't be postponed), you can use the promise to do so :
$scope.getJobs = function(uuid) {
$scope.preparing = true;
var data = RecruiterDashJobs.getJobs(uuid);
data.$promise.then(function(res) {
$scope.preparing = false;
return data;
});
};
This way, the function will not return until the promise is resolved. I added an optional $scope.preparing flag that you can use in your page to inform the user that something is loading.

How to use data from $http in other controller

How can I use the totalResults outside of the function that Im setting it? I just cant wrap my head around how to do it, I need to use the totalResults that I gather from my database and use in another function to calculate the amount of pages. I do this so I dont load all the data to the client but I still need to know the total count of rows in the database table.
My json looks like:
Object {total: 778, animals: Array[20]}
Angular:
var app = angular.module('app', []);
app.controller('AnimalController', ['$scope', 'animalSrc', function($scope, animalSrc)
{
$scope.animals = [];
var skip = 0;
var take = 20;
var totalResults = null;
//$scope.totalResults = null;
$scope.list = function()
{
animalSrc.getAll(skip, take, function(data) {
$scope.animals = $scope.animals.concat(data.animals);
// I need to be able to use this outside of function ($scope.list)
totalResults = data.total;
//$scope.totalResults = data.total;
});
};
$scope.showMore = function()
{
skip += 20;
$scope.list();
};
$scope.hasMore = function()
{
//
};
// Outputs null, should be the total rows from the $http request
console.log(totalResults);
}]);
app.factory('animalSrc', ['$http', function($http)
{
// Private //
return {
getAll: function(skip, take, callback)
{
$http({
method: 'GET',
url: 'url' + skip + '/' + take
}).
success(function(data) {
callback(data);
}).
error(function(data) {
console.log('error: ' + data);
});
}
};
}]);
You need to start thinking asynchronously. Your console.log is called before the $http has returned and totalResults has been set. Therefore, totalResults will always be null.
You need to find some way to delay the call to console.log so that the $http call can finish before you run console.log. One way to do this would be to put the console.log call inside your callback function so that it is definitely called after $http's success.
A more elegant way to do this is to use promises. angular.js implements $q, which is similar to Q, a promise library.
http://docs.angularjs.org/api/ng.$q
Instead of creating a callback function in getAll, you return a promise. Inside $http success, you resolve the promise with the data. Then, in your controller, you have a function that is called when the promise is resolved. Promises are nice because they can be passed around and they allow you to control the flow of your asynchronous code without blocking.
Here's a boilerplate I was just working on for myself for similar setup where data is an object that needs to be split into more than one scope item. Issue you weren't grasping is storing the data within the service, not just using service to retrieve data. Then the data items are available across multple controllers and directives by injecting service
app.run(function(MyDataService){
MyDataService.init();
})
app.factory('MyDataService',function($http,$q){
var myData = {
deferreds:{},
mainDataSchema:['count','items'],
init:function(){
angular.forEach(myData.mainDataSchema,function(val,idx){
/* create deferreds and promises*/
myData.deferreds[val]=$q.defer();
myData[val]= myData.deferreds[val].promise
});
/* load the data*/
myData.loadData();
},
loadData:function(){
$http.get('data.json').success(function(response){
/* create resolves for promises*/
angular.forEach(myData.mainDataSchema,function(val,idx){
myData.deferreds[val].resolve(response[val]);
});
/* TODO -create rejects*/
})
}
}
return myData;
})
app.controller('Ctrl_1', function($scope,MyDataService ) {
$scope.count = MyDataService.count;
$scope.items =MyDataService.items;
});
app.controller('Ctrl_2', function($scope,MyDataService ) {
$scope.items =MyDataService.items;
$scope.count = MyDataService.count;
});
Plunker demo

Resources