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
Related
How can I access controller scope from multi-level directives in below structure:
I create a directive that has multi-level scopes inside its.
1. Controller scope
1.2. Directive1 scope(main directive)
1.2.1. Directive2 scope
1.2.1.1 Directive3 scope
I want to get the controller scope from directive 3.
please don't refer to $parent because the parent level it's not certain and a user may use this directive inside another directive.(see below codes)
<div ng-controller="Test">
<custom-dir>
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
<custom-dir>
</div>
The users create a function in the Test controller and the function will be called in my Directive 3 scope(how to get controller scope?).
<div ng-controller="Test">
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
</div>
More details(please don't refer to syntax error):
The controller is:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal, $http, $filter, $cookieStore, Common, $cookies) {
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
the dir1 directive:
App.directive("dir1", function ($compile, $filter, $rootScope, $timeout, Common, $window, $http) {
return {
restrict: "E",
priority: 1,
terminal: false,
templateUrl: "Content/html/Table.html?version=2.6",
scope: {
model: "=",
columns: "=",
config: "=",
search: "#",
},
link: function (scope, elem, attrs) {
scope.CallFunc = function (html) {
if (typeof (html) =="function" )
return html();
else {
return scope.$parent.$eval(html + "()", {});
}
}
}
}
});
the dynamic directive compile the runTest output
App.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
If I change the line $compile(ele.contents())(scope); to $compile(ele.contents())(scope.$parent.$parent); it's work.
In this directive, I need get the controller scope without $parent because
some users may use this directive inside another directive same below:
<custom-dir>
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
</custom-dir>
The using HTML tag
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
This issue handle with following codes:
A service for storing the controller scope:
App.service('TableService', function () {
return {
MyScope: null
};
});
Inject the TableService to dynamic directive(this directive compiles dynamic content):
App.directive('dynamic', function ($compile,TableService) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(TableService.MyScope);
});
}
};
});
And finally in the controller:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal,
$http, $filter, $cookieStore, Common, $cookies,TableService) {
TableService.myScope = $scope;
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
After that, the dynamic directive can access controller scope and all dynamic events(like testHtml) will be called even if the directive put in another directive(without using the $parent).
thank you #shaunhusain, huan feng for giving me an idea.
In child controller do something like:
$scope.$broadcast('yourEvent');
In parent controller do the listener:
$scope.$on('yourEvent' , function(){
//Handle your logic
});
A special case service
.service('DirectDispatcher', function(){
return {
fireMe: angular.noop
}
});
First directive registers a function callback
.directive(
...
link:function(DirectDispatcher){
function myHandler() {window.alert('just testing')}
DirectDispatcher.fireMe = myHandler;
}
...
);
Second directive fires the function callback
.directive(
...
link:function(DirectDispatcher){
DirectDispatcher.fireMe();
}
...
);
I have a diretive with a list of events loading from my service service:
.directive('appointments', [function () {
return {
restrict: 'CE',
scope: {
ngTemplate: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.events = calendarService.getEvents();
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
Now in another directive i am updating this list, how can i update the list in the first controller?
.directive('appointmentsUpdate', [function () {
return {
restrict: 'CE',
scope: {
ngEventId: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.update = calendarService.updateEvent(scope.ngEventId).then(function(res){
// Here is where the newly created item, should be added to the List (vm.events) from first directive
)
});
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
you can use angular broadcast service for this:
in first directive use this:
$rootScope.$broadcast('greeting', data_needs_to_be_send);
in other directive listen the event to update its scope:
$scope.$on('greeting', listenGreeting)
function listenGreeting($event, message){
alert(['Message received',message].join(' : '));
}
We use require property to make communication between directives.
Something like this
return {
restrict: 'AE',
require: '^ParentDirective or ^SameLevelDirective'
}
Here is the clear explanation of Driectives That Communicate by ToddMotto
Services are singletons, so if you update the list from one place (with your calendarService.updateEvent()), then if you retrieve the data from the service in the other directive, it should be the updated list.
You could use a watch to check when the list is updated:
$scope.$watch(() => calendarService.getEvents(), (newValue, oldValue) => {
// update your scope with the new list
}, true);
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.
Context
I m actually developping an application in which I need to generate directive dynamically from a controller to a view (drag n drop system).
It works like this :
the directive
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
And in the controller, it looks like :
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
]
And in the HTML file :
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
The problem
I want to use the
$scope.$emit('event');
from my controller, to send some information to my directive, which is supposed to get it with :
$scope.$on('event',function(){console.log('yiihaaa');})
It seems that it doesnt happen anything...
I need the log to be displayed.
Can you help me ?
Thanks for advance
ngRepeate create own isolated scope, and scope in your directive link function is this isolated scope.
When you do $emit
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
For sending event to child scopes you need use $broadcast
So for solving your problem you have at least two ways
1) use $broadcast instead of $emit
$scope.rechercherStats = function () { $scope.$broadcast('reload'); };
// Code goes here
angular.module('app',[])
.controller('ctrl',function($scope){
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
];
$scope.rechercherStats = function () {console.log('reload'); $scope.$broadcast('reload'); };
});
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$on('reload',function(){console.log('yiihaaa');})
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
Sample
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
<input type="button" ng-click="rechercherStats()" value="btn"/>
</div>
2) add listener not in scope ngRepeat, but in parent scope
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$parent.on('event',function(){console.log('yiihaaa');});
}
};
});
// Code goes here
angular.module('app',[])
.controller('ctrl',function($scope){
$scope.listModules = [
{libelle: "Utilisateurs connectés", template: "<div user-connecte></div>", drag: true}
];
$scope.rechercherStats = function () {console.log('reload'); $scope.$emit('reload'); };
});
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
scope.$parent.$on('reload',function(){console.log('yiihaaa');})
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
Sample
<div ng-repeat="currentModule in listModules" dynamic="currentModule.template">
The directive loaded
</div>
<input type="button" ng-click="rechercherStats()" value="btn"/>
</div>
As #Grundy said. Use $broadcast and $on from $rootScope.
var subscription = $rootScope.$on("myEvent", function() {
console.log("yiihao");
});
Don't forget to destroy it.
$rootScope.$on('$destroy', function () {
subscription();
});
The $broadcast would be like that.
$rootScope.$broadcast("myEvent", {});
Have you tried this link: http://onehungrymind.com/angularjs-dynamic-templates/
They are loading directives on the fly. The approach can be adapted for your requirements?
I am setting up a <button> that is supposed to open a chat window.
the chat window has a ng-show depending on scope.openChat which is false to start.
When I clicked the button I update scope.openChat to true and use $scope.apply.
The scope seems to have updated but the ng-show does not do anything.
here is the html
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
</div>
and
<div ng-show="openChat" ng-controller="MessagesCtrl">
<div>CHAT WINDOW
</div>
</div>
here is the controller:
app.controller("MessagesCtrl", function MessagesCtrl($scope,Auth) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
});
Here is the directive for the button:
'use strict';
app.directive('startChat',[ 'Post', 'Auth', function (Post, Auth) {
return {
restrict: 'A',
replace: true,
bindToController: true,
controller: 'MessagesCtrl',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$apply(function() {
scope.openChat = true;
scope.message.recipient = scope.profile.$id;
scope.message.sender = Auth.resolveUser().uid;
});
});
}
}
}]);
thank you
I'd suggest not creating two instances of MessagesCtrl. Here is a simplified working example of the issue you are trying to solve. Examine the markup and see that MessagesCtrl contains both of these elements. Otherwise, you were on the right track updating $scope and calling $apply
Also note that .on() is the preferred method to .bind() see jQuery docs
JSFiddle Link
<div ng-app="app">
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
<div class="chatWindow" ng-show="openChat"></div>
</div>
</div>
app.directive('startChat', [function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function() {
scope.$apply(function() {
scope.openChat = true;
});
});
}
}
}]);
app.controller('MessagesCtrl', ['$scope', function($scope) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
}]);