angularjs component interaction for tabbed content

367 views Asked by At

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">&nbsp;'+
      '    <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?

1

There are 1 answers

1
Redd Sanso On

If you need to avoid the tabsCtrl by getting extended, you should provide in your servPane an hook to the $onDestroy lifecycle event, that can eventually remove this servPane instance from the tabs list.