Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

SUMMARY
I have a list of brands and a list of products. I am using an ng-repeat to show the list of brands, and an ng-repeat with a filter to show the list of products within their respective brands. I want each brand and each product to have a button that shows more about that brand/product. All of these buttons should use the same function on the controller.

PROBLEM
The button that shows more about the brand also shows more about each of that brand's products, UNLESS (this is the weird part to me) I click the button of a product within that brand first, in which case it will work correctly.

CODE
Please see the Plunker here, and note that when you click on 'show type' on a brand, it also shows all the types of the products within that brand: http://plnkr.co/edit/gFnq3O3f0YYmBAB6dcwe?p=preview

HTML

<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <div ng-app="myApp">
      <div ng-controller="MyController as vm">
        <div ng-repeat="brand in brands">
          <h1>
              {{brand.name}}
            </h1>
          <button ng-click="showType(brand)">
            Show Brand Type
          </button>
          <div ng-show="show">
            {{brand.type}}
          </div>
          <div ng-repeat="product in products
          | filter:filterProducts(brand.name)">
            <h2>
                  {{product.name}}
                </h2>
            <button ng-click="showType(product)">
              Show Product Type
            </button>
            <div ng-show="show">
              {{product.type}}
            </div>
          </div>
        </div>
      </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

JAVASCRIPT

var app = angular.module('myApp', []);

app.controller('MyController', function($scope) {

  $scope.brands = [{
    name: 'Kewl',
    type: 'Cereal'
  }, {
    name: 'Joku',
    type: 'Toy'
  }, {
    name: 'Loko',
    type: 'Couch'
  }]

  $scope.products = [{
    name: 'Kewlio',
    type: 'Sugar Cereal',
    brand: 'Kewl'
  }, {
    name: 'Kewliano',
    type: 'Healthy Cereal',
    brand: 'Kewl'
  }, {
    name: 'Jokurino',
    type: 'Rattle',
    brand: 'Joku'
  }, {
    name: 'Lokonoko',
    type: 'Recliner',
    brand: 'Loko'
  }, {
    name: 'Lokoboko',
    type: 'Love Seat',
    brand: 'Loko'
  }]

  $scope.showType = function(item) {
    this.show = !this.show;
  }

  $scope.filterProducts = function(brand) {
    return function(value) {
      if(brand) {
        return value.brand === brand;
      } else {
        return true;
      }
    }
  }
});

IMPORTANT NOTE: I realize I could add an attribute to the object (brand.show) and pass the object into the function, then change that attribute to true/false, but I don't want to do this because in my actual application, the button will show a form that edits the brand/product and submits the info to Firebase, and I don't want the object to have a 'show' attribute on it. I would rather not have to delete the 'show' attribute every time I want to edit the info in Firebase.

share|improve this question
up vote 0 down vote accepted

ng-repeat directive create own scope, when you do

this.show = !this.show

you create/change show property in current scope, if click brand button - for brand scope, that global for product, and when click in product button - for scope concrete product.

To avoid this, you should create this property before clicking button, for example with ng-init, like

ng-init="show=false;"

on element with `ng-repeat" directive

Sample

var app = angular.module('myApp', []);

app.controller('MyController', function($scope) {

  $scope.brands = [{
    name: 'Kewl',
    type: 'Cereal'
  }, {
    name: 'Joku',
    type: 'Toy'
  }, {
    name: 'Loko',
    type: 'Couch'
  }]

  $scope.products = [{
    name: 'Kewlio',
    type: 'Sugar Cereal',
    brand: 'Kewl'
  }, {
    name: 'Kewliano',
    type: 'Healthy Cereal',
    brand: 'Kewl'
  }, {
    name: 'Jokurino',
    type: 'Rattle',
    brand: 'Joku'
  }, {
    name: 'Lokonoko',
    type: 'Recliner',
    brand: 'Loko'
  }, {
    name: 'Lokoboko',
    type: 'Love Seat',
    brand: 'Loko'
  }]

  $scope.showType = function(item) {
    this.show = !this.show;
  }

  $scope.filterProducts = function(brand) {
    return function(value) {
      if (brand) {
        return value.brand === brand;
      } else {
        return true;
      }
    }
  }

});
/* Styles go here */

h1 {
  font-family: impact;
}
h2 {
  font-family: arial;
  color: blue;
  margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<div ng-app="myApp">
  <div ng-controller="MyController as vm">
    <div ng-repeat="brand in brands" ng-init="show=false">
      <h1>
              {{brand.name}}
            </h1>
      <button ng-click="showType(brand)">
        Show Brand Type
      </button>
      <div ng-show="show">
        {{brand.type}}
      </div>
      <div ng-repeat="product in products
          | filter:filterProducts(brand.name)" ng-init="show=false">
        <h2>
           {{product.name}}
        </h2>
        <button ng-click="showType(product)">
          Show Product Type
        </button>
        <div ng-show="show">
          {{product.type}}
        </div>
      </div>
    </div>
  </div>
</div>

share|improve this answer

The easiest fix for this, if you don't mind putting temporary properties in your data is the following changes:

<div ng-show="product.show">
   {{product.type}}
</div>

and

<div ng-show="brand.show">
   {{brand.type}}
</div>

and then in your controller

$scope.showType = function(item) {
    item.show = !item.show;
  }

Alternatively, if you don't want to touch the object properties, you can create an $scope.shownTypes array and have your click either push the object into or remove the object from the shown array. THen you can check for the object's existence in the array and show or not show the type appropriately. Let me know if you need a sample of that.

share|improve this answer
    
Related to your update, please see the 'Alternatively' section of my answer – Beartums Jan 31 '16 at 18:40

Your show boolean attribute same for whole tree (is in same scope). Using angular directive with child scope scope:true in ng-repeat helps to isolate each show property. I have forked your plunker code:

http://plnkr.co/edit/cMSvyfeCQOnTKG8F4l55?p=preview

share|improve this answer
    
scope:true - not an isolated scope, is a child scope, and also ng-repeat already create own scope – Grundy Jan 31 '16 at 8:02
    
@Grundy true, thanks for comment – Enver Dzhaparoff Jan 31 '16 at 9:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.