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

I have an angular function that I assigned to the click event of a button using the ng-click directive. I pass to it the $event object so I could retrieve the button that was clicked. After retrieving the button using $event.currentTarget, I would like to get a parent element using .closest('.parentDiv'), and search inside that element using .find('.item-id').

I have that following code but it gives me an error saying .find() is not a function:

$scope.addToCart = function ($event) {
    var itemId;
    var qty;
    var btn = $event.currentTarget;
    itemId = btn.closest('.parentDiv').find('.item-id').val();        
};

Thanks in advance.

share|improve this question
3  
When you find yourself doing stuff like this in angular, you know you're doing something wrong. You should probably have an array of objects that represent items, displaying them in an ng-repeat. When you click the button that calls addToCart, you should then pass in the entire item into the function, and you have access to all of its properties (i.e. price, id, etc.). I can put together a quick demo for you if you'd like? – mhodges 21 hours ago
    
Can you try if this works? $(btn).closest('.parentDiv').find('.item-id').val() – Gerard Reches 21 hours ago
    
@GerardReches I think that would work but I need to use a function within my controller coz I need to update some scope variables afterwards. – Rian 21 hours ago
2  
$event.currentTarget returns a DOM element if I'm not wrong. You can't use jQuery functions in a DOM element, so you should cast it to a jQuery object. btn is a DOM element, and $(btn) is a jQuery object of that element. – Gerard Reches 21 hours ago
1  
You should read the answer to “Thinking in AngularJS” if I have a jQuery background? Tl;Dr pretty much never mix jQuery and angular. – Liam 21 hours ago
up vote 0 down vote accepted

Your code gives you .find() is not a function because you are trying to use a jQuery method in a DOM element.

You need a jQuery object in order to use a jQuery method, and $event.currentTarget returns a DOM element, not a jQuery object.

If you have a variable containing a DOM element (btn in your case), you can cast it to a jQuery object in an easy way: $(yourDOMElement).

So you will be ok changing

btn.closest('.parentDiv').find('.item-id')

to

$(btn).closest('.parentDiv').find('.item-id')
share|improve this answer

The more true Angular approach to this problem would be to do as I suggested in my comment, which is to have an array of objects that represent items, displaying them in an ng-repeat. When you click the button that calls addToCart, you should then pass in the entire item into the function, and you have access to all of its properties (i.e. price, id, etc.).

Here's what the bare-bones code would look like:

var app = angular.module("myApp", [])
  .controller("myCtrl", function($scope) {
    $scope.shoppingCart = [];
    $scope.items = [{
      id: 1,
      price: 1.49,
      quantity: 0,
      name: "Soup"
    }, {
      id: 2,
      price: 4.75,
      quantity: 0,
      name: "Chicken"
    }, {
      id: 3,
      price: 2.29,
      quantity: 0,
      name: "Beef Jerky"
    }, {
      id: 4,
      price: 3.00,
      quantity: 0,
      name: "Salad"
    }, {
      id: 5,
      price: 0.99,
      quantity: 0,
      name: "Avocado"
    }];
    $scope.getCartTotal = function() {
      return $scope.shoppingCart.reduce(function(sum, curr) {
        return sum + (Number(curr.quantity) * curr.price);
      }, 0);
    };
    $scope.removeFromCart = function(cartItem) {
      var item = $scope.items[$scope.items.indexOf(cartItem)];
      item.quantity = 0;
      $scope.shoppingCart.splice($scope.shoppingCart.indexOf(cartItem), 1);
    };
    $scope.addToCart = function(item, itemForm) {
      var newQuantity = Number(itemForm.qty.$viewValue);
      var itemIndex = -1;
      $scope.shoppingCart.some(function(elem, index) {
        if (elem.id === item.id) {
          itemIndex = index;
          return true;
        }
      });
      if (itemIndex > -1) {
        var currItem = $scope.shoppingCart[itemIndex];
        currItem.quantity = newQuantity;
      } else {
        item.quantity = newQuantity;
        $scope.shoppingCart.push(item);
      }
    };
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
  <div style="width: 50%; margin-right: 2%; float: left;">
    <h3>Items</h3>
    <div ng-form="itemForm" ng-repeat="item in items">
      Name: {{item.name}}
      <br>Price: {{item.price | currency}}
      <br>Quantity:
      <input type="text" name="qty" ng-model="item.quantity" ng-model-options="{updateOn: 'submit'}" style="width: 50px;" />
      <br>
      <br>
      <button ng-click="addToCart(item, itemForm);">Add To Cart</button>
      <hr/>
    </div>
  </div>
  <div style="width: 44%; float: left;">
    <h3>Shopping Cart</h3>
    <div ng-repeat="item in shoppingCart">
      Name: {{item.name}}
      <br>Quantity: {{item.quantity}}
      <br>Total Price: {{(item.price * item.quantity) | currency}}
      <button ng-click="removeFromCart(item)">Remove</button>
      <br>
      <hr/>
    </div>
    <hr/>Total # Items: {{shoppingCart.length}}
    <br/>Grand Total: <span ng-bind="getCartTotal() | currency"></span>
  </div>
</div>

Hope this helps get you on the right track! Let me know if you have any questions =)

UPDATE:

Since you are passing your items to your view from your controller via asp.net MVC, you can inject the values directly from your view into your angular module. Here are a couple examples of how I've done it.

var poCreationApp = angular.module('myApp', []);
poCreationApp.value("defaultLocale", "@ViewBag.SelectedLocale");
poCreationApp.value("userInitials", "@ViewBag.UserInitials");
poCreationApp.controller("myCtrl", ["defaultLocale", "userInitials", "$scope", "$http", function (defaultLocale, userInitials, $scope, $http) {
    $scope.locale = defaultLocale;
    $scope.userInitials = userInitials;
    // ..... 
}]);

You can also pass through an entire view model like this:

@{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    serializer.MaxJsonLength = Int32.MaxValue;
    var jsonModel = serializer.Serialize(Model);
}

<script>
    var app = angular.module("myApp", []);
    app.value("viewModel", @Html.Raw(jsonModel));
    app.controller("myCtrl", ["viewModel", "$scope", function (viewModel, $scope) {
        $scope.model = viewModel;
        $scope.items = $scope.model.items;
        // ......
    }]);
</script>
share|improve this answer
    
Thanks for this piece of code. This is really helpful. I am using angular with asp.net mvc by the way so I actually pass the list of items to the view from my controller. Do you think that it is better to have an angular function to retrieve that list of items in json format so I could use the ng-repeat feature? Or is there any other more appropriate option? – Rian 20 hours ago
    
I use asp.net mvc as well with razor. You really have 2 options, you can get them via ajax, but if you already pass it through to the view, what you can do is you can inject the values into your angular module using .value() on angular.module(). I can post a couple of examples – mhodges 20 hours ago
    
@Rian See my updated answer – mhodges 20 hours ago

If you don't want to include another version of jQuery, just use angular.element:

angular.element($event.currentTarget)
share|improve this answer

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.