Two way databinding not working on directive - angularjs

I have a directive and I want to change the ng-model value given with this directive...
I'm setting scope: {ngModel: '='} and I'm changing the ngModel value (on click event) inside my directive but I can't see changes on my external/original object.
This plunker shows the problem...

There are a few things wrong here, all of them common mistakes.
Event handlers registered through jQuery using $(...).on(...) will be executed outside of angular context, so angular will not know when things have updated. To address this, you must wrap the contents in a scope.$apply call like so
$('#aaa').on('click', function() {
_scope.$apply(function(){
_scope.ngModel = 'Other Value';
updateTemplate();
});
});
This will update the binding to the input with ng-model. In fact you can avoid having to do this by using the ng-click directive.
With angular, you do not need to update templates like this yourself using .html(...). Binding is one of the major features of the framework. Instead of having the update function, you can use interpolation by putting an expression inside of {{ ... }} and your DOM will be updated when your model is. For example when defining the directive you can use
template: '<div id="aaa">{{ngModel}}</div>'
to set your template and {{ngModel}} will show the current value of ngModel.
ngModel is not just any attribute, it is a powerful directive. If you need your own directive to be able to declare the current model valid or invalid, or to interact with forms then you should use this through the require property on your controller (see here).
If you don't need those features then you should be calling your attribute something different to avoid conflict.
I have updated the plunker to include these points.

Related

Angular multiple directives error on md-datepicker

I'm making a dinamic list of dates. The user can adds all datepickers he wants, but I have to validate that there are not matching dates, all of them have to be different, that's the only requisite.
I've made a custom directive validation and it's triggered correctly, but when I try to use its isolate scope, I just get that error (Multiple directives). Other questions/solutions that I've seen here, propose to delete the isolate scope, but I need it to pass to the directive the array of dates and to be able to compare them with the current selected.
Here is a codepen that reproduces the problem. If you remove the noMatchingDates directive's scope, the error just disappears and you can see and add datepickers properly. I mean this scope:
scope: {
getAllDates: "&allDates"
}
I think that it has to do with this line in docs:
Multiple directives requesting isolated scope.
And it probably also has to do with the md-datepicker which would have more directives using the isolate scope. So, how can I solve this error (and still being able to send the dates list)?
If it can't be solved (keeping the scope) given the nature of the md-datepicker, how can I reach this dynamyc validation? I think it could be done using a controller and the ng-change, but I'm not sure if it would be a proper solution.
Indeed there is no reason for your directive to require an isolated scope. Use isolated scope when your directive is like a reusable "visual component". Your directive is about logic validation and shouldn't prevent another such component.
To fix your problem, you can remove the isolated scope and use your directive in the HTML this way:
<div ... no-matching-dates="overtimeList">
Then in your link function, you can retrieve the value of that attribute this way:
var dates = scope.$parse(attr.noMatchingDates);
This will give you the content of what is bound to no-matching-dates, so in this case it will return overtimeList.
I have never used the ctrl.$parsers.unshift syntax, but it seems that you can also use it to retrieve that value. Simply remove the scope.$parse line that I just gave you and write:
ctrl.$parsers.unshift(function(arrayOfDates) { ... })
This should work as well. Note that in the first approach you need to $watch for changes if you want to run the validation every time.

angularjs directive change scope property value

I'm writing an angularjs directive, and receive properties from html attributes.
Then I created an isolate scope for my directive, and modified the value of the scope properties, but the directive template does not show the modified value.
I created a demo in plunker.
Can anyone help me changing to template value 'test' to 'changeit'?
Let me describe my questions more detail:
I may have a directive and in the html like: , the 'top' is just a simple string, not any ngModel, so I use '#' to receive it in directive, but when I modify it in link, the template model value not change, then I try '=', as it is not an ngModel, so the received value is undefined, but can be dynamic changed in template {{}}.
You are using attribute based linking that is one way. For two way binding use = in your directive definition. Something like:
scope: {
title: '=title'
}
Update: I got your question now. The issue here is that, the # based binding keeps the parent scope and the isolated scope in sync. So even if you change the bound variable in the directive, it will be reverted back to the original value. You can verify this by adding a $timeout to the second console log
$timeout(function(){
console.log(scope.title);
})
the value of title is reverted back.
The option you have is to copy the title value into a new variable in the link function and bind that function to your template.

Angularjs directive: Passing a model reference to an isolated scope

I 'm trying to write a collapsible, reusable calculator directive, that binds to an input field (in the parent scope). This input field itself has a ngModel binding.
When the user presses the equals-button of my directive this parent scope model should be updated. I need to isolate the scope so I can reuse it:
Here is the simplified code and how I would like to use it:
http://plnkr.co/edit/OSOcxydJWh8K520nstAU?p=preview
I tried passing in the values as an attribute. but that does not work because I don't know how to update this attribute inside of the controller(I tried the $attrs service).
So how can I update the model from the directive?
Maybe you're overthinking it, maybe I'm underthinking it. Either way, here's all I did to change yours to make it work:
if ($scope.operator ==='+') {
$scope.field = parseInt($scope.field) + $scope.operand;
}
I uncommented your scope and then I made sure that your controller made reference to the data you had exposed in your scope. That's it.
And here's a working version of your Plunker: http://plnkr.co/edit/btBi3E
You need to use ngModelController. Here's a link with docs, with a handy example:
NgModelController

Angular - listening to binding changes in directive executed by controller

I'm attempting to watch a custom directive attribute value inside the directive. This value is a variable binding from a controller. The variable is a boolean and is updated via an action in the controller.
I can see that I'm updating this value in the controller action correctly through console.logs but I cannot seem to get the directive to watch for changes to this value. As I said, this value is the value of a custom directive: auto-focus="{{isFocused}}"
I've created a simple plunker to show my problem, any help would be great.
Angular - listening to binding changes in directive executed by controller
http://plnkr.co/edit/QwwFCQPN7L7nwuthH0CJ?p=preview
In order to watch for changes in an attribute that has an interpolation you'll need to use $attrib.$observe instead of $scope.$watch. This is described in the angularjs documentation: http://docs.angularjs.org/api/ng/service/$compile#Attributes
Also, you're comparing focusVal to true, but you're passing it as a string through the attribute. Here's an updated plunker: http://plnkr.co/edit/e8ZaBM?p=preview
In the focus-on directive, you have:
focus-on="{{isFocused}}"
This makes your directive actually watch what isFocus contains - which is "false"
Change it to:
focus-on="ifFocused"
Then your code works fine.

Controller scope object changes not reflected in directive $watch callback

I have a controller that has an object on its scope: $scope.objToTrack.
I have a directive that is inside a nested view that $watches for changes to that object.
It has isolate scope, but objToTrack is set as = so that it can be watched.
When I click the directive, it calls an expression that is a method on the controller which changes objToTrack.
Here's a plunker to illustrate my setup.
The problem is that objToTrack $watch callback isn't fired, although the object is changed.
If you switch between Test1 and Test2 states, changes made to objToTrack are visible. It's just that I don't understand why it doesn't work right away on click.
Thanks.
To answer question...if you bind your own event handlers to an element, and change angular scope within that event handler you need to call $apply so angular is made aware of the change and can run a digest
Example You have:
element.on('click',function(){
scope.onClick({number:RNG.int(200,300)});
});
Would need to be changed to:
element.on('click',function(){
scope.$apply(function(){
scope.onClick({number:RNG.int(200,300)});
});
});
It is a lot simpler if you use event directives already provided by angular. In this case you are writing considerable amount of extra code vs using ng-click. It also makes testing a lot easier when you stay within angular as much as possible
Also, if you want to pass an object into your directive you should not use curly braces.
In html, use obj-to-track="objToTrack", instead of obj-to-track="{{objToTrack}}".
Like this:
<div simple-directive obj-to-track="objToTrack" class="directive"></div>
And in directive.js: use '=' for bi-directional binding of the objToTrack.
Like this:
scope:{
objToTrack:'='
}
In your "test*.html" files, replace "on-click" by "ng-click".
"on-click" doesn't look in your current controller, "ng-click" does.

Resources