angularjs: directive breaks expression and data-ng-show? - angularjs

My html:
<div ng-app="myApp">
<div ng-controller="testCtrl">
<div in-tags text="{{ tags }}"></div>
<div data-ng-show="tags.length" in-tags text="{{ tags }}"></div>
<p data-ng-show="tags.length">another text</p>
</div>
</div>
And js:
.controller('testCtrl', function($scope){
$scope.tags = 'one two three';
})
.directive( 'inTags',function() {
return {
scope: {
text: '#'
},
template: '<span ng-repeat="item in text | splitByWords"> {{ item }} </span>'
};
})
.filter( 'splitByWords', function() {
return function( text ) {
return text.split( /\s+/ );
};
});
How it works: http://jsfiddle.net/3HT2F/12/
Question is: Why tags.length interpreted like false with directive?
extra question: How can i hide div?

For your primary question, the scope attribute on your directive (inTags) sets a new isolated scope with only one member (the connected text attribute). It's one of the stumbling blocks of Angular with nested scopes and isolated scopes. When you set a literal object for the scope and specify a mapping (such as this case with at the dom attribute binding using '#'), it creates an isolated scope that doesn't inherit any other values from its parent. So tags is no longer a member of the local scope on that element.
See the scope rules for directives
Second question, why wouldn't ngShow or ngHide work? If you're on a new enough Angular (1.2+), you can also use ngIf to complete remove elements vs just hiding them.
Edit: Here's your fiddle updated

Related

Angular JS : replace property in directive

In angular JS, we have a property called replace with possible values as true or false while defining directive. But i dont understand how this property will be used. Will it replace the HTML parent element when it is set true
Actual template:
<div class="parent">
<my-dir><div>Hello world!!</div></my-dir>
</div>
if replace is true, mir-dir tag will be removed.
<div class="parent">
<div>Hello world!!</div>
</div>
if replace is false, mir-dir tag will not be removed.
<div class="parent">
<my-dir><div>Hello world!!</div></my-dir>
</div>
Hope you understand!!. let me know if you have any queries.
Replace - If set to true will replace the element having a directive on it with a template.
PS : You have to use templateUrl/template along with replace.
HTML
<div angular></div>
<div class="angular"></div>
<angular>Simple angular directive</angular>
JS
var App = angular.module('App', []);
App.directive('angular', function() {
return {
restrict: 'ECMA',
replace: true,
template: '<img src="http://goo.gl/ceZGf"/>'
};
});
Above example angular directive will replace its contents "Simple angular directive" by contents in template i.e "Replaced content".
According to the documentation of angular (replace option):
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
Imagine you have a directive named my-directive with the template <span>directive</span> and your html code is <div my-directive></div>. Then replace : false results in:
<div my-directive><span class="replaced" my-directive="">directive</span></div>
And replace : true results in:
<span class="replaced" my-directive="">directive</span>
Please note that this option is deprecated.
See related questions:
How to use `replace` of directive definition?
Explain replace=true in Angular Directives (Deprecated)

how to have unique scope in ng-repeat in angularjs

Is it possible for me to have multiple array for $scope?
i have a list of div with child scopes that is generated from a parent scope in ng-repeat. How can i have the scope variable individually unique?
I am generating a list of ng-repeat in another ng-repeat.
<div ng-repeat="" ng-init="hide=true" ng-click="hide=!hide">
<div ng-hide="hide" ng-init="childhide=true" ng-click="childhide=!childhide">
<div ng-repeat="" ng-init="childhide" ng-hide="childhide">
<div>{{ variable }}</div>
</div>
</div>
</div>
How can i have the variable unique? Coz each time when i click on either one div, all div with childhide variable will show. Anyway to make them behave individually?
Thanks.
To get a new $scope for each div, the first ting that comes to mind is to create another directive and specify which type of scope you want.
<div class="container">
<div ng-repeat="item in items"></div>
</div>
will become:
<div class="container">
<inner-directive ng-repeat="item in items"></inner-directive>
</div>
then in inner-directive:
app.directive('innerDirective', function () {
return {
restrict: 'E',
template: '<div></div>', // this replaces what you had before
scope: {}
};
});
This will create an isolate scope, which does not inherit properties from it's parent.
There are a couple of other scope options but i cant remember off the top of my head what each one does. Easy to read in the docs though.

AngularJS : isolated scope + two-way binding + ng-repeat not working

the ng-repeat outputs nothing. in the link function, i can see $scope.clients is an array. if i remove the isolate scope, and use the parent scope, the ng-repeat works.
html with directive "clients".
<div container
ng-cloak
ng-app="summaryReportApp"
ng-controller="summaryReportController as summaryReport">
<fieldset clients="summaryReport.clients">
<legend>Clients</legend>
<div align="left">
<div ng-repeat="client in clients track by $index">
{{client}}
</div>
</div>
</fieldset>
</div>
directive
var clients = function(){
var definition = {
restrict: "A",
scope: {
clients:"=clients"
},
link: function($scope,$element,attributes){
}
}
return definition;
}
This is a common question I seem to answer frequently. Directives can have other HTML Elements nested in them, in the same way that an <input> can be nested inside a <div>. However, the Elements nested inside the Directive are not part of the directive, and are not scoped to the directive, they are scoped to the HTML they are in. The only items that have access to the Isolated Scope are the compile, link, controller, and template items in the directive definition. If you moved your inner html from inside the fieldset into a template, it would function as expected.
You can also reference http://angular-tips.com/blog/2014/03/transclusion-and-scopes/ for more examples and ways to test this.

Directive doesn't work when I which the version of Angular to 1.0.1 to 1.2.27

The following could be run in demo here.
this is html:
<div ng-controller="MyCtrl">
<h2>Parent Scope</h2>
<input ng-model="foo"> <i>// Update to see how parent scope interacts with component scope</i>
<br><br>
<!-- attribute-foo binds to a DOM attribute which is always
a string. That is why we are wrapping it in curly braces so
that it can be interpolated.
-->
<my-component attribute-foo="{{foo}}" binding-foo="foo"
isolated-expression-foo="updateFoo(newFoo)" >
<h2>Attribute</h2>
<div>
<strong>get:</strong> {{isolatedAttributeFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedAttributeFoo">
<i>// This does not update the parent scope.</i>
</div>
<h2>Binding</h2>
<div>
<strong>get:</strong> {{isolatedBindingFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedBindingFoo">
<i>// This does update the parent scope.</i>
</div>
<h2>Expression</h2>
<div>
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
<i>// And this calls a function on the parent scope.</i>
</div>
</my-component>
</div>
And this is js:
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.foo = 'Hello!';
$scope.updateFoo = function (newFoo) {
$scope.foo = newFoo;
}
}]);
This should be a good example for three kinds of scope binding in directives.However, it just doesn't work when I try to switch a higher angular version - (1.2.27). I suspect the shadow of the inherited scope within the directive, but I'm not sure of it.
This isn't going to work the way you expect. Isolated Scopes are created and provided to the Link, Compile, and Template portions of a Directive. However, the HTML within the Element itself is not actually part of the Directive. Those HTML portions are still bound to the parent $scope. If you have a tendancy to name your isolated scope objects the same, you may have just been working against the $scope unintentionally and not noticed any ill effect. If your HTML was in a Template rather than inside the Element, it would access the isolate scope.
As an example, in the HTML that is inline in the Element, you can call updateFoo(), but that would not be possible from inside a Template

Why does adding a show/hide feature break my AngularJS code?

Starting with the following working fiddle:
http://jsfiddle.net/77vXu/14/
I added a few changes to add a show/hide button
http://jsfiddle.net/77vXu/27/
var myApp = angular.module('myApp', []);
myApp.controller('test', function($scope) {
$scope.show = false;
$scope.cancelMessage = '';
$scope.clickTest = function(){
alert($scope.cancelMessage);
};
$scope.toggleShow = function(){
$scope.show = !$scope.show;
}
});
But this completely breaks the character counter. What have I done wrong?
From angularjs :Note that when an element is removed using ngIf its scope is destroyed and a new scope is created when the element is restored. The scope created within ngIf inherits from its parent scope using prototypal inheritance. An important implication of this is if ngModel is used within ngIf to bind to a javascript primitive defined in the parent scope. In this case any modifications made to the variable within the child scope will override (hide) the value in the parent scope.
Solution 1.
Please remove ng-if from textarea see here : http://jsfiddle.net/Tex3P/
<div ng-app="myApp">
<div ng-controller="test">
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<div ng-if="show">
<textarea ng-model="cancelMessage" ></textarea>
<span > {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()" ng-if="show">clickTest</button>
</div>
</div>
</div>
Solution 2.
Define cancelMessage as a object. http://jsfiddle.net/cnre6/
<div ng-app="myApp">
<div ng-controller="test">
<p>f{{cancelMessage}}</p>
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<textarea ng-model="cancelMessage" ng-if="show"></textarea>
<span ng-if="show"> {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()" ng-if="show">clickTest</button>
</div>
</div>
var myApp = angular.module('myApp', []);
myApp.controller('test', function ($scope) {
$scope.show = false;
$scope.cancelMessage = {};
$scope.clickTest = function () {
alert($scope.cancelMessage);
};
$scope.toggleShow = function () {
$scope.show = !$scope.show;
}
});
The reason it does not work is because of the way scope variables behave when they're assigned within a child scope and your model does not have a '.' in it. ng-if creates a child scope and since your ng-model does not have a '.' in it it will assign a scope variable named 'cancelMessage' in the child scope that shadows the scope variable in the 'test' controller's scope with the same name - effectively breaking two-way model binding as soon as text is entered in the textarea.
To fix this, you should have a '.' in your ng-model:
<textarea ng-model="cancelMessage.test" ng-if="show"></textarea>
By having a '.', angular will resolve what's left of the dot first, and will find the reference defined in the 'test' controller. It then binds the 'test' property of the 'cancelMessage' model.
The important point is, binding is resolving to the same model (the model which is defined on the 'test' controller's scope.
Infamous Dot in ng-Model (by Design)
Demo Plunker
If you refer to AngularJS documentation on ng-if, it says
"The ngIf directive removes or recreates a portion of the DOM tree based on an {expression}. If the expression assigned to ngIf evaluates to a false value then the element is removed from the DOM, otherwise a clone of the element is reinserted into the DOM." (https://docs.angularjs.org/api/ng/directive/ngIf)
One thing you can do is hide/show it instead of deleting it from DOM using ng-show or ng-hide
I demonstrate this in this fiddle : http://jsfiddle.net/lookman/0rfz6d1v/
<div ng-app="myApp">
<div ng-controller="test">
<button ng-if="!show" ng-click="toggleShow()">show me</button>
<div ng-show="show">
<textarea ng-model="cancelMessage" ></textarea>
<span > {{100 - cancelMessage.length}} characters remaining</span>
<button ng-click="clickTest()">clickTest</button>
</div>
</div>
</div>

Resources