70

I am brand new to AngularJS and like what I've seen so far, especially the model / view binding. I'd like to make use of that to construct a simple "add to basket" piece of functionality.

This is my controller so far:

function BasketController($scope) {
    $scope.products = [];

    $scope.AddToBasket = function (Id, name, price, image) {

        ...

    };
}

And this is my HTML:

<a ng-click="AddToBasket('237', 'Laptop', '499.95', '237.png')">Add to basket</a>

Now this works but I highly doubt this is the right way to create a new product object in my model. However this is where my total lack of AngularJS experience comes into play.

If this is not the way to do it, what is best practice?

Greg
  • 31,180
  • 18
  • 65
  • 85
  • 2
    http://stackoverflow.com/questions/14523679/can-you-pass-parameters-to-an-angularjs-controller-on-creation 2nd answer – bresleveloper Aug 13 '13 at 14:59

3 Answers3

86

You could use ng-init in an outer div:

<div ng-init="param='value';">
    <div ng-controller="BasketController" >
        <label>param: {{value}}</label>
    </div>
</div>  

The parameter will then be available in your controller's scope:

function BasketController($scope) {
        console.log($scope.param);
}
Andrejs
  • 26,885
  • 12
  • 107
  • 96
  • Can't I do this without parent `div`? – Sanghyun Lee Aug 08 '13 at 04:40
  • 6
    Yes. You can have the `ng-init` parameter on the same element as the `ng-controller` attribute if you prefer. – rossipedia Sep 06 '13 at 17:22
  • 2
    I have problems with having ng-init on the same element as ng-controller. Have this changed or do I something wrong? – Rene Groeschke Dec 03 '13 at 21:30
  • 17
    This answer is now deprecated. As stated in the update to [this similar answer](http://stackoverflow.com/a/14531643/135114), the [angular documentation for ngInit](https://docs.angularjs.org/api/ng/directive/ngInit) now explicitly states that the only approved use of `ng-init` is for "aliasing special properties of ngRepeat" (e.g. `fooIndex = $index`). – Daryn May 26 '14 at 18:04
  • Alternatively use it on the same element, but do: ng-init="init('value')", then define that function in the controller: scope.init = function(val) { scope.param=val; } – Jens Jul 11 '14 at 10:37
  • 2
    @Daryn "angular way" of doing things is using static templates and REST APIs for dynamic content. To get the data, you make HTTP request and get it (via another request). As this may be best practice, in many cases pages are generated dynamically. Also, SEO might be a problem if you are loading data only with AJAX. So I really don't see the point in deprecating that `ng-init` directive. And I really don't see alternatives to there 2 methods (except some custom directive, which does almost the same as `ng-init`). – Marius Balčytis Oct 14 '14 at 18:38
65

You could create a basket service. And generally in JS you use objects instead of lots of parameters.

Here's an example: http://jsfiddle.net/2MbZY/

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

app.factory('basket', function() {
    var items = [];
    var myBasketService = {};

    myBasketService.addItem = function(item) {
        items.push(item);
    };
    myBasketService.removeItem = function(item) {
        var index = items.indexOf(item);
        items.splice(index, 1);
    };
    myBasketService.items = function() {
        return items;
    };

    return myBasketService;
});

function MyCtrl($scope, basket) {
    $scope.newItem = {};
    $scope.basket = basket;    
}
Andrew Joslin
  • 43,033
  • 21
  • 100
  • 75
  • 7
    That makes sense - also just taught me how to do services! Nice. Thank you - However how do I create then pass the object to the controller without a series of HTML inputs?? – Greg Jul 28 '12 at 20:48
  • Depends on your use case.. you could do it just about any way you wanted to. – Andrew Joslin Jul 28 '12 at 21:12
  • Whats the scope.basketProperties for? – Adam Mills Mar 27 '13 at 15:36
  • Nothing anymore :-). Removed it and updated the link in the post. – Andrew Joslin Mar 27 '13 at 16:31
  • The bootstrap.css URL was a 404. I've created an [updated jsfiddle](http://jsfiddle.net/pmorch/2MbZY/31/) – Peter V. Mørch Jan 08 '14 at 22:19
  • Shouldn't it be considered bad practice to implement every domain-specific type as a service? What if you want to use them in an environment without Angular? – John Freeman Apr 01 '14 at 01:13
  • 1
    @AndyJoslin How does Angular know how to pass a basket to MyCtrl? – Antoine Oct 30 '14 at 17:04
  • 3
    One problem with that approach is that a singleton instance is created and is the same for the entire application, so for specific page sub controllers you might end up with conflicts or with services already having some data. It feels like having a global for storing stuff. – graffic Feb 12 '15 at 09:59
2

I'm not very advanced in AngularJS, but my solution would be to use a simple JS class for you cart (in the sense of coffee script) that extend Array.

The beauty of AngularJS is that you can pass you "model" object with ng-click like shown below.

I don't understand the advantage of using a factory, as I find it less pretty that a CoffeeScript class.

My solution could be transformed in a Service, for reusable purpose. But otherwise I don't see any advantage of using tools like factory or service.

class Basket extends Array
  constructor: ->

  add: (item) ->
    @push(item)

  remove: (item) ->
    index = @indexOf(item)
    @.splice(index, 1)

  contains: (item) ->
    @indexOf(item) isnt -1

  indexOf: (item) ->
    indexOf = -1
    @.forEach (stored_item, index) ->
      if (item.id is stored_item.id)
        indexOf = index
    return indexOf

Then you initialize this in your controller and create a function for that action:

 $scope.basket = new Basket()
 $scope.addItemToBasket = (item) ->
   $scope.basket.add(item)

Finally you set up a ng-click to an anchor, here you pass your object (retreived from the database as JSON object) to the function:

li ng-repeat="item in items"
  a href="#" ng-click="addItemToBasket(item)"