Communicate between custom directives in angularJS - angularjs

I'm relatively new to AngularJS and working on creating tabs in a page. Till now I have resolved my problems with angularjs by searching a lot on internet but I can't resolve this. Hope anyone can help me with ideas and better knowledge of angularjs.
I have two custom directives tabset and tab. 'Tabset' is the directive to maintain the tabs and 'tab' is for a single tab.
app.directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'tabset.html',
bindToController: true,
scope: {},
controller: function($scope){
$scope.tabs = [];
this.addTab = function(tab) {
$scope.tabs.push(tab);
}
console.log("In tabset controller");
},
link : function(scope){
console.log("In the tabset link");
}
}
});
//Custom Directive for the tab controls
app.directive('tab', function() {
return {
restrict: 'E',
transclude: true,
template: '<h2>Welcome to Stackoverflow</h2> <div role="tabpanel" ng-transclude></div>',
require : '^tabset',
scope: {},
link : function(scope, elem, attr, tabsetCntrl) {
tabsetCntrl.addTab(scope);
console.log("In the tab link");
}
}
});
I call these directives in my HTML page as shown below:
<tabset>
<tab>
This is one tab
</tab>
<tab>
This is another tab
</tab>
</tabset>
But, when I run the code, the link function of the tab directive is not running. The 'require : ^tabset' option gets the controller from the tabset, but the link function of the tab directive is not working.

Try adding controllerAs: '$ctrl' to your tabset directive.
Like:
angular.module('app').directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'tabset.html',
bindToController: true,
controllerAs: '$ctrl', // <---- HERE
scope: {},
controller: function($scope){
$scope.tabs = [];
this.addTab = function(tab) {
$scope.tabs.push(tab);
}
console.log("In tabset controller");
},
link : function(scope){
console.log("In the tabset link");
}
}
});
Tested
Further info found by checking the error seen in console here

Prudhvee, take a look at this demo i did to understand the making of angular tabs using nested directives.
app.directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: [ "$scope", function($scope) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
}],
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
'{{pane.title}}' +
'</li>' +
'</ul>' +
'<div class="tab-content" ng-transclude></div>' +
'</div>',
replace: true
};
});
app.directive('tab', function() {
return {
require: '^tabset',
restrict: 'E',
transclude: true,
scope: { title: '#' },
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
template:
'<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
'</div>',
replace: true
};
})
http://plnkr.co/edit/BJWWw2?p=preview

Related

AngularJS $emit.$on query

Plunker is here:-
http://plnkr.co/edit/JaEi7ftnokYhdvBn4fRh?p=preview
I have emitted data and its available in $on() under data.name.
However, I am not able to display $scope.dumbevent1_name in UI using {{ dumbevent1_name }}
What am I doing wrong ?
Code:-
// Code goes here
angular.module('myapp', [])
.controller('mycontroller', mycontroller)
.component('semantic3', { // semantic3
restrict: 'E',
transclude: true,
template: `<h6> imsemantic3</h6> <div ng-transclude></div>
`,
controller: semantic3Controller,
})
.component('semantic2', { // semantic2
scope: {},
restrict: 'E',
transclude: true,
template: `<h6> imsemantic2</h6> <div ng-transclude></div>
`,
})
.component('semantic1', { // semantic1
scope: {},
restrict: 'E',
transclude: true,
template: `<h6> imsemantic1</h6> <div ng-transclude></div>
`,
})
.component('dumbCompDisplayNames', {
scope:true,
restrict: 'E',
template: `<h6>h6dumb1</h6> {{$ctrl.names}}`,
bindings:{
names : '<' //one-way data binding
},
controller: dumbController,
});
function semantic3Controller($scope, $element, $attrs){
var self = $scope;
$scope.$on('dumbevent1', function(event, data){
console.log(data.name); //works. received.
self.dname = data.name;
$scope.dumbevent1_name = data.name; //works
// console.log($scope.dumbevent1_name); //works
// capturename( data); //works
});
// console.log($scope.dumbevent1_name); //doesnt work.
// function capturename(x){
// $scope.dumbevent1_name = x.name;
// console.log($scope.dumbevent1_name); //works
// }
}
function dumbController($scope, $element, $attrs){
// console.log($scope); //works. these r NOT positional injection
// console.log($element);
// console.log($attrs);
$scope.$emit('dumbevent1', {name: 'namedumb1'});
}
// main controller
function mycontroller(){
console.log('i am mycontroller');
}
The time wasted on commenting repeatedly is worthwhile somehow?? If don't have time to look at the plunker don't waste time commenting. Like I said I fixed the issue.

Cannot bind to controller without directive 'tabset's controller

I'm new to angular, and I'm facing some problem while making the directives to communicate, getting Cannot bind to controller without directive 'tabset's controller
Here is my Code
HTML code
<tabset>
<tab heading="Tab 1">
Hi
</tab>
<tab heading="Tab 2">
Second tab
</tab>
</tabset>
Directive for the above code
angular.module('testApp')
.directive('tab', function(){
return{
restrict: 'E',
transclude: true,
template: '<h1>Hello Developer!!</h1><div role="tabpanel"><ng-transclude></ng-transclude></div>',
require: '^tabset',
scope: {
heading: '#'
},
link: function(scope, elem, attrs, tabsetCtrl){
tabsetCtrl.addTab(scope);
}
}
}).directive('tabset', function(){
return{
restrict: 'E',
transclude: true,
scope: { },
templateUrl: 'views/tabset.html',
bindToController: true,
controllerAs: 'tabset',
contrller: function(){
var self = this;
this.tabs = [];
self.addTab = function addTab(tab)
{
self.tabs.push(tab);
}
}
}
});
Thanks in advance.
I believe this is happening because you spelled "controller" wrong. Under controllerAs, you have "contrller"

AngularJS ng-transclude works unexpectedly with custom directives

I coded two nested directives, the outer one is wrapping all inner ones to build a nav. But seems the ng-transclude all inner directives. I have debugged a while, but can't figure it out. Below are the directives and the html snippet;
angular.module('custom.directives', [])
.directive('navTabs', ['$log', function($log){
$log.info('instantaiting directive navTabs...');
var _directive = {
scope: true,
restrict: 'EA',
replace: true,
transclude: true,
templateUrl: '/jsex/ab/templates/tabs.html',
controller: ['$scope', function($scope) {
$scope.currentTab = 0;
$scope.tabs = [];
$scope.selectTab = function(tab) {
$scope.currentTab = tab;
}
return $scope;
}]
};
$log.info('finish instantaiting directive navTabs');
return _directive;
}])
.directive('navTab', ['$log', '$filter', function($log, $filter){
$log.info('instantaiting directive navTab...');
var _directive = {
scope: true,
restrict: 'EA',
replace: true,
transclude: true,
require: '^navTabs',
template: '<div ng-show="showTab()" ng-transclude></div>',
link: function(scope, element, attrs, navTabs) {
var tabId = navTabs.tabs.length;
$log.info("tabId = " + tabId + " - " + attrs.tabHeading);
navTabs.tabs.push(attrs.tabHeading);
$log.info($filter('json')(navTabs.tabs));
scope.showTab = function() {
return tabId == scope.currentTab;
};
}
};
$log.info('finish instantaiting directive navTab');
return _directive;
}]);
<nav-tabs>
<nav-tab class="tab-content" tab-heading="First Tab">
<h1> First Tab</h1>
<p>Simple tab 1
</nav-tab>
<nav-tab class="tab-content" tab-heading="Second Tab">
<h1> Second Tab</h1>
<p>Simple tab 2
</nav-tab>
<nav-tab class="tab-content" tab-heading="Third Tab">
<h1> Third Tab</h1>
<p>Simple tab 3
</nav-tab>
</nav-tabs>
Thanks.

AngularJS - accessing parent directive properties from child directives

This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}

AngularJS: how to watch tab selection?

I am using angularjs tab and pane example components
angular.module('components', []).
directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
},
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
'{{pane.title}}' +
'</li>' +
'</ul>' +
'<div class="tab-content" ng-transclude></div>' +
'</div>',
replace: true
};
}).
directive('pane', function() {
return {
require: '^tabs',
restrict: 'E',
transclude: true,
scope: { title: '#' },
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
template:
'<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
'</div>',
replace: true
};
})
SomePage.html
<tabs>
<pane title="Datos Generales">
<div ng-include src="'/resources/js/page/running/admin/templates/update-data.html'"></div>
</pane>
<pane title="LocalizaciĆ³n">
<div ng-include src="'/resources/js/page/running/admin/templates/update-location.html'"></div>
</pane>
<pane title="Datos Contacto">
<div ng-include src="'/resources/js/page/running/admin/templates/update-contacts.html'"></div>
</pane>
<pane title="Variantes">
<div ng-include src="'/resources/js/page/running/admin/templates/update-variants.html'"></div>
</pane>
</tabs>
In the second pane there is a GoogleMap. I would like to refresh the google map when the second pane is selected.
I don't know how to get selected pane in the controller. I've tried $scope.panes but is undefined
Here are three ways you could solve your problem:
define a method on your controller, specify that method in an attribute on the tabs element, use the '&' syntax on the tabs directive's isolate scope definition to enable the directive to call the specified method when a pane is selected.
define an object on your controller's scope with a "selectedPane" property. Use the '=' sytnax on the tabs directive's isolate scope definition to enable two-way databinding. Set this property whenever a pane is selected.
have the tabs directive $emit an event, and have your controller listen for it using $on.
Update: #qopuir asked for more details about option 1.
Suppose the controller method is something like the following:
$scope.paneChanged = function(pane) {
$scope.selectedPane = pane
...
}
On the tabs element, we'll specify this function with attribute pane-changed:
<tabs pane-changed="paneChanged(selectedPane)">
Then, the tabs directive can use the '&' syntax to call this method:
.directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
scope: { paneChanged: '&' },
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
$scope.paneChanged({selectedPane: pane});
}

Resources