How to make a template repeat - angularjs

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>

Related

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

Prevent angular from copying attributes when replace=true

The following directive:
var app = angular.module('demo', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
template: '<h1>Foo bar</h1>'
};
});
With the following usage:
<my:directive foo="bar"></my:directive>
Renders the following HTML:
<my:directive foo="bar"><h1>Foo bar</h1></my:directive>
Since I want to replace my directive with the provided template I set replace:true. This produces the following HTML:
<h1 foo="bar">Foo bar</h1>
Note that Angular copies my directive's attributes to the template elements (the foo="bar"). How can I prevent this behaviour?
You can manually remove the attributes in the link function of the directive:
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<h1>Foo bar</h1>',
link: function(scope, elm, attrs){
elm.removeAttr('foo');
}
};
});
Here's a fiddle with this directive working in your situation.
EDIT: You can extend this to remove all attributes dynamically with a simple loop:
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<h1>Foo bar</h1>',
link: function(scope, elm, attrs){
for(var attr in attrs.$attr){
elm.removeAttr(attr);
}
}
};
});

AngularJS - accessing parent directive properties from child directives

This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}

How to add ngshow directive inside compile/link?

I want to add ngshow in the following custom element in a dynamic way... How to do that?
<toggler on-enable="main.enable()" on-disable="main.disable()">
<div style="width:100px;height:100px;background-color:#2fa">
<on>On state</on>
<off>Off state</off>
</div>
</toggler>
cf.directive('toggler', function () {
return {
restrict: 'AE',
scope: {
state: true,
onEnable: '&',
onDisable: '&'
},
compile: function (elem, attrs) {
var onElem = elem.find('on');
var offElem = elem.find('off');
// WANT TO DO THIS
// onElem.ngShow = 'state';
// offElem.ngShow = '!state';
}
};
});
You're doing it in the wrong way. Don't forget a rule of thumb in AngularJS: avoid DOM manipulation when it's not mandatory.
I guess that <on> and <off> are also custom directives, because you can't simply add tags without any defined behaviour. So, why don't put the ngShow attribute directly in this directives? Then, a directive's controller (see the documentation) will handle the communication between <on>/<off> and <toggler>:
myApp.directive('toggler', function () {
return {
restrict: 'AE',
scope: {
state: '=',
},
controller : [
'$scope',
function ($scope) {
this.isOn = function () {
return $scope.state;
};
},
],
};
});
myApp.directive('on', function () {
return {
restrict: 'AE',
require: '^toggler',
template: '<div ng-show="isOn()" ng-transclude />',
replace: true,
scope: true,
transclude: true,
link : function ($scope, element, attributes, togglerController) {
$scope.isOn = togglerController.isOn;
},
};
});
myApp.directive('off', function () {
return {
restrict: 'AE',
require: '^toggler',
template: '<div ng-hide="isOn()" ng-transclude />',
replace: true,
scope: true,
transclude: true,
link : function ($scope, element, attributes, togglerController) {
$scope.isOn = togglerController.isOn;
},
};
});
Fiddle
This way, you will be able to simply unit test your toggler, and extend his behaviour when needed.

What's the right way to use filters with transclusion in directives?

I'm trying to apply a filter to the transcluded text in my directive but I'm not sure what's the best way to do this. A working copy of idea is at the following link but I feel I'm sorting of cheating by using the compile function to get at the transcluded text. See JSFiddle.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
phrase: '#',
text: '#'
},
template: '<span ng-bind-html-unsafe=" text | highlight:phrase:false "></span>',
compile: function (elem, attr, transclude) {
transclude(elem, function (clone) {
// grab content and store in text attribute
var txt = clone[0].textContent;
attr.text = txt;
});
}
};
});
Other way to do would be http://jsfiddle.net/3vknn/, I guess.
angular.module("MyApp").directive('highlighter', function () {
return {
restrict: 'E',
scope: {
phrase: '#'
},
controller: function($scope, $filter) {
$scope.highlight = $filter('highlight');
},
link: function (scope, elem, attr) {
scope.$watch('phrase', function(phrase) {
var html = scope.highlight( elem.text(), phrase );
elem.html( html );
});
}
};
});

Resources