Can you reuse $scope.$watch - angularjs

I found some code I want to copy / paste and use in two controllers. It watches something.
$scope.$watch('thing', function (thing) {
// do cool stuff with thing
}
Instead of copy/paste, I'd like to put it in a service and use the service from both controllers sortof like this:
angular.module('myApp')
.factory('CoolService',
function () {
$scope.$watch('thing', function (thing) {
// do cool stuff with thing
}
}
Now if I do this, it won't know what $scope is, right? (According to some reading, it won't let me do that anyway.)
Nevertheless, I'd like to say, If you have this service, you get this watch.
There's a hint I can do this: Passing current scope to an AngularJS Service
So I took his example, fixed it, and scope.watch works in there, but now I can't set other scope variables inside the watch. I just don't know enough javascript to do it, but I'm close. I really think it will work with the right syntax...
angular.module('blah', []);
angular.module('blah').factory('BlahService', function() {
//constructor
function BlahService(scope) {
this._scope = scope;
this.myFunc = function(){
this._scope.otherVar = this._scope.someVar;
};
this._scope.$watch('someVar', function(someVar) {
// do cool stuff with thing
_scope.otherVar = this._scope.someVar; // undefined
this._scope.otherVar = this._scope.someVar; // undefined
this.myFunc(); // undefined
BlahService.prototype._someFunction(); // works, but...
return someVar;
});
}
//wherever you'd reference the scope
BlahService.prototype._someFunction = function() {
if (this._scope['someVar'] == 1) // undefined
this._scope['someVar']++;
}
return BlahService;
});
angular.module('blah').controller('BlahCtrl', function($scope, BlahService) {
$scope.someVar = 4;
$scope.BlahService = new BlahService($scope);
});
angular.module('blah').controller('Blah2Ctrl', function($scope, BlahService) {
$scope.someVar = 6;
$scope.BlahService = new BlahService($scope);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<html ng-app="blah">
<body>
<div ng-controller="BlahCtrl">
1a. <input ng-model="someVar">
1b. <input ng-model="otherVar">
</div>
<div ng-controller="Blah2Ctrl">
2. <input ng-model="someVar">
2b. <input ng-model="otherVar">
</div>
</body>
</html>
The key feature that this snippet has is that the scopes are different scopes. It doesn't act like a singleton.

Passing $scopes to a service sounds like a recipe for memory leaks. If nothing else it's the long way around.
Instead consider just doing this in each directive:
scope.$watch('thing', function (thing) {
coolService.doCoolStuffWith(thing);
}
Let the directive do the watching of its own scope, and put the shared functionality in the service.

This did it, and it allows me to set other members of the scope from within the watch:
angular.module('blah', []);
angular.module('blah').factory('BlahService', function() {
//constructor
function BlahService(scope) {
this._scope = scope;
this.myFunc = function() {
this._scope.otherVar = this._scope.someVar;
};
this._scope.$watch('someVar', function(newValue, oldValue, scope) {
// do cool stuff with thing
scope.otherVar = Number(scope.someVar) + 1;
return newValue;
});
}
return BlahService;
});
angular.module('blah').controller('BlahCtrl', function($scope, BlahService) {
$scope.someVar = 4;
$scope.BlahService = new BlahService($scope);
});
angular.module('blah').controller('Blah2Ctrl', function($scope, BlahService) {
$scope.someVar = 6;
$scope.BlahService = new BlahService($scope);
});
<html ng-app="blah">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<div ng-controller="BlahCtrl">
1a. <input ng-model="someVar">
1b. <input ng-model="otherVar">
</div>
<div ng-controller="Blah2Ctrl">
2. <input ng-model="someVar">
2b. <input ng-model="otherVar">
</div>
</body>
</html>

Related

AngularJS function to another file

I have an AngularJS script (to be run on the web) and a couple of specific functions are starting to become very long, long enough that I've built a spreadsheet to generate all of the different cases, which I then simply paste into the code.
(function(){
var app = angular
.module('app',[])
.controller('HostController',HostController);
HostController.$inject = ['$scope'];
function HostController($scope){
var host = this;
host.variable = "some text";
function reallyLongFunction(a,b) {
switch(a) {
case "something":
switch(b) {};
break;
case "something else":
switch(b) {};
break;
};
}
}
})();
I want to move them out of the main file so that it's not cluttered with these long, generated functions while I work on the rest of the programme.
I could simply move the functions directly into another file, but they need to access variables of the type host.variable, and so need to be in the scope of the main Angular app, I believe?
How can I move these functions into a different file while retaining their access to these variables?
You can move your method to separate file by creating an angular service as well. Inject that service in your controller and access the method like someSvcHelper.reallyLongFunction(a,b). This aproach will also make this method of yours as generic and will be available for other controllers as well.
But in this case you will have to pass the variables required by this function as arguments.
Using nested ng-controller's you can have access to the other controller scope in the $scope.
angular.module('app', [])
.controller('ctrl', function() {
var vm = this;
vm.value = 1;
})
.controller('auxCtrl', function($scope) {
var aux = this;
aux.result = function() {
return $scope.vm.value + 5;
}
});
<div ng-app="app" ng-controller="ctrl as vm">
<div ng-controller="auxCtrl as aux">
<input type="number" ng-model="vm.value" /> <br/>
{{vm.value}} <br/>
{{aux.result()}}
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
Edit: What if i need more than one controller?
Well, in this case i think that nested controllers will be really cumbersome, so you can try a service that has an instance of the controllers scope.
angular.module('app', [])
.service('greeter', function() {
const self = this;
self.scope = {};
self.use = scope => self.scope = scope;
self.greet = () => 'Hello, ' + self.scope.myName;
})
.service('fareweller', function() {
const self = this;
self.scope = {};
self.use = scope => self.scope = scope;
self.farewell = () => 'Goodbye, ' + self.scope.myName;
})
.controller('ctrl', function($scope, greeter, fareweller) {
$scope.myName = 'Lorem Ipsum';
$scope.greeter = greeter;
$scope.fareweller = fareweller;
greeter.use($scope);
fareweller.use($scope);
});
<div ng-app="app" ng-controller="ctrl">
<input type="text" ng-model="myName"> <br>
{{greeter.greet()}} <br>
{{fareweller.farewell()}} <br>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>

Passing Object from One Controller to Another AngularJS

I need to pass an object from one controller to another and have used this solution but it is not working.
Here the code:
angular.module("customerApp", [])
.controller('MainCtrl', function ($scope, myService, $http, $location) {
var vm = this;
vm.pinFormCheck = function () {
vm.count++;
if (vm.pinForm.$valid && vm.details.PIN === vm.pin && vm.count <= 2) {
location.href = "http://localhost:51701/Home/MainMenu";
$scope.obj = {
'cid': 'vm.details.CID',
'name': 'vm.details.Name',
'pin': 'vm.details.PIN',
'bal': 'vm.details.Bal',
'status': 'vm.details.cardStatus'
};
console.log(vm.details.Bal);//the correct balance get displayed in console
} else {
vm.failPin = true;
}
};
})
.controller('CheckCtrl', function ($scope, myService) {
$scope.data = myService.getObj();
})
.factory('myService', function () {
var obj = null;
return {
getObj: function () {
return obj;
},
setObj: function (value) {
obj = value;
}
}
});
Here is the view from which the first object is passed:
<body ng-app="customerApp">
<div ng-controller="MainCtrl as vm">
<form name="vm.pinForm">
<input type="password" ng-model="vm.pin" ng-required="true" />
<p><button ng-disabled="vm.count >=3" ng-click="vm.pinFormCheck();" ng-init="vm.count=0">Proceed</button></p>
</form>
...
Here' the second view where I need the object
<html ng-app="customerApp">
<body ng-controller="CheckCtrl">
<div>
<h1>your balance is {{data.bal}}</h1>
....
The balance from vm.details.Bal from the first view must appear in data.bal in the second view, but nothing is appearing.
You can just save vm.details in some factory.
And then get it in CheckCtrl from this factory.
Factories in AngularJS implement singleton pattern. So saved data will be kept in until your app exists.
You tried to do next thing myService.getObj(); But you didn't save anything to the service.
Inject myService to the MainCtrl and then save details into it.

watch rootScope variable to change the progressBar value

app.controller("ListController1", ['$rootScope',function($rootScope) {
$rootScope.progressBar=10;
$rootScope.$watch(
function() {
return $rootScope.progressBar;
},
function(){
alert($rootScope.progressBar);
alert("changed");
},true)
}]);
app.controller("ListController2", ['$scope','$rootScope',function($scope,$rootScope) {
$scope.save=function() {
$rootScope.progressBar=20;
}
}]);
I want progressBar value form ListController2 to be reflected back in Listcontroller1. It seems i am doing something wrong with it. Please help any one. thank u.
Rather than sharing state with $routeScope, you should consider creating a service to share the state of the progress bar - this is one of the use cases of services.
When the save button is pressed in the code below, it updates the value in progressService. The value from progressService is watched in the first controller and the view is updated accordingly.
You can add progressService to as many controllers as you'd like.
var app = angular.module("app", []);
app.factory("progressService", [function() {
var service = this;
service.progressBar = 0;
return service;
}]);
app.controller("ListController1", ["$scope", "progressService", function($scope, progressService) {
progressService.progressBar=10;
$scope.progress = progressService.progressBar;
$scope.$watch(
function() {
return progressService.progressBar;
},
function(newValue) {
$scope.progress = newValue;
});
}]);
app.controller("ListController2", ['$scope','progressService',function($scope,progressService) {
$scope.save=function() {
progressService.progressBar=20;
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ListController1">
Progress: {{progress}}
</div>
<div ng-controller="ListController2">
<button ng-click="save()">Save</button>
</div>
</div>

Communication Between Two Controllers; References vs Primatives

I have seen an unexpected behaviour in Angularjs with its factories.
I used a factory to communication between two controllers.
In the first scenario it is working fine but not in second one. The only difference between them is that in first example I am accessing the name from the view but in second one I am accessing in scope variable.
Scenario 1
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory.name}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get();
}
Scenario 2
<div ng-controller="HelloCtrl">
<a ng-click="setValue('jhon')">click</a>
</div>
<br />
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
//angular.js example for factory vs service
var app = angular.module('myApp', []);
app.factory('testFactory', function() {
var obj = {'name':'rio'};
return {
get : function() {
return obj;
},
set : function(text) {
obj.name = text;
}
}
});
function HelloCtrl($scope, testFactory) {
$scope.setValue = function(value) {
testFactory.set(value);
}
}
function GoodbyeCtrl($scope, testFactory) {
$scope.fromFactory = testFactory.get().name;
}
The difference is:
Scenario I
$scope.fromFactory = testFactory.get();
<div ng-controller="GoodbyeCtrl">
<p> {{fromFactory.name}}</p>
</div>
The $scope variable is set to testFactory.get() which is an object reference. On each digest cycle the watcher fetches the value of the property name using the object reference. The DOM gets updated with changes to that property.
Scenario II
$scope.fromFactory = testFactory.get().name;
<div ng-controller="GoodbyeCtrl">
<p>{{fromFactory}}</p>
</div>
The $scope variable is set to testFactory.get().name which is a primative. On each digest cycle, the primative value doesn't change.
The important difference is that when a reference value is passed to a function, and a function modifies its contents, that change is seen by the caller and any other functions that have references to the object.

Using 'controller as' syntax to pass object values to parent controller

When using the $scope controller syntax, it's simple to set a value on a parent controller's object. For example
<div ng-controller="ParentController">
{{myValue.a}}
<div ng-controller="ChildController">
{{myValue.a}}
</div>
</div>
app.controller('ParentController', function($scope) {
$scope.myValue = {};
$scope.myValue.a = 1;
});
app.controller('ChildController', function($scope) {
$scope.myValue.a = 2;
});
The above outputs:
2
2
Is there a way to achieve the same functionality with the controller as syntax without referencing $scope in the child controller?
You could do it using a service, or you could do it referencing the scope.
The behavior that you are using, scope inheritance, is often referred to as an unwanted side affect. This is why isolated scopes are used with the controllerAs syntax.
In the following example you can see we achieve the same result using sharing the myValue property on the $scope along with the controllerAs syntax.
angular.module('app', [])
.controller('ParentController', ParentController)
.controller('ChildController', ChildController);
ParentController.$inject = ['$scope'];
function ParentController($scope) {
this.myValue = {};
this.myValue.a = 1;
$scope.myValue = this.myValue;
}
ChildController.$inject = ['$scope'];
function ChildController($scope) {
this.myValue = $scope.myValue;
this.myValue.a = 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
<div ng-controller="ParentController as parent">
parent: {{parent.myValue.a}}
<div ng-controller="ChildController as child">
child: {{child.myValue.a}}
</div>
</div>
</div>
This can be accomplished without $scope using a service:
angular.module('app', [])
.controller('ParentController', ParentController)
.controller('ChildController', ChildController)
.service('valueService', ValueService);
ParentController.$inject = ['valueService'];
function ParentController(valueService) {
this.myValue = {};
this.myValue.a = 1;
valueService.setValue(this.myValue);
}
ChildController.$inject = ['valueService'];
function ChildController(valueService) {
this.myValue = valueService.getValue();
this.myValue.a = 2;
}
function ValueService() {
var storedValue;
this.getValue = function() {
return storedValue;
}
this.setValue = function(value) {
storedValue = value;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
<div ng-controller="ParentController as parent">
parent: {{parent.myValue.a}}
<div ng-controller="ChildController as child">
child: {{child.myValue.a}}
</div>
</div>
</div>
if you don't like to use $scope you may pass outer controller downstream, see directives communication
No, it's not possible from within ChildController.
Don't think of ControllerAs as a newer style of $scope. Each has a different use.
ControllerAs does not publish values onto scope (it actually does - via the alias, but the alias should not be assumed to be known to a child Controller since the alias is defined in the View).
I use both where needed and I use the following convention:
app.controller("ParentCtrl", function($scope){
// $scope-inherited view model
var VM = $scope.VM = ($scope.VM || {});
// controller-specific view model
var vm = this;
VM.valueVisibleToChildControllers = "foo";
vm.valueVisibleOnlyToTheView = "bar";
});

Resources