Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS nested ui-view into an ng-repeat

I'm creating an app using Spotify API. This app can: 1. find albums by artist name through an input field 2. once the albums are shown, the user can click the album's title and see its tracks

I have created 2 views: 1. the albums view 2. the tracks view, child of albums view.

I'd like to show the tracks ui-view of a specific clicked album - with its ID - into the albums' ng-repeat, but the result is that I have the same tracks list of the clicked album throughout all albums list. How can I show the album's track ui-view into the clicked album container that is a result of an ng-repeat directive?

Here's the code.

Routing:

------
.config(function ($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/');

    $stateProvider
      .state('albums', {
        url: '/',
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      }).
      state('albums.tracks', {
        url: ':id/tracks',
        templateUrl: 'views/tracks.html',
        controller: 'TracksCtrl'
      });
})
-----

The albums view:

<div ng-if="albums.items">
    <h1>{{searchedArtist}}'s Album List:</h1>
    <!-- albums ng-repeat -->
    <div ng-repeat="album in albums.items | unique:'name'" id="{{album.id}}">
    <img  ng-src="{{album.images[1].url}}" width="100">
    <!-- the action to translate the state to tracks view -->
    <a ui-sref="albums.tracks({id : album.id})">{{album.name}}</a>
    <!-- tracks view -->
    <div ui-view></div>
</div>

Main albums controller:

angular.module('spotifyAngularApp')
.controller('MainCtrl', function ($scope, spotifyService) {

  var options = {
    'limit': 10
  };

  $scope.searchAlbums= function () {
    var sanitizeArtist = $scope.artist.split(' ').join('+');
    $scope.searchedArtist = $scope.artist;

    spotifyService.search(sanitizeArtist, 'album', options).then(function(data) {
        $scope.albums = data.albums;
    });
  };
});

The tracks controller:

angular.module('spotifyAngularApp')
.controller('TracksCtrl', function ($scope, $stateParams, spotifyService) {
    console.log($stateParams);
    spotifyService.albumTracks($stateParams.id).then(function(data){
        $scope.tracks = data.items;
      });
});
like image 461
DavideCariani Avatar asked Oct 23 '14 14:10

DavideCariani


3 Answers

What you're trying to do does not make sense. A state may have multiple views associated with it but these views must be predefined, not dynamically generated.

There is a conflict between the code where you statically define your only 1 view:

state('albums.tracks', {
        url: ':id/tracks',
        templateUrl: 'views/tracks.html',
        controller: 'TracksCtrl'
      });

and the code where you generate ui-view dynamically with ng-repeat. There is no logic to say which ui-view to load your html into. In this case, I suggest to have only 1 ui-view by moving it out of ng-repeat:

<div ng-if="albums.items">
    <h1>{{searchedArtist}}'s Album List:</h1>
    <!-- albums ng-repeat -->
    <div ng-repeat="album in albums.items | unique:'name'" id="{{album.id}}">
        <img  ng-src="{{album.images[1].url}}" width="100">
        <!-- the action to translate the state to tracks view -->
        <a ui-sref="albums.tracks({id : album.id})">{{album.name}}</a>
    </div
    <!-- tracks view -->
    <div ui-view></div>
</div>

Or if you really need to put it inside ng-repeat due to the way you layout your page, try ng-if to ensure that only 1 ui-view is shown. (this is a workaround and not recommended)

<div ng-if="albums.items">
        <h1>{{searchedArtist}}'s Album List:</h1>
        <!-- albums ng-repeat -->
        <div ng-repeat="album in albums.items | unique:'name'" id="{{album.id}}">
            <img  ng-src="{{album.images[1].url}}" width="100">
            <!-- the action to translate the state to tracks view -->
            <a ui-sref="albums.tracks({id : album.id})">{{album.name}}</a>
            <!-- tracks view -->
            <div ng-if="$state.is('albums.tracks',{id : album.id})" ui-view></div>
        </div>
</div>

and modify your MainCtrl a bit to inject $state so that we have access to it in the view:

angular.module('spotifyAngularApp')
.controller('MainCtrl', function ($scope, spotifyService, $state) {
  $scope.$state = $state;

  //your code
});
like image 150
Khanh TO Avatar answered Nov 07 '22 21:11

Khanh TO


Unfortunately, what you're trying to do doesn't really make sense. What you're saying with this is "repeat this state's (albums') view a number of times equal to how many items there are in the collection albums." But then, when you click on the anchor, you're telling the album.tracks state to go get the album with the id of the album you clicked on. This will then display the album.tracks state in every ui-view in the albums view (which is what you're seeing).

A single state can't be "instanced" multiple times on the same page like you're attempting to do here. See this question for more information: Does Angular JS support routing of multiple views on the same page

I would suggest building this a different way - perhaps using an ng-repeat with ngInclude inside, if you need to keep your template separate. Otherwise, it's probably just easier to build the whole template on one page, ng-repeat for all of the albums and tracks, then hide all of them and show each one when clicked via ngHide/ngShow.

like image 38
Joshua Whitley Avatar answered Nov 07 '22 19:11

Joshua Whitley


If you need to create multiple views inside an ng-repeat, would it be better to create a directive?

like image 3
Raulucco Avatar answered Nov 07 '22 21:11

Raulucco