Directive scope unreachable - angularjs

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 :)

Related

ng-repeat inside a directive template - access index

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;
});
}

AngularJS - How to pass html attribute variable value to directive?

...
...
UPDATE
HTML
<my-directive ng-repeat="item in items = ( data | filter: {isExists: true})">
something
</my-directive>
<my-second-directive counter="{{items.length}}"></my-second-directive>
JS
angular.module('directives')
.directive('myDirective', function () {
...
})
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '#'
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
Excuse me I've not described my question well. My first directive should be ngRepeated, with filter. But in my second directive, I would like to allow to show a counter, how many first directive is currently instantiated, because the user will be able to add and remove instances. So I would like to get the value of the items.length by fly with the second directive. But the link() method of the second directive is fired prior the ngRepeat, so the value of the counter will be an empty string.
Thanks in advance
UPDATE 2
.directive('cardGroupHeader', function($templateCache){
return {
restrict: 'EA',
transclude: true,
replace: true,
require: '^cardGroup',
scope: {
cbiscounter: '=?',
cbcounter: '=?',
cbisarrow: '#?'
},
template: $templateCache.get('card-group-header-tpl.html'),
link: function(scope, $element, $attrs, cardGroupController) {
scope.rowId = cardGroupController.getCurrentId();
console.log(scope.cbcounter);
scope.toggle = function () {
cardGroupController.toggle(scope.rowId)
}
angular.element(document).ready(function () {
console.log(scope.cbcounter);
});
scope.$watch('scope.cbcounter', function (n, o) {
if(n && n != o) {
console.log(n);
}
});
//scope.cbcounter++;
}
};
})
HTML
<card-group-header cbiscounter="true" cbarrow="true" cbcounter="data.length">Waiting for Approval</card-group-header>
<card-group-row cbCollapsed="false">
<card ng-repeat="approveItem in data = (approveItems | filter: {isApproved: false, isRejected: false})">
TEMPLATE
$templateCache.put('card-group-header-tpl.html', '<div class="card-group-header" ng-click="toggle()"><span ng-transclude></span><span class="card-group-counter" ng-if="cbiscounter">{{cbcounter}}</span></div>');
When I change the data.length to 2, this is transferred well. If I use the data.length the scope.cbcounter is always undefined. In case of 2 I've got it back on the console.log(scope.cbcounter);
The counter: '#' means that you are accepting a string value. If you wanted to pass an expression you could either use:
<my-second-directive counter="{{ items.length }}"></my-second-directive>
Or:
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
EDIT:
I finally quite understand the problem! It's because of attributes are not interpolated until after the link phase. You have two following options:
The first option is wrapping every in the link inside $timeout to take it away from the event loop and be executed after DOM finished manipulating:
.directive('mySecondDirective', function ($timeout) {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
$timeout(function() {
alert(scope.counter);
});
}
});
Secondly, using $observe:
attrs.$observe('counter', function(value){
console.log(value);
});
or using $watch as #jusopi suggested.
I think this would be what you want.
Html
<div ng-app="testapp" ng-controller="testctrl">
<div ng-repeat="item in filtereditems">
{{item}}
</div>
<testdir counter="filtereditems.length" />
</div>
Javascript
angular.module('testapp', [])
.directive('testdir', function(){
return {
restrict: 'E',
scope:{
counter: '='
},
link: function(scope, element, attrs) {
alert(scope.counter);
}
}
})
.controller('testctrl', function($scope, $filter){
$scope.items = [
{name: 'A', isExists: true},
{name: 'B', isExists: false},
{name: 'C', isExists: true},
{name: 'D', isExists: false}
];
$scope.filtereditems = $filter('filter')($scope.items, {isExists: true});
})
My jsfiddle is here.
In addition to #LVarayut's answer about the scope's binding expression, the reason the alert is undefined is because linking is not part of the $digest cycle. So binding and data hasn't be effected yet (don't quote me on that, it's the best way I could verbalize what I'm showing in the code below).
Instead you need to use a watcher to trigger the alert
link: ($scope, elem, attrs)->
#undefined because linking isn't part of the $digest cycle
#alert $scope.count
$scope.$watch 'count', (n, o)->
if n and n isnt o
true
#alert n
http://plnkr.co/edit/xt95gb3cTXfUEHgpWK1W?p=preview

How to make a template repeat

I have a directive, I want that template to show all the rows from data set
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<tr><td>{{Name}} </td><td>{{Surname}}</td></tr>',
replace: true,
link: function(scope, elm, attrs) {
var dat= TestFactory.dataReturn();
for (var i = 0; i < dat.length; i++) {
scope.Name = dat[i].Name;
scope.Surname = dat[i].Surname;
console.log(dat[i]);
}
// alert("hah");
}
};
}]);
How can I make it repeat like ng-repeat ?
Assuming your service returning a promise. Here is the simple code to repeat your data in table.
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<table ng-repeat="person in persons"><tr><td>{{person.Name }} </td><td>{{person.Surname}}</td></tr></table>',
replace: true,
link: function(scope, elm, attrs) {
TestFactory.dataReturn().then(function(resp){
scope.persons = resp.data;
});
}
};
}]);
Alternatively, you could redefine your element directive to represent a single object, and then ng-repeat the directive itself. Obviously, this would require moving the location where you use the factory. Your application architecture may or may not be able to accommodate this change.
directive:
app.directive('exampleDirective', [function () {
return {
restrict: 'E',
template: '<tr><td>{{person.Name}} </td><td>{{person.Surname}}</td></tr>',
replace: true,
scope: {
person: '='
}
};
}]);
And usage:
<example-directive ng-repeat="data in dataFromFactory" person="data"></example-directive>

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

Nested Directives

I have two directives one which translates strings and one which creates a container with a title. (and some other functionality that was removed to make the example shorter)
Groupbox:
myapp.directive('groupbox', function () {
return {
restrict: 'E',
priority: 200,
template:
'<fieldset>' +
'<legend data-translate>{{title}}</legend>' +
'<div data-ng-transclude></div>' +
'</fieldset>',
transclude: true,
replace: true,
scope: {title: '#'}
};
});
Translate: (also simplified)
myapp.directive('translate', ['$filter', function ($filter) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var e = $(element);
var data = attrs.translate;
var results = $filter('I')(e.html(), data);
e.html(results.text);
if (results.tooltip) e.attr('data-tooltip', results.tooltip);
}
};
}]);
I use it like this:
<groupbox title='settings'>
content
</groupbox>
The idea is that the content of the "groupbox" gets put in the div and the title in the "legend". After this the legend needs to be translated by the translate directive. This translation does not happen (it just prints settings). When i replace '{{title}}' with 'settings' it does get translated.
How can i get the translate directive to operate on the results of the groupbox directive.
I fixed it by adding a compile function that puts the title in the legend directly (without binding). That way it is no different then any other use of my translate directive.
myapp.directive('groupbox', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
template:
'<fieldset>' +
'<legend>' +
'<span data-translate></span> - ' +
'<a data-ng-click="open = !open" data-translate>toggle</a>' +
'</legend>' +
'<div data-ng-show="open" data-ng-transclude></div>' +
'</fieldset>',
compile: function(element, attrs) {
element.children('legend').children('span').html(attrs.title);
}
};
});

Resources