Angular.js ng-switch-when not working with dynamic data?
Asked Answered
T

2

31

I'm trying to get Angular to generate a CSS slider based on my data. I know that the data is there and am able to generate it for the buttons, but the code won't populate the ng-switch-when for some reason. When I inspect the code, I see this twice (which I know to be correct as I only have two items):

<div ng-repeat="assignment in assignments" ng-animate="'animate'" class="ng-scope">
    <!-- ngSwitchWhen: {{assignment.id}} -->
</div>

My actual code:

<div ng-init="thisAssignment='one'">
     <div class="btn-group assignments" style="display: block; text-align: center; margin-bottom: 10px">
         <span ng-repeat="assignment in assignments">
              <button ng-click="thisAssignment = '{{assignment.id}}'" class="btn btn-primary">{{assignment.num}}</button>
         </span>
     </div>

     <div class="well" style="height: 170px;">
         <div ng-switch="thisAssignment">
              <div class="assignments">
                   <div ng-repeat="assignment in assignments" ng-animate="'animate'">
                        <div ng-switch-when='{{assignment.id}}' class="my-switch-animation">
                        <h2>{{assignment.name}}</h2>
                        <p>{{assignment.text}}</p>
                   </div>
              </div>
         </div>  
    </div>  
</div>

EDIT: This is what I'm trying to emulate, though with dynamic data. http://plnkr.co/edit/WUCyCN68tDR1YzNnCWyS?p=preview

Tameratamerlane answered 5/2, 2014 at 0:1 Comment(5)
Describe what you would expect to happen in terms of DOM when you click on buttons. From your code it's not clear what you're trying to do. Are you trying to maintain a "selected" assignment?Epinasty
Also, notice that you're over shadowing variables inside of your ng-repeats. What I mean is, on the outer block, you assign a string 'one' to assignment, but then on your repeater blocks, you have the iterator variable with the same name assignment that you then assign to within the block. You should try changing the name of the property you use to track which is selected to something like selectedAssignment.Epinasty
I think that I've altered my code according to what you mentioned, but I'm still not quite clear. The modified code above does not work.Tameratamerlane
Did you read my answer below? Ng-switch is for hardcoded (non-dynamic) conditions. Try looking at the docs, see what other directives would swap out the DOM dynamically depending on a certain condition, like ng-if.Epinasty
Ok. I wasn't sure if your more recent comment negated anything that you'd mentioned before. I'll take a look. Sorry about that.Tameratamerlane
E
50

From the docs

Be aware that the attribute values to match against cannot be expressions. They are interpreted as literal string values to match against. For example, ng-switch-when="someVal" will match against the string "someVal" not against the value of the expression $scope.someVal.

So in other words, ng-switch is for hardcoding conditions in your templates.

You would use it like so:

<div class="assignments">
  <div ng-repeat="assignment in assignments" ng-animate="'animate'">
    <div ng-switch="assignment.id">
      <div ng-switch-when='1' class="my-switch-animation">
      <h2>{{assignment.name}}</h2>
      <p>{{assignment.text}}</p>
    </div>
  </div>
</div>

Now this might not fit your use case exactly, so it's possible you'll have to rethink your strategy.

Ng-If is probably what you need — also, you need to be aware of "isolated" scopes. Basically when you use certain directives, like ng-repeat, you create new scopes which are isolated from their parents. So if you change thisAssignmentinside a repeater, you're actually changing the variable inside that specific repeat block and not the whole controller.

Here's a demo of what you're going for.

Notice I assign the selected property to the things array (it's just an object).


Update 12/12/14: Adding a new block of code to clarify the use of ng-switch. The code example above should be considered what not to do.

As I mentioned in my comment. Switch should be thought about exactly like a JavaScript switch. It's for hardcoded switching logic. So for instance in my example posts, there are only going to be a few types of posts. You should know a head of time the types of values you are going to be switching on.

<div ng-repeat="post in posts">
  <div ng-switch on="post.type">

    <!-- post.type === 'image' -->
    <div ng-switch-when="image" class="post post-image">
      <img ng-src="{{ post.image }} />
      <div ng-bind="post.content"></div>
    </div>

    <!-- post.type === 'video' -->
    <div ng-switch-when="video" class="post post-video">
      <video ng-src="{{ post.video }} />
      <div ng-bind="post.content"></div>
    </div>
    
    <!-- when above doesn't match -->
    <div ng-switch-default class="post">
      <div ng-bind="post.content"></div>
    </div>
  </div>
</div>

You could implement this same functionality with ng-if, it's your job to decide what makes sense within your application. In this case the latter is much more succinct, but also more complicated, and you could see it getting much more hairy if the template were any more complex. Basic distinction is ng-switch is declarative, ng-if is imperative.

<div ng-repeat="post in posts">
  <div class="post" ng-class="{
      'post-image': post.type === 'image', 
      'post-video': post.type === 'video'">
    <video ng-if="post.type === 'video'" ng-src="post.video" />
    <img ng-if="post.type === 'image'" ng-src="post.image" />
    <div ng-bind="post.content" />
  </div>
</div>
Epinasty answered 5/2, 2014 at 0:26 Comment(6)
Ah! Thank you so much! Somewhat similar to joining (I think), but it just wasn't getting through. Much appreciated.Tameratamerlane
Can this method be modified to work with nested data? i.e I want to use ng-repeat to show a list of users, when a user is clicked it will show some profile info?Chef
Why would we have this functionality ? I feel it promotes magic values ... What if the switch relies on i18n translated values ?Include
Can you clarify what you mean by "magic" values? A switch is for hardcoded logic within your application. Lets say you have an ng-repeat, that iterates over posts. And you have several types of posts. Perhaps a text type, a image type, and a video type. In the switch statement, you would switch on="post.type" and then your when clauses would be for "text", "image" and "video" - and perhaps you would leave the "text" as the default type. i18n shouldn't enter into the equation, because you shouldn't switch on user input.Epinasty
A "magic" value is a value hardcoded at multiple place in the code instead of being centralized as constants. I wanted to use AngularJS contants as model data for my ng-switch-when statements. I cannot, and it makes me sad :(. I need the constants anyway, but now the value is duplicated.Saxton
Any particular reason you have single quotes around the 1 in the original example? Not criticizing, just curious if there were an actual reason.Colorimeter
B
12

Jon is definitely right on. Angular does not support dynamic ngSwitchWhen values. But I wanted it to. I found it actually exceptionally simple to use my own directive in place of ngSwitchWhen. Not only does it support dynamic values but it supports multiple values for each statement (similar to JS switch fall-throughs).

One caveat, it only evaluates the expression once upon compile time, so you must return the correct value immediately. For my purposes this was fine as I was wanting to use constants defined elsewhere in the application. It could probably be modified to dynamically re-evaluate the expressions but that would require more testing with ngSwitch.

I am use angular 1.3.15 but I ran a quick test with angular 1.4.7 and it worked fine there as well.

Plunker Demo

The Code

module.directive('jjSwitchWhen', function() {
    // Exact same definition as ngSwitchWhen except for the link fn
    return {
        // Same as ngSwitchWhen
        priority: 1200,
        transclude: 'element',
        require: '^ngSwitch',
        link: function(scope, element, attrs, ctrl, $transclude) {
            var caseStms = scope.$eval(attrs.jjSwitchWhen);
            caseStms = angular.isArray(caseStms) ? caseStms : [caseStms];

            angular.forEach(caseStms, function(caseStm) {
                caseStm = '!' + caseStm;
                ctrl.cases[caseStm] = ctrl.cases[caseStm] || [];
                ctrl.cases[caseStm].push({ transclude: $transclude, element: element });
            });
        }
    };
});

Usage

Controller
  $scope.types = {
      audio: '.mp3', 
      video: ['.mp4', '.gif'],
      image: ['.jpg', '.png', '.gif'] // Can have multiple matching cases (.gif)
  };
Template
  <div ng-switch="mediaType">
    <div jj-switch-when="types.audio">Audio</div>

    <div jj-switch-when="types.video">Video</div>

    <div jj-switch-when="types.image">Image</div>

    <!-- Even works with ngSwitchWhen -->
    <div ng-switch-when=".docx">Document</div>

    <div ng-switch-default>Invalid Type</div>
  <div>
Boswall answered 28/10, 2015 at 18:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.