Question

I'm having a problem understanding scope. ui-bootstrap creates a new scope when you use tabset, I get that. I thought parent methods were available to child scopes? Here is a jsbin showing my problem.

JSBin

I'm sure there is something simple I'm missing, but I can't see it.

Code inline in case JSBin acts up:

angular.module('app', ['ui.bootstrap'])
.controller('MainController', function($scope) {
  $scope.filters = [];
  $scope.search = '';
  $scope.providerVersions = [
    { name:'SomeOS', type:'pv' },
    { name:'OtherOS', type:'pv' }
  ];
  $scope.scripts = [
    { name:'Something', pv:'SomeOS', type:'script' },
    { name:'Somebody', pv:'SomeOS', type:'script' },
    { name:'Other thing', pv:'OtherOS', type:'script' },
    { name:'Other body', pv:'OtherOS', type:'script' }
  ];

  $scope.addFilter = function(f) {
    $scope.filters.push({ name:f.name, type:f.type });
  };

  $scope.remFilter = function(i) {
    $scope.filters.splice(i,1);
  };

  $scope.filterByName = function(n) {
      var name = n.name.toLowerCase();
      var search = $scope.search.toLowerCase();
      return name.indexOf(search) > -1;
  };

  $scope.filterByFilters = function(f) {
    if ($scope.filters.length===0) { return true; }

    var byName = _.where($scope.filters, { type:'script' });
    if (byName.length > 0) {
      return _.contains(_.pluck(byName, 'name'), f.name);
    }
    return _.contains(_.pluck($scope.filters, 'name'), f.pv);
  };
});

HTML

<body ng-app="app">
  <div ng-controller="MainController">
    <h3>This works</h3>
    <p>Filters on both name and role as expected.</p>
    <div ng-repeat="s in scripts|filter:filterByFilters">
      {{ s.name }}
    </div>
    <form name="searchForm">
      <input type="text" class="form-control" placeholder="Filter by name or role" ng-model="search">
    </form>
    <span ng-repeat="f in filters"><a ng-click="remFilter($index)">{{ f.name }}</a><span ng-if="!$last">, </span></span>
    <ul ng-show="search.length>0" class="dropdown-menu" style="display:block; position:static;">
      <li class="dropdown-header">Name</li>
      <li ng-repeat="s in scripts|filter:filterByName"><a  ng-click="addFilter(s)">{{ s.name }}</a></li>
  <li class="divider"></li>
      <li class="dropdown-header">Role</li>
      <li ng-repeat="p in providerVersions|filter:search"><a ng-click="addFilter(p)">{{ p.name }}</a></li>
</ul>

    <h3>This does not work</h3>
    <p>Only filters on role. It does not call $scope.filterByName.</p>
    <tabset>
      <tab heading="Scripts">
        <div ng-repeat="s in scripts|filter:filterByFilters">
      {{ s.name }}
    </div>
    <form name="searchForm">
      <input type="text" class="form-control" placeholder="Filter by name or role" ng-model="search">
    </form>
    <span ng-repeat="f in filters"><a ng-click="remFilter($index)">{{ f.name }}</a><span ng-if="!$last">, </span></span>
    <ul ng-show="search.length>0" class="dropdown-menu" style="display:block; position:static;">
      <li class="dropdown-header">Name</li>
      <li ng-repeat="s in scripts|filter:filterByName"><a  ng-click="addFilter(s)">{{ s.name }}</a></li>
  <li class="divider"></li>
      <li class="dropdown-header">Role</li>
      <li ng-repeat="p in providerVersions|filter:search"><a ng-click="addFilter(p)">{{ p.name }}</a></li>
</ul>
      </tab>
    </tabset>
  </div>
</body>
Était-ce utile?

La solution 2

I was able to work around this. Honestly I don't know why this works, but it does. Maybe somebody else can fill in the gaps for me. What I did was instead of trying to filter my list, I changed the filter function to return an array, and I use that result for ng-repeat to iterate.

Old filter function

$scope.filterByName = function(n) {
  var name = n.name.toLowerCase();
  var search = $scope.search.toLowerCase();
  return name.indexOf(search) > -1;
};

New filter function

$scope.filterByName = function(list, srch) {
        var ret = [];

        _.each(list, function(l) {
            if (l.name.toLowerCase().indexOf(srch.toLowerCase()) > -1) {
                ret.push(l);
            }
        });

        return ret;
    };

Old ng-repeat

<li ng-repeat="s in scripts|filter:filterByName">

New ng-repeat

<li ng-repeat="s in filterByName(scripts, search)">

Autres conseils

Scopes are interesting and confusing. All scopes, except $rootScape, have ancestors. I don't know for certain but I assume that the ui-bootstrap tabset creates its own isolate scope. That isolate scope does have a parent, but you've "isolated" it so it can't see any attributes of an ancestor scopes unless you've specifically included them within the directive. Should an isolate scope have a child scope that is not an isolate scope, it can see and manipulate its parents attributes but nothing farther back the ancestor chain. Events can pass freely up and down the chain and you should be very careful using them since, when dealing with isolate scopes, may cause side effects you aren't expecting.

If the above was just a bunch of blather, go to https://docs.angularjs.org/guide/directive and reread the info there - maybe that will make it more clear.

Here's probably one of the best dissertation on scope you'll fnd in a quick, concise and clear explanation - What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top