I have an issue where a directive i'm using for several similar elements in an ng repeat is showing the wrong element when calling the keyup function.
plunker: http://plnkr.co/edit/ARFlsgPdxikpzLScztxU?p=preview
Here's the same code:
html
<body ng-app="app">
<section ng-controller="MainController" ng-repeat="item in list">
<div ng-repeat="item in list">
<h3>Item {{$index}}</h3>
<div class="aliasContainer">
<input text="text" obj-key="alias" value="{{item.alias}}" ng-keyup="logItem($event, item)">
</div>
<div class="nameContainer">
<input text="text" obj-key="name" value="{{item.name}}" ng-keyup="logItem($event, item)">
</div>
</div>
</section>
<script src="https://code.angularjs.org/1.2.25/angular.js"></script>
<script src="script.js"></script>
</body>
js
var app = angular.module('app', []);
app.controller('MainController', ['$scope', function($scope){
console.log("hello ctrl");
$scope.list = [
{name: 'Dick Grayson', alias: 'Nightwing'},
{name: 'Bruce Wayne', alias: 'Batman'},
{name: 'Jason Todd', alias: 'Robin'}
];
}]);
app.directive('objKey', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.logItem = function($event, item) {
console.log(element);
};
}
};
});
The behavior the link function is exhibiting is for each div that is repeated, only the the input in the nameContainer gets passed on keyup (logging the element will show the input in nameContainer of the same parent div even if the input in aliasContainer was the triggering element.)
To reuse your directive and keeping its scope separate from outer scope (controller), you need to have a isolate scope to your directive
app.directive('objKey', function() {
return {
restrict: 'A',
scope: true, // << Isolating scope
link: ....
}
};
You should look into directive's scope: https://docs.angularjs.org/guide/directive
If you don't isolate the scope, your directive's scope will be the same as the scope when it's declared, in this case it will use the child scope created by ng-repeat.
To fix this, just isolate the scope with scope: true
Related
Suppose the following blueprint code:
<div ng-controller="myCtrl">
<div ng-repeat="...">
<div ng-repeat="...">
<div ng-repeat="...">
<div ng=if="..." my-directive>
</div>
</div>
</div>
</div>
</div>
myApp.directive('myDirective', function() {
return {
controller: function($scope){
console.log('controller scope');
console.log($scope);
},
link:function(scope,element){
console.log('link scope');
console.log(scope);
}
}
});
Both outputs in console will point to the scope created by ng-if directive. My question is how may I access myCtrl's scope from inside the directive . Of course not by using $parent.$parent....
The easiest way could be by using require in the directive, like:
<div ng-controller="MyCtrl">
<div my-directive></div>
</div>
var myApp = angular.module("app", []);
myApp.controller("MyCtrl", function($scope) {
this.text = "I am in Controller Scope";
this.getValue = function() { return this.text; };
});
myApp.directive("myDirective", function() {
return {
require: "^ngController",
link: function(scope, elem, attrs, ngCtrl) {
elem.text(ngCtrl.getValue());
}
};
});
EDIT
In your case, I think you could use the controller scope variables and methods in the directive by using scope binding with &; snippet below:
<div ng-controller="MyCtrl as vm">
<my-directive on-get-value="vm.getValue()">
</my-directive>
</div>
angular.module('app', [])
.controller('MyCtrl', function($window) {
var vm = this;
vm.getValue = function() { $window.alert("I am in Controller Scope"); };
})
.directive('myDirective', function() {
return {
scope: {
onGetValue:'&'
},
controllerAs:'vm',
controller: function($scope) {
$scope.onGetValue();
}
};
});
Use services to share data between angular components. This question might be a good start: Share data between AngularJS controllers. This approach will work for sharing data between controller and directive as well
When you are creating your directive, the returning function is called DDO (Directive Defining Object). One of its attributes is 'scope'. if you initialize it with scope : true, the directive will prototypically inherit the parent scope. If you set scope: false, the directive will use the parent scope. And finally, if you set scope : {...}, it will created an isolated scope.
var app = angular.module("test",[]);
app.controller("myCntrl",function($scope){
$scope.text = "Im in controller Scope";
});
app.directive("myDirective", function(){
return {
restrict: "EA",
scope: true,
template: "<div>Where are you, directive ? {{text}}</div>"
};
});
h2 {
cursor: pointer;
}
.directive {
border: 5px solid #F5BF6E;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="myCntrl">
<h2 ng-click="reverseName()">Where are you ? {{text}}</h2>
<div my-directive class='directive'></div>
</div>
</div>
You can check this link for more details : Directive Scopes
Question
Why isn't monkey and selected visible to the template?
Plunk
http://plnkr.co/edit/djS0KWyfJNKD0tfZ0IiV?p=preview
Code
<head>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular.js"></script>
<script type="text/javascript">
angular
.module('toruSelect', [])
.directive('toruSelect', function () {
return {
restrict: 'AE', // Allow usage as A - attribute, E - element
scope: { // Isolated scope
selected: '=' // Bi-directional binding to selected attribute,
},
controller: ['$scope', function ($scope) {
$scope.monkey = 'MONKEY';
console.log('toruSelect.controller.$scope', $scope);
}]
}
});
var app = angular.module('app', ['toruSelect']);
app.controller('AppCtrl', function($scope) {
$scope.val = 'initial';
$scope.appData = 'App data';
});
</script>
</head>
<body ng-controller="AppCtrl">
<h1>Directives and scopes..</h1>
<div toru-select selected="val">
<div style="color: red">RESULT: toruSelect.controller.monkey: {{monkey}}</div>
<div>EXPECTED: toruSelect.controller.monkey: MONKEY</div>
<div style="color: red">RESULT: toruSelect.controller.selected: {{selected}}</div>
<div>EXPECTED: toruSelect.controller.selected: initial</div>
</div>
</body>
Result
Directives and scopes..
RESULT: toruSelect.controller.monkey:
EXPECTED: toruSelect.controller.monkey: MONKEY
RESULT: toruSelect.controller.selected:
EXPECTED: toruSelect.controller.selected: initial
As you pointed it out on the comment of your directive, it has an isolated scope, so that value attached with monkey key is available on directive scope, not on controller one.
For selected, you have to display {{val}} and not {{selected}} as it's the variable concerned by the bi-directional binding on the directive scope.
I created directive that appends transclude value after the directive body. I cant use ng-transclude inside my directive because it creates for the simple text as transclude value and it break my page. I use
controller: function($scope, $element, $transclude) {
$element.append($transclude().contents());
}
to append it. It works great, but when I use my directive inside ng-repeat something goes wrong and $transclude().contents() don't contain my text. Can someone explain this behavior?
Here example:
http://plnkr.co/edit/1y4avkwmgjhiKkuoZlug
I can't explain why $transclude().contents() don't contain your text, but I can show how to easy fix that please code below.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('test', function(){
return {
transclude: true,
restrict: 'E',
template: '<div >From directive </div> <div ng-transclude></div><hr/>',
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MainCtrl">
<div ng-repeat="a in [1, 2, 3, 4, 5]">
<test>From transclude {{a}}</test>
</div>
</body>
I am trying to create a template that shows some transcluded content. When I use ng-show everything works fine, but using ng-if or ng-switch gives me problems. I get this error message: Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found
I understand that ng-switch creates a new scope. But the transclude should still go up to the parent chain. Is this a defect in angularjs? See http://jsfiddle.net/HgvP7/
Here is my html, modified from the documentation example:
<div ng-app="docsTransclusionExample">
<div ng-controller="Ctrl">
<my-dialog>Check out the contents, {{name}}!</my-dialog>
</div>
<!-- my-dialog.html -->
<script type="text/ng-template" id="my-dialog.html">
<div ng-switch="1+1">
<div ng-switch-when="2">
<div ng-transclude></div>
</div>
</div>
</script>
</div>
And the code:
angular.module('docsTransclusionExample', [])
.controller('Ctrl', function($scope) {
$scope.name = 'Tobias';
})
.directive('myDialog', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'my-dialog.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
I think it should be easy to use the well known angular attributes on a directive out of the box.
For example if the name of my directive is myDirective I would like to use it this way:
<div ng-controller="myController">
<my-directive ng-click="doSomething()"><my-directive>
</div>
instead of needing to define a custom click attribute (onClick) as in the example below
<div ng-controller="myController">
<my-directive on-click="doSomething()"><my-directive>
</div>
It seems that ng-click can work, but then you need to specify ng-controller on the directive tag too which I don't want. I want to define the controller on a surrounding div
Is it possible to use ng-click on a directive together with a controller defined on a parent html element?
Here is updated code. Maybe is this what you were looking for.
Html:
<div data-ng-app="myApp">
<div data-ng-controller="MyController">
<my-directive data-ng-click="myFirstFunction('Hallo')"></my-directive>
<my-directive data-ng-click="mySecondFunction('Hi')"></my-directive>
</div>
</div>
Angular:
var app = angular.module('myApp', []);
app.directive('myDirective', function(){
return {
restrict: 'EA',
replace: true,
scope: {
eventHandler: '&ngClick'
},
template: '<div id="holder"><button data-ng-click="eventHandler()">Call own function</button></div>'
};
});
app.controller('MyController', ['$scope', function($scope) {
$scope.myFirstFunction = function(msg) {
alert(msg + '!!! first function call!');
};
$scope.mySecondFunction = function(msg) {
alert(msg + '!!! second function call!');
};
}]);
Edit
Check solution that I made in jsFiddler is that what you were looking for?
http://jsfiddle.net/migontech/3QRDt/1/