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);
};
});
Related
I have a controller 'searchCntr' with ng-app = 'homePage'. I want to send data using rootscope to a controller = "HashPageCntr" with ng-app set as 'HashPage'. Can i do this using rootscope? I am new to this. Thanks in advance
var app = angular.module('homePage',[]);
app.controller('searchCntr',function($scope,$http,$rootScope) {
$scope.searchHash =function () {
$http({
method: "POST",
data: {
"search": $scope.search
},
url: "/fetchHashes"
}).success(function (data) {
alert(data);
$rootScope.$broadcast('eventName',data);
window.location.assign("/goTohashPage");
});
}
});
// This is a different controller.
var app = angular.module('hashPage',[]);
app.controller('HashPageCntr',function($rootScope){
$rootScope.$on('eventName',function(event,data){
alert("In hash Page ");
alert(data);
});
});
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!
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.
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.
I'm trying to make a single $http request to get one of my JSON files and use the data across all my controllers.
I saw on egghead.io how to share data across multiple controllers, and I've also read this StackOverflow question: "Sharing a variable between controllers in angular.js".
However, the answers there don't use the $http module. When using $http, the controllers don't have the data to work on, and by the time the response is received it's already too late.
I then found the method $q.defer and this question on StackOverflow: "AngularJS share asynchronous service data between controllers"
The solution posted there works fine, BUT it has two issues:
Each controller will trigger the $http request to obtain the same data already used in another controller; and,
If I try to manipulate the data received I have a then function.
Below you can see my code:
controllers.js
'use strict';
/* Controllers */
function appInstallerListCtrl($scope, Data) {
$scope.apps = Data;
}
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
$scope.apps = Data;
console.log($scope.apps); // <-- then function
console.log(Data); // <-- then function with $vv data returned but I can't access it
for (var i in $scope.apps) // <--- no way, baby!
console.log(i);
}
app.js
var app = angular.module('appInstaller', []);
app.factory('Data', function($http, $q) {
var defer = $q.defer();
$http.get('apps.json').then(function(result) {
defer.resolve(result.data.versions.version);
});
return defer.promise;
});
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/app', {templateUrl: 'partials/app-list.html', controller: appInstallerListCtrl}).
when('/app/:appId', {templateUrl: 'partials/app-detail.html', controller: appInstallerDetailCtrl}).
otherwise({redirectTo: '/app'});
}]);
What I'd like to have is that when launching the app, the $http request will be performed and the response will be used throughout the app across all controllers.
Thanks
I like to store my data in the service, and return a promise to the controllers, because usually you need to deal with any errors there.
app.factory('Data', function($http, $q) {
var data = [],
lastRequestFailed = true,
promise;
return {
getApps: function() {
if(!promise || lastRequestFailed) {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('apps.json')
.then(function(res) {
lastRequestFailed = false;
data = res.data;
return data;
}, function(res) {
return $q.reject(res);
});
}
return promise;
}
}
});
.controller('appInstallerListCtrl', ['$scope','Data',
function($scope, Data) {
Data.getApps()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
}]);
Any callbacks that are registered after a promise has been resolved/rejected will be resolved/rejected immediately with the same result/failure_reason. Once resolved/rejected, a promise can't change (its state). So the first controller to call getApps() will create the promise. Any other controllers that call getApps() will immediately get the promise returned instead.
Since you are using a promise, to access the data returned by promise use the callback syntax
function appInstallerDetailCtrl($scope, $routeParams, Data) {
$scope.appId = $routeParams.appId;
Data.then(function(returnedData) {
$scope.apps=returnedData;
console.log($scope.apps);
for (var i in $scope.apps)
console.log(i)
});
}
Make sure this
defer.resolve(result.data.versions.version);
resolve returns array, for the above code to work. Or else see what is there in data and ajust the controller code.
I found the way not sure weather it is a best approach to do it or not.
In HTML
<body ng-app="myApp">
<div ng-controller="ctrl">{{user.title}}</div>
<hr>
<div ng-controller="ctrl2">{{user.title}}</div>
</body>
In Javascript
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.controller('ctrl2', function($scope, $http, userService) {
userService.getUser().then(function(user) {
$scope.user = user;
});
});
app.factory('userService', function($http, $q) {
var promise;
var deferred = $q.defer();
return {
getUser: function() {
if(!promise){
promise = $http({
method: "GET",
url: "https://jsonplaceholder.typicode.com/posts/1"
}).success(function(res) {
data = res.data;
deferred.resolve(res);
})
.error(function(err, status) {
deferred.reject(err)
});
return deferred.promise;
}
return deferred.promise;
}
}
});
This will exactly make only 1 HTTP request.
My issue was that I didn't want to wait for resolve before loading another controller because it would show a "lag" between controllers if the network is slow. My working solution is passing a promise between controllers via ui-router's params and the data from promise can be loaded asynchronously in the second controller as such:
app.route.js - setting the available params to be passed to SearchController, which shows the search results
.state('search', {
url: '/search',
templateUrl: baseDir + 'search/templates/index.html',
controller: 'SearchController',
params: {
searchPromise: null
}
})
landing.controller.js - controller where the user adds search input and submits
let promise = SearchService.search(form);
$state.go('search', {
searchPromise: promise
});
search.service.js - a service that returns a promise from the user input
function search(params) {
return new Promise(function (resolve, reject) {
$timeout(function() {
resolve([]) // mimic a slow query but illustrates a point
}, 3000)
})
}
search.controller.js - where search controller
let promise = $state.params.searchPromise;
promise.then(r => {
console.log('search result',r);
})