AngularJS two directives with template on same element - angularjs

I'm trying to create a context menu directive for my angular application.
I want this directive to be used for any element in the application that needs a context menu.
The problem is that in my app there are many 'user controls', that is - directives for extending buttons, inputs, grid etc. If I try to put the my-context-menu directive on any of them I get a multidir error, since both of the directives are defining templates.
The context menu directive looks something like this:
angular.module('myApp').directive('myContextMenu', function () {
var myContextMenu= {};
myContextMenu.restrict = 'A';
myContextMenu.templateUrl = 'templates/myContextMenuTemplate.html';
// Here I have scope and controller with all the functionality
return myContextMenu;
});
Another directive for example:
angular.module('myApp').directive('myGrid', function () {
var myGrid= {};
myGrid.restrict = 'E';
myGrid.templateUrl = 'templates/myGridTemplate.html';
// Here I have scope, controller and link function with all the functionality
return myGrid;
});
I want to have a context menu on the grid, and that in the grid directive I will be able to access the context menu controller.
What I tried is this: <my-grid id="grid" my-context-menu /> which failed as expected...
Do you have any solution or other idea on how to achieve this?
Thanks in advance!
My angular version: 1.3.8

You can share controllers between directives writing the same controller on the controller attribute of the directive object. And you can use transclude to embed html inside a directive with its own template. https://docs.angularjs.org/api/ng/directive/ngTransclude

Related

Override controller functions in directive is a good idea?

I have a generic functionality implemented inside a controller. When i write a directive is it good idea to extend those controller functions inside the directive ?
Like in below implementation inside the link function.
var superCancel = scope.cancel;
// Overriding the cancel function from the controller
scope.cancel = function() {
if(element.hasClass('ng-dirty')){
element.removeClass("ng-dirty");
}
// Calling controller cancel
superCancel();
};
If your directive html is coming inside the controller in html then you can use $parent instead of rewriting
in directive:
$scope.$parent.cancel(); // only if controller coming as parent
If the controller is not coming as parent it's better to use a service or factory to implement that
Read here for more
It is better to have the directive use an attribute to set a parent scope value.
For example:
JS
app.directive("setModelApi", function() {
return {
require: "ngModel",
link: function(scope,elem,attrs, ngModelCtrl) {
scope.$eval(attrs.setModelApi, {$api: ngModelCtrl})
}
}
});
In the above example, the setModelApi directive evaluates the Angular Expression defined by the set-model-api attribute with $api exposing the ngModelController.
HTML
<input ng-model="x" set-model-api="xmodel=$api">
<button ng-click="xmodel.$setPristine()">Set Pristine</button>
The setModelApi directive sets the xmodel scope variable to the ng-model-controller API.
The button invokes the $setPristine method of the ng-model-controller API.
From the Docs:
$setPristine();
Sets the control to its pristine state.
This method can be called to remove the ng-dirty class and set the control to its pristine state (ng-pristine class). A model is considered to be pristine when the control has not been changed from when first compiled.
-- AngularJS ngModelController API Reference -- $setPristine
By using an HTML directive attribute to define the scope variable to which the API attaches, different inputs can use the directive and the connections are exposed in the HTML.
The DEMO on JSFiddle

Angular hide dirrective depending on action in another directive

I'm new in angular and i'm looking for the best way to do what I want.
In my main page I have 2 directives, one is used to display a button (and maybe other stuff). And another used to display a kind of dialog box/menu.
Each directive has its own controller.
I want to show or hide the second directive when I click on the button in the first one.
I don't really know what are goods or wrong approaches. Should I use a service injected in both controller and set a variable with ng-show in the second directive? This solution doesn't really hide the directive because I need a div inside the directive to hide its content and isn't too much to use a service only for one boolean?
Should I use a kind of global variable (rootscope?) or inject the first controller inside the second one?
Or maybe use a third controller in my main page (used with a service?) or use only one controller for both directive?
Basically without directive I would probably used only one main controller for my whole page and set a variable.
In fact the first directive is just a kind of button used to display "something", and the second directive just a kind of popup waiting a boolean to be displayed. That's why I finally used a service containing a boolean with a getter and a setter to avoid any interaction beetween both controller.
My both controller use this service, the first one to set the value when we click on the element and the second controller provide just a visibility on the getter for my ng-show.
I don't know if it is the best way to do but I am satisfied for now.
Small example here (without directive but with same logic) :
http://codepen.io/dufaux/pen/dXMrPm
angular.module('myModule', []);
angular.module("myModule")
.controller("ButtonCtrl", buttonCtrl)
.controller("PopUpCtrl", popUpCtrl)
.service("DisplayerService", displayerService);
//ButtonCtrl
buttonCtrl.$inject = ["DisplayerService", "$scope"];
function buttonCtrl(DisplayerService, $scope) {
var vm = this;
vm.display = function(){
DisplayerService.setDisplay(!DisplayerService.getDisplay());
}
}
//PopUpCtrl
popUpCtrl.$inject = ["DisplayerService"];
function popUpCtrl(DisplayerService) {
var vm = this;
vm.displayable = function(){
return DisplayerService.getDisplay();
}
}
//Service
function displayerService(){
var vm = this;
vm.display = false;
vm.setDisplay = function(value){
vm.display = value;
}
vm.getDisplay = function(){
return vm.display;
}
}
--
<body data-ng-app="myModule">
<div data-ng-controller="ButtonCtrl as btnCtrl" >
<button data-ng-click="btnCtrl.display()">
display
</button>
</div>
[...]
<div data-ng-controller="PopUpCtrl as popUpCtrl" >
<div data-ng-show="popUpCtrl.displayable()">
hello world
</div>
</div>
</body>

angularjs-- declenching events

Please, how can I change this code into angularJs
$('a.product_add').on('click', function(event){
event.preventDefault();
var collectionHolder = $('#task_tags');
var prototype = collectionHolder.attr('data-prototype');
form = prototype.replace(/__name__/g, collectionHolder.children().length);
collectionHolder.append(form);
});
First of all you need to show us what you've tried, but I'll write something here to help you
You should make a directive because you're using jquery code. Read more about directives here
AngularJS directives are extended HTML attributes with the prefix ng-.
The ng-app directive initializes an AngularJS application.
The ng-init directive initializes application data.
The ng-model directive binds the value of HTML controls (input,
select, textarea) to application data.
Example of a directive
app.directive('myDirective', function(){
function link($scope,$elem,$attrs){
$elem.on('click', function(event){
// click event code here
});
}
return {
link:link,
scope:{},
restrict:'A'
}
})
Example of usage for myDirective:
<a class='product_add' my-directive>link</a>
We can use angular custom directives.
Now you can access the element in the directive and do the same operations in the directive.
<directive-element ng-click=appendFunction()></directive-element>

How do I load an external HTML file and have the scope variables renderred properly in angularJS?

What I am trying to do is after clicking on a buddy in the buddy list, load a chat dialog template HTML file without disturbing other elements in current DOM just like chatting in facebook.
My problem is that after loading the html template file the scope variables such as {{contact.jid}} are not properly rendered, and the controller for the dialog is not even called.
How can I force a rerender or a call on the controller so that those variables are properly renderred? Or should I not use the jQuery.load function to do this? I can't figure out any other way.
Thank you.
Code of the controllers:
// Controller for the chat dialog
ctrl.controller('ChatCtrl',function($scope){
$scope.test = "Test"
});
// Controller for the buddy list
// $scope.toggleDialog is called when a buddy is clicked
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.toggleDialog = function(to){
$.("#chatarea").load("chatdialog.html")
};
});
The controller function of chat dialog is not called after loading chatdialog.html which has an attribute of ng-controller, so the {{test}} variable is not set.
You need to wrap your content that will be compiled inside a directive.
This directive receives the variables and the HTML that will be compiled.
You can wrap this directive in the element that you like:
http://plnkr.co/edit/RYVCrlDmgVXT3LszQ9vn?p=preview
For example, given the following html and variables:
// In the controller
$scope.controllerHtml = '<span>{{variables.myVariable}}</span>';
$scope.controllerVariables = {myVariable: 'hello world'};
<!--In the HTML-->
<div compile-me html-to-bind="controllerHtml" variables="controllerVariables"></div>
You will get the following:
<div>
<span>hello world</span>
</div>
You are loading the external HTML via jQuery and Angular has no way of knowing how to use it. There are two ways to solve this issue:
use ngInclude to load the template from the server, angular will load it from the server and compile it for you to use.
continue to use jQuery load the HTML from the server and use the $compile service to teach angular how to use it
I would highly suggest using method #1 to load your external template.
I suppose the:
$.("#chatarea").load("chatdialog.html")
is the jQuery .load, or something similar. I would get the template via ngInclude, checking if test is setted or not; html:
<div id="chatarea" ng-if="test">
<div ng-include="'chatdialog.html'"/>
</div>
controller:
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.test = '';
var callbackFunction = function(data) {
$scope.test = data.test;
};
$scope.toggleDialog = function(to){
AjaxRequestToGetBuddyInfoAndMessages(to, callbackFunction);
};
});
Obviously test will be a more complex object, so the ngIf test will be different (and you will need to take into account the fact that:
$scope.test = data.test
if they are objects, they will lose the reference and have an unwanted behaviour).

How to trigger ngAnimate class sequence when using element.addClass inside directive

Inside of a directive I'm adding various classes on an element based on user interaction.
How can I get the ngAnimate class sequence (e.g. my-class-add -> my-class-add-active) when using element.addClass in place of ngClass directive?
I want to use CSS transitions, not JS animations.
Thanks.
You need to add the class via the animate service (angularjs 1.2) like
module.directive('directive', function ($animate) {
return {
restrict:"A",
link: function($scope,$element) {
$element.on("click", function() {
$animate.addClass($element,"my-animation");
});
};
}
});
Doc
Ideally you'd delegate that to another directive which is tied in with ngAnimate (i.e. ng-show, ng-if, ng-repeat .etc.)
You can set a scope property and let another directive handle the animation classes.

Resources