ng-repeat inside a directive template - access index - angularjs

I have the following problem:
I have a custom directive, which shows one or more tables, by using ng-repeat inside the template string. Inside each table, several other custom directives are placed. I want these to know the index of the element used, but cant manage to get this done. My code looks like this now:
.directive('myStuffText', function ($rootScope){
return {
restrict: 'A',
require: '^form',
replace: true,
scope: true,
template:
......
'<table border="1" ng-repeat="elt in myModel.newStuffList">
......
'<tr>' +
'<td colspan="3"><div my-add-some-editor my-element-index="$index"/></td>' +
'</tr>'
'</table>',
link: function (scope, elt, attrs){
scope.cockpitPolicyModel.newPolicyList = [];
}
};
})
Independently from how I try, I always get the string $index or {{$index}} in the template function of the my-add-some-editor directive, not the value of it..
Edit - added the nested directive:
.directive('myAddSomeEditor', function($rootScope){
return {
restrict: 'A',
require: '^form',
scope: true,
template: function ($scope, $attr){
return
.....
'<span id="myAddSomeEditor" name="myAddSomeEditor" class="form-control" my-generic-editor '+
'my-input-mapping="myModel.someText"></span>'
.....
;
}
};
})

That probably happens because in your my-add-some-editor directive you have this definition in the isolate scope:
myElementIndex: '#'
That's why you're getting the literal string of what you're writing there in the HTML.
Change that to:
myElementIndex: '='
EDIT: Since you said you're not using isolated scope, try this in the parent directive: try doing my-element-index="{{$index}}". And this in the child directive's link function:
link: function (scope, elem, attr) {
attr.$observe('index', function(val) {
scope.index = val;
});
}

Related

Transclude share scope with directive template

Assume this directive:
<validation-errors field="someField">Some errors: {{errors}}</validation-errors>
I thought I could create the directive function simple as this:
return {
require: '^form',
restrict: 'E',
link: link,
scope: {
field: '#'
},
transclude: true,
template: '<span ng-show="errors" class="alert-danger" ng-transclude></span>'
};
function link(scope, el, attr, ctrl, transcludeFn) {
scope.errors = ctrl.Validator.getErrors(attr.field);
}
But since Transclusion is the process of extracting a collection of DOM element from one part of the DOM and copying them to another part of the DOM, while maintaining their connection to the original AngularJS scope from where they were taken. (from docs), the scope doesn't work like I thought it would.
So I tried this which works, except that the "Some errors" part is duplicated:
transcludeFn(function(clone, transScope) {
scope.errors = transScope.errors = ctrl.Validator.getErrors(attr.field);
el.append(clone);
});
It doesn't work if I remove el.append(clone);.
What's the best way to make the transcluded content share the directive template's scope?
If you want to create the errors using the directive, give something like this a try, I've updated the code so that it compiles the template as well, now working exactly as the ng-transclude directive would out of the box.
'use strict';
/* Directives */
angular.module('myApp.directives', []).
directive('errordirective', ['$compile',function($compile) {
return {
restrict: 'E',
scope: {
field: '#',
},
transclude: true,
//Create some error text
controller: function() {
this.errorText = "Some Errors We Created";
//Make the template available to the link fn, and make it an angular element.
this.template = angular.element('<span class="alert-danger" ng-transclude></span>')
},
//Make the controller available easily
controllerAs: 'Errors',
//Bind the scope properties to the controller, only works with controllerAs
bindToController: true,
template: '<span class="alert-danger" ng-transclude></span>',
link: function(scope, elem, attrs, ctrl, transcludefn) {
//Replace the original element with the new one that has the error text from our controller
transcludefn(scope, function(el) {
var template = ctrl.template;
var html = el.html();
//Add the transcluded content to the template
template.html(html);
//Compile the template with the appropriate scope
template = $compile(template)(scope);
//Replace with the new template
elem.replaceWith(template);
});
}
};
}]);

Angularjs templateUrl fails to bind attributes inside ng-repeat

I'm using directive to display html snippets.
And templateUrl inside the directive,
to be able to include snippets as html file.
The directive does not work, if I try to call
inside a builtin ng-repeat directive
({{snip}} is passed as is, without substitute):
div ng-repeat="snip in ['snippet1.html','snippet2.html']">
<my-template snippet="{{snip}}"></my-template>
</div>
For reference, here is the directive:
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: true,
scope: { snippet: '#'},
templateUrl: function(elem, attrs) {
console.log('We try to load the following snippet:' + attrs.snippet);
return attrs.snippet;
}
};
});
And also a plunker demo.
Any pointer is much appreciated.
(the directive is more complicated in my code,
I tried to get a minimal example, where the issue is reproducible.)
attrs param for templateUrl is not interpolated during directive execution. You may use the following way to achieve this
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: false,
scope: { snippet: '#'},
template: '<div ng-include="snippet"></div>'
};
});
Demo: http://plnkr.co/edit/2ofO6m45Apmq7kbYWJBG?p=preview
Check out this link
http://plnkr.co/edit/TBmTXztOnYPYxV4qPyjD?p=preview
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: true,
scope: { snippet: '=snippet'},
link: function(scope, elem, attrs) {
console.log('We try to load the following snippet:' + scope.snippet);
},
template: '<div ng-include="snippet"></div>'
};
})
You can use ng-include, watching the attrs. Like this:
app.directive("myTemplate", function() {
return {
restrict: 'E',
replace: true,
link: function(scope, elem, attrs) {
scope.content = attrs.snippet;
attrs.$observe("snippet",function(v){
scope.content = v;
});
},
template: "<div data-ng-include='content'></div>"
};
});
Just made changes in directive structure. Instead of rendering all templates using ng-repeat we will render it using directive itself, for that we will pass entire template array to directive.
HTML
<div ng-init="snippets = ['snippet1.html','snippet2.html']">
<my-template snippets="snippets"></my-template>
</div>
Directive
angular.module('myApp', [])
.controller('test',function(){})
.directive("myTemplate", function ($templateCache, $compile) {
return {
restrict: 'EA',
replace: true,
scope: {
snippets: '='
},
link: function(scope, element, attrs){
angular.forEach(scope.snippets, function(val, index){
//creating new element inside angularjs
element.append($compile($templateCache.get(val))(scope));
});
}
};
});
Working Fiddle
Hope this could help you. Thanks.
it seems you are trying to have different views based on some logic
and you used templateUrl function but Angular interpolation was not working, to fix this issue
don't use templateUrl
so how to do it without using templateUrl
simply like this
app.directive("myTemplate", function() {
return {
link: function(scope, elem, attrs) {
$scope.templateUrl = '/ActivityStream/activity-' + $scope.ativity.type + '.html'
},
template: "<div data-ng-include='templateUrl'></div>"
};
});
hope this is simple and esay to understand

AngularJS model not updating while typing

In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle

AngularJS How to access elements inside directive before they get replaced

How do I get the input element from within the directive before the template overwrites the contents?
html
<div xxx>
<input a="1" />
</div>
js
app.directive('xxx', function(){
return {
restrict: 'A',
template: '<p></p>',
replace: true, //if false, just leaves the parent div, still no input
compile: function(element, attrs) {
console.log(element);
return function (scope, iElement, iAttrs) {
}
}
};
});
i am on angular 1.0.x, I cannot pass in optional scope parameters with the '=?' syntax and i want to be able to override a portion of the default template of the directive in a very flexible way. instead of adding a scope variable or attribute everytime that I just plan on passing through the directive, I want to be able to supply the whole element to be used.
edit
the input must retain the scope of the directive, and not the parent.
edit
I am trying to include a partial template inside a directive that will overwrite a piece of the actual template. The piece I am including therefore needs to have access to the directive's scope and not the parent's.
Update
It seems if I do not provide a template or a template URL and instead replace the contents manually using the $templateCache I can have access to the inner elements. I want to let angular handle the template and the replacement though and just want to be able to access the contents in the directive naturally before they get replaced.
Solution
Plunkr
html
<body ng-controller="MainCtrl">
<div editable="obj.email">
<input validate-email="error message" ng-model="obj.email" name="contactEmail" type="text" />
</div>
</body>
js
app.controller('MainCtrl', function($scope) {
$scope.obj = {
email: 'xxx'
};
});
app.directive('editable', function($log){
return {
restrict: 'A',
transclude: true,
template: '<div ng-show="localScopeVar">{{value}}<div ng-transclude></div></div>',
scope: {
value: '=editable'
},
link: function(scope) {
scope.localScopeVar = true;
}
};
});
app.directive('validateEmail', function($log){
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function(scope, el, attrs, ctrl) {
console.log(attrs['validateEmail']);
}
};
});
I believe you're looking for the transclude function (link is to 1.0.8 docs). You can see what's going on with:
app.directive('xxx', function($log){
return {
restrict: 'A',
transclude: true,
compile: function(element, attrs, transclude) {
$log.info("every instance element:", element);
return function (scope, iElement, iAttrs) {
$log.info("this instance element:", element);
transclude(scope, function(clone){
$log.info("clone:", clone);
});
}
}
};
});

Directive scope unreachable

I am trying to "share" a scope between two directives as follow:
toolbarActions.directive('toolbarActions', function (toolbar) {
return {
restrict: 'E',
scope: true,
replace: true,
link: function (scope, element, attrs) {
scope.toolbarActions = toolbar.getActions();
},
template: "<div class='centered-div-container'>" +
"<toolbar-action ng-repeat='toolbarAction in toolbarActions' icon-source='{{toolbarAction.icon}}'></toolbar-action>>"+
"</div>",
};
});
The inner directive looks like this:
toolbarActions.directive('toolbarAction', function () {
return {
restrict: 'E',
scope: {
iconSource: '&'
},
replace: true,
link: function (scope, element, attrs) {
scope.imageUrl = attrs.iconSource;
},
template: "<div class='centered-div-content header-control' ng-click='actionFunction()'>" +
"<img ng-src='{{imageUrl}}' /> "+
"</div>",
};
});
In the following simple HTML:
<div class="header-content">
<toolbar-actions></toolbar-actions>
</div>
However, no matter what I do, I can't make the icon-source retrieve the correct value (toolbarAction.icon), but rather an exception is thrown:
Error: [$parse:syntax] Syntax Error: Token 'toolbarAction.icon' is unexpected, expecting [:] at column 3 of the expression [{{toolbarAction.icon}}] starting at [toolbarAction.icon}}]. http://errors.angularjs.org/1.2.0-rc.2/$parse/syntax?p0=toolbarAction.icon&p1=is%20unexpected%2C%20expecting%20%5B%3A%5D&p2=3&p3=%7B%7BtoolbarAction.icon%7D%7D&p4=toolbarAction.icon%7D%7D minErr/<#http://localhost:9000/bower_components/angular/angular.js:78
I've tried many versions of replacing the scope definition on the toolbarAction directive (such as:)
scope:true
or
scope:false
And tried many transculsion combinations as well.
What am I doing wrong?
I think the best solution in your case is to use the $parse service, remove the scope of your toolbarAction directive and watch for any modification of the parsed attribute.
In toolbarActions directive replace {{toolbarAction.icon}} by toolbarAction.icon only :
template: "<div class='centered-div-container'>" +
"<toolbar-action ng-repeat='toolbarAction in toolbarActions' icon-source='toolbarAction.icon'></toolbar-action>"+
"</div>"
And your toolbarAction directive becomes something like :
.directive('toolbarAction', function ($parse, toolbar) {
return {
restrict: 'E',
replace: true,
link: function (scope, element, attrs) {
var getImgURL = $parse(attrs.iconSource); //get the raw json path and create a function out of it
//Get notified when the value is reversed
scope.$watch(getImgURL, function(value) {
//Finally store the real value to be used in your directive template
scope.imageUrl = value;
});
},
template: "<div class='centered-div-content header-control' ng-click='actionFunction()'>" +
"<img ng-src='{{imageUrl}}' /> "+
"</div>",
};
});
I have assembled a working plunker accessible here with this you should be all set :)

Resources