I am having trouble getting a click event in a directive with an isolated scope to work using "controller as" syntax in Angular 1.3 the code for the directive is as follows:
myDirectives.directive('gsphotosquare', dirfxn);
function dirfxn() {
var directive = {
replace: false,
scope: {
photoInfo: '=',
photoBatchNum: '=',
thumbnailwidth: '='
},
restrict: 'EA',
controller: Ctrller,
controllerAs: 'ctrl',
template: '<div ng-click="ctrl.squareClicked()">test</div>',
//templateUrl: 'views/directives/gsphotosquare.html',
bindToController: true, // because the scope is isolated
link: linkFunc //adding this didn't help
};
return directive;
}
function Ctrller() {
var vm = this;
vm.squareClicked = function () {
alert('inside clickhandler for gsphotosquare directive');
};
}
function linkFunc(scope, el, attr, ctrl) {
el.bind('click', function () {
alert('inside clickhandler for gsphotosquare directive');
});
}
And here is how the directive is used in the DOM:
<span class="input-label">General Site Photos</span>
<div class=" item row">
<gsphotosquare photo-info="mt.photos.v1f1[0]" photo-batch-num="mt.photoBatchNum" ></gsphotosquare>
<gsphotosquare photo-info="mt.photos.v1f1[1]" photo-batch-num="mt.photoBatchNum" ></gsphotosquare>
<gsphotosquare photo-info="mt.photos.v1f1[2]" photo-batch-num="mt.photoBatchNum" ></gsphotosquare>
<gsphotosquare photo-info="mt.photos.v1f1[3]" photo-batch-num="mt.photoBatchNum" ></gsphotosquare>
</div>
Any ideas why clicking on the rendered directive doesn't show the alert?
Try defining your controller a little differently:
myDirectives.controller('Ctrller', Ctrller);
then in your directive:
controller: 'Ctrller as ctrl',
Related
I am trying to figure out how to pass a transclusion down through nested directives and bind to data in the inner-most directive. Think of it like a list type control where you bind it to a list of data and the transclusion is the template you want to use to display the data. Here's a basic example bound to just a single value (here's a plunk for it).
html
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data"><div>{{ source.name }}</div></outer>
</body>
javascript
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
ctrl.welcomeMessage = 'Welcome to Angular';
}])
.directive('outer', function(){
return {
restrict: 'E',
transclude: true,
scope: {
model: '='
},
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template :'<div class="inner" my-transclude></div>'
};
})
.directive('myTransclude', function() {
return {
restrict: 'A',
transclude: 'element',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(clone) {
element.after(clone);
})
}
}
});
As you can see, the transcluded bit doesn't appear. Any thoughts?
In this case you don't have to use a custom transclude directive or any trick. The problem I found with your code is that transclude is being compiled to the parent scope by default. So, you can fix that by implementing the compile phase of your directive (this happens before the link phase). The implementation would look like the code below:
app.directive('inner', function () {
return {
restrict: 'E',
transclude: true,
scope: {
source: '=myData'
},
template: '<div class="inner" ng-transclude></div>',
compile: function (tElem, tAttrs, transclude) {
return function (scope, elem, attrs) { // link
transclude(scope, function (clone) {
elem.children('.inner').append(clone);
});
};
}
};
});
By doing this, you are forcing your directive to transclude for its isolated scope.
Thanks to Zach's answer, I found a different way to solve my issue. I've now put the template in a separate file and passed it's url down through the scopes and then inserting it with ng-include. Here's a Plunk of the solution.
html:
<body ng-app="myApp" ng-controller="AppCtrl as app">
<outer model="app.data" row-template-url="template.html"></outer>
</body>
template:
<div>{{ source.name }}</div>
javascript:
angular.module('myApp', [])
.controller('AppCtrl', [function() {
var ctrl = this;
ctrl.data = { name: "Han Solo" };
}])
.directive('outer', function(){
return {
restrict: 'E',
scope: {
model: '=',
rowTemplateUrl: '#'
},
template: '<div class="outer"><inner my-data="model" row-template-url="{{ rowTemplateUrl }}"></inner></div>'
};
})
.directive('inner', function(){
return {
restrict: 'E',
scope: {
source: '=myData',
rowTemplateUrl: '#'
},
template :'<div class="inner" ng-include="rowTemplateUrl"></div>'
};
});
You can pass your transclude all the way down to the third directive, but the problem I see is with the scope override. You want the {{ source.name }} to come from the inner directive, but by the time it compiles this in the first directive:
template: '<div class="outer"><inner my-data="model"><div ng-transclude></div></div></div>'
the {{ source.name }} has already been compiled using the outer's scope. The only way I can see this working the way you want is to manually do it with $compile... but maybe someone smarter than me can think of another way.
Demo Plunker
ShowPopover doesn't work when you click on the directive. Could you please help me identify the cause of the problem?
Directive:
angular.module('landingBuilder').directive('popoverDirective', popoverDirective);
function popoverDirective() {
return {
restrict: 'E',
templateUrl: '/libs/landing-builder/directive/popover-directive/popover-directive.html',
controller: controller,
controllerAs: 'vm',
transclude: true,
bindToController: true
};
function controller($element, $document){
var vm = this;
vm.showPopover = showPopover();
function showPopover() {
console.log('show popover');
};
}
}
Template:
<div class="input-layover popup-target" ng-click="vm.showPopover"</div>
Use showPopover() instead of showPopover here.
myApp.directive('myDirective', function() {
return {
restrict: 'E',
template: '<button ng-click=vm.showPopover()>hello</button>',
controller: controller,
controllerAs: 'vm',
transclude: true,
bindToController: true
};
function controller($element, $document) {
var vm = this;
vm.showPopover = showPopover;
function showPopover() {
console.log('show popover');
};
}
});
Working Fiddle
Look at this fiddle: https://jsfiddle.net/ns0pe1ur/
You made some mistakes in your code:
it is not proper html
you need to call function on ng-click, not inside you controller
Your template should look like this:
<div class="input-layover popup-target" ng-click="vm.showPopover()"></div>
And you controller like this:
function controller($element, $document) {
var vm = this;
vm.showPopover = showPopover;
function showPopover() {
console.log('show popover');
};
}
I'm trying to pass a boolean value from my controller into my isolated scope directive. When I console.log(attrs) from the directive's link function, the someBoolean attribute is a string, rendering the actual text "main.bool" instead of a true or false value. When I toggle the boolean value from the outer controller, I want it to be updated in the directive.
https://plnkr.co/edit/80cvLKhFvljnFL6g7fg9?p=preview
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
someBoolean: '='
},
templateUrl: 'myDirective.html',
link: function(scope, element, attrs) {
console.log(scope);
console.log(attrs);
},
controller: function($scope, $element, $attrs) {
console.log(this);
},
controllerAs: 'directiveCtrl',
bindToController: true
};
});
Controller
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.bool = true;
vm.change = function() {
vm.bool = !vm.bool;
}
});
The template
<div>
Inside directive: {{someBoolean}}
</div>
As you have attached your directive Controller to directiveCtrl instead of mainCtrl, you'll access the variable someBoolean using directiveCtrl.someBoolean.
In this case, change the HTML to:
<div>
Inside directive: {{directiveCtrl.someBoolean}}
</div>
Plunker.
Another solution would be to remove the bindToController property inside your directive. With this, you don't need to use the controller name before the variable. Working Plunker.
Read more about this bindToController feature here.
I want to create a component that displays itself as a collapsible box.
When it is expanded, it should show the transcluded content; when it is collapsed it should only show its label.
myApp.directive('collapsingBox', function() {
return {
restrict: 'E',
transclude: true,
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div ng-controller="CollapseController" class="collapsingBox"><div class="label">Title: {{ ngModel.title }}</div><br/><div ng-transclude ng-show="expanded">Test</div></div>',
link: function($scope, element, attr) {
element.bind('click', function() {
alert('Clicked!');
$scope.toggle();
});
}
};
});
This component should be reusable and nestable, so I wanted to manage the values (like "title" and "expanded") in a controller that gets instantiated for every use of the directive:
myApp.controller('CollapseController', ['$scope', function($scope) {
$scope.expanded = true;
$scope.toggle = function() {
$scope.expanded = !$scope.expanded;
};
}]);
This "almost" seems to work:
http://plnkr.co/edit/pyYV0MAikXThvMO8BF69
The only thing that does not work seems to be accessing the controller's scope from the event handler bound during linking.
link: function($scope, element, attr) {
element.bind('click', function() {
alert('Clicked!');
$scope.toggle(); // this is an error -- toggle is not found in scope
});
}
Is this the correct (usual?) way to create one instance of the controller per use of the directive?
How can I access the toggle-Function from the handler?
Rather than using ng-controller on your directive's template, you need to put the controller in your directive's controller property:
return {
restrict: 'E',
transclude: true,
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="collapsingBox"><div class="label">Title: {{ ngModel.title }}</div><br/><div ng-transclude ng-show="expanded">Test</div></div>',
controller: 'CollapseController',
link: function($scope, element, attr) {
element.bind('click', function() {
alert('Clicked!');
$scope.toggle();
});
}
};
As it is CollapseController's scope will be a child scope of your directive's scope, which is why toggle() isn't showing up there.
How we can get particular isolated scope of the directive while calling link function from controller(parent)?
I am having a directive and repeating it using ng-repeat. Whenever a button in the directive template is clicked it will call a function- Stop() in directive controller which in-turn calls function test() in parent controller, inside test() it will call a method dirSample () in directive's link function.
When I print the scope inside dirSample(), it prints the scope of the last created directive not the one which called it.
How can I get the scope of the directive which called it?
Find the pluker here
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '&',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(){
console.log(scope)
console.log(element)
console.log(attrs)
console.log(ctrl)
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope)
$scope.meri(1)
};
}
}});
full code in plunker
I've changed the binding of your function from & to = since you need to pass a parameter. This means some syntax changes are in order, and also you need to pass the scope along the chain if you want to have it all the way at the end:
HTML:
<div stopwatch control="dashControl" meri="test"></div>
Controller:
$scope.test = function(scope)
{
console.log(scope);
$scope.dashControl.dirSample(scope);
}
Directive:
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '=',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(_scope){
console.log(_scope);
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope);
$scope.meri($scope);
};
}
}});
Plunker