Angular.js ng-switch-when not working with dynamic data? - angularjs

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

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>

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>

Related

close-others "true" not working in ui-accordion when content is transclude

I have implemented accordion in my application. but close-other = "true" seems not working.
I implemented a common compenent to use collapsible component in my application-
Component-
var uiCollapsiblePanel = {
transclude: {
header: 'uiCollapsiblePanelHeader',
body: 'uiCollapsiblePanelBody'
},
bindings: {
isBlockExpanded: '<',
triggerCollapse: '&'
},
templateUrl: './ui-collapsible-panel.html',
controller: 'uiCollapsiblePanelController',
controllerAs: 'ucpc'
};
angular
.module('common')
.component('uiCollapsiblePanel', uiCollapsiblePanel);
ui-collapsible-panel.html -
<div class=" ui-collapsible-panel">
<uib-accordion close-others="true">
<div uib-accordion-group is-open="$ctrl.isBlockExpanded">
<uib-accordion-heading>
<div ng-transclude="header">
</div>
</uib-accordion-heading>
<div ng-transclude="body"></div>
</div>
</uib-accordion>
</div>
Calling ui-coolapsible-component-
<div class="app-custom-accordian">
<ui-collapsible-panel is-block-expanded='false'>
<ui-collapsible-panel-header>
<request-header all-requests-data="mrtt.allRequestsData">
</request-header>
</ui-collapsible-panel-header>
<ui-collapsible-panel-body>
<request-body all-requests-data="mrtt.allRequestsData" class="request-body-container"></request-body>
</ui-collapsible-panel-body>
</ui-collapsible-panel>
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
<div class="app-custom-accordian">
calling-ui-collapsible-component
</div>
can someone help me to identify whats the issue?
In this example you implemented single accordion. This function will work if you have multiple uib-accordion-group's. And opening of one will cause other to close.
By documentation (https://angular-ui.github.io/bootstrap/): close-others $ C (Default: true) - Control whether expanding an item will cause the other items to close."
Hope, it will help.
If this is not the case, i suggest to expand example with more details
(edited)
so as i understood correctly you are trying to do generic reusable accordion. For that you created 'ui-collapsible-panel.html'.
The root wrapper 'uib-accordion' needs multiple uib-accordion-group's.
In this example you are transcluding inside one group.
<div uib-accordion-group is-open="$ctrl.isBlockExpanded">
<uib-accordion-heading>
<div ng-transclude="header"></div>
</uib-accordion-heading>
<div ng-transclude="body"></div>
</div>
Which means you wont able to use 'close-others'.
For this to work, you either need some ng-repeat or transclude directly uib-accordion-group's. Basicaly this multiple transclude (for header and body) is not usefull, because all this data must be in same parent "uib-accordion". Example of direct transclude:
<ui-collapsible-panel>
// preload some data (for example groups)
<div uib-accordion-group is-open="$ctrl.isBlockExpanded" ng-repeat="group in $ctrl.groups">
<uib-accordion-heading>
// and use group data to for request-header and request-body
<request-header all-requests-data="mrtt.allRequestsData"></request>
<uib-accordion-heading>
<request-body all-requests-data="mrtt.allRequestsData" class="request-body-container"></request-body>
</div>
</ui-collapsible-panel>
Reference https://angular-ui.github.io/bootstrap/ (orignal uib-bootstrap plunker example)

Angular: a part of view does not update

I have a directive template with the following code
<div class="colorpicker">
<div>Chosen color</div>
<div class="color_swatch" style="background-color: {{ngModel}}"> </div>
<div class="clearfix"></div>
<div>Standard colors</div>
<div class="color_squares">
<div ng-repeat="color in colorList">{{color.trim() == ngModel.trim()}} //does not update
<div class="color_swatch" style="background-color: {{ color }};"></div>
</div>
</div>
<div class="clearfix"></div>
In the directive, I update the ngmodel using the below code to the color that was clicked - the div next to "chosen color" is updated with the selected color.
But, the expression "{{color.trim() == ngModel.trim()}}" always amounts to false.
{{color.trim() == ngModel.trim()}}
I have debugged the code and the values are exactly the same.
What I am missing?
This is probably because your variable is precisely named 'ngModel' see that article for more explanation : http://zcourts.com/2013/05/31/angularjs-if-you-dont-have-a-dot-youre-doing-it-wrong/
To resume this article : never use raw fields use always a dot. So in your scope change
$scope.ngModel
By
$scope.data.ngModel
And in your html change ngModel by data.ngModel.
When using dot you may have some undefined error, this is because you have to initialize the object :
$scope.data={};
Of course you can jsut rename your variable, but you may still have a problem with others directives.
I solved this by removing curly braces around color and using ng-style
<div class="color_swatch" id="colorpicker_selected_color" ng-style="{'background-color': selectedColor}" > </div>

Why is my ng-hide / show not working with my ng-click?

I want to show the elements that contain displayCategory.name with the ng-click above it, but it's not working as expected.
.divider-row
.row-border(ng-hide="showMe")
.row.row-format
.col-xs-12.top-label
Find where you stand
%hr.profile
.row.labelRow
.col-xs-12
%ul
%li(ng-repeat='category in service.categories')
.clear.btn.Category(ng-click='thisCategory(category) ; showMe = true') {{category.name}}
.divider-row
.row-border(ng-show="showMe")
.row.row-format
.col-sm-12.col-md-12.top-label.nopadLeft
What do you think about {{displayCategory.name}}
I dropped your haml into a converter and this is what it spat out (clearly incorrect):
<div class="divider-row">
<div class="row-border">(ng-hide="showMe")
<div class="row row-format">
<div class="col-xs-12 top-label">
Find where you stand
</div>
</div>
<hr class="profile"/>
<div class="row labelRow">
<div class="col-xs-12">
<ul>
<li>(ng-repeat='category in service.categories')
<div class="clear btn Category">(ng-click='thisCategory(category) ; showMe = true') {{category.name}}</div>
</li>
</ul>
</div>
</div>
</div>
<div class="divider-row"></div>
<div class="row-border">(ng-show="showMe")
<div class="row row-format">
<div class="col-sm-12 col-md-12 top-label nopadLeft">
What do you think about {{displayCategory.name}}
</div>
</div>
</div>
</div>
So after some quick googling I found that you should be writing it like this:
.divider-row
.row-border{"ng-hide" => "showMe"}
.row.row-format
.col-xs-12.top-label
Find where you stand...
As that will convert to what you need:
<div class="divider-row">
<div class="row-border" ng-hide="showMe">
<div class="row row-format">
<div class="col-xs-12 top-label">
Find where you stand
Using curly braces instead of round ones for attributes
I cannot run your code to verify, but I think the problem is that the binding property showMe should be replaced with some object like status.showMe.
For example, define $scope.status = { showMe: false}; outside the ng-repeat (in your controller maybe).
Please check a working demonstration: http://jsfiddle.net/jx854d3y/1/
Explanations:
ng-repeat creates a child scope for each item. The child scope prototypical inherits from the parent scope. In your case, the primitive showMe is assigned to the child scope. While you use it outside the ng-repeat, where it tries to get the value from the parent scope, which is undefined. That is why it is not working.
Basic rule is: always use Object, instead of primitive types, for binding.
For more details, please refer to: https://github.com/angular/angular.js/wiki/Understanding-Scopes

AngularJS: Best practice displaying static text based on a state/variable

I have small text portions like
<div>
<h4>Why Register?</h4>
<p>As candidate...</p>
</div>
opposed to
<div>
<h4>Why Register?</h4>
<p>As company...</p>
</div>
Based on a variable in my controller I insert the correct partial with:
<div ng-switch on="role">
<div ng-switch-when="candidate">
<div ng-include="'candidate.html'"></div>
</div>
<div ng-switch-when="company">
<div ng-include="'company.html'"></div>
</div>
<div ng-switch-default>
<div ng-include="'candidate.html'"></div>
</div>
</div>
This does the job but it looks awful. Is there any way I could do it better?
You could always hold your string vars in javascript or external json file and use markup which is tied to a model like this:
<div ng-controller="something">
<h4>Why Register?</h4>
<p>{{who}}</p>
</div>
and then inside your "something" controller provide code:
if(role == "company")
$scope.who = "As company...";
else
$Scope.who = "As candidate...";
If you have many places in code that use such feature, you could consider holding variables in external json and then reading them in javascript/controller.
You can use:
<div ng-include="(role || 'candidate') + '.html'"></div>
If the parts are not that big you can put them all up there and use ng-show to filter which gets actually shown. This takes up the least markup.
<h4>Why register?</h4>
<p ng-show="isCompany">Company targeted content...</p>
<p ng-show="isCandidate">Candidate targeted content...</p>

Angular ng-switch with boolean

I want to check boolean data with angular ng-switch
this is my code. but it is not working
<div ng-switch={{Item.ItemDetails.IsNew}}>
<div ng-switch-when="true">
<p class="new fontsize9 fontWeightBold">NEW</p>
</div>
</div>
<div ng-switch={{Item.ItemDetails.IsFeatured}}>
<div ng-switch-when="true">
<div class="featured">
<p class="fontWeightBold fontsize8">featured</p>
</div>
</div>
</div>
values of {{Item.ItemDetails.IsNew}} and {{Item.ItemDetails.IsFeatured}} are true or false
Convert the boolean to a string:
<div ng-switch="Item.ItemDetails.IsNew.toString()">
<div ng-switch-when="true">
If you are just checking for true values, ng-if seems more appropriate and reduces the need for additional divs containing the code, reducing your sample too:
<div ng-if="Item.ItemDetails.IsNew">
<p class="new fontsize9 fontWeightBold">NEW</p>
</div>
<div class="featured" ng-if="Item.ItemDetails.IsFeatured">
<p class="fontWeightBold fontsize8">featured</p>
</div>
Full docs at: http://docs.angularjs.org/api/ng.directive:ngIf
This syntax works for me:
<div ng-switch="Item.ItemDetails.IsFeatured">
<div ng-switch-when="true">FEATURED ITEM HTML</div>
<div ng-switch-default>REGULAR ITEM HTML (not featured)</div>
</div>
You should remove the {{}} from the ng-switch:
Change this <div ng-switch={{Item.ItemDetails.IsNew}}>
to <div ng-switch=Item.ItemDetails.IsNew>
The attribute value of ng-switch are interpreted as literal string values to match against. (Meaning that cannot be expressions.) For example, ng-switch-when="someVal" will match against the string "someVal" not against the value of the expression $scope.someVal.
If you really have to use ng-switch, it can be forced to semi-use evaluative expressions by the workaround of the .toString() javascript method. Example, using the scope variables numeric 'lastSortIndex' and the boolean 'reverseSorted', plus the AngularJS HTML variable '$index':
<div ng-switch="((lastSortIndex === $index).toString()+(reverseSorted).toString())">
<div ng-switch-when="truetrue">
<span class="glyphicon glyphicon-chevron-up">{{ header }}</span>
</div>
<div ng-switch-when="truefalse">
<span class="glyphicon glyphicon-chevron-down">{{ header }}</span>
</div>
<div ng-switch-default>{{header}}</div>
</div>
Note the above example is concatenating the booleans together and then evaluating their string contents. Would be better to move this into a callable function that returns a string to be evaluated in the switch-case.
Though I would recommend if possible for you to keep the logic in the logic-controllers area of the code (the javascript files). You can use the ng-html-safe AngularJS directive in combination with their Sanitize feature to call a function in Javascript that switches and returns desired snippets of HTML code for you. Example:
index.html:
<html ng-app="myApp">
<head>
<script src="https://code.angularjs.org/1.3.13/angular-sanitize.js">
</head>
<body ng-controller="MainController">
<!-- add your other code, like a table code here -->
<div ng-bind-html="HeaderSortIcon(lastSortIndex, $index, reverseSorted, header)">
</div>
</body>
script.js:
var myApp = angular.module('myApp ', ['ngSanitize']);
$scope.HeaderSortIcon = function (lastSortIndex, $index, reverseSorted, header) {
if (lastSortIndex === $index) {
if( reverseSorted )
{
return '<div><span class="glyphicon glyphicon-chevron-up"></span>' + header + '</div>';
}
else{
return '<div><span class="glyphicon glyphicon-chevron-down"></span>' + header + '</div>';
}
}
else {
return header;
}
}

Resources