List of directive are listed like:
HTML:
<div ng-app="dashboard" ng-controller="MyCtrl">
<button class="btn btn-white" ng-click="minim()"><i class="fa fa-minus-square"></i></button>
<widget-item widget-id="1" widget-type ="chart"></widget-item>
<widget-item widget-id="2" widget-type ="table"></widget-item>
</div>
JS:
var dashboard = angular.module("dashboard",['ui.bootstrap','ngAnimate']);
dashboard.controller( 'MyCtrl', function ( $scope,$modal) {
$scope.on = true;
$scope.minim = function(){
$scope.on = !$scope.on;
};
});
dashboard.directive('widgetItem', ['$compile', function($compile){
restrict: 'E',
scope:true,
template: '<div class="panel"><div class="panel-heading"><div class="mypanel-btn" > <i class="fa fa-minus"></i></div></div><div class="panel-body" ng-show="on"></div></div>',
link:function(scope, element, attrs) {
scope.on = true;
scope.toggle = function () {
scope.on = !scope.on;
};
}
}]);
Here I have minimize button for individual directive as well as all directive. Individual minimize button working fine(It's define in directive link). Over all minimize button doesn't work. How do I change directive isolate value from controller?
In your case, you can use $broadcast which will emit event and dispatches the event downwards to all child scopes.
Then, you can use $on to listen some event into your directive.
If i follow your code :
Controller
(function(){
function Controller($scope, $http) {
$scope.on = true;
$scope.minim = function(){
$scope.on = !$scope.on;
//send minim event downwards to all child scope
$scope.$broadcast('minim', $scope.on);
};
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Directive
(function(){
function widgetItem($compile) {
return{
restrict: 'E',
scope: true,
templateUrl: 'template.html',
link:function(scope, element, attrs) {
//Listen event minim
scope.$on('minim', function(e, data){
//Change our scope.on data
scope.on = data;
});
scope.toggle = function () {
scope.on = !scope.on;
};
}
};
}
angular
.module('app')
.directive('widgetItem', widgetItem);
})();
And then, call your directive :
HTML
<body ng-app="app" ng-controller="ctrl">
<button class="btn btn-white" ng-click="minim()"><i class="fa fa-minus-square"></i></button>
<widget-item widget-id="1" widget-type ="chart"></widget-item>
<widget-item widget-id="2" widget-type ="table"></widget-item>
</body>
If you use directive with scope: true, it means that you new scope is a child of parent scope and you can change whatever you want,because it's not an isolated scope.
if you need more with examples, here is a cool article
http://www.w3docs.com/snippets/angularjs/change-variable-from-outside-of-directive.html
Related
In this plunk I have the following:
A control object shared between the controller and the directive.
The directive declares a method in the control object.
The controller invokes the method to set a value.
The directive is included in an Angular UI Modal.
For some reason the control object is empty when the modal is opened (look at the console log). How to invoke the method from the controller to set the field value?
HTML
<div ng-app="app" ng-controller="myCtl">
<button ng-click="openModal()">Open modal</button>
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h4 class="modal-title">The Title</h4>
</div>
<ddl control="ddlControl"></ddl>
<div class="modal-footer">
<button type="submit">Submit</button>
</div>
</script>
</div>
Javascript
var app = angular.module('app', ['ui.bootstrap']);
app.controller('myCtl', function($scope,$uibModal) {
$scope.ddlControl = {};
$scope.openModal = function() {
$scope.modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope
});
console.log($scope.ddlControl);
$scope.ddlControl.set();
};
})
.directive('ddl', function () {
var directive = {};
directive.restrict = 'EA';
directive.scope = {
control: '='
};
directive.template = '<div>someValue: {{someValue}}</div>';
directive.link = function (scope, element, attrs) {
scope.control = scope.control || {};
scope.control.set = function() {
scope.someValue = 1;
};
};
return directive;
});
There is a race condition between opening the modal and running a digest of the modal HTML.
When the button is clicked $scope.openModal() is executed. The modal opens and gets into the digest phase. But javascript is not waiting until the digesting has been completed, so it continues executing $scope.openModal() until the end.
You need to handle the promise of $uibModal.open().rendered(). The uibModal resolves the rendered promise when it's done.
$scope.openModal = function() {
$scope.modalInstance = $uibModal.open({
templateUrl: 'myModalContent.html',
scope: $scope
}).rendered.then(function() {
console.log($scope.ddlControl);
$scope.ddlControl.set();
});
};
The $uibModal.open() function returns the following:
Object {result: Promise, opened: Promise, rendered: Promise}
In the promise block of rendered, you can safely make use of the fields that has been changed by the directive.
Plunker: http://plnkr.co/edit/GnIThstxkuR06felh8Pe?p=preview
take a look at the following code:
html:
<body ng-app="myApp">
<div ng-controller="MainController">
<input type="button" ng-click="talk()" value="outside directive" />
<div my-element>
<input type="button" ng-click="talk()" value="inside directive" />
</div>
</div>
</body>
js:
var app = angular.module('myApp', []);
app.controller('MainController', function($scope){
$scope.talk = function() {
alert('HELLO1');
}
});
app.directive('myElement', function(){
return {
restrict: 'EA',
scope: {},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});
as you can see, there's a controller, which nests a directive.
there are 2 buttons, one in controller level (outside of directive), and one is inside the directive my-element.
the main controller defines a scope method talk, the nested directive controller also defines a method - talk - but keep in mind that directive has isolated scope (i'd expect that talk method won't be inherited into directive's scope).
both buttons result an alert of 'HELLO 1', while i expected the second button (inside directive) to alert 'HELLO 2' as defined in directive controller, but it doesn't - WHY?
what am i missing here? how could i get a result when the second button will alert 'HELLO 2' but with the same method name ("talk") ?
thanks all
If you want the inner content to use the directive scope, you need to use manual transclusion:
app.directive('myElement', function(){
return {
restrict: 'EA',
scope: {},
transclude: true,
link: function(scope, element, attr, ctrl, transclude) {
transclude(scope, function(clone, scope) {
element.append(clone);
});
},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});
By default, transcluded content uses a sibling of the directive scope. I actually don't know how angular handles DOM content for directives that don't use transclude (which is what makes this an interesting question), but I would assume from the behavior you are seeing that those elements use the directive's parent scope by default.
This will work for you
<body ng-app="myApp">
<div ng-controller="MainController">
<input type="button" ng-click="talk()" value="outside directive" />
<div my-element></div>
</div>
</body>
app.directive('myElement', function(){
return {
restrict: 'A',
template: '<input type="button" ng-click="talk()" value="inside directive">',
replace: true,
scope: {},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});
Well, I want to create a "summernote" directive (wysiwyg editor).
This is the template:
<summernote active="false">
<button class="edit" ng-click="edit()">Edit</button>
<button class="save" ng-click="saveData()">Save</button>
<button class="cancel" ng-click="cancel()">Cancel</button>
<div class="summernote"></div> // here will be loaded the summernote script
</summernote>
Directive code:
...
.directive('summernote', function($compile) {
return {
restrict: 'E',
replace: true, // Not sure about what this code does,
scope: {
active: '='
},
link: function($scope, elem, attrs) {
var $summernote = elem.find('.summernote'),
$edit = elem.find('.edit'),
$save = elem.find('.save'),
$cancel = elem.find('.cancel');
$scope.active = false;
$scope.$watch('active', function(active) {
// switch summernote's & buttons' state
// code ...
});
// here I have the buttons' click event defined
// QUESTION 1: Is there a better way?
// I'm doing this because the code below is not working.
$edit.on('click', function() {...});
$cancel.on('click', function() {...});
$save.on('click', function() {...});
// THIS IS NOT WORKING...
$scope.edit = function() {
alert('edit');
};
$scope.cancel = function() {
alert('cancel');
};
}
}
});
When I click save button, I want to send some data, so I have declared saveData on the mainController, but I have to send the div.summernote data and I don't know how to do
<button class="save" ng-click="saveData(getSummernoteDataFromDirectiveScope?)">Save</button>
MainController:
.controller('MainController', function($scope, myDataFactory) {
$scope.saveSummernoteData(data) {
myDataFactory.updateData('field_name', data);
}
}
The main question is, how to works with different? scopes. The thing is that I want to separate the directive logic (edit, cancel, div.summernote behaviour), and the "save" button, which its logic is declared in the MainController (or main $scope, here is my mess).
Are the $scope of the link function and the MainController $scope the same??
I think I have a little mess with all of this, so any help (documentation) would be appreciated.
Documentation can be found here directives and here compile.
Replace: true, would replace the element you have attached the directive to with your template, because in your case your template seems to be inline with the code you don't need that (also it will be removed in the next major release of angular).
Question 1: You shouldn't need to bind $on events ng-click should just work.
If you want to define your save function inside your controller you can pass your function as an attribute and call it inside your save routine defined in your directive:
In your html:
<summernote active="false" on-save="saveData()">
<button class="edit" ng-click="edit()">Edit</button>
<button class="save" ng-click="save()">Save</button>
<button class="cancel" ng-click="cancel()">Cancel</button>
<div class="summernote"></div> // here will be loaded the summernote script
</summernote>
Inside your directive:
scope: {
active: '=',
invokeOnSave: '&onSave'
},
link: function($scope, elem, attrs) {
...
$scope.save = function() {
var data = "some data"; //Whatever mechanism you use to extract the data from your div
$scope.invokeOnSave(data);
};
...
}
I have a directive that has a local scope where a partial contains ng-click.
The Fiddle is there: http://jsfiddle.net/stephanedeluca/QRZFs/13/
Unfortunatelly, since I moved my code to the directive, ng-click does not fire anymore.
The controller and the directive is as follows:
var app = angular.module('myApp', ['ngSanitize']);
app.directive('plantStages', function ($compile) {
return {
restrict: 'E',
transclude: true,
template: '<figure class="cornStages">\
<p ng-transclude style="color: skyblue"></p>\
<hr/>\
<p ng-bind-html="title"></p>\
<p ng-bind-html="subtitle">{{subtitle}}</p>\
<ul>\
<li ng-repeat="stage in stages" ng-click="changePage(stage)">{{stage}}</li>\
</ul>\
</figure>',
scope: {
stages:"=",
title:'#'
},
link: function (scope, element, attrs, ctrl, transclude) {
if (!attrs.title) scope.title = "Default title";
}
};
});
app.controller('myCtrl', function ($scope, $location, $http) {
$scope.stages = ['floraison', 'montaison'];
$scope.changePage = function (page) {
var url = "corn.page.html#/"+page;
console.log("Change page "+page+" with url "+url);
alert("about to change page as follows: document.location.href = "+url);
};
});
The html that invokes it is as follows:
<div ng-controller="myCtrl">
Stages,
<p ng-repeat="stage in stages">{{stage}}</p>
<hr/>
Plant stages
<plant-stages
title="<b>Exploration<br/>du cycle</b>"
subtitle="<em>This is a<br/>sub title</em>"
stages="stages"
>
Inner<br/>directive
</plant-stages>
</div>
Any idea?
You can't access changePage() defined in controller's scope from directive directly, since your directive has isolated scope. However, there are still several ways to do it:
Option 1:
Option 1 is the most simple option. However it is much like a workaround and I don't recommend to use it widely. You can get your controller's scope from element passed to link function and invoke changePage there:
link: function (scope, element, attrs, ctrl, transclude) {
if (!attrs.title) scope.title = "Default title";
scope.changePage = element.scope().changePage; // <= Get parent scope from element, it will have changePage()
}
Option 2:
If you don't have any logic that involves scope defined in the outer controller (as in your example), you can define inner controller for your directive and perform it there:
app.directive('plantStages', function ($compile) {
return {
...
controller: ['$scope', function($scope) {
$scope.changePage = function(page) {
var url = "corn.page.html#/"+page;
console.log("Change page "+page+" with url "+url);
alert("about to change page as follows: document.location.href = "+url);
}
}]
};
});
Option 3:
If you want do reuse logic defined in changePage() in different directives and controllers, the best way to do it is to move the logic to some service that may be injected to both controller and directive:
app.service('changePageService', function() {
this.changePage = function(page) {
var url = "corn.page.html#/"+page;
console.log("Change page "+page+" with url "+url);
alert("about to change page as follows: document.location.href = "+url);
}
});
app.controller('myCtrl', function ($scope, $location, $http, changePageService) {
...
changePageService.changePage('page');
...
});
app.directive('plantStages', function ($compile) {
...
controller: ['$scope', 'changePageService', function($scope, changePageService) {
$scope.changePage = changePageService.changePage;
}]
...
});
Option 4:
You can pass piece of code like changePage(page) as value of some attribute of the directive and inside directive define scope property with '&' that will create a function that will be executed in the outer controller's scope with arguments passed to that function. Example:
JavaScript
app.directive('plantStages', function ($compile) {
return {
restrict: 'E',
transclude: true,
template: '<figure class="cornStages">\
<p ng-transclude style="color: skyblue"></p>\
<hr/>\
<p ng-bind-html="title"></p>\
<p ng-bind-html="subtitle"></p>\
<ul>\
<li ng-repeat="stage in stages" ng-click="changePage({page: stage})">{{stage}}</li>\
</ul>\
</figure>',
scope: {
stages:"=",
title:'#',
changePage:'&'
},
link: function (scope, element, attrs, ctrl, transclude) {
if (!attrs.title) scope.title = "Default title";
}
};
});
HTML
<div ng-controller="myCtrl">
Stages,
<p ng-repeat="stage in stages">{{stage}}</p>
<hr/>
Plant stages
<plant-stages
title="<b>Exploration<br/>du cycle</b>"
subtitle="<em>This is a<br/>sub title</em>"
stages="stages"
change-page="changePage(page)"
>
Inner<br/>directive
</plant-stages>
Plunker: http://plnkr.co/edit/s4CFI3wxs0SOmZVhUkC4?p=preview
The idea of directives is to treat them as reusable components and avoid external dependencies wherever possible. If you have the possibility to define the behavior of your directive in its own controller then do it.
module.directive('myDirective', function () {
return {
restrict: 'E',
controller: function() { /* behaviour here */ },
template: '<div>Directive Template</div>',
scope: {
/* directive scope */
}
};
});
If this is not possible you can pass the function as explained in the linked question (see comment above). Check the updated fiddle.
Angular directive;
.directive('ngFilemanager', function () {
return {
restrict: 'EA',
scope: {
thefilter: '=',
},
link: function (scope, element, attrs) {
},
templateUrl: '/templates/filemanager.html',
controller: FileManagerController
}
Html:
<div id="testcontainer" ng-controller="OtherController">
...
<div ng-click="vm.myfunction">Set Filter</div>
...
<div id="thefilemanager" ng-filemanager thefilter=""></div>
...
</div>
How can i set thefilter value in a function of OtherController?
I tried setting the attribute value by jquery but my ng-view isn't updated correctly then.
You've got bi-directional isolated scope so:
function OtherController($scope){
$scope.myfilter= "";
$scope.setFilter = function(what){
$scope.myfilter = what;
}
}
and HTML:
<div id="testcontainer" ng-controller="OtherController">
<div ng-click="setFilter('fun')">Set Filter</div>
<div id="thefilemanager" ng-filemanager thefilter="myfilter"></div>
</div>
Then when you change $scope.myfilter in the OtherController's scope, scope.thefilter changes in your directive's scope.
If the "other" controller is not a direct parent, you could use $emit or $broadcast depending on where the target is.
Here's an example using $broadcast instead:
app.controller('MainCtrl', function($scope) {
$scope.setFilter = function(what){
$scope.$broadcast('setFilter', what);
}
});
then inside your directive you can listen:
link: function (scope, element, attrs) {
scope.$on('setFilter', function(e, what){
scope.thefilter = what;
});
},
To make it work anywhere, you can $broadcast from $rootScope, but at that point you might want to re-evaluate why you have to do this. Angular itself does this a lot, for example, routeChangeSuccess event, but that doesn't mean you should do it.
This will work if the other controller is a parent of ngFilemanager
<div id="thefilemanager" ng-filemanager thefilter="theFilterValue"></div>
In some other controller
...
$scope.theFilterValue = 'some other value';
...
Look the doc's isolate scope on directives section