As part of my layout, I have a banner directive:
.directive('appBanner', [function () {
return {
restrict: 'E',
replace: true,
controller: function ($scope) {
$scope.$on('BANNER_ID_UPDATED', function (e, data) {
// code to fetch banner from db here
});
}
};
}]);
Now, depending on the page or content that's displayed, the banner may have to change. I've been trying to use events to communicate between my banner directive and my other directives. It will fire the following event:
$scope.$emit('BANNER_ID_UPDATED', id);
This doesn't always work. When I place some console.log calls, it seems that the event is sometimes fired before it is being listened for. How can I fix this, or how can I otherwise ensure communication between two unrelated directives?
There is a lot of potential pitfalls when using events. For this particular case I would propose to use a service (you can bundle it with banner directive into the banner submodule).
Your directive can then register the listener to trigger banner update. Then call some updateBannerData(data) of this service which will call the listener to trigger banner change. Here is some code directly from my head (hopefully without errors...)
.factory('bannerService', function() {
var bannerService = {
listener: null,
onUpdate(listener) {
bannerService.listener = listener;
}
updateBannerData: function(data) {
if(listener) {
listener(data);
}
}
};
return bannerService;
})
// inject bannerService here:
.directive('appBanner', [function (bannerService) {
return {
restrict: 'E',
replace: true,
controller: function ($scope) {
bannerService.onUpdate(function (data) {
// code to fetch banner from db here
});
}
};
}]);
Related
Currently, I have two directives and parent controller, all on some form.
I would like to implement "related field"-like functionality. That is, data from one directive should be passed to other preferably through controller so that I have full flexibility in "routing" data between fields.
So I have this set-up:
Controller:
$scope.$watch('form.model.object', function (newValue, oldValue) {
$scope.$broadcast("cmsRelatedChanged", {key: 'subcarriers', value: newValue['#identity']});
};
Second directive:
$scope.$on('cmsRelatedChanged', function(event, aditionalData) {
$scope.related[aditionalData.key] = aditionalData.value;
console.log("[CMSCollectionDirective] updated with: ", aditionalData, " event: ", event);
});
It do not work first time, when form is pre-populated with existing object But following changeds made in browser work.
As if second directive $on registered that listener after first $broadcast was made.
Additional info:
Everything in second controller is done in link:, and second directive is also second in DOM.
Questions:
How can I delay that first broadcast enough for $on to register listener?
EDIT:
Added console.log("Adding listener") before $scope.$on, and it in fast is executed after first $broadcast, and that's why its not cough.
Maybe try to not use $broadcast and $on. For me this solution works to share data between directives:
JS:
app
.controller('MainCtrl', function () {
$scope.sharedData = {
data: 1
};
$scope.testAction = function () {
vm.sharedData.data++;
}
})
.directive("dirFirst", function () {
var directive = {
link: link,
restrict: 'AE',
scope: {
sharedData: '='
}
};
return directive;
function link(scope, element) {
scope.$watch(function () {
return scope.sharedData.data;
}, function () {
console.log("dirFirst: " + scope.sharedData.data)
});
element.on('click', function () {
scope.sharedData.data++;
console.log(scope.sharedData.data);
});
}
})
.directive("dirSecond", function () {
var directive = {
link: link,
restrict: 'AE',
scope: {
sharedData: '='
}
};
return directive;
function link(scope, element) {
scope.$watch(function () {
return scope.sharedData.data;
}, function () {
console.log("dirSecond: " + scope.sharedData.data)
});
element.on('click', function () {
scope.sharedData.data++;
console.log(scope.sharedData.data);
});
}
});
HTML usage:
<button dir-first shared-data="sharedData">
Directive1
</button>
<button dir-second shared-data="sharedData">
Directive2
</button>
<button ng-click="testAction()">ControllerAction</button>
Biggest issue in my question ended up being order of initialization of directives and inability to use $scope as transfer medium between controller and 2nd directive.
Solution would be to use service as such medium.
Controller still register watch on 1st directive, and as soon as it gets corresponding event put received data in service.
Data availability is signalled to 2nd directive by emmiting event. However 2nd directive check availability of data in service on its own on start up.
This way 2nd directive can be initialized long after some data was sent, and still act upon it.
I have a directive defined that contains a function binding:
angular
.module('my.module')
.directive("myDirective", [
function () {
return {
controller: 'MyDirectiveController',
templateUrl: 'controls/myDirective/myDirective.html',
replace: true,
restrict: 'E',
scope: {
openFunction: '&'
}
};
}]);
in my source html, I'm defining the directive like so:
<my-directive
open-function="openDrawer(open)"
</my-directive>
Then, in my directive controller, I'm calling it like this:
$scope.openFunction({
open: function() {
doSomething()
.then(function () {...})
.finally(function () {...});
}
});
And here's the parent controller openDrawer function:
$scope.openDrawer = function (open) {
$scope.alerts = null;
$scope.showActions = false;
if (service.editing) {
service.closeAndSave();
openDrawerAfterDelay(open);
} else if (otherService.editing) {
otherService.commit();
openDrawerAfterDelay(open);
} else {
open();
}
};
The problem is, when my directive controller calls the $scope.openFunction() function, nothing happens. Am I able to pass a function, to the bound function like this?
This appears to be something unrelated. I've reproduced the code in plunker here: http://plnkr.co/edit/SuzCGw5WVRClEwEcA17l?p=preview
Possible leads that I can see without seeing the entire code are:
I'm mocking the service and otherService, so the if conditions are always false - the problem may lie in the calls within the if blocks.
I'm unsure of what the doSomething promise is doing, this may be a cause.
I am using HighCharts for representing Stacked Bar Charts as well as Line Charts.My Requirement is onClick of Stacked Bar Chart the Line Chart should be displayed corresponding to the portion clicked.I acheived it using $broadcast and $on. When i use $emit in highchart.js it is not working, but when i use $broadcast or $scope.$parent.$broadcast it works. Can someone please let me know why the upward flow of event is working and downward its not working. My both directive are reffering to same Controller.
In my case why $scope.$emit or $scope.$broadcast alone does'nt work?
index.html
<div ng-controller='MainCtrl'>
<chart values='basicAreaChart' id="container" technology="3G" url="../json/HighCharts.json"></chart>
<linechart values='basicLineChart' id="container2" technology="LTE" click='clicked'></linechart>
</div>
controller.js
angular.module('chartsExample', ['lineChart','highchart']).controller('MainCtrl',
[ '$scope', '$http', function($scope, $http) {
} ]);
highchart.js
'use strict';
angular.module('highchart', []).directive('chart', function() {
return {
restrict: 'E',
scope: {
chartData: "=values"
},
transclude: true,
replace: true,
controller:function($scope,$element,$attrs,$http){
$http.get($attrs.url).success(function(data) {
$scope.chartData = data;
console.log('technology='+$attrs.technology+' '+$scope.a);
});
},
link: function($scope, $element, $attrs,$rootScope) {
'+$attrs.type);
$scope.$watch('chartData', function(value) {
if (!value){
//console.log('In return');
return;
}
// Initiate the chartData.chart if it doesn't exist yet
$scope.chartData.chart = $scope.chartData.chart ;
$scope.chartData.chart.renderTo =$attrs.id
$scope.chartData.chart.events = {
click : function() {
console.log('Chart clicked '+$scope.isClicked+' '+$scope.a);
$scope.click="false";
$scope.$parent.$broadcast('HIGH_CHART_CLICKED', 'clicked');
//This works but $scope.$emit or $scope.$broadcast does'nt work???
}
};
$scope.chartObj = new Highcharts.Chart($scope.chartData);
},true);
}
};
});
linechart.js
'use strict';
angular.module('highchart', []).directive('chart', function() {
return {
restrict: 'E',
scope: {
linechartData : "=values"
},
transclude: true,
replace: true,
controller:function($scope,$element,$attrs,$http){
$scope.$on('HIGH_CHART_CLICKED',function(e,data){
console.log('Line chart Listening event');
$http.get("../json/NewLineChart.json").success(function(data) {
$scope.linechartData = data;
console.log('this is from line chart');
});
});
},
link: function($scope, $element, $attrs,$rootScope) {
'+$attrs.type);
$scope.$watch('linechartData', function(value) {
if (!value){
//console.log('In return');
return;
}
// Initiate the chartData.chart if it doesn't exist yet
$scope.chartData.chart = $scope.chartData.chart ;
$scope.chartData.chart.renderTo =$attrs.id
}
};
$scope.chartObj = new Highcharts.Chart($scope.chartData);
},true);
}
};
});
Ok. You have two directives with the same name (chart). This answer assumes the second is actually supposed to be linechart
chart and linechart are siblings. So when you $broadcast an event from chart, it has no children, and no one hears it.
Likewise, if you $emit and event from chart, then it will get emitted to the parent scope (MainCtrl) and then on up the hierarchy to $rootScope. It will never get to linechart
When you use $scope.$parent.$broadcast, then you are telling MainCtrl to broadcast the event to it's children, which happen to be chart and linechart so it works.
Long story short - angular is working as expected here.
Unlike browser events, angular events don't bubble down and then back up. They are one way.
You could have MainCtrl listen for the event and then re-broadcast it:
In MainCtrl:
$scope.$on('HIGH_CHART....', function() {
//rebroadcast to children
var args = Array.prototype.shift.apply(arguments);
$scope.$broadcast('HIGH_CHART.....', args )
};
(or something similar. You have to remove the first element from arguments and then call broadcast with the remaining arguments - outside the scope of the question).
I am trying to load language constants from service to a directive and show them to user.
I have discovered that if I just use {{}} in div, then the text is not rendered.
However, by adding any character, i.e. '.' will make it load.
I would be grateful, if someone can explain, what is going on behind scenes and why I need those extra characters.
Directive code
directive('projectHeader', ['LangService', function(langService) {
return {
restrict: 'E',
replace: true,
scope: true,
link: function postLink($scope, tElement, tAttrs, controller) {
$scope.lang = langService.getLocalisedStrings();
},
templateUrl: "app/header.html"
};
}])
header.html
<div class="header">{{lang.header}}.</div>
LangService definition
angular.module('project.services').factory('LangService', ['$http', function ($http) {
var langConstants;
return {
init: function(lang) {
$http.get("app/lang/"+ lang + ".properties").then(function(response){
langConstants = response.data;
});
},
getLocalisedStrings: function () {
return langConstants;
}
};
}]);
You might have a race between the postLink and the $http in the init method. Try adding a watch on getLocalisedStrings() in the directive so that $scope.lang gets updated as soon as getLocalisedStrings() returns some data.
I have two directives:
module.directive('modal', function () {
return {
restrict: 'A',
templateUrl: "/templates/ui/components/modal.htm",
replace: true,
link: function ($scope, element, attrs) {
console.log("Loaded modal directive.");
},
controller: function ($rootScope, $scope) {
$scope.isOpen = false;
$scope.open = function () {
$scope.isOpen = true;
};
$scope.close = function () {
$scope.isOpen = false;
};
// Global
$rootScope.$on('openModal', function () {
console.log("open"); // How to call open function here?
});
$rootScope.$on('closeModal', function () {
console.log("close"); // How to call close function here?
});
}
}
});
module.directive('popUpWindow', function () {
return {
restrict: 'A',
templateUrl: "/templates/ui/components/pop-up-window.htm",
replace: true,
transclude: true,
link: function ($scope, element, attrs) {
console.log("Loaded pop-up window directive.");
$scope.title = attrs["title"];
},
controller: function ($rootScope, $scope) {
$scope.isOpen = false;
$scope.open = function () {
$scope.isOpen = true;
$rootScope.$emit('openModal');
};
$scope.close = function () {
$scope.isOpen = false;
$rootScope.$emit('closeModal');
};
}
}
});
The idea is that there is only ever one modal for all possible instances of popUpWindow.
I have no idea how to get these two to talk to each other. How do I get hold of my modal and call it's open and close functions? Where should I put the open and close functions, controller or link? It seems like the controller but the link has access to element.. Should I be looking to get an instance of the modal or is there some neat Angular way of doing this?
I've got a function on the modal firing using $emit on the pop up window but this doesn't feel like a great way of communicating between directives.
I've trawled the net and tried a lot of stuff but not found anything that seems to fix this. It doesn't seem obvious.
Directives are a way to teach HTML new tricks and it is primarily used for creating some reusable HTML components, hence the functionality of a directive should be self-contained and it should be independent on other directives.
If you want to maintain multiple popup windows, for instance, you should make the popupWindow directive to be driven by the model passed in. And in your controller, you should be able to track all popupWindows by tracking the model.
Directives should talk to each other via controller though you can make it happen by using message broadcasting.
Hope it can shed some light on.