Angular, two way binding with two variables - angularjs

I have two variables that are related by a function, and the user should be able to change one or the other in an input field which should automatically change the other.
How can I do that, right now I'm just using $watch for both.
Here's some sample code and a fiddle.
JS,
angular.module("test", [])
.controller("MyController", function ($scope) {
$scope.letter = 'A';
$scope.number = 1;
$scope.map = { 'A': 1, 'B': 2, 'C': 3, 'D': 4 };
$scope.$watch('letter', function(new_val, old_val){
if(new_val != old_val){
$scope.number = $scope.map[new_val];
}
});
$scope.$watch('number', function(new_val, old_val){
...
});
});
HTML,
<div ng-app="test">
<div ng-controller="MyController">
<input ng-model="letter" />
<input type="number" ng-model="number" />
</div>
</div>

There are a number of ways you can do this, and using $watch is certainly one of them. As mentioned by Matt, you could also use the ng-change directive to fire a method on your controller.
The third way that I would like to offer up, is to make use of ES5 properties, and the Controller 'as' syntax that Angular introduced in 1.2+
If you define your controller as a JS object instead of using an anonymous function, you can add properties and methods to the prototype:
myController = function () {
this.map = {'A': 1,'B': 2,'C': 3,'D': 4};
this._letter = 'A';
this._number = 1;
};
Now we can extract the work you have already done for getting your letter and number values into functions:
myController.prototype.getLetterValue = function (num) {
for (var key in this.map) {
if (this.map.hasOwnProperty(key)) {
if (this.map[key] === num) {
return key;
}
}
}
};
myController.prototype.getNumberValue = function (letter) {
return this.map[letter];
};
Lastly, we are going to declare a couple of properties on your controller that encapsulate the desired functionality using Object.defineProperty.
Object.defineProperty(
myController.prototype,
"letter", {
get: function () {
return this._letter;
},
set: function (newValue) {
this._letter = newValue;
this._number = this.getNumberValue(this._letter);
},
enumerable: true,
configurable: true
});
Object.defineProperty(
myController.prototype,
"number", {
get: function () {
return this._number;
},
set: function (newValue) {
this._number = newValue;
this._letter = this.getLetterValue(this._number);
},
enumerable: true,
configurable: true
});
Add this controller to your module:
angular.module("test", [])
.controller("MyController", myController);
And lastly, you just need to modify your binding syntax slightly in order to use the new Controller 'as' syntax. This will allow you to bind directly to properties and methods on your controller instead of having to use $scope
<div ng-app="test">
<div ng-controller="MyController as ctrl">
<input ng-model="ctrl.letter" />
<input type="number" ng-model="ctrl.number" />
</div>
</div>
Live Demo
Summary
This isn't exactly less code, but does have several advantages.
Your controller is decoupled from $scope and $watch making it more portable
The controller code is easier to read because all the functionality isn't nested inside an anonymous function
The code is a little more forward looking because future versions of Angular will probably eliminate $scope and the $digest loop altogether by using native observables.

Related

Pass toggling Boolean from service to controller

I want to show/hide an element based on the Boolean value that is changing in my service. I want the change of this Boolean to happen in my service so multiple controllers can access the true or false value, but I am having trouble returning this value to one or more controllers. Currently I'm only able to pass one value which is false, although the value does show it's changing in my service. Here is an example of my controller...
angular.module('myApp')
.service('ThisService', function(){
function toggleDisplay(){
return displayElement = !displayElement;
}
});
.controller('ThisCtrl', function (thisService, $scope) {
function init(){
$scope.displayElement = ThisService.toggleDisplay();
}
$scope.toggleElement = function(){
$scope.displayElement = ThisService.toggleDisplay();
}
init();
});
My HTML...
<div ng-show="displayElement">Show hide me</div>
<button ng-click='toggleElement()'></button>
Can you please tell me how to return the true/false value to my controller correctly?
You can use a value and then toggle that in your service. However, your service definition is not valid, you have a semi-colon in the middle of your chain of modules and you define your service with the name "ThisService", but then you try to reference it in your controller as "thisService" (it's case sensitive).
JS:
angular.module("myApp", [])
.value("DisplayElement", { value: true })
.service("ThisService", function(DisplayElement) {
this.toggleDisplay = function() {
return DisplayElement.value = !DisplayElement.value;
}
})
.controller("ThisCtrl", function(ThisService, $scope) {
function init() {
$scope.displayElement = ThisService.toggleDisplay();
}
$scope.toggleElement = function() {
$scope.displayElement = ThisService.toggleDisplay();
}
init();
});
HTML:
<div ng-app="myApp">
<div ng-controller="ThisCtrl">
<div ng-show="displayElement">Show hide me</div>
<button ng-click="toggleElement()">Toggle Display</button>
</div>
</div>
jsFiddle
You could even eliminate the service and just access the value directly in your controller (you'd have to inject it first).

How can I monitor the change of a variable in my factory

I have a very simple preloading screen script that i need to get from several controllers. However I can't get it to update automatically the variable and I don't know what I'm doing wrong.
Factory:
myApp.factory("preloader", function(){
var preload = {};
preload.loaded = true;
preload.turnOn = function () {
preload.loaded = true;
console.log('on');
}
preload.turnOff = function () {
preload.loaded = false;
console.log('off');
}
preload.getState = function() {
return preload.loaded;
}
return preload;
});
Controller
mazda.controller('preloadingHome', ['$scope', "preloader", function($scope, preload) {
$scope.users = false;
$scope.showPreload = preload.getState();
console.log(preload.loaded);
$scope.turnOn = function(){
preload.turnOn();
}
$scope.turnOff = function(){
preload.turnOff();
}
//
// $scope.state = preload.state;
// preload.turnOff();
}]);
View
<body data-ng-controller="preloadingHome">
<div>aaa: {{ showPreload }}</div>
<div>b: {{users}} </div>
<input type="checkbox" ng-model="users" />
<input type="checkbox" ng-change="turnOff()" ng-model="pene" />
<input type="checkbox" ng-change="turnOn()" ng-model="pene2" />
<!-- script src="js/scripts.min.js"></script -->
<script src="js/scripts.min.js"></script>
<script src="js/bundle.js"></script>
</body>
My problem is: The {{ showPreload }} variable load on the view always stays true no matter how I change it.
I think it is important to point out that the reason this does not work is because your getState() method returns by value, not by reference.
When your controller instantiates, it sets the showPreloaded variable to the value of the preloaded.loaded object member, which is true. Every time you change the state after that, you are updating the object member in your factory properly, but the $scope.showPreloaded value is equal to true, as it is not referencing the value in the factory.
Here is how to change your code:
In the view
<div>aaa: {{ showPreload.loaded }}</div>
In the factory:
preload.getState = function() {
return preload;
}
The factory will now be returning the reference to the object preload. Javascript always returns references when returning an object, and returns the value when returning a primitive
Your factory's method is not a two-way binding, instead, expose a variable from your factory/service, and bind to that, for example
factory.state = { isTurnedOn : true };
you can either directly bind to that factory variable, or have your controller own variable linked to the factory variable
Return the whole object rather than returning a primitive. There is no inheritance of primitives
In factory
preload.getState = function() {
return preload;
}
In controller
$scope.preloadState = preload.getState();
In view
<div>aaa: {{ preloadState.preload ?'Yes':'No'}}</div>
Now angular view watchers will detect property changes of the object and update view
You should try to write this: scope.preload = preloader;
And then you can manipulate your object scope.preload.

update angularjs model from jquery [duplicate]

I have this simple scenario:
Input element which value is changed by jQuery's val() method.
I am trying to update the angular model with the value that jQuery set. I tried to write a simple directive, but it's not doing what I want.
Here's the directive:
var myApp = angular.module('myApp', []);
myApp.directive('testChange', function() {
return function(scope, element, attrs) {
element.bind('change', function() {
console.log('value changed');
})
}
})
this is the jQuery part:
$(function(){
$('button').click(function(){
$('input').val('xxx');
})
})
and html:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input test-change ng-model="foo" />
<span>{{foo}}</span>
</div>
</div>
<button>clickme</button>
Here is the fiddle with my try:
http://jsfiddle.net/U3pVM/743/
Can someone please point me in the right direction?
ngModel listens for "input" event, so to "fix" your code you'd need to trigger that event after setting the value:
$('button').click(function(){
var input = $('input');
input.val('xxx');
input.trigger('input'); // Use for Chrome/Firefox/Edge
input.trigger('change'); // Use for Chrome/Firefox/Edge + IE11
});
For the explanation of this particular behaviour check out this answer that I gave a while ago: "How does AngularJS internally catch events like 'onclick', 'onchange'?"
But unfortunately, this is not the only problem you have. As pointed out with other post comments, your jQuery-centric approach is plain wrong. For more info take a look at this post: How do I “think in AngularJS” if I have a jQuery background?).
Hope this is useful for someone.
I was unable to get the jQuery('#myInputElement').trigger('input') event to be picked up my angular app.
I was however, able to get angular.element(jQuery('#myInputElement')).triggerHandler('input') to be picked up.
The accepted answer which was triggering input event with jQuery didn't work for me. Creating an event and dispatching with native JavaScript did the trick.
$("input")[0].dispatchEvent(new Event("input", { bubbles: true }));
I don't think jQuery is required here.
You can use $watch and ng-click instead
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input test-change ng-model="foo" />
<span>{{foo}}</span>
<button ng-click=" foo= 'xxx' ">click me</button>
<!-- this changes foo value, you can also call a function from your controller -->
</div>
</div>
In your controller :
$scope.$watch('foo', function(newValue, oldValue) {
console.log(newValue);
console.log(oldValue);
});
You have to use the following code in order to update the scope of the specific input model as follows
$('button').on('click', function(){
var newVal = $(this).data('val');
$('select').val(newVal).change();
var scope = angular.element($("select")).scope();
scope.$apply(function(){
scope.selectValue = newVal;
});
});
I made modifications on only controller initialization by adding listener on action button:
$(document).on('click', '#action-button', function () {
$timeout(function () {
angular.element($('#input')).triggerHandler('input');
});
});
Other solutions did not work in my case.
I know it's a bit late to answer here but maybe I may save some once's day.
I have been dealing with the same problem. A model will not populate once you update the value of input from jQuery. I tried using trigger events but no result.
Here is what I did that may save your day.
Declare a variable within your script tag in HTML.
Like:
<script>
var inputValue="";
// update that variable using your jQuery function with appropriate value, you want...
</script>
Once you did that by using below service of angular.
$window
Now below getData function called from the same controller scope will give you the value you want.
var myApp = angular.module('myApp', []);
app.controller('imageManagerCtrl',['$scope','$window',function($scope,$window) {
$scope.getData = function () {
console.log("Window value " + $window.inputValue);
}}]);
I've written this little plugin for jQuery which will make all calls to .val(value) update the angular element if present:
(function($, ng) {
'use strict';
var $val = $.fn.val; // save original jQuery function
// override jQuery function
$.fn.val = function (value) {
// if getter, just return original
if (!arguments.length) {
return $val.call(this);
}
// get result of original function
var result = $val.call(this, value);
// trigger angular input (this[0] is the DOM object)
ng.element(this[0]).triggerHandler('input');
// return the original result
return result;
}
})(window.jQuery, window.angular);
Just pop this script in after jQuery and angular.js and val(value) updates should now play nice.
Minified version:
!function(n,t){"use strict";var r=n.fn.val;n.fn.val=function(n){if(!arguments.length)return r.call(this);var e=r.call(this,n);return t.element(this[0]).triggerHandler("input"),e}}(window.jQuery,window.angular);
Example:
// the function
(function($, ng) {
'use strict';
var $val = $.fn.val;
$.fn.val = function (value) {
if (!arguments.length) {
return $val.call(this);
}
var result = $val.call(this, value);
ng.element(this[0]).triggerHandler('input');
return result;
}
})(window.jQuery, window.angular);
(function(ng){
ng.module('example', [])
.controller('ExampleController', function($scope) {
$scope.output = "output";
$scope.change = function() {
$scope.output = "" + $scope.input;
}
});
})(window.angular);
(function($){
$(function() {
var button = $('#button');
if (button.length)
console.log('hello, button');
button.click(function() {
var input = $('#input');
var value = parseInt(input.val());
value = isNaN(value) ? 0 : value;
input.val(value + 1);
});
});
})(window.jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="example" ng-controller="ExampleController">
<input type="number" id="input" ng-model="input" ng-change="change()" />
<span>{{output}}</span>
<button id="button">+</button>
</div>
If you are using IE, you have to use: input.trigger("change");
add .change() after setting the value.
example:('id').val.('value').change();
also don't forget to add onchange or ng-change tag in html
I did this to be able to update the value of ngModel from the outside with Vanilla/jQuery:
function getScope(fieldElement) {
var $scope = angular.element(fieldElement).scope();
var nameScope;
var name = fieldElement.getAttribute('name');
if($scope) {
if($scope.form) {
nameScope = $scope.form[name];
} else if($scope[name]) {
nameScope = $scope[name];
}
}
return nameScope;
}
function setScopeValue(fieldElement, newValue) {
var $scope = getScope(fieldElement);
if($scope) {
$scope.$setViewValue(newValue);
$scope.$validate();
$scope.$render();
}
}
setScopeValue(document.getElementById("fieldId"), "new value");
Not what OP asked, but for any soul that might be as well writing an userscript that goes through input fields and fills the required details. Nothing (fully) worked for me, but finally managed to get it done this way:
var el = $('#sp_formfield_fw_ip');
el.val("some value");
angular.element(el).triggerHandler('focus');
angular.element(el).triggerHandler('input');
angular.element(el).triggerHandler('change');
angular.element(el).triggerHandler('blur');
Open developer tools, and inspect input field for added events. There I found all of them (in my case): focus, input, change and blur.

Difference between & and = for passing functions to isolate scope

& is always described as the way to call a function on the parent scope inside a directive's isolated scope.
However, since = creates two-way binding and a function is just another value, shouldn't it be just as effective for this purpose?
The only difference I see is that using &, you can modify the passed function without affecting the parent, since it's one-way binding.
So why is & usually recommended over = for this use case?
There is also some weird behavior that I've come across. Using & gives you a function wrapper. If you unwrap it in the controller and call it, it will resolve differently than if you unwrap it as the result of an ng-click inside the directive.
I've set up an experiment in this fiddle:
app.directive('myDir', function() {
return {
restrict: 'E',
template: '<button ng-click="parentFunc1()(1); parentFunc2(1)">Directive</button>',
scope: {
parentFunc1: '&func1',
parentFunc2: '=func2',
},
controller: Ctrl2,
}
});
function Ctrl2($scope) {
//Step 1
console.log($scope.parentFunc1);
$scope.parentFunc1()(1);
$scope.parentFunc2(1);
//Step 2
$scope.oldParent1 = $scope.parentFunc1;
$scope.parentFunc1 = function (value) {
console.log(value+1);
};
$scope.parentFunc1(1);
$scope.parentFunc2(1);
//Step 3
$scope.parentFunc1 = $scope.oldParent1;
$scope.parentFunc2 = function (value) {
console.log(value+2);
};
console.log($scope.parentFunc1);
$scope.parentFunc1()(1);
$scope.parentFunc2(1);
//Step 4 -> Click the directive button
}
function Ctrl($scope){
$scope.foo = function (value) {
console.log(value);
};
}
This logs "1,1; 2,1; 1,2; 2,2". The last two pairs of values leave me puzzled because they seem to execute the same code.
Very good question!
See the difference between & and = is simple.
When you are declaring a directive scope, and you add to it & it means that you are declaring a function within the scope rather if it was = it was for a regular property.
WAIT WAIT, those two examples above just worked and they are both functions!
Well that's true but hold on,
You just used them incorrectly.
Using the :"&func" means that you are adding a function that will be evaluated soon.
Confused?
I'll type a perfect example:
<script type="text/javascript">
angular.module("exampleApp", [])
.directive("scopeDemo", function (){
return {
template: "<div><p>Name: {{local}}, City: {{cityFn()}}</p></div>",
scope:{
local: "=nameprop",
cityFn: "&city"
}
}
}
}).controller("scopeCtrl, function($scope){
$scope.data = {
name: "Shahar",
defaultCity: "London"
};
$scope.getCity = function(name){
return name == 'Shahar' ? $scope.data.defaultCity : "unknown";
}
});
</script>
<body ng-controller="scopeCtrl">
<div>
Direct Binding: <input ng-model="data.name" />
</div>
<div scope-demo city="getCity(data.name)" nameprop="data.name"></div> //Reference 1.
</body>
As you can see I've written two attributes to my scope's directive.
one accepts a PROPERTY and one accepts a FUNCTION.
As you can see the result of this directive is rather dull, but it explains the whole point.
You will not succeed doing so if you try to make a function with the '=' since Angular will just ignore that.
I hope it clears it up.
Good luck!
The difference between & and = binding strategies takes place when you want to call function on parent scope with parameters also passed from parent scope.
Let's say you have following controller:
angular.module('myApp').controller('myCtrl', function() {
$scope.mans = [{name: 'Peter'}, {name: 'Alex'}]
$scope.someMethod = function(par) {
console.log(par);
}
});
And HTML:
<div ng-repeat="man in mans">
<button my-dir="someMethod(man.name)">Click me</button>
</div>
In this case myDir directive should only use & binding strategy, because the directive knows nothing aboout passed parameters.

$watch not firing on data change

I have a watch setup against the model of a ui-select2 dropdown (from ui-bootstrap). The watch fires on load but not on data changes and I can't figure out why.
It isn't the usual problem of not $apply'ing the model change or not using the third parameter for equality comparison (at least from my code).
What do I need to do to get it to fire?
Here is a plunk demonstrating the issue.
Try passing true as a 3rd argument to .$watch()
$rootScope.Scope documentation
$watch(watchExpression, listener, objectEquality)
objectEquality(optional) – {boolean=} – Compare object for equality rather than for reference.
I fixed some stuff.
http://plnkr.co/edit/5Zaln7QT2gETVcGiMdoW?p=preview
The JS
var myMod = angular.module("myApp",[]).controller("MainController", function($scope){
$scope.myModel = {selectedId:null};
}).controller("DetailController",function($scope){
$scope.items = [1,2,3,4];
$scope.watchHitCount = 0;
$scope.$watch('myModel.selectedId', function(newVal, oldVal){
console.log(newVal + " " + oldVal);
$scope.watchHitCount++;
},true);
});
The index.html
<body ng-app="myApp">
<div ng-controller="MainController">
<ng-include src="'detail.html'" ng-controller="DetailController"></ng-include>
</div>
</body>
The detail.html
<pre>watch hit: {{watchHitCount}}</pre>
<pre>selected value: {{myModel.selectedId}}</pre>
<select ng-model="myModel.selectedId" ui-select2="">
<option></option>
<option ng-repeat="item in items" value="{{item}}">{{item}}</option>
</select>
It was complaining about not finding the controller so I set it up the way I normally would with a named ng-app and a module declared that has controllers defined on it.
I also added an object to hold the value in your model. It is bad practice to use the $scope object as your model, instead your scope should refer to an object that is your model.
There are a simple fix for that, to use watch with complex object instead of simple variable
For example (DON"T USE)
$scope.selectedType=1;//default
$scope.$watch(
function () {
return $scope.selectedType;
},
function (newValue, oldValue) {
if (!angular.equals(oldValue, newValue)) {
$scope.DoWork();
}
},
true);
But Use below
$scope.selecteditem={selectedType:1};
$scope.$watch(
function () {
return $scope.selecteditem.selectedType;
},
function (newValue, oldValue) {
if (!angular.equals(oldValue, newValue)) {
$scope.DoWork();
}
},
true);
note that "slectedTypes" in the second example located inside object, not just scope variable. this will work even in old Angular versions.
If you're using the controller-as approach some reading might suggest a syntax like this:
var myController = {
myValue: 1
};
$scope.$watch('$ctrl.myValue', function () {
...
}, true);
Instead just wrap the field in a function like this:
var myController = {
myValue: 1
};
$scope.$watch(function () {
return myController.myValue;
}, function () {
...
}, true);
The only thing that worked for me:
var ctrl = this;
this.$onInit = function () {
console.log("id " + ctrl.event.id);
};
from https://docs.angularjs.org/guide/component#!

Resources