Angular wrap transcluded elements separately? - angularjs

I am fairly new with Angular. And although I've made a lot of progress there are still a couple of things I don't know.
Currently I am running into a transclusion 'problem'.
Basically what we want is to wrap every transcluded element/directive seperately with html that's controlled by the parent directive.
Example:
<my:directive>
<my:sub-directive>Child 1</my:sub-directive>
<my:sub-directive>Child 2</my:sub-directive>
<my:sub-directive>Child 3</my:sub-directive>
</my:directive>
Desired result (I've left the directive elements to make the example a bit more clear):
<my:directive>
<ul>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 1</my:sub-directive>
</div>
</div>
</li>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 2</my:sub-directive>
</div>
</div>
</li>
<li>
<div class="panel">
<div class="header">
// Some stuff that's controlled by my:directive comes here
</div>
<div class="content">
<my:sub-directive>Child 3</my:sub-directive>
</div>
</div>
</li>
</ul>
</my:directive>
Does anyone have a clue how to handle this? I know in my example I could introduce a panel directive, but note this is just an example of the same problem.

You can pass a fifth parameter to your directive's link function transclude and then do your manipulation in there, here's a quick example:
angular.directive('myDirective', function ($compile) {
return {
restrict:'EA',
transclude:true,
link: function (scope, elm, attrs,ctrl,transclude) {
transclude(scope,function(clone) {
//clone is an array of whatever is inside your main directive
clone.filter('mySubDirective').each(function(index, value){
//create your html and you might have to $compile it before:
elm.append(myHtml);
});
});
}
};
})

Related

Expression in ng-if of AngularJS

Consider the following snippet
ng-if not working
<div class="row">
<div class="col-md-8">
<ul>
<li ng-repeat="bigL in bigLs">
<span ng-if="isObj(bigL)">{{bigL.note}}</span>
<ul ng-if="bigL instanceof Array">
<li ng-repeat="bigLl in bigL">
{{bigLl}}
</li>
</ul>
</li>
</ul>
</div>
</div>
ng-if working
<div class="row">
<div class="col-md-8">
<ul>
<li ng-repeat="bigL in bigLs">
<span ng-if="isObj(bigL)">{{bigL.note}}</span>
<ul ng-if="isArr(bigL)">
<li ng-repeat="bigLl in bigL">
{{bigLl}}
</li>
</ul>
</li>
</ul>
</div>
</div>
Controller
$scope.isArr = function(bigL){
return bigL instanceof Array;
};
I use ng-if to determine whether a nested ul is required to create (by determinate different data type inside the array bigLs), I come to a situation that ng-if cannot evaluate bigL instanceof Array, I then move this snippet inside a function, with the same context, the ng-of works properly, but still cannot understand why it is a need to wrap the expression inside a function instead of running it directly inside the ng-if.
Appreciate for any clarification, thanks!
I'm not exactly sure of the problem, but there are several things that have bad smells in your code:
Don't use 'instanceof Array', ever. It won't work in an angular expression.
Instead, use angular.isArray(). This will only work in javascript by adding a method to your scope.
So, you would want to do something like this:
Controller:
...
$scope.hasChildren = function(bigL1) {
return angular.isArray(bigL1);
}
Template:
...
<ul ng-if="hasChildren(bigL)">
...
As a bonus, it becomes much easier to unit test this code.

Reference Angular binding from javascript

I'm looking for a (best-practice) way to iterate through a list of elements in the scope of an angular controller and generate a div with an element specific id and append a svg to the element specific div. I'm very new to Angular...and suspect that the following attempt fails because I misunderstand Angular bindings?
What is a better way to do the following:
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<script type="text/javascript">
var svg_img = build_svg(args);
document.getElementById({{item.id}}).appendChild(svg_img);
</script>
</div>
</div>
</div>
Thanks!
You should place your logic inside of your controller and conditionally render as much html as necessary rather than invoking a script tag inside of an ng-repeat..
<div ng-controller="YourCtrl">
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}></div>
<div ng-bind-html="$scope.buildSvg(item)">
</div>
</div>
</div>
</div>
In your angular controller, you would then add a function to build out and return the svg for you to render.
app.controller('YourCtrl', ['$scope', function ($scope) {
$scope.buildSvg = function (item) {
// add logic here.
}
});
What does your function build_svg return?
We'd need a little more information about the kind of end-result you would like to get.
But yeah, it's not really good practice to have a script element within a ng-repeat directive.
I see two solutions here:
1- Build your SVG directly within the ng-repeat
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<svg height="{{item.svg.attrs.height}}" width="{{item.svg.attrs.width}}">
<circle cx="50" cy="50" r="40" stroke="black" stroke- width="3" fill="red" />
</svg>
</div>
</div>
</div>
Here is a plunker of this method:
http://plnkr.co/edit/g58BUPScjKHjRLAfx6ks?p=preview
2- Create a directive to generate your SVG with some additional parameters and flexibility.
<div id="top_level">
<div ng-repeat="item in items">
<div id={{item.id}}>
<my-svg attrs="item.svg.attrs"></my-svg>
</div>
</div>
</div>
The my-svg directive would generate a SVG element with the attrs parameters.

How can I call an angular controller function from my custom polymer web component?

I've seen this question and seen it answered but not in any way I can use or understand. The question is very specific and simple but somehow the answers are a bit over my head.
All I want to do is have a very simple menu work. when i menu item is clicked, I need it to of course use my angular controller function to handle it. I want all of that on the angular side. It actually does work when I simply use the elements directly without declaratively making my own.
So this works just fine and my ng-click is successfuly calling my acceptedSelect() in my controller:
<core-menu>
<div ng-repeat="order in acceptedOrders" ng-click="acceptedSelect(order)">
<paper-item class="my-paper-item" >
<div class="order-list-item">
<div class="order-number">{{order.Order}}</div>
<div class="order-date"> {{order.Date}} </div>
<div style="clear: both"></div>
<div class="order-address">
{{order.Property}}
</div>
</div>
</paper-item>
</div>
</core-menu>
But i do the same thing by wrapping the paper element in a polymer-element, it doesn't work. So this will not work:
<polymer-element name="my-app" attributes="refresh">
<template>
<style>
...
</style>
<core-menu>
<div ng-repeat="order in acceptedOrders" ng-click="acceptedSelect(order)">
<paper-item class="my-paper-item" >
<div class="order-list-item">
<div class="order-number">{{order.Order}}</div>
<div class="order-date"> {{order.Date}} </div>
<div style="clear: both"></div>
<div class="order-address">
{{order.Property}}
</div>
</div>
</paper-item>
</div>
</core-menu>
</template>
<script>
Polymer('my-app', {
});
<my-app></my-app>
I'm just not sure what I can do to make this work. Any help is greatly appreciated.

find out index of several using directive / angular

i wrote a directive that give out some clickable divs for a kicker game, it look like that:
<span>Team 1</span>
<scoreDisplay>
<div ng-click="setScore(0)" class="score-item">1</div>
<div ng-click="setScore(1)" class="score-item">2</div>
<div ng-click="setScore(2)" class="score-item">3</div>
<div ng-click="setScore(3)" class="score-item">4</div>
<div ng-click="setScore(4)" class="score-item">5</div>
<div ng-click="setScore(5)" class="score-item">6</div>
</scoreDisplay>
<span>Team 2</span>
<scoreDisplay>
<div ng-click="setScore(0)" class="score-item">1</div>
<div ng-click="setScore(1)" class="score-item">2</div>
<div ng-click="setScore(2)" class="score-item">3</div>
<div ng-click="setScore(3)" class="score-item">4</div>
<div ng-click="setScore(4)" class="score-item">5</div>
<div ng-click="setScore(5)" class="score-item">6</div>
</scoreDisplay>
after a click on a score-item, i want to get the index of the directive,
where the items inside, here the scopefunction inside the controller:
$scope.game = {
team_1:{name:'Ateam',score:0}
team_2:{name:'Bteam',score:0}
};
$scope.setScore = function(itemindex, directiveindex){
$scope.game["team_"+(directiveindex+1)].score = (itemindex+1)
}
any idea, how i can get the directive-index (the parent elemment dom index) with angular?
thanks for helping.
Use ng-repeat directive for code parts like:
<div ng-click="setScore(0)" class="score-item">1</div>
<div ng-click="setScore(1)" class="score-item">2</div>
<div ng-click="setScore(2)" class="score-item">3</div>
<div ng-click="setScore(3)" class="score-item">4</div>
<div ng-click="setScore(4)" class="score-item">5</div>
<div ng-click="setScore(5)" class="score-item">6</div>
You can replace in controller:
$scpe.teams = [...]
In template:
<div ng-repeat="team in teams" ng-click="setScore(team.score,$index)" class="score-item">{{team.name}}</div>
You can make similar change for scoreDisplay
Found a solution with a attribute "index":
<scoreDisplay index="1"></scoreDisplay>
<scoreDisplay index="2"></scoreDisplay>
This works fine, but i will search for a better solution
like this or with something like:
angular.element("body scoreDisplay").index($event.target);

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

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>

Resources