how to communicate from one directive to another directive - angularjs

I have tow directives one is for ng-grid another is for pagination when I click page numbers in one directive ng-grid directive should be changed according to that, can I have any idea on that.

There are many ways to achieve it:
For example:
First solution
You can share data between directives:
<directive-one attribute="value" other-attribute="value2" shared-variable="yourData">
<directive-two shared-variable="yourData">
And set $watch inside first directive on that value
scope.$watch('yourData',function(newVal,oldVal){ //your logic called after change });
Second solution
You can use events:
app.directive('first',function(){
return{
restrict: 'E',
template: 'Im first directive!',
scope: true,
link:function(scope,elem,attrs){
scope.$on('event',function(event,args){
alert(args);
});
}
}
});
app.directive('second',function($rootScope){
return{
restrict: 'E',
template: 'Im second directive! <button ng-click="click()">click me!</button>',
scope: true,
link:function(scope,elem,attrs){
scope.click = function(){
$rootScope.$broadcast('event','hello!');
};
}
}
});
Event is sent by $rootScope.$broadcast('event','hello!');. It means event is sent by your root scope downwards to child scopes. http://jsfiddle.net/aartek/fJs69/

Related

Angular Directive vs. Controller function

I have a list of itineraries displayed on a page. Each itinerary has a select button. This select button is a directive.
.directive('itinerarySelectBtn', ['itineraryFactory',
function(itineraryFactory){
return {
restrict: 'E', // defining the container as an element
scope: {
itinerary: '=', // html element we are injecting this directive too
},
replace: true,
templateUrl: '/content/partials/directives/results/results-list.html',
link: function(scope, element, attrs){
element.on('click' , function(){
itineraryFactory.itinerary = scope.itinerary;
});
}
}
}
])
This button does nothing else, other than receive the itinerary object via scope: '=' and then assigns it in my itineraryFactory to the itinerary object. I do this so I can utilise my getter and setter in my itineraryFactory and pull it into my controller.
In my controller:
$scope.itineraries = itineraryFactory.getItinerary();
My question is: Is this overkill, should this be a directive? Alternatively I could just have a $scope.function in my controller and do the same thing. The button could just be part of the ng-repeat that lists the itineraries on the page.
I feel like having a setItinerary() function in my controller would essentially be the same and could be executed via ng-click on the button.
Thoughts?
Regards,
You can do everything in your template.
Say you want to assign $scope.chosenItinerary in your controller from the list in $scope.itineraries
<div ng-repeat="itinerary in itineraries">
<button ng-click="chosenItinerary = itinerary">Pick this one!</button>
</div>

Nested Directives and NgModel

I feel like I'm missing a fundamental concept of Angular directives.
Referring to this Plnkr: http://plnkr.co/edit/WWp9lB6OvxHL8gyBSU5b?p=preview
I have a model of:
{
message: string,
value: number
}
And I have an itemEditor directive to edit that model:
.directive('itemEditor', function() {
return {
replace: true,
templateUrl: 'item.editor.html',
require: 'ngModel',
model: {
item: '=ngModel'
}
};
})
But I want to delegate the editing of value to a custom control:
.directive('valuePicker', function() {
return {
replace: true, // comment this to make it work
templateUrl: 'value.picker.html',
require: 'ngModel',
scope: {
ngModel: '='
},
controller: Controller
};
function Controller($scope, Values) {
$scope.values = Values;
console.log({scope:$scope});
}
})
At current, this code gives the error:
Error: $compile:multidir
Multiple Directive Resource Contention
Commenting the replace: true will allow this code to work. However, I lose the styling instructions from the parent template. I.E: the class form-control is not merged onto the select element.
What is the angular way to make this work?
You are calling value-picker twice here
<value-picker class="form-control" style="width:100%" name="item" value-picker ng-model="item.value"></value-picker>
The value-picker element contains value-picker attribute as well, both being treated as directive which in conflict causing multiple directive error. Remove the attribute value-picker, either call it as element or attribute. Or you can restrict the directive to a specific declaration.
Also remove ng-model from select element of value.picker.html template, which is causing another error:
Multiple directives [ngModel, ngModel] asking for 'ngModel'
So replace: true replaces and appends the current directive attributes to the root level of template element (in your case its select)
Updated Plnkr

Transcluded content not accessible by parent directive AngularJS

My template uses two custom directives
<div my-parent-directive>
<div my-child-directive>
<input name="foo" />
</div>
</div>
The Parent Directive adds a class to the parent element of an input element with name=foo. That parent element is added by the child directive.
angular.module('myApp', [])
.directive('myParentDirective', function() {
return {
restrict: 'A',
transclude: true,
link: function(scope, element, attrs) {
angular.element("input[name=foo]").closest(".control-group").addClass("warning");
},
template: '<div ng-transclude></div>'
}
})
.directive('myChildDirective', function() {
return {
restrict: 'A',
transclude: true,
template: '<div class="control-group"><div ng-transclude></div><span class="help-inline"></span></div>'
}
});
However, I think the child directive hasn't parsed or done its job yet when the parent directive looks for the element. I'm providing the fiddle below as well:
http://jsfiddle.net/bsy8o1p4/
How can I make sure the child directive parses first before the parent?
You would need to wait for one digest cycle to do that. You should let one digest cycle to finish so that the child directive would have rendered. Also note that you cannot select elements by tagname with jqlite selector (# angular.element()). Use a $timeout to let the child directive render and then perform your action and make your selection relative by doing
element.find("input[name=foo]").closest(".control-group")
or you could even do
element.find(".control-group")`
against a more generic angular.element("input[name=foo]") if at all it works:
$timeout(function () {
element.find(".control-group").addClass("warning");
}, false);
Fiddle

How can I create a directive that contains other directives?

I have created some new directives like this:
<admin-retrieve-button></admin-retrieve-button>
<admin-new-button></admin-new-button>
<admin-save-button></admin-save-button>
<admin-reset-button></admin-reset-button>
Is there a way I can create another directive that combines all of these?
you can use ng-transclude for that
app.directive("adminButtonGroup", function(){
return {
restrict: 'E'
transclude: true,
template: '<div class="btn-group"><div ng-transclude=""></div></div>'
}
});
OR if you do not want to transclude and have fixed set of buttons then
app.directive("adminButtonGroup", function(){
return {
restrict: 'E';
template: '<admin-retrieve-button></admin-retrieve-button>' +
'<admin-new-button></admin-new-button>' +
'<admin-save-button></admin-save-button>' +
'<admin-reset-button></admin-reset-button';
}
});
also you can use require, if your child button wants to speak with parent button.
Yes.
Create a template that contains theses tags, which is then used in your new directive by setting the templateUrl property to the path to the template file.

Pass variable from parent directive to child-directive?

I have two directives that look like this:
<g-map centerlong="{{myLocation.long}}" centerlat="{{myLocation.lat}}" zoom="12" id="map" class="map">
<g-marker poslong="{{myLocation.long}}" poslat="{{myLocation.lat}}" title="g-marker"></g-marker>
</g-map>
g-map creates a google map, and now I wish to apply g-marker to it.
Therefore g-marker needs access to the object created in g-map. How can I pass it
directive('gMap', function(googleMaps){
return{
restrict: 'E',
replace: true,
transclude: true,
template: "<div ng-transclude></div>",
scope: true,
link: function(scope, element, attrs){
scope.$on('location', function(){
//här ska den recentreras
})
//create the map
var center = googleMaps.makePosition(attrs.centerlat, attrs.centerlong)
//update map on load
var options = googleMaps.setMapOptions(center, attrs.zoom);
scope.map = googleMaps.createMap(options, attrs.id)
}
}]
};
}).
directive('gMarker', function(googleMaps, $timeout){
return{
//require: "^gMap",
restrict: 'E',
scope: true,
link: function(scope, element, attrs, controller){
var location = googleMaps.makePosition(attrs.poslat, attrs.poslong)
$timeout(function(){
//this is where I want to access the scope.map variable
googleMaps.addMarker(map, location,attrs.title)
}, 0);
}
}
})
As you said, googlempas replaces the html tag, so you use transclude, which makes the parent scope inaccessible to the child scope.
Here are some options:
The easiest solution is to make the parent scope non-isolate, so just remove scope : true in the parent directive, and set scope.map in the controller of the parent directive. This ensures that the property is immediately available in the child link function (no need for $timeout). however, if you are doing dom manipulation, you have to do it in the link function)
Or you could set up bi-directional data binding between parent and child directive:
scope : {map : '=map'}
Or, if you prefer not to expose the whole map object to the child, you could expose a method of the parent scope that the child can call:
In gMap, create a controller:
controller : [function() {
this.addMarker(location, title)
}];
in gMarker, require the gMap:
require: "^gMap"
This injects the gMap controller into your link directive:
link: function(scope, element, attrs, gMapController){
gMapController.addMarker(location, attrs,title)
}
I have made an example for each option in this plunker:
http://plnkr.co/edit/1UyNVhFZl4HtFdqeYhie?p=preview
I'd prefer the third option, since it's not necessary that a child can access the whole map object.
Please note that it's possible that other factors can cause any of the options to fail. Please report back if it doesn't work.

Resources