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

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

Related

Need to call parent function from nested directive angularjs

Below is my flow of application. I wanted to access the parent controller function from the child directive
<div ng-controller="ParentController">
<first-directive></fist-directive>
</div>
in First Directive another directives loading
<div ng-controller="FirstController">
<second-directive></second-directive>
</div>
from second-directive calling parent function
<div ng-controller="SecondController">
<a ng-click="ParentFunction(2342)"></a>
</div>
ParentFunction() is available in Parent Controller. I wanted to call the function from second-directive.
ParentController-->FirstDirective-->SecondDirective
How to call the parent function from SecondDirective for my scenario?
Make the first directive and second directive to inherit the parent scope(don't define scope property for them). If they are isolated scope then they can not access parent scope.
Then in the second directive do this -
<div ng-controller="SecondController">
<a ng-click="$parent.$parent.ParentFunction(2342)"></a>
</div>
EDIT:
If the directives have isolated scope as OP has commented here, then you have to pass the function to directive, so your code becomes like this
<div ng-controller="ParentController">
<first-directive function-to-call="parentFunction()"></fist-directive>
</div>
<div ng-controller="FirstController">
<second-directive function-to-call="functionToCall()"></fist-directive>
</div>
<div ng-controller="SecondController">
<a ng-click="functionToCall(2342)"></a>
</div>
And the in the directive you have to have some values to scope like this
first Directive
scope: {
functionToCall: '&'
}
Second Directive
scope: {
functionToCall: '&'
}

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.

Angular JS scope not updating from included template

So I have below index.html:
<div ng-controller="UsersController">
<div ng-include='"assets/users/partials/template.html"'></div>
<a ng-click="get_data()">Get</a>
</div>
Template.html:
<input type="text" ng-model="SearchUser" name="SearchUser" />
My controller:
app.controller('UsersController', ['$scope', function($scope) {
$scope.get_data = function(){ console.log($scope.SearchUser); };
}
]);
So in above case on the click anchor, I am getting undefined in the $scope.SearchUser scope value.
But if I take that input out of the template and put inside main HTML it works.
I checked for multiple controller declaration and other stuffs but nothing worked for me.
I am using angular 1.2.25 version.
ng-include defines its own scope, which inherits from the controller scope. So SearchUser is set, but as an attribute of the child scope.
As always, the solution is to have a dot in your ng-model, and to define the outer object in the controller scope:
$scope.state = {};
and, in the HTML:
<input type="text" ng-model="state.SearchUser" name="SearchUser" />
That way, angular will get the state field from the child scope. Since the child scope prototypically extends the controller scope, it will find it in the controller scope, and it will write the SearchUser attribute of the state object.

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>

Angular directive transclude create new scope?

I am trying to create directive component for modal windows, which take care about modal behaviour such as opening, closing, taking care of zIndex etc.
Content of modal components is controlled by Controller.
So far idea is good, but when i try to have
<modal ng-controller="MyController">
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
It failed and does not render {{value}}
I have to wrap modal into controller
<div ng-controller="MyController">
<modal>
Some content which will be transcluded with dynamic {{value}} from MyController
</modal>
</div>
Is there any way, how to make first example works, or it is impossible and why angular do it that way?
There is full example with plunker at the end.
var app = angular.module('plunker', []);
app.directive("modal", function() {
return {
restrict:'E',
replace:true,
transclude:true,
scope: false,
template:'<div class="modal">Modal scope {{$id}}<div ng-transclude></div></div>',
link: function($scope) {
console.log("directive scope ", $scope.$id)
}
}
})
app.controller('DetailControl', function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
});
and this HTML
<body>
main scope {{$id}}
Controller on same element as modal<br>
<modal ng-controller="DetailControl">
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
Controller outside modal
<div ng-controller="DetailControl">
Controller scope {{$id}}
<modal>
<div>
content scope (transclude) {{$id}}<br>
Some content of modal window. The name is {{name || '-unknown-'}}
</div>
</modal>
</div>
<body>
here is plunker http://plnkr.co/edit/WOgZKB3e0bQUASMhFVOp?p=preview
The issue is the ngController directive creates it's own scope. When you do <modal ng-controller="MyController"> the ngController scope is a sibling to modal so modal can't see over (sideways in a sense) into that controller.
It works when ngController is a parent because you're using scope: false which causes your directive to inherit it's scope from its parent.
Rather than use a separate ngController directive you can attach a controller to your directive:
app.directive("modal", function() {
return {
controller: function($scope, $location) {
console.log("controller scope ", $scope.$id)
$scope.name = 'World';
}
}
This approach will give your directive good encapsulation as it no longer will depend on an external controller- which is good. One plus is you no longer need to coordinate multiple scopes.
If you need multiple directives to communicate you can use require to allow multiple directives to all share access to one parent directive's controller. This is the approach Angular internally takes (for instance in 'ng-switch`)
try use transclude with passing scope :
http://docs.angularjs.org/api/ng.$compile
transcludeFn -> scope
"transcludeFn - A transclude linking function pre-bound to the correct transclusion scope. The scope can be overridden by an optional first argument. This is the same as the $transclude parameter of directive controllers. function([scope], cloneLinkingFn)."

Resources