angularjs transclude and ng-repeat: doing it right - javascript

I have the following code that produces not the expected result:
<div class="outer">1<div>content</div></div>
<div class="outer">2<div>content</div></div>
<div class="outer">3<div>content</div></div>
<div class="outer">4<div>content</div></div>
Instead the result is:
<div class="outer">1</div>
<div class="outer">2</div>
<div class="outer">3</div>
<div class="outer">4<div>content</div></div>
It seems that the ng-repeat gets done first and for the last item it handles the transclude. I know that ng-repeat create the nodes during the compile phase, but I thought in the link phase the link function is called for each node and adds the embedded content.
Can somebody explain what is happening here and how to do it correct?
<!DOCTYPE html>
<html ng-app="Transclude">
<head lang="de">
<meta charset="UTF-8">
<title>Transclude</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
</head>
<body>
<outer ng-repeat="counter in [1,2,3,4]" value="counter">
<div>content</div>
</outer>
<script>
angular.module('Transclude', [])
.directive('outer', function () {
return {
restrict: 'E',
scope: {
value: '='
},
replace: true,
transclude: true,
template: '<div class="outer">{{value}}</div>',
link: function(scope, element, attributes, controller, transclude) {
var transcludedContent = transclude();
element.append( transcludedContent );
}
};
})
</script>
</body>
</html>

transclude() by itself just links the content of the directive to the proper scope and returns it. What you want to do is actually clone the content (make copy of it), before transclude links it. As you code stands right now, your transcluded content is just getting moved from one instance to another - ending up on the last one because, well, it's the last one.
You can do this with the cloneAttachFn. You pass it in to transclude.
link: function(scope, element, attributes, controller, transclude) {
transclude(scope, function(clone) {
element.append( clone );
});
}

Related

Get Height for multiple elements without JQuery in AngularJS

Fairly new to Angular and still trying to wrap my head around a few things.
I need to get the height of multiple items on a dashboard. I have seen this answer:
Get HTML Element Height without JQuery in AngularJS
However, I can't work out how to get it to work for multiple items. Surely I don't need to write a separate directive for each element.
So playing with this Plunker, I changed the html to below, but get identical values for both elements.
hmm
script.js:
angular.module('myModule', [])
.directive('myDirective', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
scope.height = element.prop('offsetHeight');
scope.width = element.prop('offsetWidth');
}
};
})
;
and the html:
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="myModule">
<h1 my-directive>Hello Plunker! width: {{width}}, height: {{height}}
</h1>
<h3 my-directive>Smaller Hello Plunker! width: {{width}}, height: {{height}}
</h3>
</body>
</html>
The directives have no scope, so they are storing the values on $rootScope. The values reflect the height and width of the last directive to execute. Fix your directive to use either inherited scope or isolate scope.
angular.module('myModule', [])
.directive('myDirective', function($timeout) {
return {
restrict: 'A',
//set scope to inherited
scope: true,
//OR use isolate scope
//scope: {},
link: function(scope, element) {
scope.height = element.prop('offsetHeight');
scope.width = element.prop('offsetWidth');
}
};
})
;
By setting the scope property, the $compile service will create a new scope (either inherited or isolate) for the directive to store values unique to the directive.
For more information on directive scopes, see AngularJS Comprehensive Directive API -- scope.
I found this script to be very helpful
https://github.com/pocesar/angular-track-height

Angular nested directive only the first child is rendered [duplicate]

This question already has answers here:
AngularJS - html under my directive is not showing
(2 answers)
Closed 6 years ago.
I have a parent directive which includes two children directives, first and second. I noticed that only the first child is rendered. Also, if I put some arbitrary HTML markup before the first one, it's all rendered but if I put them after that then they will not show up. Why is this?
See the jsfiddle:
<!-- index.html -->
<div ng-app="myApp">
<my-parent-dir />
</div>
<!-- main.js -->
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child /> <my-second-child />'
};
});
app.directive("myFirstChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="first">',
};
});
app.directive("mySecondChild", function() {
return {
restrict: 'E',
template: '<input type="text" placeholder="second">',
};
});
Try to use it like this:
var app = angular.module("myApp", []);
app.directive("myParentDir", function() {
return {
restrict: 'E',
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
};
});
From the angular issues in github:
self-closing or void elements as the html spec defines them are very
special to the browser parser. you can't make your own, so for your
custom elements you have to stick to non-void elements ().
this can't be changed in angular.
Self defined tags are no leaf tags so you will have to use:
template:'<my-first-child></my-first-child> <my-second-child></my-second-child>'
Since you're using custom tags, you need to close the tag, because HTML spec does not allow self closing tags.
template: '<my-first-child></my-first-child> <my-second-child></my-second-child>'
JSFiddle

Angular model looses scope with ng-included in directive [duplicate]

This question already has answers here:
Losing scope when using ng-include
(4 answers)
Closed 8 years ago.
I wasn't sure on how to properly title this question, so here it goes ..
I'm trying to setup a dynamic way to change out templates inside of a directive using the ng-include method. I've set up two Plunker examples and although one should work just like the other, that doesn't seem to be the case.
HTML for both Examples:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<main></main>
</body>
</html>
Example #1:
http://plnkr.co/edit/bi3Plrm8xufuN79Nvajj?p=preview
I'm setting up two directives (one main, and one nested as a child):
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel">'
};
}
]);
Easy. When running the app both fields populate respectively as the model changes.
Example #2:
http://plnkr.co/edit/3ajcTyfJElEzbqvsWwBM?p=preview
The HTML stays the same, but the js is a little different:
angular.module('myApp', ['Test']);
angular.module('Test', [])
.directive('main', [
function () {
return {
restrict: 'E',
template: '<input type="text" ng-model="myModel"><br><br><child></child>'
};
}
])
.directive('child', [
function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.myTemplate = 'test-template.html'
},
template: "<div ng-include='myTemplate'></div>"
};
}
]);
test-template.html:
<input type="text" ng-model="myModel">
This time, if I interact with the first input that is generated, both inputs update respectively as they should. Here's when it gets interesting ... When/if I interact with the second input (the one generated by ng-include) I loose all binding. Forever... Almost as if it's created its own version of the model. Afterwards, changing the first input has no effect on the second.
What is happening here? Is it indeed creating a new instance of myModel? And if so, how can this be avoided when using this ng-include method?
This is no weird, as PSL said,
ng-include creates new scope.
If you want to create the behaviour that keeps those models attached,
You should change
<input type="text" ng-model="myModel">
To:
<input type="text" ng-model="$parent.myModel">

AngularJS: Replace an element using Transclude: 'element' but not the deprecated 'replace'?

I am trying to create a directive that can create multiple elements an replace the calling element with the multiple. Specifically I want to set the directive on a single list-item and have it create multiple list items w/o a wrapping element. (Using the <UL> for the directive works but prevent me from including 'static' items.) Here is the markup:
<ul>
<li>static first</li>
<li my-repeater="myVar"></li>
<li>static last</li>
</ul>
In my controller I'll define myVar:
$scope.myVar = ['one', 'two', 'three'];
And my directive looks like this:
myApp.directive('myRepeater', function () {
return {
restrict: 'A',
transclude: 'element',
replace: true, //<--- DEPRECATED
scope: {
val: '=myRepeater'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
};
In AngularJS v1.2.26 this works UNLESS you remove 'replace', then you get nothing. Is this just not possible? I did note that in the docs for v1.3.4 that they feel:
There are very few scenarios where element replacement is required for the application function, ...
But my case above seems to be a clear example of the need for this, unless there is a 'better way'!...?
If you don't have to do it as an attribute, then you can do it using an element:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<ul>
<li>static first</li>
<my-repeater var="myVar"></my-repeater>
<li>static last</li>
</ul>
</div>
</div>
And the directive
.directive('myRepeater', function () {
return {
restrict: 'E',
scope: {
val: '=var'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
})
I updated the fiddle to show it http://jsfiddle.net/6rjr8nq5/1/
Since unknown tags are just ignored, it works fine.
And if you need the attribute, you could stick it on the ul, transclude, and use the link function to properly place the static data, assuming you know where it goes.

how to write custom 'row' and 'col' element for angularjs

I'm trying to write a little dsl around the grid elements outlined here: http://foundation.zurb.com/docs/grid.php
basically what I wish to do is to
<row>
<column two mobile-one>{{myText}}</col>
<column four centered mobile-three><input type="text" ng-model="myText"></input></col>
</row>
transform into:
<div class="row">
<div class="columns two mobile-one">{{myText}}</div>
<div class= "columns four centered mobile-three"><input type="text" ng-model="myText"></input></div>
</div>
Ideally, I wish to write something that can take arbitrary nesting: row -> col -> row -> col -> row.....
I am having trouble getting the first step right - nesting the elements because I can't quite figure how how to get the child elements into another template without seriously compromising the compilation process.
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = tElement.children();
tElement.replaceWith(
$('', {class: 'row',}).append(content));
}
}
});
just does not do anything. The failed attempt is shown here - http://jsfiddle.net/ZVuRQ/
Please help!
I was hoping not to use ng-transclude because I found that it added an additional scope.
Here is a directive that does not use ng-transclude:
app.directive('row', function() {
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = angular.element('<div class="row"></div>')
content.append(tElement.children());
tElement.replaceWith(content);
}
}
});
You may want to use tElement.contents() instead of tElement.children().
You don't need jquery at all in your example (but you need to include it on your page/jsFiddle):
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
template: '<div class="row" ng-transclude></div>',
transclude: true,
replace: true
};
});

Categories

Resources