Angular, dynamic directive load, and event management - javascript

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?

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.

change controller $scope from a directive

I have a controller:
function myController($scope) {
$scope.clicked = false;
}
and a directive:
function myDirective() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
});
},
template: '<div>click me</div>';
replace: true;
}
}
and I´m using it like this:
<div ng-controller="myController">
<my-directive></my-directive>
</div>
How can I change the controller value of $scope.clicked ?
thanks!
As you don't use isolated scope in your directive, you can use scope.$parent.clicked to access the parent scope property.
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$parent.clicked = ...
});
},
I would not recommend using scope.$parent to update or access the parent scope values, you can two way bind the controller variable that needs to be updated into your directive, so your directive becomes:
function myDirective() {
return {
restrict: 'E',
scope: {
clicked: '='
},
link: function(scope, elem, attrs) {
elem.bind('click', function() {
// need to update controller $scope.clicked value
$scope.clicked = !$scope.clicked;
});
},
template: '<div>click me</div>';
replace: true;
}
}
now pass this clicked from parent:
<div ng-controller="myController as parentVm">
<my-directive clicked="parentVm.clicked"></my-directive>
</div>
function myController() {
var parentVm = this;
parentVm.clicked = false;
}
I would recommend reading up on using controllerAs syntax for your controller as that would really solidify the concept of using two way binding here.
I like to use $scope.$emit for such purposes. It allows to send data from directive to the controller.
You should create custom listener in your controller:
$scope.$on('cliked-from-directive', function(event, data){
console.log(data)
})
As you can see, now you have full access to your controller scope and you can do whatever you want. And in your directive just to use scope.$emit
link: function(scope, elem, attrs) {
elem.bind('click', function() {
scope.$emit('cliked-from-directive', {a:10})
});
Here I've created jsfiddle for you

Sharing Data between two Directives in AngularJS

I have the following code:
<div id='parent'>
<div id='child1'>
<my-select></my-select>
</div>
<div id='child2'>
<my-input></my-input>
</div>
</div>
I also have two directives which get some data from the data factory. I need the two directives to talk to each other such that when a value in select box is changed the input in changes accordingly.
Here's my two directives:
.directive("mySelect", function ($compile) {
return {
restrict: 'E',
scope:'=',
template: " <select id='mapselectdropdown'>\
<option value=map1>map1</option> \
<option value=map2>map2</option> \
</select>'",
link: function (scope, element, attrs) {
scope.selectValue = //dont konw how to get the value of the select
}
};
})
.directive("myInput", function($compile) {
return {
restrict: 'E',
controller: ['$scope', 'dataService', function ($scope, dataService) {
dataService.getLocalData().then(function (data) {
$scope.masterData = data.input;
});
}],
template: "<input id='someInput'></input>",
link: function (scope, element, attrs) {
//here I need to get the select value and assign it to the input
}
};
})
This would essentially do the onchange() function that you can add on selects. any ideas?
You could use $rootScope to broadcast a message that the other controller listens for:
// Broadcast with
$rootScope.$broadcast('inputChange', 'new value');
// Subscribe with
$rootScope.$on('inputChange', function(newValue) { /* do something */ });
Read Angular docs here
Maybe transclude the directives to get access to properties of outer scope where you define the shared variable ?
What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
-> https://docs.angularjs.org/guide/directive
After much research this is what worked...
I added the following:
.directive('onChange', function() {
return {
restrict: 'A',
scope:{'onChange':'=' },
link: function(scope, elm, attrs) {
scope.$watch('onChange', function(nVal) { elm.val(nVal); });
elm.bind('blur', function() {
var currentValue = elm.val();
if( scope.onChange !== currentValue ) {
scope.$apply(function() {
scope.onChange = currentValue;
});
}
});
}
};
})
Then on the element's link function I added:
link: function (scope, elm, attrs) {
scope.$watch('onChange', function (nVal) {
elm.val(nVal);
});
}
Last added the attribute that the values would get set to in the scope:
<select name="map-select2" on-change="mapId" >

AngularJS - Directive wrapping without losing connection to controller

Is there a way for not losing connection to the current controller when you are wrapping data with a directive ?
My problem is, that the directive within the wrapped template has no connection to the outside controller any more and so I can not execute the function.
Wrapping Directive:
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "="
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
Directive within the wrapped Template
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
$(elem).click(function(e) {
scope.$apply(attrs.doAction);
});
}
}
});
Conroller:
lmsApp.controller('OutsideController', function ($scope){
$sope.sayHello = function() {
alert("hello");
};
});
HTML where I want to execute the function (template.php):
<div>
<do-action="sayHello()"></do-action>
</div>
How I call the wrapContent directive which is outside (Updated):
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any"></wrap-content>
</div>
How can I execute the sayHello() function?
Thank you for your help! I would appreciate every answer.
wrapContent directive will be processed with the scope of controller.
DoAction directive will be processed with the isolateScope of wrapContent directive.
Solution1:
Get a reference to the sayHello function in wrapContent using '&' and execute it in event handler.
Solution2:
Instead of using scope in your event handler, use scope.$parent.
You should pass sayHallo function to your parent directive using &
myApp.directive('wrapContent', function() {
return {
restrict: "E",
scope: {
model: "=",
datas: "=",
sayHallo: "&"
},
templateUrl: "./any/template.php",
link: function(scope, element, attr) {
// any
}
};
});
HTML
<div ng-controller="OutsideController">
<wrap-content model="any" datas="data_any" sayHallo="sayHallo()"></wrap-content>
</div>
Then in your child directive, you will have sayHallo in your scope, to call it just do it this:
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.sayHallo();
}
}
});
And you dont need pass it again. So your child directive should looks like this:
<div>
<do-action></do-action>
</div>
UPDATE
If you want to use all your parent model functions,without passing each function. In your child directive,just use scope.model to have access to model attributes and functions.
myApp.directive('doAction', function() {
return {
restrict: "A",
link: function(scope, elem, attrs) {
scope.model.sayHallo();
}
}
});

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

Categories

Resources