I have a basic controller that displays my products,
App.controller('ProductCtrl',function($scope,$productFactory){
$productFactory.get().success(function(data){
$scope.products = data;
});
});
In my view I'm displaying this products in a list
<ul>
<li ng-repeat="product as products">
{{product.name}}
</li>
</ul
What I'm trying to do is when someone click on the product name, i have another view named cart where this product is added.
<ul class="cart">
<li>
//click one added here
</li>
<li>
//click two added here
</li>
</ul>
So my doubt here is, how do pass this clicked products from first controller to second? i assumed that cart should be a controller too.
I handle click event using directive. Also i feel i should be using service to achieve above functionality just can't figure how? because cart will be predefined number of products added could be 5/10 depending on which page user is. So i would like to keep this generic.
Update:
I created a service to broadcast and in the second controller i receive it. Now the query is how do i update dom? Since my list to drop product is pretty hardcoded.
From the description, seems as though you should be using a service. Check out http://egghead.io/lessons/angularjs-sharing-data-between-controllers and AngularJS Service Passing Data Between Controllers to see some examples.
You could define your product service (as a factory) as such:
app.factory('productService', function() {
var productList = [];
var addProduct = function(newObj) {
productList.push(newObj);
};
var getProducts = function(){
return productList;
};
return {
addProduct: addProduct,
getProducts: getProducts
};
});
Dependency inject the service into both controllers.
In your ProductController, define some action that adds the selected object to the array:
app.controller('ProductController', function($scope, productService) {
$scope.callToAddToProductList = function(currObj){
productService.addProduct(currObj);
};
});
In your CartController, get the products from the service:
app.controller('CartController', function($scope, productService) {
$scope.products = productService.getProducts();
});
how do pass this clicked products from first controller to second?
On click you can call method that invokes broadcast:
$rootScope.$broadcast('SOME_TAG', 'your value');
and the second controller will listen on this tag like:
$scope.$on('SOME_TAG', function(response) {
// ....
})
Since we can't inject $scope into services, there is nothing like a singleton $scope.
But we can inject $rootScope. So if you store value into the Service, you can run $rootScope.$broadcast('SOME_TAG', 'your value'); in the Service body. (See #Charx description about services)
app.service('productService', function($rootScope) {/*....*/}
Please check good article about $broadcast, $emit
Solution without creating Service, using $rootScope:
To share properties across app Controllers you can use Angular $rootScope. This is another option to share data, putting it so that people know about it.
The preferred way to share some functionality across Controllers is Services, to read or change a global property you can use $rootscope.
var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = true;
}]);
app.controller('Ctrl2', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = false;
}]);
Using $rootScope in a template (Access properties with $root):
<div ng-controller="Ctrl1">
<div class="banner" ng-show="$root.showBanner"> </div>
</div>
You can do this by two methods.
By using $rootscope, but I don't reccommend this. The $rootScope is the top-most scope. An app can have only one $rootScope which will be
shared among all the components of an app. Hence it acts like a
global variable.
Using services. You can do this by sharing a service between two controllers. Code for service may look like this:
app.service('shareDataService', function() {
var myList = [];
var addList = function(newObj) {
myList.push(newObj);
}
var getList = function(){
return myList;
}
return {
addList: addList,
getList: getList
};
});
You can see my fiddle here.
An even simpler way to share the data between controllers is using nested data structures. Instead of, for example
$scope.customer = {};
we can use
$scope.data = { customer: {} };
The data property will be inherited from parent scope so we can overwrite its fields, keeping the access from other controllers.
angular.module('testAppControllers', [])
.controller('ctrlOne', function ($scope) {
$scope.$broadcast('test');
})
.controller('ctrlTwo', function ($scope) {
$scope.$on('test', function() {
});
});
I saw the answers here, and it is answering the question of sharing data between controllers, but what should I do if I want one controller to notify the other about the fact that the data has been changed (without using broadcast)? EASY! Just using the famous visitor pattern:
myApp.service('myService', function() {
var visitors = [];
var registerVisitor = function (visitor) {
visitors.push(visitor);
}
var notifyAll = function() {
for (var index = 0; index < visitors.length; ++index)
visitors[index].visit();
}
var myData = ["some", "list", "of", "data"];
var setData = function (newData) {
myData = newData;
notifyAll();
}
var getData = function () {
return myData;
}
return {
registerVisitor: registerVisitor,
setData: setData,
getData: getData
};
}
myApp.controller('firstController', ['$scope', 'myService',
function firstController($scope, myService) {
var setData = function (data) {
myService.setData(data);
}
}
]);
myApp.controller('secondController', ['$scope', 'myService',
function secondController($scope, myService) {
myService.registerVisitor(this);
this.visit = function () {
$scope.data = myService.getData();
}
$scope.data = myService.getData();
}
]);
In this simple manner, one controller can update another controller that some data has been updated.
we can store data in session and can use it anywhere in out program.
$window.sessionStorage.setItem("Mydata",data);
Other place
$scope.data = $window.sessionStorage.getItem("Mydata");
1
using $localStorage
app.controller('ProductController', function($scope, $localStorage) {
$scope.setSelectedProduct = function(selectedObj){
$localStorage.selectedObj= selectedObj;
};
});
app.controller('CartController', function($scope,$localStorage) {
$scope.selectedProducts = $localStorage.selectedObj;
$localStorage.$reset();//to remove
});
2
On click you can call method that invokes broadcast:
$rootScope.$broadcast('SOME_TAG', 'your value');
and the second controller will listen on this tag like:
$scope.$on('SOME_TAG', function(response) {
// ....
})
3
using $rootScope:
4
window.sessionStorage.setItem("Mydata",data);
$scope.data = $window.sessionStorage.getItem("Mydata");
5
One way using angular service:
var app = angular.module("home", []);
app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});
app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});
app.factory('ser1', function(){
return {o: ''};
});
I've created a factory that controls shared scope between route path's pattern, so you can maintain the shared data just when users are navigating in the same route parent path.
.controller('CadastroController', ['$scope', 'RouteSharedScope',
function($scope, routeSharedScope) {
var customerScope = routeSharedScope.scopeFor('/Customer');
//var indexScope = routeSharedScope.scopeFor('/');
}
])
So, if the user goes to another route path, for example '/Support', the shared data for path '/Customer' will be automatically destroyed. But, if instead of this the user goes to 'child' paths, like '/Customer/1' or '/Customer/list' the the scope won't be destroyed.
You can see an sample here: http://plnkr.co/edit/OL8of9
I don't know if it will help anyone, but based on Charx (thanks!) answer I have created simple cache service. Feel free to use, remix and share:
angular.service('cache', function() {
var _cache, _store, _get, _set, _clear;
_cache = {};
_store = function(data) {
angular.merge(_cache, data);
};
_set = function(data) {
_cache = angular.extend({}, data);
};
_get = function(key) {
if(key == null) {
return _cache;
} else {
return _cache[key];
}
};
_clear = function() {
_cache = {};
};
return {
get: _get,
set: _set,
store: _store,
clear: _clear
};
});
Make a factory in your module and add a reference of the factory in controller and use its variables in the controller and now get the value of data in another controller by adding reference where ever you want
One way using angular service:
var app = angular.module("home", []);
app.controller('one', function($scope, ser1){
$scope.inputText = ser1;
});
app.controller('two',function($scope, ser1){
$scope.inputTextTwo = ser1;
});
app.factory('ser1', function(){
return {o: ''};
});
<div ng-app='home'>
<div ng-controller='one'>
Type in text:
<input type='text' ng-model="inputText.o"/>
</div>
<br />
<div ng-controller='two'>
Type in text:
<input type='text' ng-model="inputTextTwo.o"/>
</div>
</div>
https://jsfiddle.net/1w64222q/
FYI
The $scope Object has the $emit, $broadcast, $on
AND
The $rootScope Object has the identical $emit, $broadcast, $on
read more about publish/subscribe design pattern in angular here
To improve the solution proposed by #Maxim using $broadcast, send data don't change
$rootScope.$broadcast('SOME_TAG', 'my variable');
but to listening data
$scope.$on('SOME_TAG', function(event, args) {
console.log("My variable is", args);// args is value of your variable
})
There are three ways to do it,
a) using a service
b) Exploiting depending parent/child relation between controller scopes.
c) In Angular 2.0 "As" keyword will be pass the data from one controller to another.
For more information with example, Please check the below link:
http://www.tutorial-points.com/2016/03/angular-js.html
var custApp = angular.module("custApp", [])
.controller('FirstController', FirstController)
.controller('SecondController',SecondController)
.service('sharedData', SharedData);
FirstController.$inject = ['sharedData'];
function FirstController(sharedData) {
this.data = sharedData.data;
}
SecondController.$inject['sharedData'];
function SecondController(sharedData) {
this.data = sharedData.data;
}
function SharedData() {
this.data = {
value: 'default Value'
}
}
First Controller
<div ng-controller="FirstController as vm">
<input type=text ng-model="vm.data.value" />
</div>
Second Controller
<div ng-controller="SecondController as vm">
Second Controller<br>
{{vm.data.value}}
</div>
I think the best way is to use $localStorage. (Works all the time)
app.controller('ProductController', function($scope, $localStorage) {
$scope.setSelectedProduct = function(selectedObj){
$localStorage.selectedObj= selectedObj;
};
});
Your cardController will be
app.controller('CartController', function($scope,$localStorage) {
$scope.selectedProducts = $localStorage.selectedObj;
$localStorage.$reset();//to remove
});
You can also add
if($localStorage.selectedObj){
$scope.selectedProducts = $localStorage.selectedObj;
}else{
//redirect to select product using $location.url('/select-product')
}
I need in my app to auto refresh when the back-end changes. I added a button to reload the GET to my back-end but I don't wish do that. This is my code
<body data-ng-app="myPr">
<div ng-controller="TodosController">
<div ng-repeat="todo in todos">
<p>{{todo.title}} ...... {{todo.is_completed}}</p>
</div>
<button ng-click="reload()">Reload</button>
</div>
</body>
my app.js
var myPr = angular.module('myPr',[]);
myPr.controller("TodosController", function ($scope,$http){
$scope.reload = function () {
$http.get('http://localhost:3000/api/todos').
success(function (data) {
$scope.todos = data.todos;
});
};
$scope.reload();
});
Thanks
You could just reload your data at regular intervals. Otherwise you'd need to setup something like socket.io or Pusher and push notifications to the browser when the server updates.
var myPr = angular.module('myPr',[]);
myPr.controller("TodosController", function ($scope,$http,$timeout){
$scope.reload = function () {
$http.get('http://localhost:3000/api/todos').
success(function (data) {
$scope.todos = data.todos;
});
$timeout(function(){
$scope.reload();
},30000)
};
$scope.reload();
});
You can use $interval(fuctionToRepeat, intervalInMillisecond) as documented here.
var myPr = angular.module('myPr',[]);
myPr.controller("TodosController", function ($scope,$http){
$scope.reload = function () {
$http.get('http://localhost:3000/api/todos').
success(function (data) {
$scope.todos = data.todos;
});
};
$scope.reload();
$interval($scope.reload, 5000);
});
Note: Intervals created by this service must be explicitly destroyed when you are finished with them. In particular they are not automatically destroyed when a controller's scope or a directive's element are destroyed. You should take this into consideration and make sure to always cancel the interval at the appropriate moment.
I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view.
I wan't only information if i'm going bad...
flowHandler service:
app.service('flowHandler', function(){
var count = 0;
this.init = function() { count++ };
this.end = function() { count-- };
this.take = function() { return count };
});
The MainCTRL append into <body ng-controller="MainCTRL">
app.controller("MainCTRL", function($scope, flowHandler){
var _this = this;
$scope.pageTitle = "MainCTRL";
$scope.menu = [];
$scope.loader = flowHandler.take();
$scope.$on("$routeChangeStart", function (event, next, current) {
flowHandler.init();
});
$scope.$on("$routeChangeSuccess", function (event, next, current) {
flowHandler.end();
});
updateLoader = function () {
$scope.$apply(function(){
$scope.loader = flowHandler.take();
});
};
setInterval(updateLoader, 100);
});
And some test controller when getting a data via $http.get:
app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
var _this = this;
$scope.test = "git";
flowHandler.init();
$http.get('api/menu.php').then(function(data) {
flowHandler.end();
$scope.$parent.menu = data.data;
},function(error){flowHandler.end();});
});
now, I already inject flowHandler service to any controller, and init or end a flow.
It's good idea or its so freak bad ?
Any advice ? How you do it ?
You could easily implement something neat using e.g. any of Bootstrap's progressbars.
Let's say all your services returns promises.
// userService ($q)
app.factory('userService', function ($q) {
var user = {};
user.getUser = function () {
return $q.when("meh");
};
return user;
});
// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
return $resource('role.json', {}, {
query: { method: 'GET' }
});
});
// ipService ($http)
app.factory('ipService', function ($http) {
return {
get: function () {
return $http.get('http://www.telize.com/jsonip');
}
};
});
Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.
app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {
_.extend($scope, {
loading: false,
data: { user: null, role: null, ip: null}
});
// Initiliaze scope data
function initialize() {
// signal we are retrieving data
$scope.loading = true;
// get user
userService.getUser().then(function (data) {
$scope.data.user = data;
// then apply role
}).then(roleService.query().$promise.then(function (data) {
$scope.data.role = data.role;
// and get user's ip
}).then(ipService.get).then(function (response) {
$scope.data.ip = response.data.ip;
// signal load complete
}).finally(function () {
$scope.loading = false;
}));
}
initialize();
$scope.refresh = function () {
initialize();
};
});
Then your template could look like.
<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>
<div ng-show="loading" class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%">
Loading, please wait...
</div>
</div>
<div ng-show="!loading">
<div>User: {{ data.user }}, {{ data.role }}</div>
<div>IP: {{ data.ip }}</div>
<br>
<button class="button" ng-click="refresh();">Refresh</button>
</div>
This gives you two "states", one for loading...
...and other for all-complete.
Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.
//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>
/* loading indicator */
app.directive('loadingIndicator', function () {
return {
restrict: 'E',
scope: {
isLoading: '#'
},
link: function (scope) {
scope.$watch('isLoading', function (val) {
scope.isLoading = val;
});
},
template: '<div ng-show="isLoading" class="progress">' +
' <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
' Loading, please wait...' +
' </div>' +
'</div>'
};
});
Related plunker here http://plnkr.co/edit/yMswXU
I suggest you to take a look at $http's pendingRequest propertie
https://docs.angularjs.org/api/ng/service/$http
As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending.
Then you could have a div showing a loading bar with a ng-show attribute that watches this function
I would also encapsulate this requests in a Factory or Service so my code would look like this:
//Service that handles requests
angular.module('myApp')
.factory('MyService', ['$http', function($http){
var Service = {};
Service.requestingSomeURL = function(){
for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
if($http.pendingRequests[i].url === ('/someURL')) return true;
}
return false;
}
return Service;
}]);
//Controller
angular.module('myApp')
.controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){
$scope.pendingRequests = function(){
return MyService.requestingSomeURL();
}
}]);
And the HTML would be like
<div ng-show="pendingRequests()">
<div ng-include="'views/includes/loading.html'"></div>
</div>
I'd check out this project:
http://chieffancypants.github.io/angular-loading-bar/
It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.
Its very simple and very useful :)
I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:
angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
$scope.$watch(function () {
return $http.pendingRequests.length;
}, function () {
var requestLength = $http.pendingRequests.length;
if (requestLength > 0)
$scope.loading = true;
else
$scope.loading = false;
});
});
Inject it into a controller
angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));
I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope
I used a simpler approach:
var controllers = angular.module('Controllers', []);
controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
function($scope, Project) {
$scope.projects_loading = true;
$scope.projects = Project.query(function() {
$scope.projects_loading = false;
});
}]);
Where Project is a resource:
var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
return $resource('../service/projects/:projectId.json', {}, {
query : {
method : 'GET',
params : {
projectId : '#id'
},
isArray : true
}
});
} ]);
And on the page I just included:
<a ng-show="projects_loading">Loading...</a>
<a ng-show="!projects_loading" ng-repeat="project in projects">
{{project.name}}
</a>
I guess, this way, there is no need to override the $promise of the resource
I am delving into Angularjs and got to the point where I am taking my application beyond the simple todo list stages.
A scenario I have, which must be common, is to have one controller that needs to be shared across multiple areas on my view.
In my case I have a profileController which I want to use to get the profile of the user from the server on each load.
When calling the loadUserProfile from within the profileController I saw that it was called 'n'-times (for the number of times I referenced the controller on the view), which is cool, so I changed the profileController to simply expose variables such as userProfile and isAuthenticated which simply references variables that I set in the $rootScope. These variables are set in a controller that is loaded once per page declared on my <body data-ng-controller="baseController as vm">, however this does not seem to work, and I am wondering what I am missing.
So it get's the data from the profileService, but when updating the $rootScope variables, it does not reflect it on my view.
Below is my baseController:
(function () {
'use strict';
var controllerId = 'baseController';
angular.module('app.controllers').controller(controllerId, ['$location', 'common', 'authClientSvc', 'profileService', '$rootScope', baseController]);
function baseController($location, common, authClientSvc, profileService, $rootScope) {
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
var vm = this;
$rootScope.isAuthenticated = false;
$rootScope.userProfile = {
email: '',
name: '',
surname: ''
};
activate();
function activate() {
var promises = [getUserProfile()];
common.activateController([promises], controllerId)
.then(function () { log('Activated Secure View'); });
}
function getUserProfile() {
profileService.getProfile('a#a.com').then(function (data) {
setTimeout(function () {
$rootScope.$apply(function () {
$rootScope.userProfile = data;
$rootScope.isAuthenticated = authClientSvc.isAuthenticated()
})
}, 1000);
});
}
}
})();
And my profileController where I expose the $rootScope variables below:
(function () {
'use strict';
var controllerId = 'profileController';
angular.module('app')
.controller(controllerId, ['common', 'authClientSvc', 'profileService', '$rootScope', profileController]);
function profileController(common, authClientSvc, profileService, $rootScope) {
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
var vm = this;
vm.logoutUrl = '/';
vm.isAuthenticated = $rootScope.isAuthenticated;
vm.profile = $rootScope.userProfile;
activate();
function activate() {
var promises = [];
common.activateController(promises, controllerId)
.then(function () { log('Activated Profile controller'); });
}
vm.logOut = function () {
authClientSvc.logout();
}
}
})();
I am not sure what is wrong, because in Fiddler I can clearly see the data coming back, and when I debug it and put log messages in the baseController after if gets the profile, it gets back the right data, but for some reason it is not working on my elements. Below is a sample of how I use it on the view:
<div class="login-info" data-ng-controller="profileController as vm">
<span ng-switch="vm.isAuthenticated">
<a ng-switch-when="true" href="javascript:void(0);" id="show-shortcut" data-action="toggleShortcut">
<img src="/Content/img/avatars/male.png" alt="me" class="online" />
<span data-localize="{{vm.profile.name}}.{{vm.profile.surname}}">
{{vm.profile.name}}.{{vm.profile.surname}}
</span>
<i class="fa fa-angle-down"></i>
</a>
<span ng-switch-when="false">
<img src="/Content/img/avatars/male.png" alt="guest" class="online" />
<span data-localize="Guest.user">
Guest User
</span>
</span>
</span>
</div>
Any help would be greatly appreciated, or even suggestions on how to achieve this in a better way.
Thanks.
According to Angular documentation:
When a Controller is attached to the DOM via the ng-controller directive, Angular will instantiate a new Controller object, using the specified Controller's constructor function. A new child scope will be available as an injectable parameter to the Controller's constructor function as $scope.
Meaning: Controllers are instantiated when the ng-controller directive is attached to the DOM. If you have two ng-controllers, then you will have two separate instances of ng-controller.
And
Do not use controllers to:
Share code or state across controllers — Use angular services instead.
DOM manipulation within an Angular controller seems to be wrong. But this is not coming without a few headaches :)
I have a button, and on ng-click, it will perform an asynchronous request in the background. During the time of that asynchronous request I would like all the buttons (and maybe a few more elements on the page) to be disabled and the clicked button to have a loading icons playing.
What is the cleanest way of doing this?
I usually do this with a variable on the $scope called loading. Whenever an asynch operation is happening, just set it to true. Then anything that's need to be disabled or otherwise affected can base it's state off of that.
Here's a dummy control:
function TestCtrl($scope, $http) {
$scope.loading = false;
$scope.doASynch = function () {
$scope.loading = true;
$http.get("/url").success(function () {
$scope.loading = false;
});
}
}
And here's a sample template.
<div ng-controller="TestCtrl">
<a class="button" ng-disabled="loading" ng-click="doASynch()">
<span ng-hide="loading">Click me!</span>
<span ng-show="loading">Loading....</span>
</a>
</div>
Here is exactly what you are looking for
Loading animations with Asynchronous HTTP Requests in Angular JS
var app = angular.module('myapp', ["ui.utils", "ui.router"]);
app.factory('iTunesData', function($http) {
return {
doSearch: function(sQuery) {
//return the promise directly.
return $http.jsonp('http://itunes.apple.com/search', {
params: {
"callback": "JSON_CALLBACK",
"term": sQuery
}
});
}
}
});
app.controller('iTunesSearch', function($scope, $location, $routeParams, iTunesData) {
$scope.search = function() {
iTunesData2.doSearch($scope.searchTerm)
.then(function(result) {
$scope.data = result.data;
$location.path($scope.searchTerm);
});
}
$scope.searchTerm = $location.$$path.split("/")[1];
if($scope.searchTerm!="") {
$scope.search();
}
});