I created a service to share some data between two controllers. The thing is that this controllers set and get some data from this service. But I don't know why when I try to get data, all this variables are not set.
My code is this one:
var appModule = angular.module('app', ['mgcrea.ngStrap'])
// custom service to share/collect data between controllers
// this objects are populated by the controllers
.service('sharedProperties', function () {
this.searchPattern = {
basicFilters: {},
advanceFilters: {}
};
});
// controller for main section
appModule.controller('parentController', function ($scope, $aside, sharedProperties) {
$scope.basicFilters = {
category: 'undef',
masterbrand: {value:'undef', text: 'Any'},
page: 1,
perPage:10,
q:''
};
// populate object in service
$scope.updatePatternSearch = function(newValue, oldValue, scope) {
sharedProperties.searchPattern.basicFilters = 'HI!, I have a value!';
};
$scope.$watch('basicFilters', 'updatePatternSearch', true);
// get variables from service
$scope.search = function() {
// ** PROBLEM **
// firebug says that is:basicFilters: {},
// advanceFilters: {}
// empties????, why??
console.log(sharedProperties.searchPattern);
}
}
});
There are lots of typos and erros in your plnkr code.
I have forked and updated it. It's working fine.
Have a look at this - http://plnkr.co/edit/BVFjufOQFFlhB2gTqIeB?p=preview
Related
I need to execute functions of some controllers when my application ends (e.g. when closing the navigator tab) so I've thought in a service to manage the list of those functions and call them when needed. These functions changes depending on the controllers I have opened.
Here's some code
Controller 1
angular.module('myApp').component('myComponent', {
controller: function ($scope) {
var mc = this;
mc.saveData = function(objectToSave){
...
};
}
});
Controller 2
angular.module('myApp').component('anotherComponent', {
controller: function ($scope) {
var ac = this;
ac.printData = function(objects, priority){
...
};
}
});
How to store those functions (saveData & printData) considering they have different parameters, so when I need it, I can call them (myComponent.saveData & anotherComponent.printData).
The above code is not general controller but the angular1.5+ component with its own controller scope. So the methods saveData and printData can only be accessed in respective component HTML template.
So to utilise the above method anywhere in application, they should be part of some service\factory and that needs to be injected wherever you may required.
You can create service like :
angular.module('FTWApp').service('someService', function() {
this.saveData = function (objectToSave) {
// saveData method code
};
this.printData = function (objects, priority) {
// printData method code
};
});
and inject it wherever you need, like in your component:
controller: function(someService) {
// define method parameter data
someService.saveData(objectToSave);
someService.printData (objects, priority);
}
I managed to make this, creating a service for managing the methods that will be fired.
angular.module('FTWApp').service('myService',function(){
var ac = this;
ac.addMethodForOnClose = addMethodForOnClose;
ac.arrMethods = [];
function addMethodForOnClose(idModule, method){
ac.arrMethods[idModule] = {
id: idModule,
method: method
}
};
function executeMethodsOnClose(){
for(object in ac.arrayMethods){
ac.arrMethods[object].method();
}
});
Then in the controllers, just add the method needed to that array:
myService.addMethodForOnClose(id, vm.methodToLaunchOnClose);
Afterwards, capture the $window.onunload and run myService.executeMethodsOnClose()
I have two controllers. The first one sets a variable into my service and my second one is supposed to get this variable, but it's undefined.
Aren't angular services supposed to be singletons ? Because my service seems to be instantiated for each controller.
Here's my code :
First controller
angular.module('myApp').controller('HomeCtrl', ['$scope', 'User', function ($scope, User) {
$scope.join = function () {
User.setRoom("test");
console.log(User.getRoom()); // displays 'test'
$window.location.href = '/talk';
}
}]);
In my second controller, I've just a
console.log(User.getRoom()); // displays ''
And here's my service
angular.module('myApp').factory('User', function () {
var data = {
room: ''
};
return {
getRoom: function () {
return data.room;
},
setRoom: function (room) {
data.room = room;
}
};
});
Do you have an idea?
You are using $window.location.href = '/talk'; to navigate - this triggers a full page reload, and all services are therefore also reinitialized.
You probably want to use the $location service. See the documentation and/or this answer for a summary of the difference between the two.
I'm not sure what I've done wrong this is how I've setup other factories to share data as far as I can see, but even though the array should be passed by reference and I don't overwrite the array so the reference isn't lost the template never loads with the data I can see coming down from the find all request. Can anyone see what it is that I have/haven't done to get this factory to work and shared between controllers?
I originally set this up not using a service so I know the data returned from the request does work with the view, and if you look at my stubs the data is there (and it looks the same), but the view never updates. If this is something other than a glaring error that I've missed and don't seem to understand I'd appreciate a quick comment as well since I assumed it was the fact objects/arrays are passed by reference that makes this all work.
Contacts Controller
// Stripped down for brevity...
(function () {
'use strict';
function ContactsController(ContactsFactory,
ContactsResource) {
var vm = this;
vm.contacts = ContactsFactory.get();
vm.findAll = function () {
ContactsResource
.all()
.then(function (response) {
ContactsFactory.add(response.contacts); // <-- an array of objects for example: [{id: 5, username: "Richard39", email: "Sanford.Marilyne#Gibson.org",…},…]
});
};
vm.remove = function (contact, index) {
ContactsResource
.remove()
.then(function (response) {
ContactsFactory.remove(index);
});
};
function init() {
vm.findAll();
}
init();
}
ContactsController.$inject = [
'ContactsFactory',
'ContactsResource'
];
angular
.module('app')
.controller('ContactsController', ContactsController);
})();
Contacts Service
// Stripped down for brevity...
(function () {
'use strict';
function ContactsFactory() {
var contacts = [];
function get() {
return contacts;
}
function add(contacts) {
console.log(contacts) // <-- array of objects
angular.forEach(contacts, function(contact) {
console.log(contact) // <-- object
contacts.list.push(contact);
});
console.log(contacts) // <-- objects pushed onto array [Object, Object, Object, Object, Object,…]
// But no update in view, which worked with same resource
// request, prior to switching to factory to share between
// controllers
}
function remove(index) {
contacts.splice(index, 1);
}
// ---
// PUBLIC API.
// ---
return {
get: get,
add: add,
remove: remove
};
}
ContactsFactory.$inject = [];
angular
.module('app')
.factory('ContactsFactory', ContactsFactory);
})();
UPDATE
Using #Rob's suggest makes this work, and I noticed if I added service.contacts as the return value to the getter it also works, and I verified contacts could be removed. So I'm still interested in knowing why this works versus just using an array for contacts like the examples above. Are arrays not passed by reference as I originally thought, and it's only objects? I'd just like to put a name to what I did wrong so it's memorable.
(function () {
'use strict';
function ContactsFactory() {
var service = {
contacts: []
};
function get() {
return service.contacts;
}
function add(contacts) {
angular.forEach(contacts, function(contact) {
service.contacts.push(contact);
});
console.log(contacts);
}
function remove(index) {
contacts.splice(index, 1);
}
return {
contacts: service.contacts, // <-- Rob's suggestion
get: get, // <-- this also works now as well
add: add,
remove: remove
};
}
ContactsFactory.$inject = [];
angular
.module('app')
.factory('ContactsFactory', ContactsFactory);
})();
I have two controller in an angularApp with following codes
Controller:Devicectr
function Devicectr($scope, $http) {
$scope.Devices = [{ Id: 1, Devicename: "MasterDeviceA" },
{ Id: 1, Devicename: "MasterDeviceB" },
{ Id: 1, Devicename: "MasterDeviceC" }];
$scope.ActiveDevice = $scope.Devices[0] //
};
Controller:PreviewCtr
function Devicectr($scope, $http) {
$scope.ViewDevice=null; // Here i want to read the $scope.ActiveDevice from **Devicectr** Controller
};
I want read the $scope.ActiveDevice value of Devicectr controller from PreviewCtr controller. How i can do it
Usually you use services when you want to share data between controllers.
Like:
yourModule.factory('yourService', function() {
var sharedData = ...
var getData = function() {
return sharedData;
}
return {
getData : getData
}
}
and you then use this in both your controllers:
function Devicectr($scope, $http, yourService) {...}
function Previewctr($scope, $http, yourService) {...}
You could also nest the controllers to include the same scope, but I would say that is a worse idea.
Create a DeviceService which keeps track of the currently selected device. It should expose setActive(device) and getActive() method.
In your PreviewCtr inject the service to determine the active device. You need to watch for the Device assignment or change
If you assign the service to the scope like
$scope.deviceService=deviceService; you can do something like
$scope.$watch(function(){ return $scope.deviceService.getActive();},function(newValue,oldValue) {
//This gets called when the device change
});
You and also use scope $broadcast method (using $rootScope) in the service to raise an event when the Device changes in setDevice(device) function. You can catch that event any where using $scope.$on handler. See documentation here.
I have a 'messages' factory that will query my database for a list of messages.
I'm using the list of messages in two different places. Once to add a message count indicator, and then once to show a list of messages. Since I'm injecting the service into two different controllers, it seems like it's creating two instances of my factory, and hitting the database twice for the list.
How would I set things up to only ask for the list once, and use the list for both display and count purposes in both controllers?
My factory looks like this:
myApp.factory('messagesService', [
'$rootScope',
function($rootScope) {
var messages = [];
function query() {
// Would actually hit the database asyncronously
messages = ['one', 'two', 'three', 'four'];
console.log('query');
$rootScope.$emit('messages.update');
}
function all() {
return messages;
}
return {
query: query,
all: all
}
}
]);
My controllers are using blocks like this to watch for changes:
$rootScope.$on('messages.update', function() {
$scope.messagesCount = messagesService.all().length;
});
But it means i need a messagesService.query(); in each controller for things to be reliable.
So here are a few jsFiddle examples of it as I have things now:
Doesn't work (only updates the header): http://jsfiddle.net/TSLfc/1/
Works but would break if I didn't load the dashboard controller:
http://jsfiddle.net/TSLfc/2/
Works every time, but queries the server twice:
http://jsfiddle.net/TSLfc/3/
Is there a better way to organize my code? Should I build out the messages factory into it's own full module?
Here (Plunkr) is how I would do it:
I have gone back and modified my previous answer, updating with what we discussed in the comments below as well as using promises instead of the timeout as an asynchronous simulation I was showing before (see revision history for reference).
I also removed every variable/function that didn't need to be returned to the controller from the service object, if it doesn't need to be accessed via the controller than it doesn't need to be included on the returned object.
var myApp = angular.module('myApp', []);
myApp.factory('messagesService', [
'$q',
'$rootScope',
'$http',
function ($q, $rootScope, $http) {
var mService = {};
mService.messages = [];
var queryInit = false;
// We don't need to access this function in the controller
// So I am not going to attach to the returned object
var getMessages = function () {
// Stops each controller from getting messages when loaded
if (!queryInit) {
queryInit = true;
// Using the $q promise library we use 'then()' to handle
// What happens after the async call is returned
// The first function parameter is the success/resolve callback
// The second function parameter is the error/reject callback
mService.query().then(function (successResults) {
// Tell all of the controllers that the data has changed
$rootScope.$broadcast('messages.update');
}, function (errorResults) {
console.error(errorResults);
});
}
};
// Used to force an update from the controller if needed.
mService.query = function () {
var deferred = $q.defer();
$http.get('path/to/file.php')
.success(function (data, status, headers, config) {
// assign the returned values appropriately
mService.messages = data;
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(data);
});
return deferred.promise;
};
mService.getCount = function () {
return mService.messages.length;
};
mService.all = function () {
return mService.messages;
};
// Initialize the messages
// so we don't need to get the messages in each controller
getMessages();
return mService;
}]);
In your html, on your first controller setup an init function (ng-init="init()") that instantiates the factory:
<div ng-app="myApp">
<div ng-controller="HeaderCtrl" class="header" ng-init="init()">
Messages Count: {{ messageCount }}
</div>
<div ng-controller="DashboardCtrl" class="dashboard">
<ul ng-repeat="message in messages">
<li>{{ message }}</li>
</ul>
<button ng-click="getMessages()">Check for new messages.</button>
</div>
</div>
And in your controllers you just have the $rootScope.$on('messages.update' fn) and you can call manually by calling the services query() function which returns the promise:
myApp.controller('HeaderCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messageCount = messagesService.getCount();
});
// Manual call, if needed
$scope.getMessageCount = function () {
messagesService.query().then(function (successCallback) {
$scope.messageCount = messagesService.getCount();
});
};
}]);
myApp.controller('DashboardCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messages = messagesService.all();
});
// Manual call, if needed
$scope.getMessages = function () {
messagesService.query().then(function (successCallback) {
$scope.messages = messagesService.all();
$rootScope.$broadcast('messages.update');
});
}
}]);
You can set cache:true on a $http request. There are numerous ways to data bind within angular without needing to use the $broadcast approach you are using. Also note, $broadcast from a scope will be receievd by all descendent scopes, so no need to inject $rootSCope just for that purpose, can listen on $scope.
Here's one approach that controllers use promise of $http to retrieve data. I used a button click to retrive data for DashControl so can see that request does get cached
myApp.factory('messagesService',function($http) {
return{
query:function query(callback) {
/* return promise of the request*/
return $http.get('messages.json',{ cache:true}).then(function(res){
/* resolve what data to return, can set additional properties of the service here if desired*/
return res.data
}).then(callback);
}
}
});
myApp.controller('HeaderCtrl',function($scope, messagesService) {
messagesService.query(function(messages){
$scope.messagesCount = messages.length;
});
});
myApp.controller('DashboardCtrl', function($scope, messagesService) {
/* use button click to load same data, note in console no http request made*/
$scope.getMessages=function(){
messagesService.query(function(messages){
$scope.messages = messages;
})
}
});
Essentially in this scenario, whatever controller calls the factory service first will generate the data cache
DEMO
I would do it like that:
myApp.factory('messagesService', function() {
var expose = {
messages: []
};
expose.query = function () {
// Would actually hit the database asyncronously
expose.messages = ['one', 'two', 'three', 'four'];
console.log('query');
};
// Initialization
expose.query();
return expose;
}
);
And in your controllers:
$scope.messagesCount = messagesService.messages.length;
Model with broadcasting and pre-hitting database looks heavy for me.
So here is code, that can be embedded in service:
var sv = this;
var deferred = sv.$q.defer();
if (sv._running) {
return sv._running;
}
sv._running = deferred;
It based on reusing promise. To make it query database once - just don't set sv._running to false and it will always return first obtained result.