Angularjs Directive that repeats over an array - javascript

I have setup the following directive:
app.directive('starRating', function () {
return {
restrict: 'EA',
scope: {
rating: '=rating'
},
template:
'<ul class=\'list-unstyled\'>' +
'<li><span class=\'glypicon glyphicon-star\'></span></li>' +
'</ul>'
};
});
I then have the following HTML:
<star-rating rating="rating"></star-rating>
rating is an array as such: [1,3,2,4,5] and this implies that the first rating is 1 star, 2nd rating is 3 stars, ect.
The goal of the directive is to repeat the amount of .glyphicon-star icons of the rating.

You can use a for loop to concatenate the 'li' elements inside the 'ul' in the template. This is cheaper than using an ng-repeat. You probably dont need to use a list (ul's and li's) at all.
Also your scope can simply be:
scope: {
rating: '='
},

Related

how to avoid calling `$parent` scope in directive

app.directive('hidefileId',['$document','documentService',function($document,documentService){
return{
scope: false,
restrict: 'AE',
link : function($scope,element,attrs){
element.on('click',function(e){
angular.element('#fileId').removeClass("errorhilight");
angular.element('#docerrormsg').html('');
})
}
}
}]);
I have many directives in my js controller, when I use the following directive I am getting the parent scope in
$scope.$parent.$parent.$parent.$parent.// here I am getting the scope of my controller.
even I use scope: false, it is creating new scope.
I want to use the my controller scope only
You can use the require directive option. This will give you access to a parent controller(s). The directive with throw an exception if the controller(s) can not be found. You will have access to anything you put on that controller. This is a great method to use if you need a parent child relationship where a child directive needs access to the parent. It also allows for more modular code.
e.g.
I have written a table directive with an exposed API to add formatting to cells. This is very useful as you can write many additional table directives that can be used interchangeably throughout an application. As an example I will show you a child HoverTitle directive that uses the parent table directive to add a hover tooltip in a given cell.
function HoverTitle(CellProcessService){
'ngInject';
return{
require: '^cTable', // require c-table
link: function(scope, element, attrs, cTable){
cTable.addCellRenderProcess(renderCell, CellProcessService.priorities["HOVER-TITLE"]);
function renderCell(curr, column, row){
if(angular.isFunction(cTable.hoverClass)){
var hoverTitle = cTable.hoverClass(row, curr, column);
if(hoverTitle){
var placement = cTable.placement || 'right';
var tag = column.link ? "span" : 'a'
return '<' + tag + ' tooltip-placement="'+placement+'" uib-tooltip="' + hoverTitle + '">' + curr +'</ ' + tag + '>';
}
}
return curr;
}
}
}
}
Here is how it could be used:
<c-table
table-sort
hover-title
data="$ctrl.data"
columns="$ctrl.columns">
</c-table>
https://docs.angularjs.org/guide/directive#creating-directives-that-communicate

Repeat a directive in Angular

I have a directive which build a star icon. I want to repeat it 'n' times. And then I want to change the class in 'active' to 'n' stars.
This is the directive which build a star and, on click, it add the class 'active'.
<div ng-app="app">
<div ng-controller="ctrl">
<div class="presentation">
<button-star repeatStars="7" limitActiveStar="5" classToAdd="active"></button-star>
</div>
.directive('buttonStar', function() {
return {
scope: {
repeatStars: "=",
limitActiveStar: "=",
classToAdd: "="
},
restrict: 'E',
template: '<button class="btn btn-icon"><span class="glyphicon glyphicon-star"></span></button>',
link: function(scope, elem) {
elem.addClass('active');
}
};
})
So, in my example, I'd like to repeat the directive 7 times and add to the first 5 elements the class 'active'
Couldn't you use a ng-repeat directive in that case ?
admitting you have 7 items in items
<button-star ng-repeat="item in items track by $index" ng-class="{'active': $index < 5}">
Add this in your controller :
$scope.number = 7;
$scope.getNumber = function(num) {
return new Array(num);
}
then
<button-star ng-repeat="i in getNumber(number) track by $index" repeat-stars="7" limit-active-star="5" class-to-add="{{$index < 5 ? 'active' : ''}}"></button-star>
should work
I made a plunker with a working solution, here:
https://plnkr.co/edit/kX9yXUVwOC7ZD0qnyMt7?p=preview
I created a parent directive called buttonStars that has several child directive elements buttonStar inside it. The first limitActiveStars will have the class active added to them, while the rest won't have it.

AngularJs Directive update

I want to update my directive content only at some desired places, but not at others. I have simulated my problem here:
http://jsfiddle.net/Lvc0u55v/2945/
The problem is, I have an 'editor' directive which is applied in two places:
<span class="editor1" editor ></span>
<span class="editor2" editor ></span>
I want to update the content of span class="editor1" on button click.
How do I do it?
Why not go with a relatively Angularesque approach by isolating the scope of the directive and to a maximum extent, avoid jQuery in your logic.
So you could have your directive defined as such:
.directive('editor', function() {
return {
scope: {
upd : '=',
editordata : '=data'
},
template: '<div>{{editordata}}</div>',
controller: function($scope, $rootScope, $element) {
$rootScope.$on('update', function(evt, data) {
if(data.upd === $scope.upd){
$scope.editordata = data.txt;
}
})
},
link: function(scope, el, attr) {}
}
})
Here, you are passing the required information which the editor directive depends upon through its scope, namely upd (which I suppose is how you want to uniquely identify the items by) and the text data.
Meanwhile, you can define a list of the editor items in the common parent controller MyCtrl and iterate over them in the DOM with ng-repeat.
// MyCtrl controller
$scope.list = [
{upd: 'editor1', data: 'original data for editor1'},
{upd: 'editor1', data: 'original data for editor2'}
]
<!-- HTML -->
<div ng-repeat="item in list" upd="item.upd" data="item.data"></div>
Demo
You can check if the current directory has the "editor1" class and if so complete your logic.
You can look at this example :
element[0].querySelector('.editor1') !== undefined'

Problems animating ng-repeat directive template with d3

I'm trying to make a category list with Angular and D3. I've created a directive for this list and some of the child nodes in my template use ng-repeat because I'd like to have angular build my html instead of d3.
My problem is that when trying to reference the ng-repeated elements with d3, they haven't been created yet. If I use the link function there are no items in the list. If I use the compile function I'm able to see 1 list item, but there should be at least 2.
Seen Here
Taken from fiddle:
angular.module('myApp').directive('targetingCategories', function(){
return {
restrict: 'E',
scope: {
data: '='
},
template: '<div>'+
'<ul class="catList">'+
'<li ng-repeat="cat in data.categories">'+
'{{cat.name}}'+
'<ul class="subCatList">'+
'<li ng-repeat="subcat in cat.categories">{{subcat.name}}</li>'+
'</ul>'+
'</li>'+
'</ul>'+
'</div>',
replace: true,
compile: function($tEl, $attrs){
// attach d3 on the TEMPLATE element, and look for list items
var vis = d3.select($tEl[0]);
var catList = vis.select('.catList');
var catListFirstItem = vis.select('.catlist>li');
var catListItems = vis.selectAll('.catList>li');
console.log(catList); // 1 item returned (the ul)
console.log(catListFirstItem); // 1 item returned (the first li)
console.log(catListItems); // 1 items returned ??
return function($scope, $el, $attrs){
// attach d3 to INSTANCE element and look for list items
var vis = d3.select($el[0]);
var catList = vis.select('.catList');
var catListFirstItem = vis.select('.catlist>li');
var catListItems = vis.selectAll('.catList>li');
console.log(catList); // 1 item returned (the ul)
console.log(catListFirstItem); // 1 item returned (the first li)
console.log(catListItems); // 0 items returned ??
};
}
};
});
The idea is to have angular build my markup, and d3 animate it. I like the control that d3 gives me versus using CSS animations.
When I used d3 to create my list items this was working just fine. I'm tempted to do that again, but I think putting the LIs in the template is a better architecture.
I'm thinking that I need to call a compile, or wait for one or something. I'm almost at the limit of my Angular knowledge so any education here is appreciated.
I'm adding my own potential answer, but leaving unmarked incase anyone else has something better.
For now, it seems that one way to accomplish this is to replace the LIs in my main template with other nested directives, specifically "mainCategory" and "subCat". I am then able to attach d3 to the appropriate instance element for animation. By including an index attribute property, I can control the delay to make animations staggered. It feels a bit convoluted and "backbone-esque" (objects for the sake of objects), but it solves my problems. It also allows me to attach some ng-clicks which would have been next to cumbersome/impossible to do with d3 creating my LIs.
Yes, I'm aware that my attribute is called "data" and could be problematic, but I'm not worried about that now. Any corrections on my use of services/controllers/directives are appreciated though :)
updated JSFiddle
angular.module('myApp').directive('targetingCategories', function(){
return {
restrict: 'E',
scope: {
data: '='
},
template: '<div>'+
'<ul class="catList">'+
'<main-category ng-repeat="cat in data.categories" index="$index">'+
'{{cat.name}}'+
'<ul class="subCatList">'+
'<sub-cat ng-repeat="subcat in cat.categories" index="$index" data="subcat">{{subcat.name}}</sub-cat>'+
'</ul>'+
'</main-category>'+
'</ul>'+
'</div>',
replace: true
};
});
angular.module('myApp').directive('mainCategory', function(targetingService){
return {
restrict: "E",
scope: {
index: "=",
data: "="
},
template: '<li ng-transclude ng-click="selectCategory($event, data);"></li>',
replace: true,
transclude: true,
link: function($scope, $el, $attrs){
d3.selectAll($el).transition()
.duration(500)
.delay(function(){ return $scope.index*200; })
.ease('elastic')
.style('width', '100%')
.style('padding', '10px');
$scope.selectCategory = function($event, cat){
$event.stopPropagation();
targetingService.selectCategory(cat);
};
}
};
});
angular.module('myApp').directive('subCat', function(targetingService){
return {
restrict: "E",
scope: {
index: '=',
data: '='
},
template: '<li ng-transclude ng-click="selectSubCategory($event, data)"></li>',
replace: true,
transclude: true,
link: function($scope, $el, $attrs){
d3.selectAll($el).transition()
.duration(100)
.delay(function(d,i){ return 500+($scope.index*200); })
.ease('linear')
.style('height', '30px')
.style('padding', '8px');
$scope.selectSubCategory = function($event, subCat){
$event.stopPropagation();
targetingService.selectSubCategory(subCat);
};
}
};
});

Angularjs/Jquery data()

How to attach arbitrary data to an html element declaratively, and retrieve it.
Please see the code. http://plnkr.co/edit/sePv7Y?p=preview
Angular has the jQuery data() support.
So, I want to attach data to each li element (say _data = node ) in the template, and later on to retrieve it using
var li = elm[0]....
console.log('li-', li.data('_data'))
li - {id:1}
Code:
'use strict';
var app = angular.module('Directives', []);
app.controller('MainCtrl', function ($scope) {
$scope.data = [
{id:1}, {id:2}, {id:3}
];
});
app.directive('test', function ($timeout) {
return {
template: '<li class="ch" ng-repeat="node in data">' +
'<span class="span2">' + 'id - {{node.id}}' + '</span>' +
'</li>',
restrict: 'A',
link: function (scope, elm, attrs) {
console.log(elm[0].children);
}
};
});
Edit:
Updated the code with how I like to set data.
template: '<li class="ch" ng-repeat="node in data" data-node="node">' +
couldn't select the li element properly now to see whether it is working
tried,
elm[0].children[0].data()
elm.children[0].data()
etc..
First of all, if it were some third party lib that you are trying to integrate with angular, that might be ok, but now you're generating DOM with angular and embedding data in the DOM. This is very strange.
Second, your test directive template uses ngRepeat, which creates isolate scope and you won't be able to access li items declaratively. You will have to use DOM traversal, which is also not very angular-way-ish.
Third, your view should be bound to model by angulars two-way bindings. Do not try to simulate opposite behaviour on top of that. Either you should not use angular or you should change your approach to your problem, because it will be pain to develop and maintain otherwise.
I would provide a real answer if you could describe what are you trying to achieve and why exactly do you need that model in data. Now the easiest solution would be ditching test directive and rewriting it as such:
controller's template:
<ul>
<li ng-repeat="node in data" model-in-data="node">
<span class="span2">id - {{node.id}}</span>
</li>
</ul>
directive modelInData
.directive('modelInData', function($parse) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
var model = $parse($attrs.modelInData)($scope);
$attrs.$set('data', model);
}
}
});
Here each li element adds it's model to the data attribute.

Categories