I am using the example from AngularJS Developer Guide to create a tabbed interface (https://docs.angularjs.org/guide/component - Intercomponent Communication). For static content, everything works fine, but when I update the underlying document (data) structure, the tab list is just extended with tabs for the new content and the previous tabs just link to nothing (as that content has disappeared). I am convinced there must be an "Angular way" to update the tabs controller, so it at any time will reflect the actual content of the panes controller. My attempts (shown as comments in the code below) using $onInit() or $onChanges() completely removes the tabs. I probably (hopefully just) miss a proper binding between the two components, but I can't figure out how to do that.
Here's my code:
HTML:
<div class="alert alert-danger">
<serv-tabs>
<acp-service srv="srv" ng-repeat="srv in data.services"></acp-service>
</serv-tabs>
</div>
JavaScript:
app.component('acpService', {
template:
'<serv-pane title="{{$ctrl.srv.LABEL}}">'+
' <div>SIK: <input type="text" ng-model="$ctrl.srv.SIK"> '+
' <input type="button" class="btn-torch" ng-click="$ctrl.searchForServ()"></input>'+
' </div>'+
'</serv-pane>',
bindings: {
srv: '='
},
controller: function AcpServiceController($rootScope, AcpModel) {
// Data model -----------------------------
var acpdata = AcpModel.getData();
this.services = acpdata.services;
// Event handlers -------------------------
this.searchForServ = function() {
$rootScope.DEBUG = "searchForServ("+this.srv.SIK+") was triggered";
};
}
});
// TAB navigation for services
app.component('servTabs', {
transclude: true,
controller: function ServTabsController() {
var panes = this.panes = [];
this.$onInit = function() {
//panes = [];
console.log('servTabs::onInit() called');
};
this.$onChanges = function(chObj) {
//this.panes = [];
//panes.length = 0;
console.log('servTabs::onChanges() called');
};
this.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
};
this.addPane = function(pane) {
if (panes.length === 0) {
this.select(pane);
}
panes.push(pane);
};
},
template:
'<div class="tabbable">\n'+
' <ul class="nav nav-tabs">\n'+
' <li ng-repeat="pane in $ctrl.panes" ng-class="{active:pane.selected}">\n'+
' <a href="" ng-click="$ctrl.select(pane)">{{pane.title}}</a>\n'+
' </li>\n'+
' </ul>\n'+
' <div class="tab-content" ng-transclude></div>\n'+
'</div>\n'
});
app.component('servPane', {
transclude: true,
require: {
tabsCtrl: '^servTabs'
},
bindings: {
title: '@',
},
controller: function() {
this.$onInit = function() {
this.tabsCtrl.addPane(this);
console.log(this);
};
this.$onChanges = function(chObj) {
console.log('servPane::onChanges() called');
};
},
template: '<div class="tab-pane" ng-show="$ctrl.selected" ng-transclude></div>'
});
The AcpModel can retrieve a new list of services from the backend, and the content blocks (servPane) looks alright, but the tab-list (servTabs) just gets extended.
Does anyone have some hints to what I should do?
If you need to avoid the
tabsCtrl
by getting extended, you should provide in yourservPane
an hook to the$onDestroy
lifecycle event, that can eventually remove thisservPane
instance from the tabs list.