Mixing Static And Dynamic Data In An AngularJS Select Menu

Posted February 19, 2013 at 9:54 AM by Ben Nadel

Tags: Javascript / DHTML

Due to the data-driven, two-way binding nature of AngularJS input elements, mixing static and dynamic data in a single Select menu can be challenging. If your data it completely dynamic, it's a total breeze. But, the moment you want to add a static option, suddenly you have an asymmetric list of references that need to be mapped on to your View Model (ie. $scope). But, after a good deal of tinkering, I think I found a fairly straightforward way of doing this.


 
 
 

 
  
 
 
 

Since your mixed-list of Select Options contains references to different types of data, you can no longer have a simple ngModel value that binds to a $scope reference. Instead, you have to maintain a "selection" model that is dedicated to the Select menu; then, you have to watch for changes in that selection value to see if other $scope changes need to be precipitated.

To demonstrate this approach, I have created a Select menu that contains a "dynamic" list of friends and a static list of pseudo-options. If a pseudo-option is selected, it may change the currently-selected friend.

In order to build the Select menu, I have created a composite collection of options that composes both the static values and the dynamic values. In order to make selection easy, I have architected the ngOptions directive to use a property of the options list as the selection target:

ng-options="option.value as option.text for option in options"

This directive configuration means that when an option is selected, use [option.value] as the reference to store in the ngModel expression. In using this approach, I can create a composite options list in which each option can have a completely decoupled value target.

In the following code, notice that I am mapping my "friends" collection onto an option collection in which the "value" of the option points back to the friend instance.

  • <!doctype html>
  • <html ng-app="Demo" ng-controller="DemoController">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Mixing Static And Dynamic Options In An AngularJS Select Menu
  • </title>
  • </head>
  • <body>
  •  
  • <h1>
  • Mixing Static And Dynamic Options In An AngularJS Select Menu
  • </h1>
  •  
  • <form>
  •  
  • <select
  • ng-model="selection"
  • ng-options="option.value as option.text for option in options">
  •  
  • <!-- You can have ONE default, null selection option. -->
  • <option value="">- - Make Selection - -</option>
  •  
  • </select>
  •  
  • <button type="button" ng-click="selectJoanna()">
  • Select Joanna
  • </button>
  •  
  • <button type="button" ng-click="selectNull()">
  • Select null
  • </button>
  •  
  • </form>
  •  
  • <p ng-show="selectedFriend">
  •  
  • {{ selectedFriend.id }} - {{ selectedFriend.name }}
  •  
  • </p>
  •  
  •  
  •  
  • <!-- Load jQuery and AngularJS from the CDN. -->
  • <script
  • type="text/javascript"
  • src="//code.jquery.com/jquery-1.9.1.min.js">
  • </script>
  • <script
  • type="text/javascript"
  • src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js">
  • </script>
  •  
  • <!-- Load the app module and its classes. -->
  • <script type="text/javascript">
  •  
  •  
  • // Define our AngularJS application module.
  • var demo = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I am the main controller for the application.
  • demo.controller(
  • "DemoController",
  • function( $scope ) {
  •  
  •  
  • // -- Define Scope Methods. ----------------- //
  •  
  •  
  • // I explicitly select Joanna just to demonstrate how
  • // the two-way data binding will behave. Once we
  • // select Joanna explicitly, the ngModel will reflect
  • // that change in the Select menu.
  • $scope.selectJoanna = function() {
  •  
  • $scope.selection = $scope.friends[ 1 ];
  •  
  • };
  •  
  •  
  • // One more demo to show how selection will be
  • // reflected in two-way data binding.
  • $scope.selectNull = function() {
  •  
  • $scope.selection = null;
  •  
  • };
  •  
  •  
  • // -- Define Scope Variables. --------------- //
  •  
  •  
  • // I am the "dynamic" data portion of the select menu.
  • // Dynamic in the sense that it may have come from a
  • // data source and we don't know who big the list is.
  • $scope.friends = [
  • {
  • id: 1,
  • name: "Tricia"
  • },
  • {
  • id: 2,
  • name: "Joanna"
  • },
  • {
  • id: 3,
  • name: "Sarah"
  • }
  • ];
  •  
  •  
  • // I am the "static" data portion of the select menu.
  • // This will be hard-coded in our Controller and used
  • // to facilitate selection.
  • $scope.staticOptions = [
  • {
  • value: "random",
  • text: "Random Friend"
  • },
  • {
  • value: "hrule",
  • text: "- - -"
  • }
  • ];
  •  
  •  
  • // When it comes to populating the Select options,
  • // we can't simply feed in the collection of friends
  • // because, well, that's not what that list is. Sure,
  • // it *contains* friends; but, it's NOT a list of
  • // friends. As such, we need to create a separate list
  • // of options that houses BOTH our dynamic data and
  • // our static data.
  • //
  • // As we do this, we want to map Friend collection
  • // onto a collection that mimics the Value/Text
  • // structure of our other options. This will make
  • // rendering the list easier.
  • $scope.options = $scope.staticOptions.concat(
  • $.map(
  • $scope.friends,
  • function( friend ) {
  •  
  • // NOTE: "Value" here will reference our
  • // friend object, which will be mirrored
  • // in the selection variable.
  • return({
  • value: friend,
  • text: friend.name
  • });
  •  
  • }
  • )
  • );
  •  
  •  
  • // Since the select list is NOT just a list of
  • // friends, we have to differentiate between our
  • // list of friends and our selection.
  • $scope.selection = null;
  • $scope.selectedFriend = null;
  •  
  •  
  • // -- Define Scope Events. ------------------ //
  •  
  •  
  • // As the select menu changes, it will change our
  • // selection. When that happens, we have to map that
  • // change onto our Friends collection.
  • $scope.$watch(
  • "selection",
  • function( value ) {
  •  
  • // No "valid" value was selected.
  • if ( value === "hrule" ) {
  •  
  • // Reset the selection
  • $scope.selection = null;
  •  
  • // A random friend was selected.
  • } else if ( value === "random" ) {
  •  
  • var index = Math.floor( Math.random() * 3 );
  •  
  • $scope.selection = $scope.friends[ index ];
  •  
  • // NULL or an actual friend was selected.
  • // In either case, we can use the selection
  • // value as our selected friend.
  • } else {
  •  
  • $scope.selectedFriend = $scope.selection;
  •  
  • }
  •  
  • }
  • );
  •  
  •  
  • }
  • );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

Since our options list contains mixed value reference, we have to explicitly react to changes in the select menu model. In my $watch() handler, I inspect the newly selected value to see if it needs to be copied into the selectedFriend model.

This approach is a bit tedious; but again, this only needs to be done when the Select menu is driven by a combination of both static and dynamic data. Notice, however, that since we are using an object property to define our Select value, we can easily change the selection by pointing it a given friend reference (as within the selectJoanna() method).




Reader Comments

There are no comments posted for this web log entry.

Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
May 20, 2013 at 4:24 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I thought when you used array notation to reference queries you always had to have the row or it would throw a similar error as well? ... read »
May 20, 2013 at 11:45 AM
Using jQuery's Animate() Step Callback Function To Create Custom Animations
This is really useful. I found out that you don't actually have to use a dummy css property (surprisingly). To animate a property in a linear-gradient for instance I did this this.css('someLinearGra ... read »
May 20, 2013 at 10:51 AM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Josh, Oh snap! You're totally right! I'm not sure I've ever tried that. I did know that you can call a number of other array-methods on ColdFusion query columns: http://www.bennadel.com/blog/167 ... read »
May 20, 2013 at 10:45 AM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Ben - I believe you can achieve the same functionality with ColdFusion's built in ArrayToList() function. ArrayToList( users[ "id" ] ); ... read »
May 20, 2013 at 10:21 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Is there any error logging and handling framework in angularjs, if not then in what way I can do this. ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools