How to get ng-repeat item inside transcluded template? - angularjs

How I can use ngRepeat item inside transcluded template? Is it possible?
Directive template:
<ng-transclude ng-repeat="record in records | filter1 | filter2"></ng-transclude>
Directive:
app.directive('myDirective', function () {
return {
templateUrl: '/views/directives/mydirective.html',
restrict: 'A',
transclude: true,
scope: {
records: '='
}
};
});
Controller view:
<div my-directive records="myRecords">
{{ myDirective.record }}
</div>

Doesn't look like it from the way you're doing it.
But you can $compile the template in the directive to achieve this.
http://jsbin.com/mirisixodo/edit?html,js,console,output

(Realizing this is almost certainly too late for you to use...)
Looks like this was discussed in detail in this AngluarJS GitHub issue, and there is, thanks to moneytree-doug, a way to solve your issue without resorting to compile.
The deal seems to be that, at least starting at AngularJS 1.2.18, transcluding creates a child scope of its own, so your iteration variable is no longer accessible.
$parent is accessible, however, and we can use that to access the iteration variable and accomplish what you're looking to do.
Building off of Micah's jsbin...
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-controller="TestCtrl">
<my-directive records="myRecords">
?: {{$parent.record}}
</my-directive>
</div>
</body>
</html>
JavaScript
var app = angular.module('app', [])
.controller('TestCtrl', function($scope) {
$scope.myRecords = ['foo', 'bar', 'baz'];
});
app.directive('myDirective', function () {
return {
scope: {
records: '='
},
transclude: true,
template: 'HELLO' +
'<div id="inner-transclude"' +
' ng-transclude' +
' ng-repeat="record in records">X</div>',
controller: function ($scope) {
console.log($scope.records);
},
restrict: 'E'
};
});
Result
HELLO
?: foo
?: bar
?: baz
JsBin of reasonable success.
So, again, the crux of this is changing the line if your controller from {{ myDirective.record }} to ?: {{$parent.record}}.

Related

How to control invoking of directive

In this plnkr :
https://plnkr.co/edit/F0XsOPZKq5HArFo9vtFs?p=preview
I'm attempting to prevent a custom directive being invoked by the use of ng-show. But if check console output when the directive is invoked 4 times : console.log('invoked') But ng-show shows/hides html elements it does not control what is rendered within the custom directive itself.
Is there a mechanism to pass the ng-show to the custom directive and if it's false then do call the directive ? I think could pass a new variable to the directive which contains same value as ng-show and then wrap the body of the directive in a conditional ?
src :
goob.html :
goob
http-hello2.html:
2. http-hello2.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<label>Filter: <input ng-model="search"></label>
<div ng-show="false">
<div ng-repeat="sourceUrl in sourceUrls | filter:search track by $index ">
<status-viewer url="sourceUrl"> </status-viewer>
</div>
</div>
</div>
</body>
</html>
mytemplate.html :
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http , $interval) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
console.log('invoked')
scope.isFinishedLoading = false;
$http.get(scope.url).success(function (data) {
scope.model = data;
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello2.html'
,'http-hello2.html'
,'test.html'
,'goob.html'];
}
test.html :
test
Instead of ng-show you should use ng-if directive to avoid directive linking before show
Forked plunker example

AngularJS: How do I access ng-model values injected from custom directive template?

I ran into a problem with AngularJS concerning directives and ng-model.
Assume the following example:
Within my HTML file:
<div ng-controller="MyCtrl">
<div ng-repeat="item in data">
<directive-item data="item"/>
</div>
<div>
<span>This is some input: {{ myinput }} </span>
</div>
</div>
...
My app.js looks like this (stripped for readability):
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.data = [
{ value: 'something' }
];
}]);
app.directive('directiveItem', function($compile) {
var template = '<div>'
+ '<label for="myinput">{{ data.value }}</label>'
+ '<input type="text" ng-model="myinput" />'
+ '</div>';
var linker = function(scope, element, attrs) {
element.html(template).show();
$compile(element.contents())(scope);
};
return {
restrict: 'E',
link: linker,
scope: {
data: '='
}
};
});
Maybe you can see my problem.
Everything works fine, except the display of {{ myinput }} outside of my directive.
It works perfect, if I display it within the injected template, but not outside of it. I did a LOT of google-research, but didn't find anything to help me out.
To clear some things out in front: $scope.data contains multiple objects with different data sets in my real application. So please look at this only as a quick example.
Also I do inject some more templates from my directive depending on a given $scope.data.object.type. The given code is only a rough example of what I have. As mentioned, everything else works without flaws.
Anyone here got an idea?
Regards!
€dit:
#Zeeshan did come up with a good way. Not yet 100% what I am looking for, but it pushes my thinking in another direction.
If anyone has the perfect solution, I am free for ideas! Thanks!
Angular Best Practice: Use the scope option to create isolate scopes when making components that you want to reuse throughout your app. I have tried a few cases to build understanding, with object (reference | alias behavior), with plain string. Following snippet simulates:
(function(angular) {
'use strict';
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function($scope) {
$scope.data = [{ value: 'something' }];
$scope.bar = {value:'barInitValueAsObject'};
$scope.tar = 'tarInitValueAsNonObject';
}])
.directive('oneItem', function($compile) {
return {
restrict: 'E',
scope: {
foo: '=',
bar:'=',
tar:'=',
},
template: '<div><label for="bar">{{ foo }} : </label> <input type="text" ng-model="bar.value" /></div>'
+ '<div><label for="bar">{{ foo }}</label> : <input type="text" ng-model="tar" /></div>'
}
})
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example15-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="item in data">
<one-item foo="item.value" bar="bar" tar="tar"></one-item>
</div>
<div>
<br><br>
<span>This is bar # Parent : {{ bar.value }} </span>
<br><br>
<span>This is tar # Parent : {{ tar }} </span>
</div>
</div>
</body>
</html>
Plnkr here
Happy Helping!
You can use another two-way binding in the directive's isolate scope. You already are passing in the data variable, so just add another variable to the scope that will bind with your myInput. Since this is a two-way binding, updating the value in one way will update the value elsewhere too. You'll probably just want to let the directive (and its HTML input) handle the input.
...
return {
restrict: 'E',
link: linker,
scope: {
data: '=',
myInput: '='
}
Finally, your scopes are not lining up properly because of your ng-repeat. It's not clear whether you want your display within the ng-repeat or not, so I just put the display also within the ng-repeat. In your controller's HTML:
<div ng-repeat="item in data">
<directive-item data="item" my-input="myInput"></directive-item>
<span>This is some input: {{ myinput }} </span>
</div>
<div>
</div>
Check out this plunker.

Building an Angular Directive with an ng-if

I want to use an Angular Directive for my Forms and based on the Attributes it should use an Input Tag or a Select Tag. I tried to do this with an ng-if:
-> If the select-options Attribute is set use a Select Tag
By using the ng-if in the Template of the Directive the Databinding to the model becomes somehow one-way. (i think the whole approach doing this by ng-if is wrong?)
Here is an Example:
<!DOCTYPE html>
<html>
<head>
</head>
<body ng-app="myApp" ng-controller="AppController">
Parent: <input type="number" ng-model="data.a"/><br>
Data.a: {{data.a}}<br/>
Data.b: {{data.b}}<br/>
InputDirective: <my-directive ng-model="data.a"></my-directive>
SelectDirective: <my-directive ng-model="data.b" select-options="{'aa':'Two As','bb':'Two Bs'}"></my-directive>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.3/angular.min.js"></script>
<script>
var app = angular.module("myApp",[]);
app.directive('myDirective', function()
{
return {
restrict: 'E',
scope: {
value: '=ngModel',
selectOptions: '='
},
template: '<input ng-if="!selectOptions" type="number" ng-model="value"/><select ng-if="selectOptions" ng-model="value" ng-options="k as v for (k,v) in selectOptions"></select>'
};
});
app.controller("AppController", function( $scope )
{
$scope.data = {
a: 54,
b: 'aa'
};
});
</script>
</body>
</html>
I found a solution: in the template change
ng-model="value"
to
ng-model="$parent.value"
ng-if is creating a child scope and it is creating this kind of problem https://github.com/angular/angular.js/wiki/Understanding-Scopes
i still dont know if ng-if is the right aproach to this.

Transclude inside ng-repeat, controller function incorrect transclude param

I created directive that appends transclude value after the directive body. I cant use ng-transclude inside my directive because it creates for the simple text as transclude value and it break my page. I use
controller: function($scope, $element, $transclude) {
$element.append($transclude().contents());
}
to append it. It works great, but when I use my directive inside ng-repeat something goes wrong and $transclude().contents() don't contain my text. Can someone explain this behavior?
Here example:
http://plnkr.co/edit/1y4avkwmgjhiKkuoZlug
I can't explain why $transclude().contents() don't contain your text, but I can show how to easy fix that please code below.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
});
app.directive('test', function(){
return {
transclude: true,
restrict: 'E',
template: '<div >From directive </div> <div ng-transclude></div><hr/>',
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="MainCtrl">
<div ng-repeat="a in [1, 2, 3, 4, 5]">
<test>From transclude {{a}}</test>
</div>
</body>

Render a directive inside another directive (within repeater template)

I am trying to render a directive inside another directive (not sure if the repeater inside the template is working this), and it seems to just output as text rather than compiling the directive (plunker code here: http://plnkr.co/edit/IRsNK9)
Any ideas on how I can actually get it to render properly my-dir-one, my-dir-two, my-dir-three directives inside the repeater?
index.html
<!doctype html>
<html ng-app="plunker" >
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
<script src="app.js"></script>
<script id="partials/addressform.html" type="text/ng-template">
partial of type {{type}}<br>
</script>
</head>
<body>
<div container></div>
<br /><br /><br />
<b>Below is just to test the directives are actually usable outside the repeater</b>
<div my-dir-one></div>
<div my-dir-two></div>
<div my-dir-three></div>
</body>
</html>
app.js
var app = angular.module('plunker', []);
app.directive('container', function () {
return {
restrict: 'A',
scope: {},
replace: true,
template: '<div class="views">' +
' <div class="view" ng-repeat="view in views">' +
' <div {{view.dir}}>{{view.dir}}</div>' +
' </div>' +
'</div>',
link: function (scope, elm) {
scope.views = [
{ dir: 'my-dir-one' },
{ dir: 'my-dir-two' },
{ dir: 'my-dir-three' }
];
}
}
});
app.directive('myDirOne', function () {
return {
restrict: 'A',
scope: {},
replace: true,
template: '<div>This is directive one.</div>'
}
});
app.directive('myDirTwo', function () {
return {
restrict: 'A',
scope: {},
replace: true,
template: '<div>This is directive two.</div>'
}
});
app.directive('myDirThree', function () {
return {
restrict: 'A',
scope: {},
replace: true,
template: '<div>This is directive three.</div>'
}
});
I managed to work around this issue by re-writing the code:
First I updated the template code as follows:
template: '<div class="views">' +
' <div class="view-wrapper" ng-repeat="view in views">' +
' <div view="{{view.dir}}"></div>' +
' </div>' +
'</div>',
Note that I created a new 'view' directive. Next the view directive definition as follows:
app.directive('view', ['$compile', function (compile) {
return {
restrict: 'A',
scope: {
view: '#'
},
replace: true,
template: '<div class="view"></div>',
controller: ['$scope', function (scope) {
scope.$watch('view', function (value) {
scope.buildView(value);
});
}],
link: function (scope, elm, attrs) {
scope.buildView = function (viewName) {
var view = compile('<div ' + viewName + '></div>')(scope);
elm.append(view);
}
}
}
}]);
So essentially, the view.dir variable is passed as an attribute to the 'view' directive, which then watches it's value and compiles a template with the directive in it.
This is in part a timing problem...I think that by the time it's resolving the {{}} expressions, it's already parsed out and rendered directives. It's not the nesting or the repeater that are the problem, per se.
What you're after here, though, is 'decide which directive to render based on the value of a variable'. There are a couple ways to do that.
Here's one that should work, though it might not scale as nicely as you'd like:
<div class='views' ng-repeat='view in views'>
<div ng-switch='view.dir'>
<div ng-when='my-dir-one' my-dir-one />
<div ng-when='my-dir-two' my-dir-two />
<div ng-when='my-dire-three' my-dir-three />
</div>
</div>
Other options for similar tricks: it looks like you could use ngBindTemplate to take a string from your data and use it as the template for an element. This would probably permit some tricky (and illegible) behavior.
You can specify a directive for an element as a class, but I don't know whether using ngClass to do this would allow you to dynamically select the directive, or whether that would come too late in the pipeline.

Resources