angular how to use scope from a different controller - javascript

I have a user.list.ctrl and a user.detail.cntr. All the controllers are build as a module and are injected in a "user-module" which I inject in the app.js. (see the complete code in the plunker below)
my controller module
angular.module('user-module', ['user-module.controllers']);
my user-module
angular.module('demo.app', ['user-module']);
In both controllers i inject user-Fctr with data from a REST factory. (works well)
user.list.cntrl has a $scope.refresh()
user.detail.cntrl has a $scope.update()
user.list.cntrl
When I enter a new record, i call the $scope.refresh() so I can refresh the list. (this is working fine)
user.detail.cntrl
When i click a user from the list, the user detail loads in a different view (works ok)
when I update the user.detail, I want to call $scope.refresh() to update the user.list , but it is not working. I cannot call $scope.refresh()
I thought that since I inject the same factory into both controllers I can use each others $scopes.
Any ideas on how I can use $scope.refresh() (or update the list when I update the user.detail.js)
I make a plunker with all the js files (the plunker is not functional, it is only to show the code that I have)
http://plnkr.co/edit/HtnZiMag0VYCo27F5xqb?p=preview
thanx for taking a look at this

This is a very conceptual problem.
You have created a controller for each "piece" of view because they are meant for different activities. This is the purpose of controllers. So that is right.
However, you are trying to access the refresh function, written in one controller, in another one. Taken literally, this is wrong, since then, refresh is out of place either inside the user list controller or the detail controller.
A function that is meant to control (literally) what is happening on a specific piece of view is a controller. - There you are right having a controller for the list and one for the details.
A function that is meant to be shared between controllers must be a service. This is exactly what you want for your refresh function to be.
Whenever you inject the same factory into n controllers, you can't use the scope of every controller. This isn't the purpose of a controller.
However, whenever you inject the same factory into n controllers, you can use its exposed methods.
The problem you have, can be solved as follows:
app.factory( 'sharedFunctions', [ 'factoryId', function sharedFunctions( factoryId ) {
var refresh = function () {
factoryId.getAll(/*your params to query*/)
.success( function ( response ) {
//This will return the list of all your records
return response;
});
};
return sharedFunctions;
}]);
With this factory service registered, then you can inject it to your controllers and whenever you need to refresh, just call the exposed method of the service and plot the new information into the view.
Hope it works for you!

i ended up doing this:
I added in the list.contrl this:
factoryId.listScope = $scope;
since I already have the factoryId (my data service) injected in the detail controller, I can call this:
factoryId.listScope.refresh();
it works but I don't know if this is the best way. any comments?

Related

How to pass Data from One Controller to Service and access that data on Other Controller on same event?

I'm facing some issue while trying to pass the data from one controller to the other using my service.
I've been implementing the prototype inheritance using the $rootScope in my controller and broadcasting that object, so that I can access that data in the other controllers.
As I'm using the $rootScope, I'm polluting the global namespace, I'd like to pass the data from this controller to the other.
I'm just displaying minimal data inside a table, when the user clicks on specific record of the table, I want to display entire data inside the object.
This is how I'm handling.
<tr ng-repeat="contact in filterContacts = (contacts.contactsData | filter:sensitiveSearch | orderBy:selectBox) " ng-style="{'background-color': contact.email == selectedContact.email ? 'lightgrey' : ''}" ng-click="selectContact(contact)">
In Controller A: I'm calling this function from view and injecting the specific row contact detail.
$scope.selectContact = function(contact) {
contactService.selectedContactInfo(contact);
};
In the Service I'm just returning that data -
var selectedContactInfo = function(contact) {
return contact;
};
How can I access this data in the Controller B during the same event and display it on my view.
Here is the reference Link: http://plnkr.co/edit/beOmiv?p=info
I don't want to use the $rootScope, but I'd like to access the data onto the other controller.
You could use an angular service for this.
The angular services are singletons and you inject them in controllers so you could inject the same service in two different controllers and you are effectively passing state between them.
Imagine you have a property in your service which could be called selectedUserID. You update this property when you click on the specific row. Then in the next controller, you inject the same service and use this property to determine which details you will load.
so you could have a method inside your service which could look like this :
updateSelectedUser = function(userID) {
this.selectedUserID = userID;
}
Your controller then calls this method from the service when the click action happens :
myService.updateSelectedUser($scope.selectedUserID);
This is just an example of course, put your own values in there.
One thing to keep in mind : services can hold state, they are objects after all and singletons so you always inject the same instance.
It makes sense to make sure that the state stored inside the service is not modified by outside actions which do not go through this service. In other words make sure that nothing else changes this selectedUserID so your service state data never gets out of sync. If you can do this, then you are golden.

How service can know when the controller who injected it has destroyed?

I have a books service to search for book.
Most of the time the service, give for example only 20 books.
I want the service to be able to change properties of the books on the screen, after the controller recieve the data.
For example:
I have a controller that show list of 20 books (from a search query, and limit properties).
I want that the service will be able to change the books that the controller got, after the controller got the data (realtime change)
controller($scope,bookService){
$scope.data=bookService.getList(query,20)
}
service(function(){
var dataBindedToController=[]
return{
getList:function(query,limit){
dataBindedToController.push([{name:'book1'},{name:'book2'}])
return dataBindedToController[dataBindedToController.length-1]
}
}
})
In the example above every time controller ask for list of books I add the returned data to the service by reference. After that for example if I do in the service: dataBindedToController[0][2].name='Moshe', it will automatically update the controller. the Controller $scope.data === dataBindedToController[0]
Now the question is: When the controller have destroyed, how the service can now this, and remove the bindedData from it's array?
I want to keep the controller ASAP (as simple as possible).
Another example:
A working JSFiddle, that use the bind technique to update a counter in a service, after the controller got the data:
https://jsfiddle.net/tLLtn45j/
var app=angular.module('app',[])
.controller('a',function($scope,service){
$scope.data=service
})
.service('service',function($interval){
var data={counter:3}
$interval(function(){data.counter++},500)
return data
})
The question is: how the service can now when to stop update the counter, when the controller have been destroyed
You can pass the scope like this
controller($scope,bookService){
$scope.data=bookService.getList($scope, query,20)
}
then save the $scope in your service, then attach the event listener there. I'm not sure though if it's a good practice to pass the $scope to the service, and I think it's not
you can listen to $destroy event
$scope.$on('$destroy', function() {
bookService.destroy($scope.$id);
})
you might want to index the dataBindedToController with $scope.$id so you will know what to remove

Heavy controller communication in AngularJS

I have implemented a single page application with AngularJS. The page consists of a content area in the middle and sections assembled around the center that show additional info and provide means to manipulate the center.
Each section (called Side Info) and the content area have a separate AngularJS controller assigned to them. Currently, I communicate via $rootScope.$broadcast and $scope.$on(), e.g.
app.controller('PropertiesController', function ($scope, $rootScope) {
$scope.$on('somethingHappened', function(event, data){
// react
});
});
I then call to communicate with other controllers:
$rootScope.$broadcast('somethingHappened', data);
I have quite a lot of communication happening between the Controllers. Especially if something is going on in the content area, several side info elements have to adopt. The other way around is also frequent: a user submits a form (located in a side info) and the content area and other side info elements have to adopt.
My question:
Is there a better way to handle SPA with heavy controller communication?
The code works fine but it is already getting a bit messy (e.g. it is hard to find which events are handled where etc.). Since the application is likely to grow a lot in the next weeks, I'd like to make those changes (if there are any better solutions) asap.
This is really interesting. Pub/Sub should be a right solution here.
You could add extra order to your project by using Angular services as your MVC's model, and update this model for each change. The issue here is that you should implement an observable pattern inside your service and register to them, in order for this to be live synced. So - we're back to Pub/Sub (or other Observable solution that you could think about...).
But, the project will be better organised that way.
For example - SideInfo1Service will be a service/model. Each property change will trigger an observable change which will change all listeners:
myApp.factory('SideInfo1Service', function($scope){
var _prop1;
return {
setProp1: function(value){
$scope.$broadcast('prop1Changed', value);
_prop1 = value;
},
getProp1: function(){
return _prop1;
}
}
});
You could find those really interesting blog posts about using Angular Services as your MVC's model:
http://toddmotto.com/rethinking-angular-js-controllers/
http://jonathancreamer.com/the-state-of-angularjs-controllers/
And, this post is about observable pattern in Angularjs:
https://stackoverflow.com/a/25613550/916450
Hope this could be helpful (:
You have multiple options in order to avoid broadcasts calls:
Share data between controllers using services like it was mentioned in the comments. You can see how to this at: https://thinkster.io/egghead/sharing-data-between-controllers
Create a main controller for the whole page and child controllers for each section (Content Area and Side Info). Use scope prototype inheritance. For example:
if in main controller you have:
$scope.myObject = someValue;
in child Controllers you can set:
$scope.myObject.myProperty = someOtherValue;
you can access myObject.myProperty from your Main Controller
You can use
$rootScope.$emit('some:event') ;
because it goes upwards and rootscope ist the top level
use
var myListener = $rootScope.$on('some:event', function (event, data) { });
$scope.$on('$destroy', myListener);
to catch the event
Then you have a communication on the same level the rootscope without bubbling
Here is my implemented eventbus service
http://jsfiddle.net/navqtaoj/2/
Edit: you can use a namespace like some:event to group and organize your event names better and add log outputs when the event is fired and when the event is catch so that you easy can figure out if fireing or catching the wrong eventname.
Very important question and very good answers.
I got inspired and created three plunks showing each technique:
Broadcasting: http://embed.plnkr.co/lwSNDCsw4gjLHXDhUs2R/preview
Sharing Service: http://embed.plnkr.co/GptJf2cchAYmoOb2wjRx/preview
Nested Scopes: http://embed.plnkr.co/Bct0Qwz9EziQkHemYACk/preview
Check out the plunks, hope this helps.

$rootScope $broadcast working on two different controllers

I am trying to build a single page application where I have a setting form where I add a variable called Ticket Work type. When I do an update or save on the setting form, I am broadcasting the new Ticket Work Type array so that it reflects the listing as well as Ticket create page.
But the problem is that broadcast works only on the settings form page controller and not on the Ticket Add controller.
This is the code from the Settings controller where after "addTicketWorkTypes" factory method, I do a broadcast. This works :
$scope.addNewTicketWorkType = function(newticketWorkType) {
if (newticketWorkType != '') {
ticketFact.addTicketWorkTypes(newticketWorkType, $scope.workspaceId).then(function(response) {
$rootScope.$broadcast('handleTicketWorkType',response.data);
ticketFact.ticketWorkType = '';
});
}
};
And this updates the list on the same controller
/*update ticket work type when changes at any place*/
$scope.$on('handleTicketWorkType', function(events, ticketWorkType) {
$scope.ticketWorkType = ticketWorkType;
console.log('Scope updated settingsEditCtrl');
});
But the same $scope.$on doesn't work on a different controller. Can you please help.
The whole code is on Github for reference as well:
The settings controller is here: https://github.com/amitavroy/my-pm-tools/blob/master/public/assets/js/dev/settings/controllers/settingsEditCtrl.js
and the Ticket Add screen controller is here: https://github.com/amitavroy/my-pm-tools/blob/master/public/assets/js/dev/tickets/controllers/ticketAddCtrl.js
Any given scope will only be notified of events that are broadcast from a scope higher in the inheritance hierarchy. To send an event up the hierarchy use $rootScope.$emit().
If you want to be sure the event will be sent and received and you're not worried about other scopes responding to the event, you can do this:
function $broadcast() {
return $rootScope.$broadcast.apply($rootScope, arguments);
}
...
$broadcast('handleTicketWorkType', response.data);
In your other controller:
$rootScope.$on('handleTicketWorkType', function(data) {...});
If that doesn't work
It must be (as suggested by #charlietfl) that the target $scope does not yet exist and is hence not being notified of the event. In that case create a ticketWorkTypes service that is nothing but a list of ticketWorkTypes. Then replace:
$rootScope.$broadcast('handleTicketWorkType',response.data);
with
ticketWorkTypes.push(response.data);
Then inject ticketWorkTypes service (array) into any controller that needs that data.

How to have an angularJS directive on a page called via ajax?

I have the following html (which can be accessed directly or called via ajax):
<section id="content" ng-controller="setTreeDataCtrl" get-subthemes>
<dl ng-repeat="subtheme in allSubthemes">
<dt>{{subtheme.Title}}</dt>
</dl>
Then I'm using the following directive:
myApp.directive('getSubthemes', function() {
return function($scope, element, attrs) {
$scope.allSubthemes = [];
angular.forEach($scope.data.Themes, function(value, key) {
angular.forEach(value.SubThemes, function(value2, key2) {
$scope.allSubthemes.push({
'ThemeTitle': value.Title,
'ThemeUrlSlug': value.UrlSlug,
'Title': value2.Title,
'UrlSlug': value2.UrlSlug
});
});
});
}
});
$scope.allSubthemes seems ok, but the dl's don't get rendered.
I can see for a second everything rendered properly and then it get's back to {{subtheme.Title}}, almost like it's being "unrendered"... any ideas of what I'm doing wrong?
Demo jsFiddle: http://jsfiddle.net/HMp3a/
rGil fixed the jsFiddle. It was missing a ng-app="pddc" declaration on an element so Angular did not know where to begin its magic.
I'd like to mention another way to render to the data in question. I suggest using an ng-repeat within an ng-repeat. See my forked & updated fiddle here. You can actually refer to the parent theme within the ng-repeat of the subtheme, so you don't have to copy values from the parent theme into each subtheme (which effectively eliminates the need for the directive in this example).
Another reason to use a nested ng-repeat is because of async issues that could come up when pulling data from a web service asynchronously. What could happen is when the directive executes, it may not have any data to loop through and populate because the data hasn't arrived yet.
If you use two ng-repeats, Angular will watch the $scope.data and re-run the ng-repeats when the data arrives. I've added a 500 ms delay to setting the data in my example to simulate web service latency and you'll see that even with the "latency", the data eventually renders.
There are two other ways around the async issue:
Use scope.$watch() in your directive, to watch for the data manually, or
Use the "resolve" functionality from Angular's routing feature to make sure the data is retrieved prior to controller execution.
While these alternative methods work, I think both are more complicated then just using two ng-repeats.

Categories

Resources