I'm trying to understand how Angular data binding works. I have created this sample that displays {{values.count}} inside a span element:
<body ng-app="testApp" ng-controller="testCtrl as values">
<span>Count = {{values.count}}</span>
<button id="incr">++</button>
</body>
<script>
testApp = angular.module("testApp", []);
testApp.controller("testCtrl", function () {
var values = this;
values.count = 5;
$("#incr").on('click', function () {
values.count += 1;
});
});
</script>
If I click on the button, values.count is incremented, but the span is not updated. I can get it to work if I place a ng-click="values.Increment()" on the button and create a values.Increment method in the controller, but what is the difference that makes one work and the other not?
You are doing it the wrong way. When using angular, you don't need to bind event handlers/access DOM elements for this purpose. Use built-in ng-click directive.
<button ng-click="values.incr()">++</button>
and
values.incr = function(){
values.count++;
}
You could even do it inline, <button ng-click="values.count = values.count+1">++</button>
The reason why your code did not update DOM is because of the way angular works. Even though you are incrementing the value inside the manually bound event handler angular is unaware of the update and it does not update DOM binding with the new value. Though a bad practice in your specific case you can achieve that by doing $scope.$apply() (but ofcourse you need to inject $scope in your controller) as well inside the event handler to manually invoke the digest cycle. General thumb rule when working with angular is the controller do not access DOM directly, Instead it just sets up data for the view and with the help of bindings and other built-in or custom directives you get the intended behavior in your application.
Read about scope life cycle and digest cycle
Related
When my controller or directive reinisilize angular.element(document).ready(function(){...}); function always runs. Why?
I have two controllers and one directive. I change value from parent controller which assign to ng-repeat. Whenever I change this variable, controller reinsilize and ready function recall too.
See this link: angualr ready function
Your code is set up such that you are destroying and recreating things when you click the "change index" button:
that.changeIndex = function(){
that.arr = [{name : g++}];
that.isValid = !that.isValid;
}
ng-repeat destroys and recreates your myCon2 controllers when you create a new arr array:
<div ng-repeat="m in con.arr track by m.name" ng-controller="myCon2 as con1">
And ng-if destroys or recreates your my-dir directive when you change isValid:
<div my-dir ng-if="con.isValid">hey directive!!!!</div>
The controller and directive both attach a handler to document ready, which will run as long as the document is ready (not just as soon as it becomes ready). This makes sense because if your code loads after the document initially becomes ready, you'd usually still want it to run.
By the way, you shouldn't need to use document ready inside Angular controllers and directives. You can put your initialization code at the top of them, or you can use ng-init.
I've seen a javascript solution that goes a little something like this:
var select = document.getElementById('selectId')
select.click();
Is there an AngularJS approach/best practice to the same thing? (Off the top of my head, you'd wrap the above code in an ng-click)
Yes there is. Here's the angular equivalent of what you have in JavaScript
angular.element('#selectId').trigger('click');
Working example
Any DOM manipulation in angular should occur inside of a directive.
View
<div id="selectId" clickMe>content</div>
Inside of a directive the link function triggers after the view is compiled. The second parameter in the link function is the element which the directive is placed on, this gives performance benefits since there is no need to traverse the dom. It is a JQlite element which you can directly call methods on.
Directive
app.directive('click-me', function(){
return{
link(scope, el, attr){
$(el).trigger('click');
}
}
});
In my project, I have an <div> where I specifically apply my Knockout.js bindings. I have to instantiate different viewmodels in that area depending on what the user clicks.
To prevent getting a cannot call bindings twice on the same element error, I first have to "Clean" the bindings to make the area available again. I call the initial applyBindings() function:
ko.applyBindings(new ViewModel("Planet", "Earth"), document.getElementById("bindings-area"));
Eventually, I will clean the <div> and call the new bindings:
var element = $("#bindings-area")[0];
ko.cleanNode(element);
ko.applyBindings(new ViewModel("NEW", "Bindings"), document.getElementById("bindings-area"));
Problem: When I include an HTML button in the #bindings-area div, it will no longer work after I clean the bindings and instantiate the new model. I'm sure it has to do with the ko.cleanNode() function somehow removing the button bindings as well. How can I re-initiate them or prevent cleanNode() from operating on the button in the first place?
Fiddle: http://jsfiddle.net/jL6L01xs/2/
This issue is nicely described in Knockout documentation. This quote describes what the issue is and what needs to be done:
When removing an element, Knockout runs logic to clean up any data
associated with the element. As part of this logic, Knockout calls
jQuery’s cleanData method if jQuery is loaded in your page. In
advanced scenarios, you may want to prevent or customize how this data
is removed in your application. Knockout exposes a function,
ko.utils.domNodeDisposal.cleanExternalData(node), that can be
overridden to support custom logic. For example, to prevent cleanData
from being called, an empty function could be used to replace the
standard cleanExternalData implementation:
ko.utils.domNodeDisposal.cleanExternalData = function () {
// Do nothing. Now any jQuery data associated with elements will
// not be cleaned up when the elements are removed from the DOM.
};
Here is the updated jsFiddle.
In AngularJS I am currently passing the form object to a function:
<a ng-click="clearSearch(searchForm)">clear</a>
The goal of this function is to clear an input field (name = search).
I can access the search input field like this:
searchForm.search
What I need to do is something like:
searchForm.search.$element[0].focus();
However, I cannot see a way to access the form elements (in my contrived example $element)
No, in Angular you generally don't have access to a DOM element from a scope object (although you can get the reverse).
Angular has a pretty strict separation between Controllers and HTML elements, instead they use Directives to manage interaction with DOM elements.
To handle this you can write a directive that is responsible for triggering focus on an input element (in this example the formFocus directive). We can then send messages from the Controller to the Directive via the Angular scopes
Here is the example code:
JavaScript:
myModule.controller('myController', function($scope){
$scope.clearSearch = function(form){
// Your current behaviour for clearing the form goes here.
// ...
// Broadcast an event down to your child scopes.
// If anything is interested in your custom 'formFocus'
// event then it will listen and respond to the event.
$scope.$broadcast('formFocus');
};
})
.directive('formFocus', function(){
return {
link: function(scope, element, attr){
// Add our listener for the 'formFocus' event
// and write the behaviour to focus on the input element.
scope.$on('formFocus', function(){
element[0].focus();
});
}
};
});
HTML:
<form ng-controller="myController"
name="searchForm">
<!-- Add the 'form-focus' directive to the input. -->
<input type="text" name="search" form-focus>
<a ng-click="clearSearch(searchForm)">clear</a>
</form>
It might seem a bit odd but the separation of the DOM from the Controllers makes things really flexible. For example, your code doesn't need to care which input element will receive focus.
Here's a Fiddle showing it in action: http://jsfiddle.net/Sly_cardinal/bJuS5/1/
I ended up storing a local copy of the form element
// this = controller
this.searchInputField = $element.find('#search')[0];
Then later on I could just go:
this.searchInputField.focus();
This seemed cleaner than firing and listening to an event every time in this case.
I have an input like this :
<input ng-model="mysearch.myfield" id="myid"/>
that is bound to a filter
<table><tr ng-repeat="row in list|filter:mysearch">...</tr></table>
If I modify the input value in the GUI, it works perfectly, but if I try to modify its value via javascript/jquery
$("#myid").val("newvalue")
The input value is updated but the mysearch.myfield is not modified
Actually, I have a list that appears on user actions (it does not exist on page load):
<li onclick="changeTheInputValue('newvalue1')">newvalue1</li>
<li onclick="changeTheInputValue('newvalue2')">newvalue2</li>
...
with
function changeTheInputValue(v) {
$("#myid").val(v);
}
And it does not work when I click on an "li" (the input value is updated, ut the mysearch.myfield is not modified)
I also tried
<li ng-click="mysearch.myfield = 'newvalue1'">newvalue1</li>
<li ng-click="mysearch.myfield = 'newvalue2'">newvalue2</li>
...
but it does not work :(
Any idea ?
Thanks,
any javascript executing outside the angulars event loop won't be efective in angular untill you apply it. in order to do that you need to get the relevant scope and $apply the changes, since is not clear, how and why you are modifying the value outside the angular scope there is nothing much i can say except you could do something like
function changeTheInputValue(v) {
$("#myid").val(v);
angular.element($("#myid")).scope().$apply();
}
that should let angular know about the changes, how ever this is a bad design if using angular. there are far better ways to accomplish this same thing w/o having mixed execution scopes. (angular/the rest);
You are modifying the value of the input "#myid" using direct DOM manipulation. AngularJS is not aware of this. If you want both the html and the value of mysearch.myfield to update correctly, you must do so by modifying the mysearch.myfield property directly, either in a controller or via an ng-model binding or something similar.
The main reason this isn't working for you has to do with how AngularJS modifies the DOM. When you use jQuery to modify the DOM, you are circumventing angular. Angular has no way of knowing if you have changed something in the DOM except if you do it directly through Angular itself. In particular, if you are curious, read about the $compile and $digest services.
Hope this helps shed some light on the subject!