Shared variables and multiple controllers AngularJS - javascript

I have multiple controllers on a small app I'm writing, and I have successfully shared a 'selected' variable between the controllers like so.
app.service('selectedEmployee', function () {
var selected = null;
return
{
getSelected: function() {
return selected;
},
postSelected: function(employee) {
selected = employee;
}
};
});
I have a side nav bar with a list of employees. When I click on an employee I call the postSelected function then the getSelected to set $scope.selected.
$scope.selectEmployee = function(employee) {
//Calling Service function postSelected
selectedEmployee.postSelected(employee);
$scope.selected = selectedEmployee.getSelected();
if ($mdSidenav('left').isOpen()) {
$mdSidenav('left').close();
}
}
I have a third controller for my main content area, and this is where I don't understand what to do. I want information from the selected employee to be displayed, but angular is compiling the whole page before the first employee has a chance to get set as selected, and subsequent selections of an employee aren't reloading the main content page (because I haven't told them to I think). Here's my main content controller:
app.controller('mainContentController', ['$scope','selectedEmployee',
function ($scope, selectedEmployee) {
$scope.selected = selectedEmployee.getSelected();
console.log($scope.selected);
}
]);
My main content view is very simple right now
<h2>{{selected.firstName}}{{selected.lastName}}</h2>
My question is how I can tell one controller to effectively update its partial view so that when I select an employee it displays information.
GitLab repo

Don't rely on messy broadcasts if your goal is simply to display & modify the data in the controller's template.
Your controllers do NOT need to "know" when the Service or Factory has updated in order to use it in the template as Angular will handle this for you, as you access the data via dot notation. This is the important concept which you should read more about.
This Fiddle shows both ways of accessing the data, and how using the container object in the template causes Angular to re-check the same actual object on changes - instead of the primitive string value stored in the controller:
http://jsfiddle.net/a01f39Lw/2/
Template:
<div ng-controller="Ctrl1 as c1">
<input ng-model="c1.Bands.favorite" placeholder="Favorite band?">
</div>
<div ng-controller="Ctrl2 as c2">
<input ng-model="c2.Bands.favorite" placeholder="Favorite band?">
</div>
JS:
var app = angular.module("app", []);
app.factory('Bands', function($http) {
return {
favorite: ''
};
});
app.controller('Ctrl1', function Ctrl1(Bands){
this.Bands = Bands;
});
app.controller('Ctrl2', function Ctrl2(Bands){
this.Bands = Bands;
});

First of all lets start by good practices, then solve your problem here...
Good Practices
At least by my knowledge, i dont intend to use services the way you do... you see, services are more like objects. so if i were to convert your service to the way i normally use it would produce the following:
app.service('selectedEmployee', [selectedEmployeeService])
function selectedEmployeeService(){
this.selected = null;
this.getSelected = function(){
return this.selected;
}
this.postSelected = function(emp){
this.selected = emp;
}
}
You see there i put the function seperately, and also made the service an actual object.. i would reccomend you format your controller function argument like this... If you want to disuss/see good practices go here. Anways enough about the good practices now to the real problem.
Solving the problem
Ok The Andrew actually figured this out!! The problem was:that he need to broadcast his message using $rootScope:
$rootScope.$broadcast('selected:updated', $scope.selected);
And then you have to check when $scope.selected is updated.. kinda like $scope.$watch...
$scope.$on('selected:updated', function(event, data) {
$scope.selected = data;
})
After that it autmoatically updates and works! Hope this helped!
PS: Did not know he anwsered already...

So after much research and a lot of really great help from Dsafds, I was able to use $rootScope.$broadcast to notify my partial view of a change to a variable.
If you broadcast from the rootScope it will reach every child controller and you don't have to set a $watch on the service variable.
$scope.selectEmployee = function(employee) {
selectedEmployee.postSelected(employee);
$scope.selected = selectedEmployee.getSelected();
$rootScope.$broadcast('selected:updated', $scope.selected);
if ($mdSidenav('left').isOpen()) {
$mdSidenav('left').close();
}
}
And in the controller of the main content area
function ($scope) {
$scope.$on('selected:updated', function(event, data) {
$scope.selected = data;
})
}
I don't think you have to pass the data directly, you could also just as easily call selectedEmployee.getSelected()
$rootScope also has to be included in the Parent controller and the broadcasting controller.

Related

What is the right way to send data between modules in AngularJS?

One of the great things about angular is that you can have independent Modules that you can reuse in different places. Say that you have a module to paint, order, and do a lot of things with lists. Say that this module will be used all around your application. And finally, say that you want to populate it in different ways. Here is an example:
angular.module('list', []).controller('listController', ListController);
var app = angular.module('myapp', ['list']).controller('appController', AppController);
function AppController() {
this.name = "Misae";
this.fetch = function() {
console.log("feching");
//change ListController list
//do something else
}
}
function ListController() {
this.list = [1, 2, 3];
this.revert = function() {
this.list.reverse();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="app" ng-app="myapp" ng-controller="appController as App">
<div class="filters">
Name:
<input type="text" ng-model="App.name" />
<button ng-click="App.fetch()">Fetch</button>
</div>
<div class="list" ng-controller="listController as List">
<button ng-click="List.revert()">Revert</button>
<ul>
<li ng-repeat="item in List.list">{{item}}</li>
</ul>
</div>
</div>
Now, when you click on Fetch button, you'll send the name (and other filters and stuff) to an API using $http and so on. Then you get some data, including a list of items you want to paint. Then you want to send that list to the List module, to be painted.
It has to be this way because you'll be using the list module in diferent places and it will always paint a list and add some features like reordering and reversing it. While the filters and the API connection will change, your list behaviour will not, so there must be 2 different modules.
That said, what is the best way to send the data to the List module after fetching it? With a Service?
You should be using Angular components for this task.
You should create a module with a component that will be displaying lists and providing some actions that will modify the list and tell the parent to update the value.
var list = angular.module('listModule', []);
list.controller('listCtrl', function() {
this.reverse = function() {
this.items = [].concat(this.items).reverse();
this.onUpdate({ newValue: this.items });
};
});
list.component('list', {
bindings: {
items: '<',
onUpdate: '&'
},
controller: 'listCtrl',
template: '<button ng-click="$ctrl.reverse()">Revert</button><ul><li ng-repeat="item in $ctrl.items">{{ item }}</li></ul>'
});
This way when you click on "Revert" list component will reverse the array and execute the function provided in on-update attribute of HTML element.
Then you can simply set your app to be dependent on this module
var app = angular.module('app', ['listModule']);
and use list component
<list data-items="list" data-on-update="updateList(newValue)"></list>
You can see the rest of the code in the example
It is very simple. Please take a look at this small snippet. comments are added to highlight.
You can have a common module that contains all the data that needs to be shared across modules by two steps
adding module dependency
injecting corresponding provider in the respective module's controller
angular.module('commonAppData', []).factory('AppData',function(){
var a,b,c;
a=1;
return{
a:a,
b:b,
c:c
}
})
angular.module('list', ['commonAppData']).controller('listController', ListController);
var app = angular.module('myapp', ['list','commonAppData']).controller('appController', AppController);
function AppController(AppData) {
//assigning a variable
AppData.a=100;
this.name = "Misae";
this.fetch = function() {
console.log("feching");
//change ListController list
//do something else
}
}
function ListController(AppData) {
//Using the data sent by App Controller
this.variableA=AppData.a;
this.list = [1, 2, 3];
this.revert = function() {
this.list.reverse();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="app" ng-app="myapp" ng-controller="appController as App">
<div class="filters">
Name:
<input type="text" ng-model="App.name" />
<button ng-click="App.fetch()">Fetch</button>
</div>
<div class="list" ng-controller="listController as List">
<b>Shared variable : {{List.variableA}}</b>
<br>
<button ng-click="List.revert()">Revert</button>
<ul>
<li ng-repeat="item in List.list">{{item}}</li>
</ul>
</div>
</div>
There are a few ways to handle objects acress multiple controllers. Here are two.
1. Using Angulars $rootScope
You can assign an object to $rootScopewhich will hold up all information. This object can be passed into every controller through Angulars dependency injection. Also you can listen up to changes on your object by watching it through $watch or $emit.
Using $rootScope is an easy way, but may lead to performance issues on larger applications.
2. Using services
Angular provides a possibility to share object through services. Instead of defining your object inside of your controller, you could also do that inside of a service. Doing so you could inject that service into any controller and use it's values across your application.
function AppController(listService) {
// reference to the injected data
}
function ListController(listService) {
// update data
}
There are many ways to pass data from one module to another module and many ppl have suggested different ways.
One of the finest way and cleaner approach is using a factory instead of polluting $rootScope or using $emit or $broadcast or $controller.If you want to know more about how to use all of this visit this blog on
Accessing functions of one controller from another in angular js
By simply inject the factory you have created in main module and add child modules as dependancy in main module, then inject factory in child modules to access the factory objects.
Here is a sample example on how to use factory for sharing data across the application.
Lets create a factory which can be used in entire application across all controllers to store data and access them.
Advantages with factory is you can create objects in it and intialise them any where in the controllers or we can set the defult values by intialising them in the factory itself.
Factory
app.factory('SharedData',['$http','$rootScope',function($http,$rootScope){
var SharedData = {}; // create factory object...
SharedData.appName ='My App';
return SharedData;
}]);
Service
app.service('Auth', ['$http', '$q', 'SharedData', function($http, $q,SharedData) {
this.getUser = function() {
return $http.get('user.json')
.then(function(response) {
this.user = response.data;
SharedData.userData = this.user; // inject in the service and create a object in factory ob ject to store user data..
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Controller
var app = angular.module("app", []);
app.controller("testController", ["$scope",'SharedData','Auth',
function($scope,SharedData,Auth) {
$scope.user ={};
// do a service call via service and check the shared data which is factory object ...
var user = Auth.getUser().then(function(res){
console.log(SharedData);
$scope.user = SharedData.userData;// assigning to scope.
});
}]);
In HTML
<body ng-app='app'>
<div class="media-list" ng-controller="testController">
<pre> {{user | json}}</pre>
</div>
</body>
It depends on the circumstance. Is there a parent child relationship? Is the relationship unknown, or you simply want to avoid having to worry about it at all?
I think this post lays it out well (it always seems to be helpful):
http://mean.expert/2016/05/21/angular-2-component-communication/
AngularJS provides $on, $emit, and $broadcast services for event-based communication between controllers.
So, if we want to pass data from the inner controller (listController) to outer controller (appController) then we have to use $emit. It dispatches an event name upwards through the scope hierarchy and notify to the registered $rootScope.
Working Plunker : https://plnkr.co/edit/szf9jHvvOPLOvQc5sQI2?p=preview
This plunker is not as per the exact requirement as I don't know the API response but this sample plunker describe the problem statement.
I think you can use the publisher-subscriber pattern implemented in $rootScope.
In the ListController you have to inject $rootScope and after that you have to subscribe to an arbitrary called event that could have the pattern _data_received
$rootScope.$on('ingredients_data_received', function(ingredients) { prepare_recipe();});
so in your AppController you have to call $rootScope.$emit once the data of that list has been received
$rootScope.$emit('ingredients_data_received', ingredients);
This is just a way to pass data, you can also push those data or the promise in a $rootScope property but this is not a good practice, or you can create your own Service that manage the data for you (remember that you are working with a frontend framework so the controller has to control the view, the business logic has to be transfered to a service, not to a crontroller).
I like to use angular resource with a caching layer to persist data to multiple controllers using a singular method. This has a few benefits namely any controller has the same entry point to data. Keep in mind sometimes you need to fetch fresh data when navigating from one portion of the site to another so persisting http data is not always ideal.
'use strict';
(function() {
angular.module('customModule')
.service('BookService', BookService);
BookService.$inject = ['$resource'];
function BookService($resource) {
var BookResource = $resource('/books', {id: '#id'}, {
getBooks: {
method: 'GET',
cache: true
}
});
return {
getBooks: getBooks
};
function getBooks(params) {
if (!params) {
params = {};
}
return BookResource.getBooks(params).$promise;
}
}
})();
In any controller
BookService.getBooks().then(function(books) {
//books will be cached so invoking the call will return the same set of data anywhere
});
Services in angular are used to share functionality between components. Please try to keep them simple and with a single responsibility/purpose.
An idea would be creating a store service that will cache every response from the api in your application. Then you don't have to request it every time.
Hope it helps! ;)

Angular: how to make a "search" take you to another route and display results?

I have a main page with a nav, and each nav option takes you to another route. It all looks like a single page app, but each "page" has it's own route and controller.
My problem is that I want to put a search box in the navbar. When someone uses the searchbox, I want to take the user to the "search" route and then display the results. I'm having a lot of trouble figuring out these two issues:
Where do I store this "searchbox" logic? E.g. when someone searches, they choose the type of search from a dropdown, then the search query in the inputbox. I have special logic to automatically choose which dropdown value based on the value typed in the inputbox.
How do I redirect to the
"search" route and display the results based on the input from the
previous page?
It's probably clear I'm a newby to Angular. I'm happy to work out the details, but I'm mainly looking to understand how to structure the solution to this problem. Thanks in advance for your help.
What I love about Angular the most is the amount of options you can apply.
Your goal can be reached either by using a service. A service is a singleton class which you can request from controllers. Being a singleton what ever value you store in the service is available to all controllers. You can than either $watch for value change, use $broadcast to notify data change or use $routeParams to send data with route change.
A service is built as follows :
The following assume you have a global module var named 'app'
app.service('myService', function(){
var myValue;
this.getMyValue = function(){
return myValue;
};
this.setMyValue = function(value){
myValue = value;
};
});
Then you request a service from a controller like you request an angular service such as $scope.
app.controller('myController', ['$scope', 'myServce', function($scope, myService){
$scope.myValue = myService.getMyValue();
//Example watch
$scope.$watch('myValue',function(){
//Search criteria changed!!
}, true);
}]);
Angular is terrific..have fun coding
Basically you would want an own state for your search page, so this is where we begin (I expect you to use the ui-router and not Angulars built in router):
.state('search', {
url: "/search",
templateUrl: "pages/search.html",
controller: 'SearchController as ctrl',
params: { searchString: {} }
})
As you can see, I've defined an additional parameter for the search string that is not part of the URL. Of course, if you like, you could change that and move the parameter to the URL instead:
.state('search', {
url: "/search/:searchString",
templateUrl: "pages/search.html",
controller: 'SearchController as ctrl'
})
The actual search input is pretty straight forward as well, because it's only HTML:
<input type="text" ng-model="searchString" on-key-enter="ctrl.goSearch(searchString)">
The function for the state change has to be placed in the controller for the primary template (e.g. the controller of your navigation bar if the search is located there):
var vm = this;
vm.goSearch = goSearch;
function goSearch(searchString) {
$state.go('main.search', { searchString: searchString });
}
Of interest is also the on-key-enter directive that I've added:
angular.module('your.module')
.directive('onKeyEnter', OnKeyEnter);
function OnKeyEnter() {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.onKeyEnter);
});
event.preventDefault();
}
});
};
}
On pressing the enter-key, it will call the function you supply as attribute value. Of course you could also use a button with ng-click instead of this directive, but I think it simply looks better.
Last, but not least, you need a Search Controller and a HTML template for your search page, which I won't give to you, as it is up to you what you display here. For the controller, you only need to know how you can access the search string:
angular.module('your.module')
.controller('SearchController', SearchController);
SearchController.$inject = ['$scope', '$stateParams'];
function SearchController($scope, $stateParams) {
$scope.searchString = $stateParams.searchString;
/* DO THE SEARCH LOGIC, e.g. database lookup */
}
Hope this helps to find the proper way. :)

$rootscope between controllers and refreshing it

I am using a jsonn object to load in data between objects. currently using a factory to return the object and bind it between the controllers. Right now I am making a copy like so :
var LevelsHere = $http.get("my.json")
.success(function(data){
var dataCopy = angular.copy(data);
return dataCopy;
});
return {
all: function() {
return LevelsHere;
}
};
This works fine, but I have a button that I want to call this function and refresh it so It gets a clean copy from the my.json (so any changes are reverted).
Just to clarify, in each controller I call it in into a scope within the controller like so
UserService.all().then(function(data){
$scope.storeHere= data.data;
});
I am thinking maybe something like a $rootscope might be the way to go because I am sharing between controllers. So - have the root scope (which is a copy of the json) be shared between controllers. Then when I press my refresh button it would refresh that $rootscope with a fresh copy of my.json so changes would revert back.
Maybe I could use the method I am trying now? I tried the having the refresh button call the $get again but it wasnt binded to both places so it was only refreshing in one controller.
To quickly review - I have json I'm bringing in and using in 2 controllers with a factory calling it. I want to be able to refresh that so it refreshes in both places.
Here is my attempt at the refresh :
$scope.cancelProcedure = function() {
//refresh data
UserService.all().then(function(data){
$scope.levels = data.data;
};
The problem with this is it calls the current data, and doesn't refresh with a new call. I'm not sure how to make it refresh in both places. Thanks!!
To give you an answer I assume the following:
There's one resource you want to get my.json and update from time to time.
You want to access and update the data from two (or more) controllers
You don't want to pollute your $rootScope
In that case the ideal solution would be to store the method to get/update the data in the factory as well as the current data. In each controller, where you need that data, you simply inject the factory and assign it to the $scopeof that controller.
Here's one example:
angular.factory('dataFactory', ['$http', function ($http) {
var dataFactory={};
dataFactory.currentData = null;
dataFactory.update = function () {
return $http.get("my.json")
.success(function(data){
dataFactory.currentData = data;
return data;
});
};
return dataFactory;
}]);
angular.controller('firstCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
angular.controller('secondCtrl', ['$scope', 'dataFactory', function ($scope, dataFactory) {
$scope.data = dataFactory;
}]);
In your HTML you can then use e.g. ng-bind="data.currentData" and ng-click="data.update()".
Further thoughts: If you don't want to put the factory in your controllers $scope you might even consider to further break down your logic and create one or two directives that are based on that factory. If that makes sense is not easy to tell with the given information, however.

How does passing data around different views/pages work on angularJS

I am learning angularJS, went through few tutorials and sort of know my why around. It seems that the page never refreshes, therefore a value created in one view should be available in another view, right? I am testing this in a shop scenario. If we are at the main view, and we click on "add to cart" that should trigger a function in the background and add the item in an array. Then when we go to the cart view, we can see the item listed there. But this does not work.
I have a cart controller:
angular.module('shoppingCartApp')
.controller('CartCtrl', function ($scope) {
$scope.cart = [
'one item'
];
$scope.pushing = function(item){
this.cart.push(item);
};
});
In the main view (which doesn't have access to this controller) I have.
<div ng-controller="CartCtrl">
add to chart
</div>
And on the cart view I display the cart object
<div ng-repeat="item in cart">
{{item}}
</div>
We only see the one item. I have also added the ng-click attribute to this page as well, just to test, and it does work, however, if we go home and come back, the item is gone.
From the idea that the page never reloads, should the pushed items stay in the array? here is the simple example in action
Thanks
Controllers are not singletons, so when you change the view the $scope gets destroyed and a new controller will be initialized. If you want persistent data across different views then you want to look at using a service to store it, since they are singletons.
If you create something like
angular.module('app')
.service('cartService', [function() {
var cart = [];
var add = function(item) {
cart.push(item);
};
var get = function() {
return cart;
};
return {
add: add,
get: get
};
}]);
Then you can add that as a dependency in your controllers and use that for backing your data rather than using the $scope.
angular.module('app')
.controller('Ctrl', ['cartService', function(cartService) {
$scope.cart = cartService.get();
$scope.pushing = function(item) {
cartService.add(item);
};
}]);

Create a model based on a Database entity

I'm new to AngularJS and I would like to understand how to properly separate the model from the controller. Till now I've always worked with the models inside the controllers. For instance:
angular.module("app").controller("customerController", ["Customer", "$scope", "$routeParams",
function (Customer, $scope, $routeParams){
$scope.customer = Customer.find({ID:$routeParams.ID});
}]);
This function retrieves a customer from the database and exposes that customer to the view. But I would like to go further: for example I could have the necessity to ecapsulate something or create some useful functions to abstract from the row data contained in the database. Something like:
customer.getName = function(){
//return customer_name + customer_surname
};
customer.save = function(){
//save the customer in the database after some modifies
};
I want to create a model for the Customer and reuse that model in lots of controllers. Maybe I could then create a List for the customers with methods to retrieve all customers from the database or something else.
In conclusion I would like to have a model that reflects a database entity (like the customer above) with properties and methods to interact with. And maybe a factory that creates a Customer or a list of Customers. How can I achieve a task like this in AngularJS? I would like to receive some advices for this issue from you. A simple example will be very useful or a theoretical answer that helps me to undestand the right method to approch issues like these in Angular. Thanks and good luck with your work.
Angular JS enables you to have automatic view updates when a model change or an event occur.
TAHTS IT!
it does so by using $watches which are a kind of Global Scope java script objects and stay in primary memory through out the life cycle of the angular js web app.
1.Please consider the size of data before putting anything onto the $scope because each data object you attach to it does +1 to $watch. As you are reading from a database you might have 100+ rows with >4 columns and trust me it will eat up client side processing.Pls do consider the size of your dataset and read about angular related performance issues for huge data set
2.to have models for your database entity i would suggest having plain javascript classes i.e. dont put everything on $scope (it will avoid un necessay watches! ) http://www.phpied.com/3-ways-to-define-a-javascript-class/
3.You wish to fire up events when the user changes the values. For this best i would suggest that if you are using ng-repeat to render the data in your array then use $index to get the row number where the change was done and pass this in ng-click i.e. and use actionIdentifier to distinguish in the kinds of events you want
ng-click="someFunc($index,actionIdentifier)"
You need to create a factory/service to do do the job, check jsfiddle
html:
<div ng-app="users-app">
<h2>Users</h2>
<div ng-view ></div>
<script type="text/ng-template" id="list.html">
<p>Users: {{(user || {}).name || 'not created'}}</p>
<button ng-click='getUser()'>Get</button>
<button ng-click='saveUser(user)'>Save</button>
</script>
</div>
js:
angular.module('users-app', ['ngRoute'])
.factory('Users', function() {
function User (user) {
angular.extend(this, user);
}
User.prototype.save = function () {
alert("saved " + this.name);
}
return {
get: function() {
return new User({name:'newUser'});
}
}
})
.config(function($routeProvider) {
$routeProvider
.when('/', {controller:'ListCtrl',templateUrl:'list.html'});
})
.controller('ListCtrl', function($scope, Users) {
$scope.getUser = function() {
$scope.user = Users.get();
}
$scope.saveUser = function(u) {
u.save();
}
})
Hope that help,
Ron

Categories

Resources