Share selected select with another controller - javascript

I am trying to to share a selected option with another controller (And have it update when I select a new option). Something that would work like the 2-way data binding between controllers.
I've attempted this by setting up a factory like so
.factory("shareObjective", function($scope){
var shareObjective = {};
return {
shareObjective: shareObjective,
};
})
Then I inject this into the controller and bind it to the model of the select like so
$scope.selectModel = shareObjective.shareObjective;
I seem to be having some trouble getting this to work. I Basically just want to share the selected option (it's .name to be precise) with another controller and am struggling to do so. My first step was to get it to share into the factory to begin with, but I seem to be having no luck attempting this. Should I be using something like the $broadcast to keep the stream of information open? Thanks!
Edit - here's a plunkr http://plnkr.co/edit/L5lz4etQ7mUEhf9viNOk?p=preview

Yes, this won't work by default because you use two different scopes thus different ngModels.
Using a service also won't help because even if a click on a select with a ngModel in one scope will trigger a digest loop in that scope, it won't trigger it in the other scope.
As you suggest yourself you need to somehow notify the other scope to update itself, and yes you can do this through events ($broadcast and $on):
Both controllers contain
<select
ng-model="foo"
ng-options="foo for foo in foos"
></select>
And this is the JS:
var foos = [
'foo1',
'foo2',
'foo3'
];
function MyCtrl($scope, $rootScope) {
$scope.foo = foos[0];
$scope.foos = foos;
// detect broadcasts from other controllers and set our value to the one we received
$rootScope.$on('foo-event', function(e, data) {
$scope.foo = data;
});
// detect when foo changes and broadcast it's value
$scope.$watch('foo', function() {
$rootScope.$broadcast('foo-event', $scope.foo);
});
}
myApp.controller('MyCtrl1', ['$scope', '$rootScope', MyCtrl]);
myApp.controller('MyCtrl2', ['$scope', '$rootScope', MyCtrl]);
Here's the fiddle.
Notice that my code does not use a single controller, just a single controller implementation. You can write your own MyCtrl1 and Myctrl2 implementations that both have this code inside.
Also, while it would look like this generates an infinite loop between $watch and $on, it does not. $scope.foo = data; does not trigger $watch,

Related

Pass data to different controllers using Angularjs

I have the below code in one controller.
$scope.DataModel= [];
$scope.DataModelTexts= { buttonDefaultText: '' };
I will be using the same code in one more controller. Now instead of writing the same code in 2nd controller too, i want to know if there is a way to put this in a common code in some factory or service and use that in both the controllers. I have tried to read about factory and services and i am getting a bit confused of how to use any one of these in my scenario.
Any information would help me gain more knowledge about factory and services in angularjs. Thanks.
You're on the right track, use can use a factory or a service to share code between controllers. Note that in angular services(and factories) are singletons; they are instantiated once when the app starts and then anytime you inject it into a controller, you are referencing the same instance. Consider the following code:
var myApp = angular.module('myApp',[]);
myApp.service('MyService', function() {
let _someValue = 'Initial Value';
this.setValue = function(value){
_someValue = value;
}
this.getValue = function(){
return _someValue;
}
});
//First Controller Run
myApp.controller('ControllerA', function($scope, MyService) {
MyService.getValue(); //Initial Value
MyService.setValue("BRAND NEW VALUE!!!");
});
//Run after ControllerA
myApp.controller('ControllerB', function($scope, MyService) {
MyService.getValue(); //BRAND NEW VALUE!!!
});
Her you'll see that MyService holds the state of someValue. ControllerA get MyService injected to it and can use the methods of that service to set a new value. Now for any subsequent call for that same state, like for instance by ControllerB, the updated value will be returned.
You can use the .config() or a run() blocks (good SO on these here: AngularJS app.run() documentation?) to bind these reused variables to $rootScope, then call them from $rootScope.DataModel and $rootScope.DataModelTexts from within your controllers or services (as long as you inject $rootScope into these controllers and services).

Shared variables and multiple controllers AngularJS

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.

Where do I put regular functions, Angularjs

I'm getting into Angularjs. I'm want to re-use a function, resetForm() function. My question is, do I still put that inside my controller $scope or create a factory or service?
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
//do I add my function here?
function name() {};
}]);
if it is a resetForm() function then I assume it is dealing with DOM. I would suggest you to declare this function inside your controller since you will need access to $scope to reset form fields (direct DOM access is strictly prohibited in AngularJS). You can refer to below sample code
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
var resetForm = function() {
// your logic to reset form with help of $scope
};
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
}]);
Note: You don't need to declare resetForm function as $scope.resetForm if you don't plan to call it from your template file.
If you want to re-use it across multiple controllers, a Factory or a Service is probably the best way to share it without duplication of code. You can then call on either one of these from all your controllers.
The added benefits to this pattern are that, not only do you save yourself from duplicating code, but you can also store variables and share those as well.
Both will work, but you can read some interesting discussion on Factory vs Service if you have trouble with which one to choose.
The things goes like this:
We will write functions in controllers if that function is normally manipulating model and is only relevant to that controller.
We write services normally for giving data to controllers such as from a asynchronous API call, and for sharing data in between controllers.
In your case, if you want a utility function you can use a service, but resetForm function is more like controller specific, because it's gonna clear some model values. In future you may want to add more conditions and operations in that function which may produce complex code, if you use a service for that.
If that function is a 'non-gonna change function' using a service is good way to go. (code re-usability and all), but otherwise, wrap all logic in one place is more good.
(write it in controller)

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.

$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.

Categories

Resources