1

I'm using angularjs and I wrote a combobox and a tabs directive for my application which I want to use like this:

<tabs>
   <tab>Some content</tab>
   <tab>Some other content 
     <my-combobox options="options" selection="selection">
   </tab>
</tab>

My problem is that at the moment, I can't watch properties changed by the combobox when it's inside a tab, meaning if I do this:

$scope.$watch("selection", function(){
     console.log("this code doesn't run when selection changes")
});

My watch doesn't fire when the user selects item from the combobox.

What puzzles me is that it does work when I take the combobox out of the tab

<!-- The watch works where -->
<my-combobox options="options" selection="selection"> 
<tabs>
   <tab>Some content</tab>
   <tab>Some other content</tab>
</tab>

So my question is how are the tabs affecting the watch of elements transcluded in them and how to fix it?

this is the code for the tabs directive:

angular.module("ip").directive("tabs", [function () {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        templateUrl: "/Content/Directives/tabs.html",
        bindToController: true,
        controllerAs: "tabsController",
        scope: {
            onTabSelected: "=?"
        },
        controller: function () {
            var self = this;
            self.tabs = [];

            self.addTab = function (tab) {
                self.tabs.push(tab);

                if (self.tabs.length === 1) {
                    tab.active = true;
                    self.onTabSelected(tab);
                }
            }

            self.select = function (selectedTab) {
                angular.forEach(self.tabs, function (tab) {
                    if (tab.active && tab !== selectedTab) {
                        tab.active = false;
                    }
                });

                selectedTab.active = true;
                if (self.onTabSelected) { self.onTabSelected(selectedTab); }
            }
        }
    };
}]);

(Edit) This is my code for the tabs.html file:

<div class="ip-tabs">
    <ul>
        <li role="presentation" ng-repeat="tab in tabs" ng-click="select(tab)" ng-class="tab.active ? 'active' : ''">
            <span>{{tab.tabTitle}}</span>
        </li>
    </ul>

    <ng-transclude>
    </ng-transclude>
</div>

And the tab.js directive

angular.module("ip").directive("tab", [function () {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        templateUrl: "/Content/Directives/tab.html",
        require: "^tabs",
        scope: {
            tabTitle: "@"
        },
        link: function (scope, element, attr, tabsCtrl) {
            scope.active = false;
            tabsCtrl.addTab(scope);
        }
    };
}]);
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Eduardo Wada
  • 2,606
  • 19
  • 31
  • $scope.$watch is only working on $scope.variable could you please check... – Shantanu Sharma May 07 '19 at 12:28
  • I'm not sure what you mean by that. My point is that it works normally when the combobox is out of the tab, but doesn't when it's in the tab (for any variable I bind to the selection) – Eduardo Wada May 07 '19 at 12:31
  • Can you share `tabs.html` content and how are you using it? and where did you used `transclude` in directive template? – Satpal May 07 '19 at 12:35
  • You are using an isolated scope in the directive aren't you? And isn't your watch supposed to watch for a change in scope variable? The scope within the directive and outside are different. So in order to watch the combobox in transcluded state, try using a watch within the link function or let your directive use parent scope. – Dblaze47 May 07 '19 at 12:37
  • can you bind data from $rootScope i think then $scope.$watch is workerd. – Shantanu Sharma May 07 '19 at 12:37
  • @Satpal just added tabs.html to the question – Eduardo Wada May 07 '19 at 12:42
  • (New) can you bind data from $rootScope i think then $rootScope.$watch is workerd. – Shantanu Sharma 7 mins ago – Shantanu Sharma May 07 '19 at 12:46
  • @Dblaze47 When you say "isolated scope" do you mean using a controller? I was suspicious that the controller had something to do with it but if I don't know how to do the `require: "^tabs"` part of the the tab.js without it. – Eduardo Wada May 07 '19 at 12:49
  • Please add code for directive combobox – alphapilgrim May 07 '19 at 12:51
  • I think he means when you add ‘scope:{}’ it’s now isolated scope – alphapilgrim May 07 '19 at 12:55
  • @EduardoWada If you define scope: false, it means scope is not defined in the directive and it will use the parent scope, But here you defined scope : { tabtile: "@" }, which means your directive is using it's personal scope and it is not tracking the parent scopes variables, only will use the string which will be binded to it – Dblaze47 May 07 '19 at 13:00
  • 1
    Looks like dot-issue. Please create plunk or give full example (your my-combobox directive and how you change selection). I am quite sure that you have same behavior if you put my-combobox inside simple
    – Petr Averyanov May 07 '19 at 14:09
  • This is a data hiding problem. The corrected and working [DEMO on PLNKR](https://plnkr.co/edit/ynDu31ypZwsZrygQuNKv?p=preview). Scope inheritance is normally straightfoward... until you need **2-way data binding**. If you try to bind to a **primitive** (e.g., number, string, boolean) in the parent scope from inside the child scope. It doesn't work the way most people expect it should work. The child scope gets its own property that hides/shadows the parent property of the same name. To fix, define objects in the parent for your model, then reference a property of that object in the child. – georgeawg May 07 '19 at 17:18
  • @georgeawg I didn't get the time yet to read the full answer from the duplicate question but I can confirm that creating an object on the parent scope and binding to it's properties indeed worked. – Eduardo Wada May 08 '19 at 15:02
  • When a directive defines a new isolate scope, the $compile service creates a separate new inherited scope for linking transcluded contents, the so-called "correct transclusion scope" in the documentation. See [AngularJS Comprehensive Directive API Reference - DDO `controller` property](https://docs.angularjs.org/api/ng/service/$compile#-controller-). – georgeawg May 08 '19 at 15:28

1 Answers1

0

This is a data hiding problem. The corrected and working DEMO on PLNKR.

  <body ng-app="app" ng-init="x={}">
    <select ng-model="x.selection">
       <option value="a">A</option>
       <option value="b">B</option>
       <option value="c">C</option>
    </select>
    selection={{x.selection}}
    <tabs>
      <tab>Some content</tab>
      <tab>Some other content selection={{x.selection}}<br>
         <my-combobox options="options" selection="x.selection">
         </my-combobox>
      </tab>
   </tabs>
  </body>

Scope inheritance is normally straightfoward... until you need 2-way data binding. If you try to bind to a primitive (e.g., number, string, boolean) in the parent scope from inside the child scope. It doesn't work the way most people expect it should work. The child scope gets its own property that hides/shadows the parent property of the same name. To fix, define objects in the parent for your model, then reference a property of that object in the child.

For more information, see

When a directive defines a new isolate scope, the $compile service creates a separate new inherited scope for linking transcluded contents, the so-called "correct transclusion scope" in the documentation.

See

georgeawg
  • 48,608
  • 13
  • 72
  • 95