I'm trying to customize directuve 'angular-popover'.
<a angular-popover
direction="bottom"
template-url="assets/app/common/templates/popovers/page-choose.html"
class="content_paginator_trigger openPaginator inline-block pull-left">
<span class="content_paginator_trigger_text popoverTriger">
Page {{$ctrl.data.current_page}} of {{$ctrl.data.last_page || 1}}
</span>
</a>
That directive use inherit scope.
scope: true
$ctrl outside, inside and in the 'template-url' is the same.
If I change it on isolate scope then I'm loosing $ctrl context in template. Template is adding via ng-transclude attribute
scope: {
onOpen: "&onOpen"
}
How can I pass some function from $ctrl to 'angularPopover' directive?
You can create a service that gets called by both the controller and the directive.
(function (ng) {
'use strict';
ng.module('ServiceDemo')
.service('DemoService', DemoService)
.controller('DemoController', DemoController)
.directive('DemoDirective', DemoDirective);
function DemoService () {
var self = this;
self.onOpen = onOpen;
function onOpen () {
console.log('onOpen called!');
}
}
function DemoController (DemoService) {
var demoControllerVM = this;
demoControllerVM.onOpen = DemoService.onOpen;
}
function DemoDirective (DemoService) {
return {
restrict: 'A',
scope: {},
templateUrl: 'path/to/directive/template.html',
controllerAs: 'demoDirectiveVM',
bindToController: true,
controller: function ($scope, $element, $attrs) {
var demoDirectiveVM = this;
demoDirectiveVM.onOpen = DemoService.onOpen;
}
};
}
})(angular);
Related
I looked at the documentation about scope but can't wrap my head around it. My directive is a loading-overlay with an isolated scope. I tried both scope: true and scope: false, but neither has the desired effect. On top of that I read that scope: {} is considered to be best practice.
I understand that you can pass values to the isolated scope via # or =, but that's not what I need (I think?). What i need, is to add my directive(=the overlay) to the parent-scope when clicking on a breadcrumb.
tl;dr: My overlay doesn't appear when I navigate with the breadcrumbs.
Here's the HTML:
<div id="myApp">
<ui-breadcrumbs template-url="Scripts/app/uiBreadcrumbs.tpl.html" displayname-property="data.breadcrumbLabel" abstract-proxy-property="data.breadcrumbProxy"></ui-breadcrumbs>
<div loading-Overlay></div> **This is my isolated scope**
<div id="MainBody" ui-view="main">
#RenderBody()
</div>
</div>
This is the directive:
(function () {
"use strict";
angular
.module("myApp")
.directive("loadingOverlay", ["$http", "$timeout",
function ($http, $timeout) {
return {
restrict: "A",
replace: true,
templateUrl: "./Scripts/app/loading.overlay.html",
scope: {},
link: function (scope, element) {
scope.isRouteLoading = false;
scope.isRouteStillLoading = false;
scope.$on('$stateChangeStart', function () {
scope.isRouteStillLoading = true;
scope.isRouteLoading = true;
element.addClass("show");
});
scope.$on('$stateChangeSuccess', function () {
scope.isRouteStillLoading = false;
scope.isRouteLoading = false;
$timeout(function () {
if (scope.isRouteStillLoading)
scope.isRouteLoading = true;
element.removeClass("show");
}, 500);
});
scope.$watch(scope.isRouteLoading,
function (value) {
return value ? element.addClass("show") : element.removeClass("show");
});
}
};
}]);
})();
Been trying to figure this out for too long now. Maybe someone can shed some light:
Am experimenting with custom directives and as an exercise I'm trying to create a method within the custom directive's controller that can be called from a simple button within the view. But the method isn't being called, even though I can see the method (using console) as a property within isolated scope object. Any ideas please?
HTML:
<my-dir>
<p>My dir content</p>
<p><button ng-click="hideMe()">Hide element with isolated scope</button></p>
</my-dir>
JS:
var app = angular.module('myApp', []);
app.directive('myDir', function() {
return {
restrict: 'EA',
scope: {},
controller: ['$scope', function ($scope) {
$scope.hideMe = function(){
console.log('hideMe called');
};
}]
};
})
You have to declare your template inside the directive using template: property or inside an external .html file using templateUrl:"path/to/template.html"
Example using template :
var app = angular.module('myApp', []);
app.directive('myDir', function() {
return {
restrict: 'EA',
scope: {},
template : '<p>My dir content</p><p><button ng-click="hideMe()">Hide me</button></p>',
controller: ['$scope', function ($scope) {
$scope.hideMe = function(){
console.log('hideMe called');
};
}]
};
})
Example using templateUrl :
var app = angular.module('myApp', []);
app.directive('myDir', function() {
return {
restrict: 'EA',
scope: {},
templateUrl : 'my-dir.tpls.html',
controller: ['$scope', function ($scope) {
$scope.hideMe = function(){
console.log('hideMe called');
};
}]
};
})
Template : my-dir.tpls.html
<p>My dir content</p>
<p><button ng-click="hideMe()">Hide me</button></p>
HTML:
<my-dir></my-dir>
You can try this,
Directive:
app.directive('myDir', function() {
return {
restrict: 'EA',
scope: {},
link: function($scope, element, attrs) {
$scope.hideMe = function() {
alert('hideMe called');
}
}
}
});
HTML:
<div ng-controller="MyCtrl">
<my-dir>
<p>My dir content</p>
<p>
<button ng-click="hideMe()">Hide element with isolated scope</button>
</p>
</my-dir>
</div>
DEMO
I am trying to bind a callback to a component through a template. The template contains instance of another directive. This just doesn't seems to work. I'm invoking the directive from a modal, not sure if this can cause a problem. I tried many of the solution suggested in previous questions and still no luck. I ran it with a debugger, and the '$ctrl.onSelectionChanged' is defined to be as it should:
function (locals) { return parentGet(scope, locals); }
My code:
my-component.js:
The inner-directive as no reference to the callback, should it have?
angular.module('myModule')
.component('myComponent', {
template: '<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>',
bindings: {
$router: '<',
onSelectionChange: '&'
},
controller: MyComponentController
});
/* #ngInject */
function MyComponentController(MyService, $filter, $log, $q) {
var $ctrl = this;
$ctrl.$routerOnActivate = function () {
};
$ctrl.selectedItems = [];
}
calling-component-controller.js:
function CallingComponentCtrl(toastr, $scope, $uibModal, $log) {
var $ctrl = this;
$ctrl.loadDone = false;
$ctrl.grid = {
enableSorting: true,
data: [],
columnDefs: [
{name: 'id'},
{name: 'name'},
{name: 'description'}
],
enableRowSelection: true,
enableRowHeaderSelection: false,
multiSelect: false,
noUnselect: true,
onRegisterApi: function (gridApi) {
$ctrl.gridApi = gridApi;
}
};
this.$onInit = function () {
if (angular.isUndefined($ctrl.abc)) {
return;
}
syncData();
$ctrl.loadDone = true;
};
this.$onChanges = function () {
// TODO
};
function syncData(){
$ctrl.grid.data = $ctrl.abc;
}
$ctrl.myFoo = function(items_list) {
alert("This is never happening");
};
$ctrl.onPress = function (event) {
var modalInstance = $uibModal.open({
template: '<my-component on-selection-change="$ctrl.myFoo(items_list)"></my-component>',
windowClass: 'modal-window'
});
};
}
Any thoughts?
Use the $compile service
link: function(scope, element) {
var template = $compile('<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>')(scope);
element.append(template);
}
Remember to inject the compile service to the directive function
Trying changing your child component to this:
.component('myComponent', {
template: '<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>',
bindings: {
$router: '<'
},
require: {
parent: '^^CallingComponent'
},
controller: MyComponentController
});
With require you inherit the parent controller.
Then in the init function you can make the call:
function MyComponentController(MyService, $filter, $log, $q) {
this.$onInit = function() {
this.parent.myFoo(items_list);
}
var $ctrl = this;
$ctrl.$routerOnActivate = function () {};
$ctrl.selectedItems = [];
}
--Old answer
Try changing the template to:
<my-component on-selection-change="$ctrl.myFoo(items_list)"></my-component>
You're calling it from the $scope when it's declared as a controller function.
Well, I found the problem. While invoking the modal, I used a template used a component in this template. To the component, I passed a callback that is defined in the '$ctrl'. The problem was that the modal defined its own scope and couldn't reached this $ctrl. So I defined a controller to the modal, and called through it the function I needed. This is my solution, I highlighted the changes and adds:
calling-component-controller.js:
function CallingComponentCtrl(toastr, $scope, $uibModal, $log) {
var $ctrl = this;
....
$ctrl.myFoo = function(items_list) {
alert("This is never happening");
};
$ctrl.onPress = function (event) {
var modalInstance = $uibModal.open({
template: '<my-component on-selection-change="$ctrl.myNewFoo(items_list)"></my-component>',
**controllerAs: '$ctrl',**
windowClass: 'modal-window',
**controller: function($uibModalInstance){
var $ctrl = this;
$ctrl.myNewFoo= function(items_list) {
$uibModalInstance.close(items_list);
};
}**
});
**modalInstance.result.then(function(items_list) {
$ctrl.myFoo(items_list);
});**
};
}
I wrote a plunker to see how to use bindToDirective to isolate scopes and using directive controller to call main controller function, but, I am doing something wrong. Could you suggest?
This is the plunker: http://plnkr.co/edit/UJLjTmIiHydHr8qRzAsX?p=preview
Code sample:
.controller('Ctrl', function() {
var self = this;
self.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
var self = {};
self.link = function (scope, elem, attrs, ctrl) {
elem.bind('click', function () {
ctrl.ctrlFunc();
});
elem.addClass('fa fa-file-excel-o fa-lg');
};
return {
restrict: 'E',
scope: {},
controller: function () {
},
controllerAs: 'DirCtrl',
bindToController: {
ctrlFunc: '&'
},
link: self.link
};
}])
html sample to associate main controller function to directive:
<div ng-controller="Ctrl">
<my-directive ctrlfunc="Ctrl.func()"></my-directive>
</div>
You have a number of issues:
You need a hyphen in your directive argument name and you should be passing the function reference, not calling the function directly (with params):
<my-directive ctrl-func="ctrl.func"></my-directive>
Second, you are using alias syntax in your controller (var self = this;), but not in your template. You need to update it to the following:
<div ng-controller="Ctrl as ctrl">
<my-directive ctrl-func="ctrl.func"></my-directive>
</div>
Finally, pass down the function reference with two-way binding instead of with & since that passes down values for implicit evaluation.
bindToController: {
ctrlFunc: '='
},
See working plunkr
I'm not sure you need bindToController...
This version calls your Ctrl's function: http://plnkr.co/edit/Rxu5ZmmUAU8p63hR2Qge?p=preview
JS
angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
}) angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
return {
template: '<pre>[clickme]</pre>',
replace: true,
restrict: 'E',
scope: {
target: '&'
},
link: function (scope, elem, attrs) {
elem.bind('click', function () {
var fn = scope.target && scope.target(scope);
fn && fn();
});
elem.addClass('fa fa-file-excel-o fa-lg');
}
};
}])
HTML
<div ng-controller="Ctrl">
<my-directive target="func"></my-directive>
</div>
I have two directives, say dir1, dir2.
dir1 is some common directive.
I transclude dir2 into dir1.
Here is the script:
<script>
var app = angular.module('myapp', []);
app.directive('dir1', function() {
return {
scope: {},
controller: D1Controller,
controllerAs: 'd1c',
transclude: true,
templateUrl: 'd1template'
}
function D1Controller() {
var vm = this;
vm.someValue = "some data";
}
});
app.directive('dir2', dir2);
function dir2() {
return {
tempalteUrl: 'dir2t',
scope: true,
bindToController: {},
controller: D2Controller,
controllerAs: 'mc',
link: link,
require: '^dir1'
}
}
D2Controller.$inject = ['$scope']; // <-- injecting something?
function D2Controller($scope) {
var vm = this;
var getDataListener = $scope.$watch('someValue', function fnGetData(someValue) {
if (!!someValue) {
vm.data = someValue;
getDataListener();
}
});
// $scope.$parent.$parent.someValue <-- this will work but seems not elegant way to do it.
}
function link(scope, iElement, iAddr, controller) {
scope.someValue = controller.someValue; // get data form parentDirective controller
}
</script>
<script type="text/ng-template" id="d1template">
<div>
...
<div ng-transclude></div>
...
</div>
</script>
I can only access D1Controller form dir2's link function(controller) and pass it to the D2Controller via scope.
Is there any way I can (like inject something to D2Controller) access D1Controller from D2Controller?
Or is there any other way to communicate between directive scope and transclude scope?
Thanks for Robert's suggestion.
Here is my solution.
I find an elegant way is to pass the D2Controller to D1Controller and let D1Controller to set the data.
I add set data function in D1Controller
vm.setData = function(controller) {
controller.data = vm.SOMEDATA;
}
In dir2's link function I call d1controller.setData.
function link(scope, iElement, iAttr, controller) {
controller.setData(scope.mc);
}