Why is this code broken in AngularJS 1.2? - angularjs

Goodevening,
The following code seems to work in older versions of Angular, but not in the 1.2 version. I am not sure why that is. What happens in Angular 1.2, is that the $watch directive does not get hit. It does not execute. It does execute in Angular 1.0.1.
Any idea why?
HTML
<fieldset validatedmarker>
<legend>User</legend>
<input type="text" name="name" class="form-control" ng-model="name" required>
</fieldset>
And the JS.
var APP = angular.module('myApp', []);
APP.directive('validatedmarker', function() {
return {
restrict: 'AE',
scope: { },
link: function(scope,e,a) {
scope.$watch(function() {
return scope.name;
}, function(newValue, oldValue) {
console.log("change detected: " + newValue)
});
}
};
});
Working example (Angular 1.0.1)
http://jsfiddle.net/2w8xW/
Non working example (Angular 1.2.0)
http://jsfiddle.net/24P86/
Thanks!

The most probable reason for this is the treatment of isolated scopes, that has changed in AngularJS 1.2.0
In earlier version everything inside the directive element got the isolated scope, but now the elements inside the directive get the original parent scope. So when you are setting the value of scope.name you are setting on parent scope. The watch is on child scope.
See this SO answer AngularJS Scope differences between 1.0.x and 1.2.x and the changelog.
To make your example work you would have to create a template for your directive and set transclude: true in directive definition.

the main reason why this is hapening is probably due to the fact that you are creating an isolated scope over an already liked html since you are not providing a template but rather using the existing html wich is most likely to be linked to the parent scope so you should take your directives innerhtml and use it as a template for your directive
like
APP.directive('validatedmarker', [function() {
return {
restrict: 'AE',
scope: { },
template:'<fieldset><legend>User</legend><input type="text" name="name" class="form-control" ng-model="name" required="true"/>',
replace:true,
link: function(scope,e,a) {
scope.$watch(function() {
return scope.name;
}, function(newValue, oldValue) {
console.log("change detected: " + newValue)
});
}
};
}]);
<div validatedmarker></div>
you can see it here
http://jsfiddle.net/24P86/1/

You are specifying an isolated scope, but with no attributes inside. You can either add attributes inside the scope property of your directive, or remove it altogether if you don't need to isolate your directive's scope.
More info on the attributes can be found here (search on 'isolate' scope):
https://docs.angularjs.org/api/ng/service/$compile

Related

AngularJS $watch controller variable from a directive with scope

From the directive, I want to track changes to a controller variable using $watch.
I have created this jsfiddle. (https://jsfiddle.net/hqz1seqw/7/)
When the page loads, the controller and both directives $watch function gets called but when I change the radio buttons, only the controllers and dir-two $watch function gets called. Why isnt dir-ones $watch function being called?
I want both the directives $watch to fire however, I can only get one of them to (i.e. dir-two). Not sure what I need to change. Does it have something to do with isolated scope? Is there a better way of doing this?
AngularJS Code:
var mod = angular.module("myApp", []);
//Controller
mod.controller("myCtrl", function($scope){
$scope.tempformat = "C";
$scope.one="25 - dir-one";
$scope.$watch('tempformat', function(nv){
alert("nv from controller");
});
$scope.two="35 - dir-two";
});
//dir-one directive
mod.directive("dirOne", function(){
return{
restrict: 'E',
template: "<p>{{info}}</p>",
scope: {info: '='
},
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-one");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}});
//dir-two directive
mod.directive("dirTwo", function($window){
return{
restrict: "EA",
template: "<p></p>",
link: function (scope, element, attr) {
scope.$watch('tempformat', function(nv){
alert("nv from directive-two");
if(scope.tempformat === "C"){
element.find("p").append("C");
}
else if(scope.tempformat === "F"){
element.find("p").append("F");
}
});
}
}
});
HTML Code:
<div ng-app="myApp" ng-controller="myCtrl">
<h2>Temperature</h2>
<input type="radio" ng-model="tempformat" value="C"/> Celcius
<input type="radio" ng-model="tempformat" value="F"/> Farenheit
<dir-one info="one"></dir-one>
<dir-two info="two"></dir-two>
</div>
Does it have something to do with isolated scope?
The problem is the fact that dir-one separates its scope from the parent. There are some alternatives that can be done in this situation such as:
scope.$watch('$parent.tempformat', function(nv){ //...
which will look to the parent for the specified content.
Another alternative is to bind to the directive itself:
scope: {
info: '=',
tempformat: '='
},
and then in the html:
<dir-one info="one" tempformat="tempformat"></dir-one>
see: the documentation for more information. Particularly the Isolating the Scope of a Directive area.
Is there a better way of doing this?
In general isolate scopes help construct reusable components (as noted in the documentation) so if this is something that is being attempted (from the content noted in the answer) then I would support something along the lines of the second option where you can specify that watch content on the directive itself and consider that the "better" way of doing this.
From my experience, and this is solely my own preference, I would bind it to the directive since I usually isolate my scope(s) for a reason.

Use ng-model with a primitive value inside ng-transclude

In a legacy project, I want to create a new directive that uses transclude.
A trimmed down version of the directive code is:
app.directive('controlWrap', function() {
return {
restrict: 'E',
transclude: true,
scope: { label: "#" },
templateUrl: "control-wrap-template.html"
}
})
And the template is:
<div>
<label>{{label}}</label>
<div>
<ng-transclude></ng-transclude>
</div>
</div>
This directive is used like this
<control-wrap label="Just a example">
<input type="text" ng-model="input" />
</control-wrap>
Test: {{input}}
I know that the workaround is to use a object in the scope instead of primitive value (ng-model inside ng-transclude). But that is no option for me. It is a ugly, poorly coded, legacy code that relies in those attributes directly on the scope.
Is there a something I can do in the directive to make that html works without change?
You can manually transclude (instead of using ng-transclude) and apply whatever scope (which is, in your case, scope.$parent) you need to the transcluded content:
transclude: true,
scope: { label: "#" },
template: '<div>\
<label>{{label}}</label>\
<placeholder></placeholder>\
</div>',
link: function(scope, element, attrs, ctrls, transclude){
transclude(scope.$parent, function(clone){
element.find("placeholder").replaceWith(clone);
});
}
Demo
The cleanest solution is to do some refactoring and passing an object instead of a primitive value, but if for some reason you cannot do that, you're not out of the options.
However, I wouldn't recommend any of these options
1) Bind input from the parent scope, that prevents creating a new value on the child scope upon write - butt keep in mind that accessing the parent scope hurts reusability of your directive.
Angular 1.2:
<input type="text" ng-model="$parent.input" />
Angular 1.3:
<input type="text" ng-model="$parent.$parent.input" />
(The difference is because the parent of the transcluded scope is the directive scope from 1.3)
2) Create some kind of wrapper object and pass that instead of the primitive value
$scope.inputWrapper = {};
Object.defineProperty($scope.inputWrapper, 'input', {
get: function() { return $scope.input },
set: function(newValue) { $scope.input = newValue; }
})
and pass this to the directive. But again, I would do some refactoring instead.

angularjs model view update through angular directive

I am trying to update my view using model in angular directive here is the directive I have created
app.directive('aboutOptions', [function() {
return {
restrict: 'C',
scope: {
testing: '='
},
transclude: true,
link: function(scope, elem, attr) {
scope.$watch(attr.ngModel, function() {
console.log(scope.$eval(attr.ngModel));
scope.testing = scope.$eval(attr.ngModel);
});
}
}
}]);
here is html model and file name is ab.html
<input type="text" class="about-options" ng-model="testing" />
Here is the view to be updated and file name is show.html
<h2 class="about-options">{{testing}}</h2>
ab.html file will be loaded as a template inside jquery ui dialog and my show.html file is in main page
If I remove
scope: {
testing: '='
},
Console is showing what I am typing
Update - 1
Tried with the following changes
testing: '=test'
And in html
<input type="text" class="about-options" ng-model="testing" />
<h2 class="about-options">{{test}}</h2>
What your doing at the end there with the scope is isolating the scope, so if an attribute called testing isn't found on the element where that directive is used you should be seeing an error in the console. If you desire an isolate scope (a good idea a lot of the time) then you'll need to provide the testing="someScopeVar" as an attribute of the element the directive is applied to.
Take a look at this repo to see how I'm doing it.
https://github.com/jedininjaster/angular-mask-money/blob/master/demo/js/angular.maskMoney.js
also take a look at the angular-ui mask directive. That is how I built my directive.
https://github.com/angular-ui/ui-utils/blob/master/modules/mask/mask.js
I apologize I don't have time right now to write a full explanation. Will try and update tomorrow

AngularJS directive with callback in scope prevents other directives from working

I've created a directive with an isolated scope:
directive("myDirective",function() {
return {
restrict: "A",
scope: {aaa: '='},
link: function(scope, elem) { }
};
})
The directive works well, but it prevents all other dierctives on the same element from working...
I've reproduced the problem on this jsfiddle: http://jsfiddle.net/ironshay/k5ndr/
I'm using angular 1.0.7.
Am I doing something wrong? or is it some kind of an angular bug?
The issue is that you've created a child scope by using scope: {} on the directive. So the foo variable isn't on that scope anymore, it's on the parent. By using $parent.foo everything works correctly:
<input type="text" placeholder="with directive" ng-disabled="!$parent.foo || $parent.foo == ''" my-directive func="myFunc()" />
jsfiddle: http://jsfiddle.net/k5ndr/2/
You are creating isolated scope, which i think is causing the other directives also to get the isolated scope.
You can try to pass the foo variable to the isolated scope as you did the myFunc function.
<input type="text" placeholder="with directive" foo='foo' ng-disabled="!foo || foo == ''" my-directive func="myFunc()" />
I tried to create a fiddle for the same
http://jsfiddle.net/njXjj/

How can I change the scope of my element from an attribute directive?

<input type='text' ng-model='foo' my-dir='customFunction' />
{{foo}}
.directive('myDir',function(){
scope:{ customFunc : "&myDir"},
});
Now the scope will be overridden with myDir and foo won't be updated on the screen. But still each control that has my-dir attribute should have customFunction in an isolated scope.
Is it possible?
As mentioned in the comments above, one directive probably won't work everywhere. If the directive will be used with other directives like ng-model, ng-repeat, etc., an isolate scope probably won't work. Here's an example of a directive that uses $eval, and does not create a new scope:
<div ng-controller="MyCtrl">
<input type='text' ng-model='foo' my-dir='customFunction'>
<br>foo={{foo}}
</div>
app.directive('myDir', function() {
return {
link: function(scope, element, attrs) {
scope.$eval(attrs.myDir);
},
}
});
function MyCtrl($scope) {
$scope.customFunction = alert('hi');
$scope.foo = '22';
}
Fiddle
See also When writing a directive in AngularJS, how do I decide if I need no new scope, a new child scope, or a new isolated scope?

Resources