1

I have a directive with scope variables that depend on other scope variables. I would expect that if the scope variable on the right side of the equation changed it would update the left, but this doesn't seem to be happening.

In the example below when running selectProduct() it should update the product information including the product title, but it does not work unless I update scope.title directly as in the commented line at the bottom of the function.

controller:function($scope){
        $scope.products = $scope.productGroup.products;

        $scope.selected_product = $scope.productGroup.products[$scope.productGroup.selected_product];
        $scope.title = _.isEmpty($scope.selected_product) ? $scope.productGroup.title : $scope.selected_product.title;
        $scope.excerpt = _.isEmpty($scope.selected_product) ? $scope.productGroup.excerpt : $scope.selected_product.excerpt;
        $scope.description = _.isEmpty($scope.selected_product) ? $scope.productGroup.description : $scope.selected_product.description;

        $scope.selectProduct = function(){
            $scope.selected_product = $scope.productGroup.products[1];
            console.log($scope.selected_product);
            //$scope.title = $scope.selected_product.title;
        }
    },
michael
  • 396
  • 5
  • 16

1 Answers1

1

In your template just bind to {{ selected_product.title }} instead of setting $scope.title = $scope.selected_product.title and binding to {{ title }}.

As a rule of thumb don't misuse the $scope as your model. Not even as your ViewModel. JavaScript's prototypal inheritance and variable reference system makes it so that Angular does not really follow a traditional MVVM pattern where $scope would be your ViewModel.

Without having a working example of your problem I would like to try a shot in the dark and suggest you to refactor your code into something like that.

controller:function($scope){
    $scope.products = $scope.productGroup.products;
    $scope.selected_product = $scope.products[$scope.productGroup.selected_product] || {
        title: $scope.productGroup.title,
        excerpt: $scope.productGroup.excerpt,
        description: $scope.productGroup.description
    };

    $scope.selectProduct = function(){
        $scope.selected_product = $scope.productGroup.products[1];
    }
},

Make sure to read this one for more background info:

What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Your problem actually doesn't exactly seem to be related to the prototypal inheritance thing but still make sure to read and understand it. As far as I see, in your code, the real problem is that your are setting $scope.title once and then expect it to change according to changes to $scope.selected_product automatically but why should it? There is no magical connection between those two. However, if you refactor your code to the one I provided, you will not only get the bindings to work but also you will benefit from having less duplicate _.isEmpty() checks (which I doubt you need in the first place actually ;-))

Community
  • 1
  • 1
Christoph
  • 26,519
  • 28
  • 95
  • 133
  • Thanks. But what if I have some external function change `$scope.productGroup.selected_product` from 0 to 1 and now I want the scope.selected_product to update accordingly vs explicitly setting the $scope.selected_product. Does angular not know that one scope object depends on another scope objects property? It would seem to me that since select_product's value depends on the productGroup.selected_product property that changing the select_product property should also update selected_product in the digest cycle. – michael Sep 29 '13 at 17:52
  • You could create a manual watch like this `$scope.$watch('productGroup.selected_product', function(){$scope.selectedProduct = ...}`. But to be honest, why offload this task to Angular? I would say it's much better API design if your productGroup exposes a method `setSelectedProduct(index)` and then internally sets `productGroup.selected_product` to the instance of the product (rather than the index). And then in your template, just bind to `productGroup.selectedProduct`. Does that make sense to you? – Christoph Sep 29 '13 at 19:12
  • Yes, I am confused as to where to put the selectedProduct function though. That is the way I wanted to do it. My productGroup is just json data from a service. What would be the proper way to turn it into a larger object such that I can add an API to it (FYI, I have multiple product groups on the same screen, so I couldn't think of a way to implement this properly with a factory). – michael Sep 29 '13 at 23:19
  • The way I usually solve it is to have `ProductGroup` constructor function where you can define prototype functions etc. Then when you fetch your JSON product groups from the server I would just map them `productGroups.map(function(group){ return angular.extend(new ProductGroup(), group);})` – Christoph Sep 30 '13 at 09:04