Binding stops working in input within ng-if - angularjs

For some reason binding doesn't work on an input within ng-if block in a directive
so this, doesn't work:
app.directive 'foo', ->
restrict: 'E'
scope:
type:'='
template: "<input ng-if=\"type === 'string'\" ng-model='filterText'>
<div> {{filterText}} </div>"
<foo type="'string'" />
it works fine outside of directive or without ng-if. Wrapping input inside of a div with ng-if not helping. Is it a bug?
jsbin link

It is caused by the ng-if introducing a new scope combined with the fact that you ng-model "has not dot in it".
This works:
template: "<div ng-init='holder={}'> <input ng-if=\"type === 'string'\" ng-model='holder.filterText'></div>
<div> {{holder.filterText}}</div>"
See the directive info at https://docs.angularjs.org/api/ng/directive/ngIf and notice the text "This directive creates new scope."
For the "dot-in-model", see for example
Does my ng-model really need to have a dot to avoid child $scope problems?
or
https://egghead.io/lessons/angularjs-the-dot
Basically when reading the value it will be read correctly traversing scope prototypes, but when modifying the value it will be written to the very own scope.

Since ng-if creates a new scope, you just need to do this
ng-model='$parent.filterText'
Also, please check this answer.

The <div> tag isn't closed in your example, nor is the ng-if applied to that node.
Try this:
template: "<input ng-model='filterText'>
<div ng-if=\"type === 'string'\"> {{filterText}}"</div>"

Related

Along with Transclude element, can i pass its scope too to a directive?

Moment i felt i have understood enough about Transclude i came across this statement :
Transclude allows us to pass in an entire template, including its scope, to a directive.
Doing so gives us the opportunity to pass in arbitrary content and arbitrary scope to a directive.
Does this mean, if there is a scope attached to Transclude element and it can be passed on to the directive ? If that's true then am not able to access that scope property inside directive template.
Let me take couple of steps back and explain with code about what am trying to do :
JSFiddle Link
My directive is directive-box and transclude: true is defined in Directive Definition Object(DDO).
Now there is a Child Div, which is the element to be Transcluded
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
and it has controller TransCtrl attached to it.
Now am trying to access $scope.name property which is part of TransCtrl from directive level after defining this in DDO :
scope: {
title: '#directiveTitle',
name: '='
}
Is this possible ?
This is more like a Parent scope trying to access Child scope property, is this permitted in JavaScript Protoypical inheritance ? Or is there something else i need to know ??
If this is not possible what does first statement mean ?
Transclude allows us to pass in an entire template, including its scope, to a directive.
UPDATE 1 :
My primary concern is Controller should remain with Transclude element, still we should be able to pass its (Transclude element) scope to Directive and then Directive should be able to consume that scope i.e., name from TransCtrl controller .
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
Above line of code should remain as is.
I may be completely wrong with my question but please let me if this can be accomplished.
The problem seems to be with the way the controller is defined within the ng-transcluded html.
I have made it clearer by using
the bindToController construct
using a controller at the directive level
Refer this fiddle for a working example.
controllerAs: "TransCtrl",
bindToController: true
And your statement, 'Parent scope trying to access Child scope property' is incorrect right? Since we are trying to use the parent scope property, i.e. name from within the child (ng-transcluded content), which is possible with protypical inheritance, and not the other way around.
Does this answer your question: https://jsfiddle.net/marssfa4/4/?
In it I have created a new controller on the outside (effectively replacing the functionality of your rootScope for inside the directive) and I made the directive's controller be set inside your controller template.
The long and short of it is though that you can see that it is possible to transclude html along with its scope even into a directive with its own scope.
The html:
<div ng-app='myApp' ng-controller="OutsideScope">
<h1>{{externalWorld}}</h1>
<div directive-box directive-title='{{directiveWorld}}' name='name'>
<div>Inside Transclude Scope : {{name}}</div>
</div>
</div>
JS (includes Update 1):
angular.module('myApp', [])
.directive('directiveBox', function() {
return {
restrict: 'EA',
scope: {
title: '#directiveTitle',
name: '='
},
transclude: true,
template: '<div ng-controller="TransCtrl">\
<h2 class="header">{{ title }}</h2>\
<div class="dirContent">Directive Element</div>\
<div>Outside Transclude Scope : {{name}}</div>\
<div class="content" ng-transclude></div>\
</div>'
}
})
.controller('TransCtrl', function($scope) {
$scope.name = 'Transclude World'
})
.controller('OutsideScope', function($scope) {
$scope.name = 'External World'
})
.run(function($rootScope) {
$rootScope.externalWorld = 'External World',
$rootScope.directiveWorld = 'Here comes directive'
});
UPDATE 1: JSFIDDLE
I restored the original scope declarations as the scope: false was a mistake.
If I understand your comment correctly you want to leave the controller on the element to be transcluded but still have the {{name}} within that element ignore its immediate controller and use as controller its parent (i.e. the directive's) scope.
The reason I placed the controller within the template directive is because that is the only way to limit the directive's scope on the directive and not its transcluded elements. If you are explicitly placing a controller on an element, then regardless of whether it is contained within a directive with another scope, its closest scope will override whatever scope has been declared on the directive. In other words, regardless of what the directive's scope is, the {{name}} in
<div ng-controller='TransCtrl'>Inside Transclude Scope : {{name}}</div>
will always be whatever $scope.name is in TransCtrl.

directive's scope inside ng-repeat angularjs

I'm trying to understand directive's scope inside ng-repeat, still wondering if it comes from ng-repeat but it looks like.
Here is my code
directive.js
myApp.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="name" />',
replace: true,
restrict: 'AE'
}
});
mainController.js
angular.controller('mainController',function($scope){
$scope.name = 'name'
});
index.htm
<div ng-repeat="i in [1, 2, 3, 4]">
<dir-sample></dir-sample>
</div>
<dir-sample></dir-sample>
<dir-sample></dir-sample>
When i make a change in one of the last two directives (which are not inside ng-repeat) it works well, changes on one are reflected on the other.
Problem :
1 - if i change an input value of a directive generated by ng-repeat , changes are not reflected anywhere else.
2 - if i change value of input on one of the two last directives , the directives inside ng-repeat change too, but if touch ( change input value ) of any directive , changes will not be reflected on that directive but will keep being reflected on the other directives.
Can someone please explain why the scope has that behavior ?
Thanks.
Binding primitives is tricky, as is explained here: Understanding scopes. It has to with how Javascript works. Your 'name' variable will get shadowed once it is altered within the ng-repeat block. The suggested fix (from the link above):
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
They also provide a link to a video explaining exactly this problem: AngularJS MTV Meetup
So a fix looks like this:
app.controller('mainController',function($scope){
$scope.attr= {}
$scope.attr.name = 'name'
});
app.directive('dirSample', function () {
return {
template: '<input type="text" ng-model="attr.name" />',
replace: true,
restrict: 'AE'
}
});

Accessing parent scope from isolated scope

I'm trying to write attribute directive that wraps element and decorates it with some other things along with additional directives. And I need to keep content inside the element intact. Something like this:
app.controller 'myCtrl', ($scope)->
$scope.name = "Gwendolyn"
app.directive 'niceName',($compile)->
restrict: 'A'
replace: yes
scope: niceName:"="
template: (element, attrs)->
"<div>
<div style='background:lightgray'>
#{element.html()}
</div>
<div>
<h1>{{nice}}</h1>
</div>
</div>"
controller: ($scope, $element)->
$scope.$watch 'hover',->
$scope.nice = if $scope.hover then $scope.niceName else ''
compile:(element, attrs)->
element.attr('ng-mouseover',"hover = true")
element.attr('ng-mouseout',"hover = false")
element.removeAttr('nice-name') # removing itself to avoid from falling into infinite loop
pre:(scope, iElement, iAttrs)->
$compile(iElement)(scope)
Markup:
<div nice-name="uglyname">
<h2>{{uglyname}}</h2>
<div>
Now that thing doesn't work at all, it won't render h2 part because now uglyname is unknown to the current scope. I can compile it with passing the parent scope, but then it totally breaks my controller. (see jsbin below)
So somehow I have to compile content of the element applying parent scope, add it to the template and then compile again applying local scope? Or I need to find a way to inherit properties of parent scope? Or I can't do it with attribute directive? Maybe I need to use element directive and transclusion?
Any ideas?
jsbin
i would do what #marfarma suggested as well as change you html to not include the brackets so:
scope: { niceName: "="},
and
nice-name="uglyname"

Angular directive: using ng-model within isolate scope

I'm having trouble working out how I can define a custom directive that both:
Uses isolate scope, and
Uses the ng-model directive in a new scope within in its template.
Here's an example:
HTML:
<body ng-app="app">
<div ng-controller="ctrl">
<dir model="foo.bar"></dir>
Outside directive: {{foo.bar}}
</div>
</body>
JS:
var app = angular.module('app',[])
.controller('ctrl', function($scope){
$scope.foo = { bar: 'baz' };
})
.directive('dir', function(){
return {
restrict: 'E',
scope: {
model: '='
},
template: '<div ng-if="true"><input type="text" ng-model="model" /><br/></div>'
}
});
The desired behaviour here is that the input's value is bound to the outer scope's foo.bar property, via the the directive's (isolate) scope model property. That doesn't happen, because the ng-if directive on the template's enclosing div creates a new scope, so it's that scope's model that gets updated, not the directive's scope's.
Ordinarily you solve these ng-model issues by making sure there's a dot in the expression, but I can't see any way to do that here. I wondered if I might be able to use something like this for my directive:
{
restrict: 'E',
scope: {
model: {
value: '=model'
}
},
template: '<div ng-if="true"><input type="text" ng-model="model.value" /><br/></div>'
}
but that doesn't work...
Plunker
You are right - ng-if creates a child scope which is causing a problem when text is entered in the input text field. It creates a shadow property named 'model' in child scope which is a copy of the parent scope variable with the same name - effectively breaking the two-way model binding.
The fix for this is simple. In your template, specify the $parent prefix:
template: '<div ng-if="true">
<input type="text" ng-model="$parent.model" /><br/>
</div>'
This ensures that it will resolve 'model' from the $parent scope, which you've already setup for two-way model binding through the isolated scope.
In the end, the '.' in ng-model saves the day. I find it useful to think about anything left of the dot as a way for Angular to resolve the property through scope inheritance. Without the dot, resolving the property only becomes an issue when we're assigning scope variables (otherwise, lookups are fine, including read-only {{model}} binding expressions).
ng-if creates an additional prototypally inheriting scope, so ng-model="model" binds to the inherited property of the new scope and not to the 2-way binded property of the directive scope.
Change it to ng-show and it will work.
You can use a small Firebug extension i've written to inspect angular scopes.

Adding attributes to HTML element in an AngularJS Directive

I am new to angular and am attempting to add validation to a directive based on attributes. Here is how I use the directive:
<div sc-textbox data-bind-to="fieldToBind" data-field-name="fieldName" data-required="someValue != 'Office'"></div>
The data-required attribute may or may not be present. The template for the directive is:
<input id="{{fieldName}}" name="{{fieldName}}" type="text" data-ng-model="bindTo" />
When the data-required attribute is present I would like to add data-ng-required="{{required}}" to the input. How would I go about doing this?
The scope for the directive is:
scope: {
'bindTo': '=',
'fieldName': '#',
'required': '='
}
Any help is greatly appreciated.
Why not just leave ng-required there anyway. This is what it's for.
Because you have limited scope, you need a boolean in your directive's scope that represents "someValue != 'Office'".
Or put someValue in scope and do this:
<input id="{{fieldName}}"
data-ng-required="(someValue != 'Office') && required"
name="{{fieldName}}" type="text"
data-ng-model="bindTo" />
Update
The attribute you want to add "data-ng-required" is a directive. You cannot dynamically add and remove directives from the DOM in Angular. If you really don't want "data-ng-required" in parts of your DOM, and you want this to be dynamic, the closest thing you can do is $compile and append this input as 2 templates-> one with required, and one without

Resources