AngularJS Cascaded Selects

As a follow up to jQuery Mobile Cascaded Selects using MVC4 and KnockoutJs, I have created this AngularJS Cascaded Selects tutorial that gets it’s data from the Chinook Web API Project. The application uses angular data-binding and the ngOptions attribute to dynamically generate a list of option elements for each of the three HTML select elements.

index.html
<!DOCTYPE HTML>
<html ng-app="ChinookApp">
<head>
	<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
    <script src="libs/angular-resource.min.js"></script>
	<script src="app.js" type="text/javascript"></script>
	
	<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
	<link rel="stylesheet" href="app.css">

	<title>Angular Cascading Selects</title>
</head>
<body>
	<div ng-controller="MainCtrl">

        <h2>Angular Cascading Selects</h2>
        <div class="span6 body-content">
            <form class="form-horizontal">
                <div class="control-group">
                    <label class="control-label" for="inputArtist">Artist</label>
                    <div class="controls">
                        <select id="inputArtist"
                            ng-model="artist.selected"
                            ng-options="artist.ArtistId as artist.Name for artist in artists"
                            ng-change="artistChanged(artist.selected)">
                        </select>
                    </div>
                </div>
                <div class="control-group">
                    <label class="control-label" for="inputAlbum">Album</label>
                    <div class="controls">
                        <select id="inputAlbum"
                            ng-disabled="artistNotSelected"
                            ng-model="album.selected"
                            ng-options="album.AlbumId as album.Title for album in albums"
                            ng-change="albumChanged(album.selected)">
                        </select>
                    </div>
                </div>
                <div class="control-group">
                    <label class="control-label" for="inputTrack">Track</label>
                    <div class="controls">
                        <select id="inputTrack"
                            ng-disabled="albumNotSelected"
                            ng-model="track.selected"
                            ng-options="track.TrackId as track.Name for track in tracks">
                        </select>
                    </div>
                </div>
            </form>
		</div>
	</div>
</body>

If your connecting to your REST Web API with a URL that has a port number, such as the built-in Visual Studio Development Server, you will need to escape it with two backslashes:

chinookApp.constant('urlBase', 'http://localhost\\:65374/api/');
app.js
'use strict';

var chinookApp = angular.module('ChinookApp', ['ngResource']);
chinookApp.constant('urlBase', 'http://responsiveresourcesgroup.com/chinook/api/');
chinookApp.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.useXDomain = true;
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
}]);

chinookApp.factory('ArtistsService', function($resource, urlBase){
    return $resource(urlBase + 'artists', {}, {
        query: {method:'GET', isArray:true}
    });
});
chinookApp.factory('AlbumsService', function($resource, urlBase){
    return $resource(urlBase + 'albums', { artistid: '@artistid' }, {
        query: {method:'GET', isArray:true}
    });
});
chinookApp.factory('TracksService', function($resource, urlBase){
    return $resource(urlBase + 'tracks', { albumid: '@albumid' }, {
        query: {method:'GET', isArray:true}
    });
});

chinookApp.controller('MainCtrl', function($scope, ArtistsService, AlbumsService, TracksService) {

    $scope.artistNotSelected = true;

    $scope.artists = ArtistsService.query({}, function (artists) {
        artists.unshift({"ArtistId":0,"Name":"-- Select Artist --"});
        $scope.artist = {selected: artists[0].ArtistId};
    });

    $scope.albumNotSelected = true;
    $scope.album = {selected: "0"};
    $scope.trackNotSelected = true;
    $scope.track = {selected: "0"};

    $scope.artistChanged = function(selectedArtistId) {
        $scope.artistNotSelected = !selectedArtistId;
        if ($scope.artistNotSelected)
        {
            $scope.album = {};
        }
        else {
            $scope.albums = AlbumsService.query({ artistid: $scope.artist.selected }, function (albums) {
                albums.unshift({"AlbumId":0,"Title":"-- Select Album --"});
                $scope.album = {selected: albums[0].AlbumId};
                $scope.albumNotSelected = true;
                $scope.tracks = [];
            });
        }
    };

    $scope.albumChanged = function(selectedAlbumId) {
        $scope.albumNotSelected = !selectedAlbumId;
        if ($scope.albumNotSelected)
        {
            $scope.track = {};
        }
        else {
            $scope.tracks = TracksService.query({ albumid: $scope.album.selected }, function (tracks) {
                tracks.unshift({"TrackId":0,"Name":"-- Select Track --"});
                $scope.track = {selected: tracks[0].TrackId};
            });
        }
    };
});
View, Run, Edit this code in JSFIDDLE

Resources

Published by

Jim Frenette

Web Developer - views here are my own except those taken from people more clever than me.