2

I'm trying to create a sigle-page app that contains shop list, in every shop card is the link to another view that contains table with products.

A shop looks like:

shop = {
  id: 1,
  name: "foo",
  description: 'bar',
  products: [item1, itemn];
};

app.js:

angular
  .module('lightpointTestApp', [
    'ngCookies',
    'ngRoute',
    'ui.sortable'
  ])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
      .when('/about', {
        templateUrl: 'views/about.html',
        controller: 'AboutCtrl'
      })
      .when('/products/:shopID', {
        templateUrl: 'views/products.html',
        controller: 'ProductsCtrl'
      })
      .otherwise({
        redirectTo: '/'
      });
  });

Main.html view where are shop list:

<h3>Shop list</h3>
<div class="row shopsContainer" ui-sortable ng-model="shops">

  <div class="col-lg-3 shopCard" ng-repeat="shop in shops">
    <button class="btn close cardClose" ng-click="removeShop($index)">&times;</button>
    <div class="cardNumber">{{ shops.indexOf(shop) + 1 }}</div>
    <div class="cardHeader">{{ shop.name }}</div>
    <div class="cardBody">
      {{ shop.address }}<br />
      {{ shop.hours }}<br />      
      <a href="/#/products/{{ shop.id }}">View {{ shop.products.length }} products</a>
    </div>
  </div>

</div>
<div class="row">
  <input type="text" ng-model="newShop.name" placeholder="Shop name" class="col-lg-3" />
  <input type="text" ng-model="newShop.address" placeholder="Shop address" class="col-lg-3" />
  <input type="text" ng-model="newShop.hours" placeholder="Shop hours" class="col-lg-3" />
  <button class="btn btn-primary col-lg-3" type="button" ng-disabled="!newShop.name || !newShop.address || !newShop.hours" ng-click="addShop()">Add Shop</button>
</div>

</span>
</div>
</div>

products.js - controller for products page

angular.module('lightpointTestApp')
  .controller('ProductsCtrl', function ($scope, $routeParams, shops) {
    $scope.shopList = shops;
    $scope.shop = {};

    $scope.getShop = function (id) {
      for (var i = 0; i < $scope.shopList.length; i++) {
        if ($scope.shopList[i].id === id) {
          return $scope.shopList[i];
        }
      }
      return null;
    };

    var shopID = $routeParams.shopID;
    $scope.shop = $scope.getShop(shopID);

  })

products.html where is the table with products

<h2>{{ shop.name }}</h2>
  <table class="table table-hover">
    <tr>
      <th>Product Name</th>
      <th>Product Description</th>
    </tr>
    <tr ng-repeat="product in shop.products">
     <td> {{ product.name }} </td>
     <td> {{ product.description }} </td>      
    </tr>
  </table>

The problem is that products.html doesn't bind with products.js and show something like {{shop.name}} and an empty table.

P.S. I think that products.js isn't correct, but I tried everything to do it well.

Thanks.

6
  • Have you included the js file in the products.html? Commented Apr 16, 2015 at 19:18
  • Yes, it is included in index.html (index contains ng-view where change shops view and products view) Commented Apr 16, 2015 at 19:20
  • 1
    Are you getting an error in your console? I don't think ProductsCtrl is going to know what shops is. Commented Apr 16, 2015 at 19:20
  • Hm...There's error: Error: [$injector:unpr] Unknown provider: shopsProvider <- shops <- ProductsCtrl It seems you're right. But why this happened? Commented Apr 16, 2015 at 19:23
  • 1
    I would expect that shops will be null and you'll get an error while trying to iterate through the $scope.shopList.length in getShop Commented Apr 16, 2015 at 19:24

2 Answers 2

1

You have a parameter shops in ProductsCtrl, but there is nothing that will pass a value for it, so it is going to be null. You set the value of $scope.shopList to it, and then try to iterate over a NULL array, so you get an exception.

You can store the values of shops in a service, and then pass them around your app via injection. You can initialize their values within main.js, or within the service itself, and then the values will be available if you inject them into ProductsCtrl, something like

angular.module('lightpointTestApp')
      .controller('ProductsCtrl', ['$scope', '$routeParams', 'shopsService', 
         function ($scope, $routeParams, shopsService) {
             $scope.shopList = shopService;
             $scope.shop = {};

             $scope.getShop = function (id) {

                for (var i = 0; i < $scope.shopList.length; i++) {
                  if ($scope.shopList[i].id === id) {
                     return $scope.shopList[i];
                  }
                }
                return null;
             };

            var shopID = $routeParams.shopID;
            $scope.shop = $scope.getShop(shopID);

        }]);

shopsService could look something like

angular.module('lightpointTestApp')
   .service('shopsService', function() {
       return [ 
                // add whatever fields you need here from code in main.js
                { name: 'shop1', address: 'addr1' },
                { name: 'shop2', address: 'addr2' }
              ];
   });
Sign up to request clarification or add additional context in comments.

7 Comments

When I moved initializing data from main.js to shopsService.js, in console appeared Unknown provider: shopsServiceProvider <- shopsService <- MainCtrl Why does it happen? angular.module('lightpointTestApp') .controller('MainCtrl', function ($scope, shopsService) - should I declare shopsService anywhere else?
if you're going to initialize the shopService values in MainCtrl, then you will need to inject it the same way I did for ProductsCtrl, something like .controller('MainCtrl', ['$scope', 'shopsService', function($scope, shopsService) { ...
If I remove it, shopsService will be undefined, as it was to be expected =)
If I do .controller('MainCtrl', ['$scope', 'shopsService', function($scope, shopsService) { .. it gives unknown provider.. Uggrrhh
When you inject the dependencies as an array, your main.js is not closing the array after defining the function. Change line 40 of your main.js to }]); and I got the page to start working. The code you have checked in does not have the injection for shopsService in ProductsCtrl, btw
|
0

Where are your shop objects coming from? You are passing in shop, in products.js but not referencing it in the code. You should also use $q to use promises for async data. Also use the filter() function rather than a for loop to find the shop by shopId.

Are you hitting an API with shops or storing a local json for now? With angular, you should separate your data logic manipulation in a factory or service as such:

productService.js

angular.module('lightpointTestApp')
  .factory('shopService',function($http, $q){
    var shops = [];
    return {
      getShops: function () {
        var deferred = $q.defer();
        $http.get('<path to product.json or api>').success(function(data){
          shops = data;
          deferred.resolve(data);
        }) 
        return deferred.promise;
      },
      getShopById: function(shopID) {
        var deferred = $q.defer();
        deferred.resolve(shops.filter(function(chain){
          return chain.id === shopID;
        })[0]);
        return deferred.promise;
      }
    } 
  });

product.js

angular.module('lightpointTestApp')
 .controller('ProductsCtrl', function ($scope, $routeParams, $q,shopService) {
    $scope.shopList = [];
    $scope.shop = {};
    var shopID = $routeParams.shopID;

    shopService.getShops.then(function(shops){
      $scope.shopList = data;    
    })

    $scope.getShopById = function(shopID) {
      shopService.getShopById(shopID).then(function(shop){
      $scope.shop = shop;
     });
    }
  });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.