Nested angular directives with typescript - javascript

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

Related

How to call the controller method after directive render using $timeout?

I need to call one function after directive render.
Actually i tried using $timeout function. But it's not working.
HTML Code:
<div ng-app='myApp' ng-controller='module-menu-controller'>
<layout-render></layout-render>
<div after-grid-render="getGridObject()"></div>
</div>
JS Code:
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope, $compile) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive("layoutRender", function() {
return {
restrict : "E",
template : "<h1>Testing</h1>"
};
});
app.directive('afterGridRender', ['$timeout', function ($timeout) {
var def = {
restrict: 'A',
terminal: true,
transclude: false,
link: function (scope, element, attrs) {
$timeout(scope.$eval(attrs.getGridObject),0); //Calling a scoped method
}
};
return def;
}]);
JS Fiddle Link: https://jsfiddle.net/bagya1985/k64fyy22/1/
Here's a working fiddle.
Just have an additional attribute on the directive with the function:
HTML:
<div after-grid-render fnc="getGridObject()"></div>
Directive:
$timeout(scope.$eval(attrs.fnc),0);
Try this? Simple and clear
HTML
<div ng-app='myApp' ng-controller='module-menu-controller'>
<grid after-grid-render="getGridObject"></grid>
</div>
JS
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive("grid", function($timeout) {
return {
restrict : "E",
template : "<h1>Testing</h1>",
scope:{
afterGridRender:'='
},
link: function (scope, element, attrs) {
$timeout(scope.afterGridRender(),0); //Calling a scoped method
}
};
});
JSFiddle: https://jsfiddle.net/6o62kx3e/
Update
Do you mean you want it to be an attribute?
How about this one: https://jsfiddle.net/rt747rkk/
HTML
<div ng-app='myApp' ng-controller='module-menu-controller'>
<my-directive a='5' after-grid-render="getGridObject"></my-directive>
</div>
<script type="text/ng-template" id="myDirectiveTemplate.html">
<div> {{a}} {{b}} </div>
</script>
JS
var app = angular.module("myApp", []);
app.controller("module-menu-controller", function($scope) {
$scope.getGridObject = function() {
alert("After render");
};
});
app.directive('myDirective', function () {
return {
restrict: 'E',
replace: true,
templateUrl:"myDirectiveTemplate.html",
scope:{
a:'='
},
link: function (scope, element, attrs) {
scope.b=123;
scope.gridObject="my grid object here";
}
};
});
app.directive('afterGridRender', ['$timeout', function ($timeout) {
var def = {
restrict: 'A',
transclude: false,
link: function (scope, element, attrs) {
$timeout(function(){
scope.getGridObject();
alert(scope.$$childHead.gridObject);
});
}
};
return def;
}]);
I'm not really sure what you want to do.
If you want the attribute directive to access the scope of the element (as shown in the second alert box in the example), I don't think there's an elegant way. One way is to use scope.$$childHead, it works but variables prefixed with $$ are angular internal variables and we should not use them generally speaking.

List of directives inside of an ng-repeat

I'm working on a page that is made up of 5 directives, for example:
<directive-one></directive-one>
<directive-two></directive-two>
<directive-three></directive-three>
<directive-four></directive-four>
<directive-five></directive-five>
I would like to be able to re-order these on demand so that a user can control how their page looks. The only way I could think of doing that was putting them in an ng-repeat:
$scope.directiveOrder = [{
name: "directive-one",
html: $sce.trustAsHtml('<directive-one></directive-one>'),
order: 1
}, ...
HTML:
<div ng-repeat="directive in directiveOrder" ng-bind-html="directive.html">
{{directive.html}}
</div>
This will give me the right tags, but they aren't processed as directives by angular. Is there a way around that? I'm assuming it's something to do with $sce not handling it, but I might be way off?
Try creating a new directive and using $compile to render each directive:
https://jsfiddle.net/HB7LU/18670/
http://odetocode.com/blogs/scott/archive/2014/05/07/using-compile-in-angular.aspx
HTML
<div ng-controller="MyCtrl">
<button ng-click="reOrder()">Re-Order</button>
<div ng-repeat="d in directives">
<render template="d.name"></render>
</div>
</div>
JS
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
})
.directive("render", function($compile){
return {
restrict: 'E',
scope: {
template: '='
},
link: function(scope, element){
var template = '<' + scope.template + '></' + scope.template + '>';
element.append($compile(template)(scope));
}
}
})
.controller('MyCtrl', function($scope, $compile) {
$scope.directives = [{
name: 'directive-one'
}, {
name: 'directive-two'
}];
$scope.reOrder = function () {
$scope.directives.push($scope.directives.shift());
console.log($scope.directives);
};
});
I hope You can easily done it.
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
});
myApp.controller('ctrl',function($scope){
$scope.data = [{name:'directive-one'},{name:'directive-two'}];
});
<html ng-app='myApp'>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
</head>
<body ng-controller='ctrl'>
<div ng-repeat='item in data'>
<item.name></item.name>
<directive-one></directive-one>
</body>
</html>

Interact with angular directive via button placed anywhere

I have a directive somewhere on my page. I assign the id attribute to that directive. It doesn't matter what the directive actually does.
The key point is that I now want a second directive that basically activates the first directive.
So something like this:
<body>
<!-- bunch of elements -->
<my-directive id="test"></my-directive>
<!-- bunch of elements -->
<controlling-directive directive-id="test"></controlling-directive>
</body>
The controllingDirective would be defined like so:
.directive('controllingDirective', function() {
link: function(scope, elem, attrs) {
element.on('click', function(){
// call directive with attrs.directiveId
});
}
}
The question is: how do I actually achieve this and what is the best way?
I thought of two things:
By using document.getElementById and angular.element(..).isolateScope() like so:
.directive('myDirective', function() {
scope: {},
link: function(scope, elem, attrs) {
scope.actionToStart = function() {...};
}
}
.directive('controllingDirective', function() {
link: function(scope, elem, attrs) {
element.on('click', function(){
angular.element(document.getElementById(attrs.directiveId))
.isolateScope().actionToStart();
});
}
}
Or by using a event on $rootScope:
.directive('myDirective', function($rootScope) {
scope: {},
link: function(scope, elem, attrs) {
$rootScope.$on(attrs.id + 'Start', function(){...});
}
}
.directive('controllingDirective', function($rootScope) {
link: function(scope, elem, attrs) {
element.on('click', function(){
$rootScope.$emit(attrs.directiveId + 'Start');
});
}
}
I somehow don't like both possiblities. Is there a simple thing I'm missing, how something like this is supposed to be done?
Note that I can't use the 'require'-option, since the directives are not related in the DOM.
Methinks you not need use angular.element or even events, just simple watch and attributes
var myApp = angular.module('myApp', [])
.directive('myDirective', function() {
return {
template: '{{cur}}',
scope: {
cur: '='
},
link: function(scope, elem, attrs) {
if (!scope.cur) scope.cur = 1;
scope.$watch(function() {
return scope.cur;
}, function(newVal, oldVal) {
if (newVal !== oldVal) {
if (oldVal == 4) scope.cur = 1;
}
});
}
};
}).directive('controllingDirective', function($rootScope) {
return {
scope: {
directiveCur: '='
},
template: '<button type="button" ng-click="click()">Click me</button>',
link: function(scope, elem, attrs) {
scope.click = function() {
scope.directiveCur += 1;
}
}
};
});
div {
margin-bottom: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<div ng-app="myApp">
<div>
<my-directive data-cur="test"></my-directive>
<controlling-directive directive-cur="test"></controlling-directive>
</div>
<div>
<my-directive data-cur="test2"></my-directive>
<controlling-directive data-directive-cur="test2"></controlling-directive>
</div>
</div>
UPDATE variant with $rootScope
var myApp = angular.module('myApp', []);
myApp.controller('Test', function($scope) {});
myApp.directive('myDirective', function($rootScope) {
return {
template: '{{test}}',
scope: {
cur: '#'
},
link: function(scope, elem, attrs) {
if (!$rootScope[scope.cur]) scope.test = $rootScope[scope.cur] = 1;
console.log($rootScope[scope.cur], scope.test);
scope.$watch(function() {
return $rootScope[scope.cur];
}, function(newVal, oldVal) {
if (newVal !== oldVal) {
if (oldVal == 4) scope.test = $rootScope[scope.cur] = 1;
else scope.test = $rootScope[scope.cur];
}
});
}
};
});
myApp.directive('controllingDirective', function($rootScope) {
return {
scope: {
directiveCur: '#'
},
template: '<button type="button" ng-click="click()">Click me</button>',
link: function(scope, elem, attrs) {
scope.click = function() {
$rootScope[scope.directiveCur] += 1;
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<div ng-app="myApp">
<my-directive data-cur="test"></my-directive>
<controlling-directive directive-cur="test"></controlling-directive>
<br/>
<my-directive data-cur="test2"></my-directive>
<span ng-controller="Test">
<controlling-directive data-directive-cur="test2"></controlling-directive>
</span>
<div>test: {{test}}
<br/>test2: {{test2}}</div>
</div>
UPDATE2 variant with factory
var myApp = angular.module('myApp', []);
myApp.factory('shared', function() {
return {
inc: function(cur) {
if (this[cur] == 4) this[cur] = 1;
else this[cur] += 1;
}
}
});
myApp.controller('Test', function($scope) {});
myApp.directive('myDirective', function(shared) {
return {
template: '{{shared[cur]}}',
scope: {
cur: '#'
},
link: function(scope, elem, attrs) {
if (!shared[scope.cur]) shared[scope.cur] = 1;
scope.shared = shared;
}
};
});
myApp.directive('controllingDirective', function(shared) {
return {
scope: {
directiveCur: '#'
},
template: '<button type="button" ng-click="click()">Click me</button>',
link: function(scope, elem, attrs) {
scope.click = function() {
shared.inc(scope.directiveCur);
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<div ng-app="myApp">
<my-directive data-cur="test"></my-directive>
<controlling-directive directive-cur="test"></controlling-directive>
<br/>
<my-directive data-cur="test2"></my-directive>
<span ng-controller="Test">
<controlling-directive data-directive-cur="test2"></controlling-directive>
</span>
</div>

Using the AngularJS require option to call into another directive

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

Can an angular directive require its own controller?

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
}
}
});

Categories

Resources