Angularjs - Updating a scope variable after an AJAX operation - angularjs

var foo = angular.module('foo', []);
foo.factory('apiService', function($http){
'use strict';
return {
getItems: function() {
return $http.get('api/get-items')
.then(function(result) {
return result;
});
}
};
});
OutlastApp.controller('ItemListCtrl', function ($scope, apiService) {
'use strict';
apiService.getItems().then(function(result) {
$scope.items = result.data;
});
});
OutlastApp.controller('AddItemCtrl', function ($scope, $http, $rootScope, apiService) {
'use strict';
...
$scope.saveItem = function(item) {
$http({
url: 'api/add-item',
method: 'GET',
params: {
name: item.name,
description: item.description,
...
}
}).success(function(data, status, headers, config) {
// TODO: What to do?
});
};
});
As you can seem, I have setup $scope.items to get its contents from an AJAX call.
What I am trying to accomplish is that I want to try to update the $scope.items so that it will reflect the newly added item.
How do I do this? Is this the correct way to do some sort a two-way binding between API and scope variables?

Given what I can see here you need to probably share the "items" collection between the two controllers. In order to do this you can use a factory to store the items collection (approximately):
app.factory('SharedDataService', function () {
return {
items: []
};
});
After you have created this you can use inject it into both controllers and the "items" object will be shared between them. Then you can just push the new item after it is saved:
$http({
// run your call here
}).success(function(data, status, headers, config) {
// Here is where you push the new/saved item into the shared data:
SharedDataService.items.push(item);
});
That should really do it. Best of luck!
Video Tutorial - In case you haven't seen this concept before - as of now - John Lindquist has an excellent video tutorial here.

Related

AngularJS HTTP Get Scope

I'm new to AngularJS and I'm trying to modify this code to wrap my head around the conventions:
https://github.com/flatlogic/angular-material-dashboard/blob/master/src/app/components/services/MessagesService.js
I'm modifying it to use a REST service to fetch messages instead of using the messages array.
Here's the MessageService code:
(function(){
'use strict';
angular.module('app')
.service('messagesService', [
'$scope', '$http',
messagesService
]);
function messagesService($scope,$http){
return {
loadAllItems : function() {
//return $q.when(messages);
return $http({
method: 'GET',
url: 'http://localhost:3000/messages',
})
.then(function(data) {
console.log(data);
return data;
})
}
};
}
})();
But I'm getting an error about the scope:
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- messagesService
As Matthew Cawley said, services cannot access scope. Nor can you return data from loadAllItems() the way you are.
This is probably what you want with a controller example:
(function() {
'use strict';
var app = angular.module('app');
app.service('messagesService', ['$http', messagesService]);
function messagesService($http) {
return {
loadAllItems: function() {
//This will make the get request and return the promise that $http sends back.
return $http({
method: 'GET',
url: 'http://localhost:3000/messages',
});
}
};
};
app.controller('testCtrl', ['$scope', "messagesService", function($scope, messagesService) {
//Onload
messagesService.loadAllItems().then(function(data) {
console.log(data); //Your data from the messageService
}, function(err) {
console.log(err); //Error from the messageService
});
}]);
}());
$http returns a promise that can be accessed in your controller. You can then set your variable in scope and make the data accessible to the view.
Ex:
$scope.items = data; within testCtrl.
Take all of your references to $scope out, you can't inject scope into a service.

$scope not being acessed in the view

I'm using Angular in an application. After getting a specific object (a movie in my case), I'm assigning the object to $scope ($scope.movie = response), so that I can use it in the view. The problem is that my view seems not to display anything I use in $scope. I've tried deleting everything and doing a dummy test like $scope=name="whatever" and when I use something like {{name}} in the view nothing is rendered. Have anyone faced this problem ? I've already searched for this error, and it seems like it would be a good idea to use $apply(). I've tried that and it didn't work. The function that fetches the data is below:
var app = angular.module('movies');
app.factory('Films', ['$resource',function($resource){
return $resource('/films.json', {},{
query: { method: 'GET', isArray: true },
create: { method: 'POST' }
})
}]);
app.factory('Film', ['$resource', function($resource){
return $resource('films/:id.json', {}, {
show: {method: 'GET' },
update: { method: 'PUT', params: {id: '#id'} },
delete: { method: 'DELETE', params: {id: '#id'} }
});
}]);
app.controller('MoviesController', ['$scope', '$http', '$location', '$resource', '$routeParams', 'Films', 'Film', function($scope, $http, $location, $resource, $routeParams, Films, Film){
$scope.movies = Films.query();
$scope.user = document.getElementById('name').innerHTML; // Find a better way to interact with devise via angular
$scope.createMovie = function() {
$scope.movies = Films.query();
$http.get(
'/categories.json'
).success(function(data,status,headers,config){
$scope.categories = data;
}).error(function(data, status, headers, config){
alert("There was an error while fetching the categories on the database. Error " + status);
});
$location.path("/" + 'new').replace();
};
$scope.listMovies = function() {
$location.path("/").replace();
};
$scope.save = function(){
if($scope.form.$valid){
Films.create({film: $scope.movie}, function(){
$scope.form.$setPristine();
}, function(error){
alert("Movie not created");
});
}
};
$scope.deleteMovie = function(movie){
Film.delete(movie);
$scope.movies = Films.query();
};
$scope.viewDetails = function(movie){
$scope.name="ola";
alert(movie.id);
$location.path("/" + movie.id);
var Movie = $resource('films/:filmId'+'.json', {filmId: '#id'});
$scope.movie = Movie.get({filmId: movie.id});
$scope.movie.$promise.then(
function(response){
$scope.$apply();
$scope.movie = response;
console.log("filme e: " + response.name);
},
function(error){
console.log("request failed");
}
);
};
}]);
I had a look at your repository and I think where your problem is. You are trying to reuse the MoviesController in all of your routes. But AngularJS will create a new instance for every route and therefore you can't access your previous data because it will be destroyed.
So I would start by creating a separated controller for each view, so you can move the code of your viewDetails method to a new MovieDetailController. To have access to the movie id in this controller, you need to use the $routeParams service.
angular.module('movies').controller('MovieDetailController', MovieDetailController);
function MovieDetailController($scope, $resource, $routeParams) {
var Movie = $resource('films/:filmId'+'.json', {filmId: '#id'});
Movie.get({filmId: $routeParams.id}).then(
function(response) {
$scope.movie = response;
},
function(error){
console.log('request failed');
}
);
}
Change your route definition to use the new controller.
.when('/movies/:id', {
controller: 'MovieDetailController',
templateUrl: 'movie_details.html'
})
And now your viewDetails method in the MoviesController just need to redirect to the movie detail url.
$scope.viewDetails = function(movie) {
$location.path('/movies/' + movie.id);
}
I hope it works for you. Let me know when you try!

AngularJS factory with $http and multiple controllers

I want multiple controllers to be able to update a single view attached to one controller using a factory with $http.
My list view:
<div class="list" ng-repeat="image in images" ng-controller="controller1">
<div lass="item"><img src="{{image.url}}" /></div>
</div>
Service:
.factory("imageService", function($http) {
return {
getImages: function() {
return $http({
method: "get",
url: "http://example.com/images",
params: { user: window.localStorage['user_id'] }
})
}
}
});
Controller 1:
.controller('controller1', function($scope, imageService) {
window.localStorage['user_id'] = '101';
var handleSuccess = function(data, status) {
$scope.images = data;
};
imageService.getImages().success(handleSuccess);
})
This all works. When the app is loaded, the list immediately is populated with a list of images for user '101'.
In another controller, I want to be able to switch users and automatically re-poupulate the image list in the view from controller 1 with new images.
Controller 2:
.controller('controller2', function($scope, imageService) {
window.localStorage['user_id'] = '202';
imageService.getImages();
})
So controller 2 will run getImages() and I can see the $http request working via chrome dev tools / XHR. And I know why the list view attached to controller1 is not populating, but I dont know how to make it populate. I have tried moving the success callback into the service and setting a 'images' property on the service and a $scope.images in controller1, but no luck there.
How can I force the new list of images into the view attached to controller 1?
You should just manage a list into your service that you will bind to your controller1 :
.factory("imageService", function($http) {
var service = {};
service.images = {};
service.images.list = [];
service.getImages = function(userId) {
window.localStorage['user_id'] = userId;
return $http({
method: "get",
url: "http://example.com/images",
params: { user: userId }
}).success(function(data){
service.images.list = data
});
}
//at the initialization of the service, you launch the getImages once with the localStorage value.
service.getImages(window.localStorage['user_id']);
return service;
});
Then you can bind it like this in your controllers :
.controller('controller1', function($scope, imageService) {
$scope.images = imageService.images;
//then access it in the view with images.list
imageService.getImages(101);
})
.controller('controller2', function($scope, imageService) {
$scope.images = imageService.images;
//then access it in the view with images.list
imageService.getImages(202);
})
Note that using a sub object (images.list instead of images) is important.
If you want some more precise informations about why this sub object is needed you can read this answer on this subject
Hope it helped.
I think you can just use one controller. At some time point, say, a user clicks some button, reload the image lists and re-render the view to show the list.
For example, make the factory like:
.factory("imageService", function($http) {
return {
getImages: function(userId) {
return $http({
method: "get",
url: "http://example.com/images",
params: { user: userId }
});
}
};
});
And in the controller:
.controller('imageController', function($scope, imageService) {
function refreshImages (userId) {
imageService.getImages(userId).success(function(data) {
$scope.$apply(function () {
$scope.images = data;
});
});
}
refreshImages('101');
$scope.loadImages = function (userId) {
refreshImages(userId);
};
});

AngularJS Two way data binding between controller and service

I have a service in angular JS which is defined as follows:
'use strict';
angular.module('Offering.AccountService', [])
.service('Account', function($http, config) {
var account = this;
account.accountDetails = {};
this.getAccountDetails = function(){
var request = $http({
method: 'GET',
url: config.accountURL,
headers: {
'X-Parse-Application-Id': config.applicationID,
'X-Parse-REST-API-Key': config.restAPIKey,
}
})
.success(function(data, status, headers, config) {
console.log('Successfully aqquired account details');
account.accountDetails = data.results[0];
})
.error(function(data, status, headers, config) {
console.warn(status);
});
}
});
This service calls out to an endpoint to retrieve some data, I am calling this method in the run function of the main module as is shown below:
'use strict';
angular.module('Offering', [
'routerRoutes'
])
.run(function(Account){
Account.getAccountDetails();
})
In my controller, I have a variable called balance, in which I want to store the value retrieved from the service, as is shown in the code below:
'use strict';
angular.module('Offering.HomeController', [])
.controller('HomeCtrl', function(Account) {
this.data = {
message: "Account Details",
updateBalance: function() {
Account.getAccountDetails()
},
balance: Account.accountDetails.balance
}
// this.$watch(
// function () { return Account.accountDetails; },
// function (data) {
// $scope.data.balance = data.balance
// },
// true
// );
})
The data from the service is returned after the controller data object has been set up and thus the balance field is not getting populated. If I use the $scope.watch approach that is commented out, then the data object gets populated properly. However, I was under the assumption that this need not be done as there is a two way data binding between the service and the controller. Any help is greatly appreciated. Thanks !
The .run function doesn't stop to wait for the getAccountDetails to return a value. And so, when you assign this.data.balance = Account.accountDetails.balance, you are essentially doing this: this.data.balance = undefined.
As per comment above, the typical way is to have getAccountDetails() to return a promise, like so:
this.getAccountDetails = function(){
return $http({...})
.then(function(response){
console.log("successfully returned balanace", response.data);
return response.data;
});
}
Then, in the controller:
var vm = this;
Account.getAccountDetails()
.then(function(accountDetails){
vm.balance = accountDetails.balance;
});
You can not put a watch on the function which is returning promise, better you could put watch on this.data, Their is different way of put watch while using this keyword inside controller.
CODE
$scope.$watch(angular.bind(this, function () {
return this.data;
}), function (newVal, oldVal) {
// now we will pickup changes to newVal and oldVal
});
Hope this could help you , Thanks.

angularjs service not working as expected

I have this service:
angular.module('autotestApp').factory('GroupService', ['$http', function ($http) {
var groups = [];
return{
list: function () {
return groups;
},
retrieve: function () {
$http({
method: "get",
url: "/enterprises/_groups"
}).success(function (response) {
groups = response;
}).error(function () {
console.log("failed")
});
}
}
}]);
and this is my controller:
angular.module('autotestApp').controller('GroupsController', function($scope, $http, GroupService) {
GroupService.retrieve();
$scope.items = GroupService.list();
});
So, in my controller, I am first getting the result from the API so that the variable groups(in the service) gets assigned, then I am using the list() method to assign the values to $scope.items.
I am sure that API is returning results. But the
groups = response
part is not working correctly. When I changed it to
groups.push(response)
it is working but the result is a list inside a list, which I dont't want: [[ Object, Object, Object ]]
I want this: [ Object, Object, Object ]
How to fix this?
The reason
groups = response
is not working is because you're sending an async request that will replace the groups reference after you've already retrieved the old reference via the list function. The reason it works with the push modification is because Angular creates a watch that notices that the collection has changed and updates your view. However, your code is now working, but you don't understand why it works, and it's prone to breaking unexpectedly.
When dealing with asynchronous code, the best way to deal with it is to write your own code to take that into account. Here's a version that does the whole thing in an async fashion:
angular.module('autotestApp').factory('GroupService', ['$http', function ($http) {
var groupsResult;
return{
retrieve: function () {
if (groupsResult) { return groupsResult; }
return groupsResult = $http({
method: "get",
url: "/enterprises/_groups"
}).then(function (response) {
return response.data;
}, function () {
console.log("failed")
});
}
}
}]);
angular.module('autotestApp').controller('GroupsController',
['$scope', 'GroupService', function($scope, GroupService) {
GroupService.retrieve().then(function (groups) {
$scope.items = groups;
});
}]);
One of the fixes you could use is:
for (var i = 0; i < response.length; i++) {
groups.push(response[i]);
});
That way you would have [ Object, Object, Object ]
EDIT:
One thing you could try is the following, change your retrieve function to return your promise:
return{
list: function () {
return groups;
},
retrieve: function () {
var promise = $http({
method: "get",
url: "/enterprises/_groups"
}).success(function (response) {
groups = response;
}).error(function () {
console.log("failed")
});
return promise;
}
}
and then in your controller:
angular.module('autotestApp').controller('GroupsController', function($scope, $http, GroupService) {
GroupService.retrieve().finally(function () {
$scope.items = GroupService.list();
});
});
I think your groups = response is working, but when you do $scope.items = GroupService.list() the request isn't finished yet.
do groups.concat(response)
this will flatten the items & add it to parent list rather than appending a single element.

Resources