AngularJS - Correct way to share data between two controllers - javascript

I'd like some advice on how to share some data between two or more controllers in AngularJS.
For now I'm just dealing with two controllers, but in the future I will have more controllers that will also want to use this same data. Right now I have a navigation-controller which is controlling the side navigation and the header. And for ease of understanding, let's say the second controller is called content-controller which is responsible for dealing with all the content.
I want to dynamically load the content based on whatever the user searches for and the search bar is in the side navigation, so this searchTerm needs to be accessible by both controllers. In the future, I would also implement some other features which would probably need to access this searchTerm as well.
In terms of the HTML structure, the content-controller is inside the navigation-controller.
My first thought was to make searchTerm globally available by sticking it in $rootScope, but I'm unsure if this is an efficient/secure way to do it.
My second thought was to take the searching aspects and put them into a service. Inside this service I would put functions which would speak to the API in order to get the necessary data. This would mean on the search bar, I can make the submit search button access the service and run something like FooService.update(searchTerm).
What do you think the best way to deal with this scenario is?

Sharing data between controllers has always been a prominent requirement. You have a couple of options out there :
Factory
Services
You can refer to this answer, for more details upon the differences.
Using services is definitely the better option, since you won't be polluting the root scope with extra variables [That are destined to grow in numbers as your have already mentioned].
A possible way to store your data in services, and access them in controllers and HTML effortlessly can be described as :
Create a service, that will hold all the model variables.
angular.service("dataService", function() {
this.value1 = "";
this.value2 = "";
});
reference that service in your controllers, saving their reference in the scope.
angular.controller("myCntrl1", function($scope, dataService) {
$scope.dataService = dataService;
});
angular.controller("myCntrl2", function($scope, dataService) {
$scope.dataService = dataService;
});
Now in your html, you refer all your modal variables using the service reference :
// Controller 1 view
<div ng-controller="myCntrl1">
<input type="text" ng-model="dataService.value1" />
</div>
// Controller 2 view
<div ng-controller="myCntrl2">
The value entered by user is {{dataService.value1}}
</div>

First of all i don't know whether it's gonna work for you.
You can use local storage.
By using this the same data can be accessed in any controller
Here's an example how it worked for me.
app.controller("loginCtrl", function($scope, $window){
$scope.submit = function(){
$window.localStorage.setItem = ("username", $scope.username);
};
});
app.controller("homeCtrl", function($scope, $window){
$scope.logout = function(){
$window.localStorage.getItem = ("username");
};
});

Related

refactoring $rootScope.$apply in Angular

my MusicPlayer.js Angular service has a callback function wrapped in $rootScope.$apply that updates a specific object (musicPlayer.currentPlaybackTime) and is shared to all other controllers ( via applying to $rootScope).
I understand that you'll ideally want to limit any $rootScope pollution, so i'm looking at possible refactoring options that take away calling apply methods to $rootScope but allows my updated object to be shared across multiple controllers.
My research indicates that i'll need to register the other controllers (i.e. PlayerDashboardCtrl.js, PlaylistCtrl.js and AlbumListCtrl.js) that need my currentPlaybackTime object, but i'd like to understand what's the most efficient way of doing this.
Thank you.
var setSong = function(song) {
currentBuzzObject = new buzz.sound(song.audioUrl, {
formats: ['mp3'],
preload: true
});
currentBuzzObject.bind('timeupdate', function() {
$rootScope.$apply(function() {
musicPlayer.currentPlaybackTime = currentBuzzObject.getTime();
});
});
musicPlayer.currentSong = song;
};
The best way to share data between controllers is to make a service/factory and get data using these service from whichever controller you want. You will have to inject this service in all the controllers where you want to access them.
This egghead video will give you a clear understanding: Share data between controllers
Stackoverflow question similar to this: Stackoverflow answers to sharing data between controllers.
Live Demo: Fiddle to show data sharing.

Can you access a variable from one scope to another in Angular

I have an Angular.js application that I am building. It is truly my first real Angular.js application, so I am learning real world issues as I go along and how to solve them.
My application is to be used by judges while presiding over hearings in the courtroom. It has the following views:
Calendar
Documents
Parties
It also has a Preferences screen where they set their default selections for courthouse, courtroom and type of law they normally work in.
My issue is with the preferences screen. I am needing to provide something like this:
<div>
<div>Civil</div><div></div>
<div>Courthouse</div><div><--dropdown of courthouses set to their default if selected already--></div>
<div>Courtroom</div><div><-- dropdown of courtrooms in the selected courthouse. Should only populate and be selectable after courthouse is selected--></div>
</div>
I already have code in another controller that grabs the courtrooms filtered by courthouse and type of law and would like to reuse that here. Is the best way to populate a variable in a factory and then refer to that in any of the controllers? Thus, I might have:
angular.module('DepartmentService', []).factory('DeparrmentService', ['$rootScope', '$route', function ($rootScope, $route) {
// Do stuff to populate the department here
return DepartmentService;
}]);
I could then do:
$scope.departments = DepartmentService;
Is that correct or is there another/better way to gain access to these variables across the various controllers in my application?
I know that using global variables at the rootScope is frowned upon, but it seems to me the easiest way would be if I could have a variable that doesn't go away when the page refreshes and is accessible to any controller.
You have a good basic idea. Something like this will be better, as a factory is a singleton. You should also always return promises from your services, where possible.
angular.module('DepartmentService', []).factory('DeparrmentService', ['$rootScope', '$route', '$q', function ($rootScope, $route, $q) {
var data;
populateData();
return {
getData:getData
};
function populateData(){
//get the data
data = responseFromServer;
}
function getData(params){
var deferred = $q.defer();
var filteredData = data.filter(function(d){
//do filtering here based on the params that you passed.
})
deferred.resolve(filteredData);
return deferred.promise;
}
}]);
In your controller you will call it like this:
angular.module('app').controller('MyController', function($scope, DepartmentService){
DepartmentService.getData({courthouse:"Some Court House"})
.then(function(filteredDepartments){
$scope.departments = filteredDepartments;
})
})
I typically store shared code and data access layers in a service/factory. I can then pass those services/factories to any controller that needs the underlying data either by instantiation or by singleton pattern depending on the nature of the model (does it change or have a state vs immutable). This pattern lends itself well to the dependency injection available in angular.

Angularjs Best Practice for Data Store

My angular app have 2 controllers. My problem is that the controllers does not keep the data when the user navigates away from the page.
How can I store the selected data on of my controllers into a data store so it can be used between other controllers?
Option 1 - custom service
You can utilize a dedicated angular service to store and share data between controllers (services are single instance objects)
service definition
app.service('commonService', function ($http) {
var info;
return {
getInfo: getInfo,
setInfo: setInfo
};
// .................
function getInfo() {
return info;
}
function setInfo(value) {
info = value;
}
});
usage in multiple controllers
app.controller("HomeController", function ($scope, commonService) {
$scope.setInfo = function(value){
commonService.setInfo(value);
};
});
app.controller("MyController", function ($scope, commonService) {
$scope.info = commonService.getInfo();
});
Option 2 - html5 localStorage
You can use the built-in browser local storage and store your data from anywhere
writing
$window.localStorage['my-data'] = 'hello world';
reading
var data = $window.localStorage['my-data']
// ...
check out this awesome project:
https://github.com/grevory/angular-local-storage
Option 3 - via web server api
If you need to persist data among different users, you should save it somewhere in the server side (db / cache)
function getProfile() {
return $http.get(commonService.baseApi + '/api/profile/');
}
function updateProfile(data) {
var json = angular.toJson(data);
return $http.post(commonService.baseApi + '/api/profile/', json);
}
EDIT See Jossef Harush's answer where he has written an in-depth response that covers other methods including this one.
I'd recommend using either localStorage or sessionStorage - http://www.w3schools.com/html/html5_webstorage.asp.
HTML local storage provides two objects for storing data on the client:
window.localStorage - stores data with no expiration date
window.sessionStorage - stores data for one session (data is lost when the browser tab is closed)
This assumes that you don't want to POST/PUT the data to your web service (windows service mention in your question).
If you data is an array or some sort, you can convert it to JSON to store as a string and then when you need it you can parse it back as follows - How do I store an array in localStorage?:
var names = [];
names[0] = prompt("New member name?");
localStorage["names"] = JSON.stringify(names);
//...
var storedNames = JSON.parse(localStorage["names"]);
There is an option not mentioned in other answers (AFAIK).
EVENTS
You can use events for communication between controllers.
It's a straightforward communication that doesn't need a mediator
(like service) and can't be wiped by the user (like HTML storage).
All the code is written in controllers that you are trying to
communicate with and thus very transparent.
A good example how to leverage events to communicate between controllers can be seen below.
The publisher is the scope that wanna publish (in other words let others know something happened). Most don't care about what has happened and are not part of this story.
The subscriber is the one that cares that certain event has been published (in other words when it gets notified hey, this happened, it reacts).
We will use $rootScope as a mediator between publisher and a subscriber. This always works because whatever scope emits an event, $rootScope is a parent of that scope or parent of a parent of a parent.. When $rootScope broadcasts (tells everyone who inherits) about an event, everyone hears (since $rootScope is just that, the root of the scope inheritance tree) so every other scope in app is a child of it or child of a child of a child..
// publisher
angular.module('test', []).controller('CtrlPublish', ['$rootScope','$scope',
function ($rootScope, $scope) {
$scope.send = function() {
$rootScope.$broadcast('eventName', 'message');
};
}]);
// subscriber
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('eventName', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Above we see two controllers communicating a message to each other using an event. The event has a name, it has to be unique, otherwise, a subscriber doesn't differentiate between events. The event parameter holds autogenerated but sometimes useful data, the message is the payload. In this example, it's a string but it can be any object. So simply put all the data you wish to communicate inside an object and send it via event.
NOTE:
You can avoid using root scope for this purpose (and limit the number of controllers that get notified of an event) in case two scopes are in direct inheritance line of each other. Further explanation below:
$rootScope.$emit only lets other $rootScope listeners catch it. This is good when you don't want every $scope to get it. Mostly a high level communication. Think of it as adults talking to each other in a room so the kids can't hear them.
$rootScope.$broadcast is a method that lets pretty much everything hear it. This would be the equivalent of parents yelling that dinner is ready so everyone in the house hears it.
$scope.$emit is when you want that $scope and all its parents and $rootScope to hear the event. This is a child whining to their parents at home (but not at a grocery store where other kids can hear). This is a shortcut to use when you wanna communicate from the publisher that is a child or n-th child of the subscriber.
$scope.$broadcast is for the $scope itself and its children. This is a child whispering to its stuffed animals so their parents can't hear.
EDIT: I thought plunker with a more elaborate example would be enough so I decided to keep is simple here. This elaborate explanation should be better.
To share data between two controllers on the same page, you can use factories/services. Take a look at Share data between AngularJS controllers for example.
However, if this is across page reloads/refreshes, you will need to store the data in local storage and then read it upon reloading. An example of that is here: How do I store data in local storage using Angularjs?
Checkout this library https://www.npmjs.com/package/angularjs-store
This can help you manage your application state much simpler as it will force you to have a one way data flow on your application.

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

Private variables in the controller scope

In angular, following the demos, I can define a controller as:
function TodoCtrl($scope) {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false},
{text:'empty dishwasher', done:false}];
$scope.oldtodos = [];
var oldtodos2 = [];
...
You'll note that I have two oldtodos. One on the $scope and one that is just a local var. I think the latter approach is the way to go if you want to encapsulate this variable - i.e. no-one other than the controller has any interest in it and the former is good if you want to pass the variable back to the model
Am I correct?
It looks like you just want to keep a private copy of oldTodos around in the event you have to either refer back to them or resurrect one of them or something. In that case, it probably makes sense not to put the oldTodos into the scope. As Maxim said, scope is relevant if you want to bind values to the view. If you just want to save the oldTodos so keep a reference to them, a normal variable is fine.
Then, if you want to bring one of them back, just copy it into the $scope.todos and back it comes.
Note that you can also abstract all of the todos into a service and inject the service into your controller for another level of encapsulation and better testing strategy. It will also enable you to share the todos across controllers if that's necessary.
Long story short, if you want AngularJS app to interact with the application scope, then you need to add the data in the scope. That's it.
$scope.oldtodos is the correct way to add data to the scope and it can be referred by name oldtodos in HTML template. While var oldtodos2 is private in your controller, so angular will not be able to access this data in the template since it is not in the scope.

Categories

Resources