Aman Aman - 1 month ago 11
AngularJS Question

Ionic detail view with external JSON file

I'm currently building an iOS app using Cordova and Ionic/Angular. Essentially, there is JSON file which holds data regarding various places of interest (i.e. restaurants, bars etc).

The data is then being parsed using a service and into a controller. Then it is used within a list view, with an item for each place. Whilst the data is also being plotted onto a Google Map (using the JavaScript API). However, I'm having a few difficulties in trying to create the detail view for each place. Each list view item needs to link to a view for each place respectively.

I currently have tried a few solutions, the code I have at the moment is below.

States (I've only included the relevant ones):

.state('tabs.findaplace', {
url: '/find-a-place',
views: {
'findaplace-tab': {
templateUrl: 'views/find-a-place.html',
controller: 'FindAPlaceCtrl',
resolve: {
allplaces: function(Places) {
return Places.all(); }
}
}
}
})
.state('tabs.place-detail', {
url: '/place/:placeId',
views: {
'findaplace-tab': {
templateUrl: 'views/place-detail.html',
controller: 'PlaceDetailCtrl',
resolve: {
allplaces: function($stateParams, Places) {
return Places.all($stateParams.placeId); }
}
}
}
})


Controllers:

.controller('FindAPlaceCtrl', function($scope, $ionicLoading, $ionicPopover, $ionicPopup, IonicClosePopupService, $compile, $http, allplaces) {
// Pull in Places data parsed through the function in services.js
$scope.places = allplaces;
...
})

.controller('PlaceDetailCtrl', function($scope, place) {
$scope.place = place;
})


Service/Factory:

.factory('Places', function($http, $q) {
var places = [];
return {
all: function(){
var dfd = $q.defer();
$http.get('../data/places.json').then(function(response){
places = response.data;
//console.log(places);
dfd.resolve(places);
});
return dfd.promise;
},
get: function(placeId) {
for (var i = 0; i < places.length; i++) {
if (places[i].id === parseInt(placeId)) {
return places[i];
}
}
return null;
}
}
});


List View Markup (Within find-a-place.html):

<ion-item class="list-item" ng-repeat="place in places" ui-sref="place({ placeId: place.id })">
<img ng-src="{{ place.images.image1 }}" alt="{{ place.title }}" />
<span class="favourite-icon"></span>
<div class="list-details">
<p class="sub-heading">{{ place.title }}</p>
<p>{{ place.type }}</p>
<p>{{ place.location }}</p>
</div>
</ion-item>


The data is appearing correctly in the 'Find A Place' tab, both on the map and within the list view.

However, when I click on a list view item the following error appears in the console:
Error: Could not resolve 'places' from state 'tabs.findaplace'

FYI:
The code for the factory etc was sourced from this post on stackoverflow: Ionic Framework with External JSON File

Whilst the current solution was based on: http://learn.ionicframework.com/formulas/sharing-data-between-views/

Any help is appreciated!

Answer

Here is a working snippet:

angular.module('ionicApp', ['ionic'])

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider
    .state('tabs', {
      url: '/tab',
      abstract: true,
      templateUrl: 'views/tabs.html'
    })
    .state('tabs.findaplace', {
      url: '/find-a-place',
      views: {
        'findaplace-tab': {
          templateUrl: 'views/find-a-place.html',
          controller: 'FindAPlaceCtrl',
          resolve: {
            allplaces: function(Places) {
              console.log("resolve allplaces");
              return Places.all();
            }
          }
        }
      }
    })
    .state('tabs.place-detail', {
      url: '/place/:placeId',
      views: {
        'place-detail-tab': {
          templateUrl: 'views/place-detail.html',
          controller: 'PlaceDetailCtrl',
          resolve: {
            place: function($stateParams, Places) {
              return Places.get($stateParams.placeId);
            }
          }
        }
      }
    });

  $urlRouterProvider.otherwise("/tab/find-a-place");

})

.controller('FindAPlaceCtrl', function($scope, $ionicLoading, $ionicPopover, $ionicPopup, $compile, $http, allplaces) {
  // Pull in Places data parsed through the function in services.js
  console.log("FindAPlaceCtrl");
  $scope.places = allplaces;
})

.controller('PlaceDetailCtrl', function($scope, place) {
  $scope.place = place;
  console.log("PlaceDetailCtrl");
})

.factory('Places', function($http, $q, $timeout) {
  var places = [];
  return {
    all: function(){
      var dfd = $q.defer();
      //$http.get('../data/places.json').then(function(response){
      $timeout(function() {
        //places = response.data;
    places = [{
      "id": "0",
      "title": "Aquila",
      "category": "Restaurant",
      "type": "Italian Restaurant",
      "location": "City Centre, Bristol",
      "distance": "0.4",
      "price": "4",
      "rating": "../img/rating-4point5.svg",
      "description": "We are a modern Italian restaurant bringing the very best of traditional dishes from the regions of Italy to Bristol city centre. The sleek design of the restaurant is complimented by the service given by our front of house team led by General Manager Andrea Pintore. Our central location on Baldwin Street gives great views.",
      "menu": "",
      "address": "30-34 Baldwin Street, Bristol, BS1 1NR",
      "latitude": "51.453149",
      "longitude": "-2.594549",
      "marker": "../img/marker-restaurant.svg",
      "phone": "0117 321 0322",
      "website": "http://www.aquila-restaurant.com/",
      "opening": "10:00 - 23:00",
      "images": {
        "image1": "../img/aquila-1.png",
        "image2": "../img/aquila-2.png",
        "image3": "../img/aquila-3.png",
        "image4": "../img/aquila-4.png"
      }
    }, {
      "id": "1",
      "title": "Colston Hall",
      "category": "Entertainment",
      "type": "Concert Hall",
      "location": "City Centre, Bristol",
      "distance": "0.4",
      "price": "3",
      "rating": "../img/rating-4.svg",
      "description": "Colston Hall is Bristol’s largest concert hall, presenting concerts and entertainment by major names in rock, pop, jazz, folk, world and classical music, stand up comedy and light entertainment, as well as local choirs, orchestras and schools.",
      "menu": "",
      "address": "Colston St, Bristol, BS1 5AR",
      "latitude": "51.454785",
      "longitude": "-2.598254",
      "marker": "../img/marker-entertainment.svg",
      "phone": "0844 887 1500",
      "website": "http://www.colstonhall.org/",
      "opening": "10:00 – 18:00",
      "images": {
        "image1": "../img/colstonhall-1.png",
        "image2": "../img/colstonhall-2.png",
        "image3": "../img/colstonhall-3.png"
      }
    }];
        //console.log(places);
        dfd.resolve(places);
        console.log("places.all");
      });
      return dfd.promise;
    },
    get: function(placeId) {
      for (var i = 0; i < places.length; i++) {
        if (places[i].id === placeId) {
          return places[i];
        }
      }
      return null;
    }
  }
});
<html ng-app="ionicApp">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
  <title>Ionic detail view with external JSON file</title>
  <link href="http://code.ionicframework.com/nightly/css/ionic.min.css" rel="stylesheet">
  <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
</head>

<body>

  <ion-nav-bar class="bar-positive">
    <ion-nav-back-button>
    </ion-nav-back-button>
  </ion-nav-bar>

  <ion-nav-view></ion-nav-view>

  <script id="views/tabs.html" type="text/ng-template">
    <ion-tabs class="tabs-icon-top tabs-color-active-positive">
      <ion-tab title="findaplace" icon-off="ion-ios-pulse" icon-on="ion-ios-pulse-strong" href="#/tab/find-a-place">
        <ion-nav-view name="findaplace-tab"></ion-nav-view>
      </ion-tab>
      <ion-tab title="place-detail" icon-off="ion-ios-pulse" icon-on="ion-ios-pulse-strong" href="#/tab/place">
        <ion-nav-view name="place-detail-tab"></ion-nav-view>
      </ion-tab>
    </ion-tabs>
  </script>

  <script id="views/find-a-place.html" type="text/ng-template">
    <ion-view title="findaplace">
      <ion-content>
        <ion-item class="list-item" ng-repeat="place in places" ui-sref="tabs.place-detail({ placeId: place.id })">
          <img ng-src="{{ place.images.image1 }}" alt="{{ place.title }}" />
          <span class="favourite-icon"></span>
          <div class="list-details">
            <p class="sub-heading">{{ place.title }}</p>
            <p>{{ place.type }}</p>
            <p>{{ place.location }}</p>
          </div>
        </ion-item>
      </ion-content>
    </ion-view>
  </script>

  <script id="views/place-detail.html" type="text/ng-template">
    <ion-view title="place-detail">
      <ion-content>
        <h3>tab-place-detail</h3>
        <ion-item class="list-item">
          <img ng-src="{{ place.images.image1 }}" alt="{{ place.title }}" />
          <span class="favourite-icon"></span>
          <div class="list-details">
            <p class="sub-heading">{{ place.title }}</p>
            <p>{{ place.type }}</p>
            <p>{{ place.location }}</p>
          </div>
        </ion-item>
      </ion-content>
    </ion-view>
  </script>
  
</body>
</html>