Using Angular, is it possible to compare an old and new value when it is changed (programmatically, or otherwise)?
I guess what I'm after is functionality similar to ng-change that can be applied to any element and is triggered whenever the value in the expression is updated.
Of course, I could use $scope.$watch on the entire data model, find the individual changed values (and injecting a 'changeDirection' property) but this feels excessive.
Edit: Consider a data model of an array of 500 objects, each with 5 properties, 2 of which are integers that I need to know if they have increased/decreased.
You can use $watch on dot delimited paths to signify individual properties in the model you wish to watch.
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng-controller="ctrl">
<input ng-model="model.name.first" />Update:{{watchVals}}
</div>
<script>
var app = angular.module("app", []);
app.controller("ctrl", ["$scope",
function($scope) {
$scope.model = {
"name": {
"first": "John",
"last": "Doe"
}
};
$scope.val = "";
$scope.$watch("model.name.first", function(newVal, oldVal) {
$scope.watchVals = oldVal + " -> " + newVal;
});
}
]);
angular.bootstrap(document, [app.name]);
</script>
</body>
</html>
i recommend use $watch for watching object changes and you can pass new value and old value to function like this:
$scope.$watch('myObject', function(newValue, oldValue){
if(!angular.equals(newValue, oldValue)){
doSomeThing();
}
});
There are few key differences between ng-change (angular built in directive) and $watch.
Consider this html markup
<input type=text ng-model=obj.name ng-change="triggerNameChanged()">
Using ng-change would call triggerNameChanged() function only on the
actual changes to the input by the user.However Watch are also called in
other cases too- right when they’re being defined the first time and
on changes made to the value not by the user, e.g. programmatically.
Whenever you write {{}} expression internally a watch is set up to look for changes in your model.
Using ng-change is a bit more performant, since it uses one less watch expression. Since Angular knows it should only call the expression on change events it does not need to keep evaluating it on every digest cycle, as opposed to watch.
By default $watch compares by reference.So If you set the third parameter to true, Angular will instead "shallow" watch the object for changes.
An Example of watch
$scope.$watch('myForm.modified', handler)
in this case handler will be called if some form elements actually contains new data or if it reversed to initial state.you can use modified property of individual form elements to actually reduce amount of data sent to a server .
Related
I'm creating a very simple custom directive and trying to pass a simple string variable from the parent scope to the custom directive isolated scope by using the '<' symbol. Then, I try to assign a new value to the variable in my custom directive controller and display the new value in the template but it's still displaying the old value.
Here is my parent controller:
(function(){
angular.module("app").controller("ctrl", ctrl);
function ctrl(){
ctrl = this;
ctrl.fullname = "Paul G.";
}
})()
Here is my custom directive:
(function () {
'use strict';
angular.module('app').directive('dir', dir);
function dir() {
return {
scope:{
name:'<',
},
bindToController: true,
template: `<h1>I'm a directive</h1>
<h1>Name: {{vm.name}}</h1>`
,
controller: function($scope){
this.name = "Tony J";
},
controllerAs:'vm'
};
}
})();
Here is my Html:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js" data-semver="1.5.11"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-resource/1.6.4/angular-resource.js"></script>
<script src="app.js"></script>
<script src="ctrl.js"></script>
<script src="directive.js"></script>
</head>
<body ng-controller="ctrl as $ctrl">
<p>Hello {{$ctrl.fullname}}!</p>
<dir name="$ctrl.fullname"></dir>
</div>
</body>
</html>
The output is below:
However, I expected it see Name: Tony J in the output.
First a word of caution
Inputs should be using < and # bindings. The < symbol denotes one-way bindings which are available since 1.5. The difference to = is that the bound properties in the component scope are not watched, which means if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent and component scope reference the same object, so if you are changing object properties or array elements in the component, the parent will still reflect that change. The general rule should therefore be to never change an object or array property in the component scope. # bindings can be used when the input is a string, especially when the value of the binding doesn't change.
— AngularJS Developer Guide - Component-based Application Architecture
Now what you can do is change the value of the variable after the directive has initialized(you can study the AngularJS Component life cycle here) something like this.
controller: function($scope) {
ctrl = this;
ctrl.$onInit = function() {
ctrl.name = "some name"
}
},
here is a working fiddle I developed from your given example.
I'm not aware of < (one way data binding) as an option for the scope property of a directive, this works with components in the property bindings.
If you want to do one way data binding in your isolated directive scope you can use the # symbol instead.
This question is based in the answer provided here
I want to output the result from a lodash function applied to a scope ($scope.names) when another scope ($scope.switch) is set to true, and to output the result of that lodash function applied to a filtered scope when $scope.switch is set to false.
So my ng-repeat would be like this:
<ul>
<li ng-repeat="things in (filtered=(names | filter:filterType))">{{things}}</li>
</ul>
I use two ng-click to change the state of $scope.switch and to apply/unapply the filter:
<a ng-click="switch = false; filterType=''">unswitch - No Filters</a><br><br>
<a ng-click="switch = true; filterType={name:'!Jimmy'}">switch - Not Jimmy</a>
And I used this code in order to watch the changes in $scope.switch:
$scope.$watch('switch', function() {
$scope.thingScope =$scope.switch ? _.map($scope.names,"styles") : _.map($scope.filtered,"styles");
});
Here is the working plunkr with all the code (it's much easier to notice the problem here)
The problem is that the output of $scope.thingScope doesn't correspond to the values showed in the ng-repeat (right now every time I click, it shows the values of the previous one, that is, it goes one step behind). Thanks in advance!
Ok, I found few problems.
0 - You are working with a generated list element (Filtered. which is the result of apply the filter over names). When the switch change, the generated list is not finished. So, you will not find the elements.
1 - You have a lot of elements Trying to work over the same (the Swtich/Filter over names).
2 - You are showing using: $scope.thingScope = $scope.switch ? _.map($scope.names,"styles") : _.map($scope.filtered,"styles"); So, you are showing $scope.names when $scope.switch == true. So wrong conditional.
3 - You are applying first the change of the switch and then the filterType in the ng-click.
So, you can do 2 things.
Work with duplicated filters in 2 places (Not recommended.)
The ng-repeat work with the processed-list. And work all the filters in the controller. As #shaunhusain did it.
P.S. I think it could be cleaner with an active to filter. And not by Name, check this Plnkr: http://plnkr.co/edit/4uJ5AxP9xntgBe1hQrBW?p=preview
You can select by Name (only clicking) but you must click again in update filters to apply changes.
Think your logic in the view is getting to the slightly heavy and not making sense due to all the watching that goes on with things bound in the view... this is easier to solve if you instead do it procedurally.
http://plnkr.co/edit/ZExywkLtDxuT82Ud6gYl?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
</head>
<body>
<div ng-controller="myCtrl" ng-app="myApp">
0. <b>Switch</b>: {{switch}} <br/><br/>
1. <b>Output</b>: {{thingScope}} <br/><br/>
2. <b>Filtered List</b>:
<ul>
<li ng-repeat="things in filtered">{{things}}</li>
</ul>
<a ng-click="changeSwitch(false, '')">unswitch - No Filters</a><br><br>
<a ng-click="changeSwitch(true, {name:'!Jimmy'})">switch - Not Jimmy</a>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $filter) {
$scope.switch = false;
// set the default sort type
$scope.filterType = '';
var names=[{name:"Johny", styles:["one","two","three"]},{name:"Jimmy", styles:["two","three","four"]},{name:"Kevin", styles:["three","four","five"]}];
// Option B:
function filterData(searchObj){
$scope.filtered = $filter('filter')(names, searchObj);
};
filterData();
$scope.changeSwitch = function(newVal, filterType){
$scope.switch = newVal;
filterData(filterType);
$scope.thingScope = !$scope.switch ? _.map(names,"styles") : _.map($scope.filtered,"styles");
}
});
</script>
</body>
</html>
I'm trying to making some custom elements with AngularJS's and bind some events to it, then I notice $scope.var won't update UI when used in a binding function.
Here is a simplified example that describing the probelm:
HTML:
<!doctype html>
<html ng-app="test">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="Ctrl2">
<span>{{result}}</span>
<br />
<button ng-click="a()">A</button>
<button my-button>B</button>
</div>
</body>
</html>
JS:
function Ctrl2($scope) {
$scope.result = 'Click Button to change this string';
$scope.a = function (e) {
$scope.result = 'A';
}
$scope.b = function (e) {
$scope.result = 'B';
}
}
var mod = angular.module('test', []);
mod.directive('myButton', function () {
return function (scope, element, attrs) {
//change scope.result from here works
//But not in bind functions
//scope.result = 'B';
element.bind('click', scope.b);
}
});
DEMO : http://plnkr.co/edit/g3S56xez6Q90mjbFogkL?p=preview
Basicly, I bind click event to my-button and want to change $scope.result when user clicked button B (similar to ng-click:a() on button A). But the view won't update to the new $scope.result if I do this way.
What did I do wrong? Thanks.
Event handlers are called "outside" Angular, so although your $scope properties will be updated, the view will not update because Angular doesn't know about these changes.
Call $scope.$apply() at the bottom of your event handler. This will cause a digest cycle to run, and Angular will notice the changes you made to the $scope (because of the $watches that Angular set up due to using {{ ... }} in your HTML) and update the view.
This might be also a result of different problem but with the same symptoms.
If you destroy a parent scope of the one that is assigned to the view, its changes will not affect the view in any way even after $apply() call. See the example - you can change the view value through the text input, but when you click Destroy parent scope!, model is not updated anymore.
I do not consider this as a bug. It is rather result of too hacky code in application :-)
I faced this problem when using Angular Bootstrap's modal. I tried to open second modal with scope of the first one. Then, I immediately closed the first modal which caused the parent scope to be destroyed.
use timeout
$timeout(function () {
code....
},
0);
I don't use Angular regularly, but I understand that one of the key features is that when data is updated on a form element, it is automatically updated in the model.
If you are instead using a library like jQuery, you must manually attach an event to the form input that updates the model when it is changed, as in $('#myInput').on('change', updateModel);
Although the above handler will be fired when myInput is changed by the user, it will not be fired if myInput is changed by Javascript code such as $('#myInput').val('hello world');
My question is, how does Angular know when a form input is changed in Javascript code?
Angular applies a scope digest every time it's needed (by an Angular function) during which it checks the states of all the scope variables, including the models used, of course.
If you modify some of those variables manually, using JavaScript, jQuery, etc... Angular will not know that the changes have occured and you need to tell it so either by doing $scope.$apply() or by wrapping the code block in a $timeout callback (these are the most commonly used methods).
If you don't do it manually, you'd have to wait for some (if any) other Angular event to trigger the digest cycle, which is never good.
See this example, note how nothing happens when you just update the value, but you need to do it manually (ng-click does it) in order for DOM to update:
angular.module('app', [])
.controller('Ctrl', function($scope){
$scope.ourValue = 'Initial Value';
window.exposedFunc = function(v, digest) {
$scope.ourValue = v;
if (digest) {
$scope.$apply();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
<button onclick="exposedFunc('First Button Value')">Update Value - No Digest</button>
<button onclick="exposedFunc('Second Button Value', true)">Update Value - Force Digest</button>
<button ng-click="">Force Digest only</button>
<p>{{ourValue}}</p>
</div>
Here's a super simple example of binding using keyup event. It should be enough to get you started on your projects:
var res = document.getElementById('r');
function handleChange(v) {
res.textContent = v;
}
<input onkeyup="handleChange(this.value)" type="text" value="Initial value" />
<p id="r">No binding yet</p>
I've started working with angular js and have a problem that requires getting the current state of the DOM inside of my controller. Basically I'm building a text editor inside of an contenteditable div. Revisions to the text in the div can come from an external service(long polling pushes from the server) as well as the user actually typing in the field. Right now the revisions from the server are manipulating my angular model, which then updates the view through an ng-bind-html-unsafe directive. The only problem with this is that this blows away the users current cursor position and text selection.
I've figured out a way around the problem, but it requires directly manipulating dom elements in my controller, which seems to be discouraged in angular. I'm looking for either validation of my current method, or reccomendations on something more "angulary".
Basically what I've done is added two events to my model, "contentChanging" and "contentChanged". The first is fired right before I update the model, the second right after. In my controller I subscribe to these events like this.
//dmp is google's diff_match_patch library
//rangy is a selection management library http://code.google.com/p/rangy/wiki/SelectionSaveRestoreModule
var selectionPatch;
var selection;
scope.model.on("contentChanging", function() {
var currentText = $("#doc").html();
selection = rangy.saveSelection();
var textWithSelection = $("#doc").html();
selectionPatch = dmp.patch_make(currentText, textWithSelection);
});
scope.model.on("contentChanged", function() {
scope.$apply();
var textAfterEdit = $("#doc").html();
$("#doc").html(dmp.patch_apply(selectionPatch, textAfterEdit)[0]);
rangy.restoreSelection(selection);
});
So basically, when the content is changing I grab the current html of the editable area. Then I use the rangy plugin which injects hidden dom elements into the document to mark the users current position and selection. I take the html without the hidden markers and the html with the markers and I make a patch using google's diff_match_patch library(dmp).
Once the content is changed, I invoke scope.$apply() to update the view. Then I get the new text from the view and apply the patch from earlier, which will add the hidden markers back to the html. Finally I use range to restore the selection.
The part I don't like is how I use jquery to get the current html from the view to build and apply my patches. It's going to make unit testing a little tricky and it just doesn't feel right. But given how the rangy library works, I can't think of another way to do it.
Here's a simple example of how you would start:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
function Ctrl($scope) {
$scope.myText = "Here's some text";
}
angular.module("myApp", []).directive('texteditor', function() {
return {
restrict: 'E',
replace: true,
template: '<textarea></textarea>',
scope: {
text: '=' // link the directives scopes `text` property
// to the expression inside the text attribute
},
link: function($scope, elem, attrs) {
elem.val($scope.text);
elem.bind('input', function() {
// When the user inputs text, Angular won't know about
// it since we're not using ng-model so we need to call
// $scope.$apply() to tell Angular run a digest cycle
$scope.$apply(function() {
$scope.text = elem.val();
});
});
}
};
});
</script>
</head>
<body>
<div ng-controller="Ctrl">
<texteditor text="myText"></texteditor>
<p>myText = {{myText}}</p>
</div>
</body>
</html>
It's just binding to a textarea, so you would replace that with your real text editor. The key is to listen to changes on the text in your text editor, and update the value on your scope so that the outside world know that the user changed the text inside the text editor.