This is a simple question but I can't seem to find any relevant documentation...
I'm trying to find out if an angular directive can both inherit a parent controller as well as its own. Consider the following examples:
Simple Inheritance From Self
app.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomething = function() {
};
},
link: function($scope, el, attrs, ctrl) {
// ctrl now contains `doSomething`
}
}
});
Inheritance From Parent
app.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomething = function() {
};
}
}
});
app.directive('widget', function() {
return {
scope: true,
require: '^screen',
link: function($scope, el, attrs, ctrl) {
// ctrl now contains `doSomething` -- inherited from the `screen` directive
}
}
});
There's even multiple inheritance...
app.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomething = function() {
};
}
}
});
app.directive('widget', function() {
return {
scope: true,
require: ['^screen','^anotherParent'],
link: function($scope, el, attrs, ctrl) {
// ctrl[0] now contains `doSomething` -- inherited from the `screen` directive
// ctrl[1] now contains the controller inherited from `anotherParent`
}
}
});
What I can't figure out is how to make a directive inherit both a parent controller and its own. Like so:
app.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomething = function() {
};
}
}
});
app.directive('widget', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
// isolated widget controller
},
link: function($scope, el, attrs, ctrl) {
// I need the `screen` controller in ADDITION to the isolated widget controller accessible in the link
}
}
});
Is this possible/proper form (or is it some kind of anti-pattern I am unaware of)?
Well that turned out to be a lot more obvious than I thought... a little trial and error showed that a directive can actually require itself as well.
The proper way to inherit parent + local controllers seems to be:
app.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomething = function() {
};
}
}
});
app.directive('widget', function() {
return {
scope: true,
require: ['^screen','widget'],
controller: function($scope) {
this.widgetDoSomething = function() {
};
},
link: function($scope, el, attrs, ctrl) {
// ctrl[0] contains the `screen` controller
// ctrl[1] contains the local `widget` controller
}
}
});
Related
I have the following script with dateTimePicker as a AngularJS directive:
(function () {
'use strict';
var module = angular.module('feedApp');
module.directive('datetimepicker', [
'$timeout',
function ($timeout) {
return {
require: '?ngModel',
restrict: 'EA',
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
link: function ($scope, $element, $attrs, controller) {
$($element).on('dp.change', function () {
$timeout(function () {
var dtp = $($element).data('DateTimePicker');
controller.$setViewValue(dtp.date().format('MM/DD/YYYY HH:mm'));
$scope.onChange();
});
});
$($element).on('click', function () {
$scope.onClick();
});
controller.$render = function () {
if (!!controller) {
if (controller.$viewValue === undefined) {
controller.$viewValue = null;
}
else if (!(controller.$viewValue instanceof moment)) {
controller.$viewValue = moment(controller.$viewValue);
}
$($element).data('DateTimePicker').date(controller.$viewValue);
}
};
$($element).datetimepicker($scope.$eval($attrs.options));
}
};
}
]);
})();
And I want to rewrite it using TypeScript. This is the code I have for now:
namespace feedApp {
'use strict';
DateTimePicker.$inject = ['$timeout'];
function DateTimePicker($timeout: ng.ITimeoutService): ng.IDirective {
var directive = <ng.IDirective>{
require: '?ngModel',
restrict: 'EA',
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
link: link
};
function link(scope: ng.IScope, element: JQuery, attrs: ng.IAttributes, controller: ng.INgModelController): void {
$(element).on('dp.change', function () {
$timeout(function () {
var dtp = $(element).data('DateTimePicker');
controller.$setViewValue(dtp.date().format('MM/DD/YYYY HH:mm'));
scope.onChange();
});
});
$(element).on('click', function () {
scope.onClick();
});
controller.$render = function () {
if (!!controller) {
if (controller.$viewValue === undefined) {
controller.$viewValue = null;
}
else if (!(controller.$viewValue instanceof moment)) {
controller.$viewValue = moment(controller.$viewValue);
}
$(element).data('DateTimePicker').date(controller.$viewValue);
}
};
$(element).datetimepicker(scope.$eval(attrs.options));
}
return directive;
}
angular.module("feedApp")
.directive("datetimepicker", DateTimePicker);
}
I'm not experienced with TypeScript and I don't really know what is the best practise to change jQuery elements like $(element).on using TS. How to work with
scope: {
options: '#',
onChange: '&',
onClick: '&'
},
inside TypeScript link function? Thank you in advance for any help.
Use bindToController to access scope variables inside directive controller.
For better structuring/understanding please declare different components of a directive in different files like controller should be in an unique file, html should have its own file etc.
Please refer Directive testing question and typescript github sample project for more info regarding it.
If you required more clarification, please do tell me.
Regards
Ajay
I want to call alertForm directive in loginForm directive. Where I want call 'alertForm' directive in 'loginForm' is highlighted as //i want to call here
alertForm directive
angular.module('myApp')
.directive('alertForm', function () {
return {
templateUrl: 'app/directives/alert/alertForm.html',
restrict: 'E',
scope: {
topic: '=topic',
description: '=description'
},
controller: function($scope) {
$scope.words = [];
this.showAlert = function() {
$scope.description.push("hello");
};
}
};
});
loginForm directive
angular.module('myApp')
.directive('loginForm', function() {
return {
templateUrl: 'app/directives/loginForm/loginForm.html',
restrict: 'E',
scope: {
successCallback: '&',
errorCallback: '&',
emailField: '='
},
link: function (scope, element, attrs) {
},
controller: function ($rootScope, $scope, authenticationService) {
$scope.loginFormData = {};
$scope.inProgress = false;
$scope.onLogin = function (form) {
if (form.$valid) {
$scope.inProgress = true;
authenticationService.loginUser('password', $scope.loginFormData).then(function () {
$scope.successCallback({formData: $scope.loginFormData});
}, function (err) {
$scope.inProgress = false;
if (err.message) {
**// i want to call here**
}
});
}
}
}
};
});
You can use require config of directive.
When a directive requires a controller, it receives that controller as
the fourth argument of its link function. Ref : Documentation
You can implement this in your code
angular.module(‘myApp')
.directive('loginForm', function() {
return {
templateUrl: 'app/directives/loginForm/loginForm.html',
restrict: 'E',
require:'alertForm',
scope: {
successCallback: '&',
errorCallback: '&',
emailField: '='
},
link: function (scope, element, attrs, alertFormCtrl) {
scope.alertFormCtrl = alertFormCtrl;
},
controller: function ($rootScope, $scope, authenticationService) {
$scope.loginFormData = {};
$scope.inProgress = false;
$scope.onLogin = function (form) {
if (form.$valid) {
$scope.inProgress = true;
authenticationService.loginUser('password', $scope.loginFormData).then(function () {
$scope.successCallback({formData: $scope.loginFormData});
}, function (err) {
$scope.inProgress = false;
if (err.message) {
// Calling showAlert function of alertFormCtrl
$scope.alertFormCtrl.showAlert();
}
});
}
}
}
};
});
Add the following line in the app/directives/loginForm/loginForm.html :
<alertForm topic="something" description = "something" ng-if="showAlert"></alertForm>
Now inside the loginForm directive's controller : // i want to call here
use
$scope.showAlert = true;
Note: you can use some variable to setup the topic and description as well inside the alertForm.
I was just reading here about accessing one directive's controller from within another directive via the require option:
http://jasonmore.net/angular-js-directives-difference-controller-link/
The directive droppable and dashboard declarations in on my view - on two different divs:
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-lg-12" data-droppable drop="handleDrop">
<div id="dash" dashboard="dashboardOptions" class="dashboard-container"></div>
</div>
</div>
However I can't seem to get it to work. My dashboardCtrl param below is NULL.
Here in my droppable directive, I use the REQUIRE option:
.directive('droppable', function () {
return {
scope: {
drop: '&',
},
//****************** dashboard directive is optionally requested ************
require: '?dashboard',
link: function (scope, element, attributes, dashboardCtrl) {
el.addEventListener('drop', function (e) {
if (e.preventDefault) { e.preventDefault(); }
this.classList.remove('over');
var item = document.getElementById(e.dataTransfer.getData('Text'));
this.appendChild(item.cloneNode(true));
// *** CALL INTO THE dashboardCtrl controller ***
dashboardCtrl.addWidgetInternal();
return false;
}, false);
}
}
});
and the dashboard directive :
angular.module('ui.dashboard')
.directive('dashboard', ['WidgetModel', 'WidgetDefCollection', '$modal', 'DashboardState', '$log', function (WidgetModel, WidgetDefCollection, $modal, DashboardState, $log) {
return {
restrict: 'A',
templateUrl: function (element, attr) {
return attr.templateUrl ? attr.templateUrl : 'app/shared/template/dashboard.html';
},
scope: true,
controller: ['$scope', '$attrs', function (scope, attrs) {
// ommitted for brevity
}],
link: function (scope) {
scope.addWidgetInternal = function (event, widgetDef) {
event.preventDefault();
scope.addWidget(widgetDef);
};
};
}
}]);
However, my dashboardCtrl parameter is NULL. Please help me to figure out how to use require.
I actually need to call the addWidget() function, which is within the link option; but I suppose I can copy or move that into the controller option.
thank you !
Bob
Here is an example of "parent" directive dashboard requiring droppable, and communication between the two making use of require and passing dashboardCtrl
Here is a good article to see directive to directive communication
Fiddle example also built from your previous question
JSFiddle
app.directive('droppable', [function () {
return {
restrict: 'A',
require: 'dashboard',
link: function (scope, elem, attrs, dashboardCtrl) {
dashboardCtrl.controllerSpecificFunction('hello from child directive!');
scope.addWidgetInternal = function(message) {
console.log(message);
}
}
}
}]);
app.directive('dashboard', [function () {
return {
restrict: 'A',
controller: function ($scope) {
$scope.handleDrop = function(message) {
$scope.addWidgetInternal(message)
}
this.controllerSpecificFunction = function(message) {
console.log(message);
}
}
}
}]);
Edit
Based on discussion, here is a solution for what I currently understand the problem to be
Parent directive dashboard optionally requires child directive droppable and there needs to be communication between the two
<div dashboard>
<button id="dash" droppable ng-click="handleDrop($event)">Handle Drop</button>
</div>
app.directive('droppable', [function () {
return {
restrict: 'A',
require: '^?dashboard',
link: function (scope, elem, attrs, dashboardCtrl) {
scope.handleDrop = function($event) {
dashboardCtrl.addWidgetInternal($event);
}
}
}
}]);
app.directive('dashboard', [function () {
return {
restrict: 'A',
controller: function ($scope) {
this.addWidgetInternal = function($event) {
console.log($event);
}
}
}
}]);
Updated JSFiddle
I understand that I can dynamically set a templateUrl base on an option DOM attribute template-url="foo.html" given the following code:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.templateUrl || 'some/path/default.html'
}
}
});
However, I need to take this a step further and pass this string one level deeper, to a child directive.
Given this HTML:
Usage in Main project
<parent parent-template="bar.html" child-template="foo.html"></parent>
The child will not be exposed in most cases, so if child-template is set, it needs to implicitly replace templateUrl for all child <child></child> elements that are located in the parent foo.html.
The require: '^parent' attribute passes data from scope to scope, but I'm not seeing this available in templateUrl when it's declared.
foo.html
<h1>Title</h1>
<child ng-repeat="item in array"></child>
Directives
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '=childTemplate'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// code
},
templateUrl: function(elem,attrs) {
return ??? // parent.attribute.childTemplate? || 'some/path/default.html'
},
require: '^parent',
scope: {
childTemplate: '=childTemplate'
}
}
});
Update
The old answer (see bellow) won't work because it's only possible to access the controller of the required directives inside the link functions, and the templateUrl function gets executed before the link functions.
Therefore the only way to solve this is to handle everything in the templateUrl function of the child directive. However this function only takes 2 arguments: tElement and tArgs.
So, we will have to find the element of the parent directive and access the attribute child-template. Like this:
angular.module('testApp', [])
.directive('parent', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
},
transclude:true,
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'default.html'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require:'^parent',
templateUrl: function(elem,attrs) {
//if jQuery is loaded the elem will be a jQuery element, so we can use the function "closest"
if(elem.closest)
return elem.closest("parent").attr("child-template") || 'default.html';
//if jQuery isn't loaded we will have to do it manually
var parentDirectiveElem=elem;
do{
parentDirectiveElem=parentDirectiveElem.parent();
}while(parentDirectiveElem.length>0 && parentDirectiveElem[0].tagName.toUpperCase()!="PARENT");
return parentDirectiveElem.attr("child-template") || 'default.html';
}
}
});
Example
Old Answer
Since you are isolating the scope, you could try this, it's a bit hacky but I guess that it should work:
angular.module('foo').directive('parent', function() {
return {
restrict: 'E',
controller: function($scope) {
this.childTemplate=$scope.childTemplate;
},
link: function(scope, element, attrs) {
},
templateUrl: function(elem,attrs) {
return attrs.parentTemplate || 'some/path/default.html'
},
scope: {
childTemplate: '#'
}
}
})
.directive('child', function() {
return {
restrict: 'E',
require: '^parent',
link: function(scope, element, attrs, parentController) {
if(parentController.childTemplate)
element.data("childTemplate", parentController.childTemplate);
},
templateUrl: function(elem,attrs) {
return elem.data("childTemplate") || 'some/path/default.html'
}
}
});
In my question, I was attempting to provide an override for the templateUrl of an off-the-shelf directive that didn't have one. My original question doesn't mention this, however, I wanted to add this as a reference to others who may have forgotten, as I did. Angular allows you to decorate directives and override their properties.
app.config(function($provide) {
$provide.decorator('child', function($delegate) {
var directive = $delegate[0];
directive.templateUrl = 'path/to/custom.html';
return $delegate;
});
});
Is it possible to use Typescript with nested angular directives?
http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
How would the following Javascript look as typescript code?
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}
That code should work just as it is. However as a better practice you could use TypeScript classes for controllers if they become too large http://www.youtube.com/watch?v=WdtVn_8K17E&hd=1