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

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

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.

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.

angular how to use scope from a different controller

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?

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

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