Common Watch for Multiple Controllers in Angular - angularjs

mainApp has two controllers with different functionalities. Some of their functions are common.
mainApp.controller("WriteController", function($scope) {
$scope.$watch('task.file.prefix', function (term) {
term = term.replace(/\\/g,'/');
$scope.task.file.prefix = term;
});
});
mainApp.controller("ReadController", function($scope) {
$scope.$watch('task.file.prefix', function (term) {
term = term.replace(/\\/g,'/');
$scope.task.file.prefix = term;
});
});
You can clearly see that I am using two watch (same) for both of controllers. Is there a way that we move out these $watch out of controller to somewhere outside (service or so) to satisfy DRY.

Not sure if someone got better workaround but you can try this
mainApp.service('watcher', function(){
return {
term : function (newValue) {
// we don't need scope here because newValue is the scope being watched.
// newValue changed = equivalent of $scope. being watch changed.
newValue = newValue.replace(/\\/g,'/');
}
}
})
mainApp.controller("WriteController", function($scope, watcher) {
// passing the function (do not execute it)
$scope.$watch('task.file.prefix', watcher.term);
});
mainApp.controller("ReadController", function($scope, watcher) {
// passing the function (do not execute it)
$scope.$watch('task.file.prefix', watcher.term);
});

Related

How to store controller functions in a service and call them in AngularJS

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()

Auto trigger function throuigh service in angularjs

hi all i am using angulrajs passing one value from one controller to another controller using service it's work fine but my need is when service value change in controller 2 i get the service value in one scope when scope value change i need trigger the function it's called refresh function when service value change and that i need to call the refresh function here my fiddle
https://jsfiddle.net/ctawL4t3/10/
You can just $watch your value.storeObject. Though it's not best of the practices, but it suits this kind of feature.
$scope.$watch('value.storedObject', function(newVal) {
if(newVal !== '') {
refresh()
}
})
working fiddle (open console to see refresh function logging)
You can try to use angular default $emit, $broadcast, or try to do 2 simple functions in own service
angular.module('app').factory('StoreService', function() {
var listeners = {};
var emit = function(name, val) {
if(listeners[name]) {
listeners[name](val)
}
}
var on = function(name, callback) {
listeners[name] = callback;
}
return {
emit: emit,
on: on,
storedObject: ''
};
});
JSFiddle example
JSFiddle example $watch
JSFiddle example ng-change is better because, you can use easily debounce
you can use broadcast function for that
Please check this SO link to find the related answer
How to call a function from another controller in angularjs?
app.controller('One', ['$scope', '$rootScope'
function($scope) {
$rootScope.$on("CallParentMethod", function(){
$scope.parentmethod();
});
$scope.parentmethod = function() {
// task
}
}
]);
app.controller('two', ['$scope', '$rootScope'
function($scope) {
$scope.childmethod = function() {
$rootScope.$emit("CallParentMethod", {});
}
}
]);

AngularJS - share variable between two controllers

I have two controllers that have to communicate each other.
The first reference to a video player and the second one to a timeline.
From the first one, I get the currentTime of the video playback and I want to pass it to the second one that should move the time-bar as the video is playing.
I tried using the factory to share a variable called time between controllers but this doesn't change during the time.
First Controller:
angular.module('videoCtrl', ['vjs.video'])
.controller('videoController', ['$scope', 'Timeline', function (scope, Timeline) {
scope.mediaToggle = {
sources: [
{
src: 'http://static.videogular.com/assets/videos/videogular.mp4',
type: 'video/mp4'
}
],
};
//listen for when the vjs-media object changes
scope.$on('vjsVideoReady', function (e, videoData) {
videoData.player.on('timeupdate', function () {
var time = this.currentTime();
Timeline.setTime(time); // setting the time on factory
})
});
}]);
Second Controller:
angular.module('timelineCtrl', ['mt.media-timeline'])
.controller('timelineController', function ($scope, Timeline) {
$scope.time = Timeline.getTime(); // here I'm trying to get the time
});
Factory:
.factory('Timeline', function(){
var timelines = [];
var time = null;
return {
getTime: function() {
return time;
},
setTime: function(_time) {
time = _time;
}
}
});
time appears to be a primitive, which means it is returned byVal rather than byRef. In other words, each call to getTime will return the value that time is currently set to, and calls to setTime will change the value for future calls, but not for anything that already called it. This is a classic case of the angular rule, Always use a dot.
Try changing time to an object instead:
.factory('Timeline', function() {
var timelines = [];
var time = {
value: null
};
return {
getTime: function() {
return time;
},
setTime: function(_time) {
time.value = _time;
}
}
});
In your HTML, use {{time.value}}.
Saving in $rootScope instead of $scope would give you the ability to access a variable across all your app and your controllers. But have in mind that creating a large number of $rootScope could affect your app's performance.
Do not forget to inject $rootScope into the controller (like you did with $scope), so you can access it.
Well as far as I can tell what're doing in the second controller is that you retrieve the value of time on instantiation of the controller. Of course further changes of the value in the service can't be picked up this way. To do that can use $scope.$watch in the second controller:
angular.module('timelineCtrl', ['mt.media-timeline'])
.controller('timelineController', function ($scope, Timeline) {
$scope.time = Timeline.getTime(); //Set the time once so it's not undefined
$scope.$watch(
function() {return Timeline.getTime();},
function(newVal) {$scope.time = newVal;}
);
});
Angular will call the first function in every $digest cycle(That's about at least every 10ms if I recall correctly) and will call the second function when a change has been detected. Detailed documentation for $watch can be found here
This is one way to do it. You could also add a function to your $scope(e.g. getTime()), which should return the current time, and then call this function in the HTML template: {{getTime()}}. Both ways pretty much work the same way, except that the second one leaves the 'dirty' work to angular(creating watchers and updating values)

How to setup data binding between Factory service and $scope value in particular controller?

I want automatically refresh $scope.variable in both controllers to new value if data.variable in SharedFactory was changed:
.controller('FirstController', function($scope, SharedFactory) {
$scope.variable = SharedFactory.getVal();
})
.controller('SecondController', function($scope, SharedFactory) {
$scope.variable = SharedFactory.getVal();
SharedFactory.setVal("test string 2");
})
.factory("SharedFactory", function () {
var data = { // all variables by default
variable : 'test string'
};
return {
getVal: function () {
return data.variable
},
setVal: function (i) {
data.variable = i;
}
}
});
http://plnkr.co/edit/b1RNcl6Pz2iuRr2t2Q9x?p=preview
So at this example correct result must be "test string 2" in both controllers. How to do that?
Easiest (and possibly more efficient) would be to have a reference to SharedFactory.data directly in your controllers - rather than to SharedFactory.data.variable. That way, when the value of data.variable changes it would change in all controllers as you reference the data-variable rather than the specific value. Using primitives is generally not reccomended.
Another solution would be to use $scope.$watch in your controllers, and just watch for changes on the value and update the local-variable when it changes.
Because you are using primitive variable instead of using object so once you set it you actually lose your reference to original object, so instead of returning data object value (which is primitive) you can return all data object...
getVal: function () {
return data;
}
Here is update plunker...

How to access/update $rootScope from outside Angular

My application initializes an object graph in $rootScope, like this ...
var myApp = angular.module('myApp', []);
myApp.run(function ($rootScope) {
$rootScope.myObject = { value: 1 };
});
... and then consumes data from that object graph (1-way binding only), like this ...
<p>The value is: {{myObject.value}}</p>
This works fine, but if I subsequently (after page rendering has completed) try to update the $rootScope and replace the original object with a new one, it is ignored. I initially assumed that this was because AngularJS keeps a reference to the original object, even though I have replaced it.
However, if I wrap the the consuming HTML in a controller, I am able to repeatedly update its scope in the intended manner and the modifications are correctly reflected in the page.
myApp.controller('MyController', function ($scope, $timeout) {
$scope.myObject = { value: 3 };
$timeout(function() {
$scope.myObject = { value: 4 };
$timeout(function () {
$scope.myObject = { value: 5 };
}, 1000);
}, 1000);
});
Is there any way to accomplish this via the $rootScope, or can it only be done inside a controller? Also, is there a more recommended pattern for implementing such operations? Specifically, I need a way to replace complete object graphs that are consumed by AngularJS from outside of AngularJS code.
Thanks, in advance, for your suggestions,
Tim
Edit: As suggested in comments, I have tried executing the change inside $apply, but it doesn't help:
setTimeout(function() {
var injector = angular.injector(["ng", "myApp"]);
var rootScope = injector.get("$rootScope");
rootScope.$apply(function () {
rootScope.myObject = { value: 6 };
});
console.log("rootScope updated");
}, 5000);
Except for very, very rare cases or debugging purposes, doing this is just BAD practice (or an indication of BAD application design)!
For the very, very rare cases (or debugging), you can do it like this:
Access an element that you know is part of the app and wrap it as a jqLite/jQuery element.
Get the element's Scope and then the $rootScope by accessing .scope().$root. (There are other ways as well.)
Do whatever you do, but wrap it in $rootScope.$apply(), so Angular will know something is going on and do its magic.
E.g.:
function badPractice() {
var $body = angular.element(document.body); // 1
var $rootScope = $body.scope().$root; // 2
$rootScope.$apply(function () { // 3
$rootScope.someText = 'This is BAD practice :(';
});
}
See, also, this short demo.
EDIT
Angular 1.3.x introduced an option to disable debug-info from being attached to DOM elements (including the scope): $compileProvider.debugInfoEnabled()
It is advisable to disable debug-info in production (for performance's sake), which means that the above method would not work any more.
If you just want to debug a live (production) instance, you can call angular.reloadWithDebugInfo(), which will reload the page with debug-info enabled.
Alternatively, you can go with Plan B (accessing the $rootScope through an element's injector):
function badPracticePlanB() {
var $body = angular.element(document.body); // 1
var $rootScope = $body.injector().get('$rootScope'); // 2b
$rootScope.$apply(function () { // 3
$rootScope.someText = 'This is BAD practice too :(';
});
}
After you update the $rootScope call $rootScope.$apply() to update the bindings.
Think of modifying the scopes as an atomic operation and $apply() commits those changes.
If you want to update root scope's object, inject $rootScope into your controller:
myApp.controller('MyController', function ($scope, $timeout, $rootScope) {
$rootScope.myObject = { value: 3 };
$timeout(function() {
$rootScope.myObject = { value: 4 };
$timeout(function () {
$rootScope.myObject = { value: 5 };
}, 1000);
}, 1000);
});
Demo fiddle

Resources