I'd like to load my JavaScript class after the end of ng-repeat.
AngularJS version : 1.6.4
My JavaScript class :
+(function($) {
'use strict';
document.addEventListener('DOMContentLoaded', function() {
var buttons = document.querySelectorAll('[data-dialog="true"]');
console.log(buttons.length); // return 0
});
});
My AngularJS view :
<tr ng-repeat="item in filteredItems">
<td>{{item.title}}</td>
<td>
<a href="/page/duplic/{{item.id}}" data-dialog="true"
data-dialog-action-title="duplic item">file_copy</a>
</td>
</tr>
Issue : My JS class is loaded before the AngularJS render, and the console.log() return no elements.
Inject the code with a custom directive:
app.directive("dialog",function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
var button = elem;
console.log(attrs.dialogActionTitle);
if (attr.dialog == "true") {
//script here
};
}
})
The postLink function will be invoke by the $compile service after the ng-repeat directive appends the element to the DOM.
For more information, see
AngularJS Developer Guide - Creating Custom Directives
There is no such thing as an event for when ng-repeat finished. But angularJS creates the following attributes:
$first: true
$last: false
$middle: false
$even: true
$odd: false
You can check it using $last and a custom directive as following:
HTML
<div ng-repeat="item in filteredItems" isitthelastone>
{{item.name}}
</div>
APP.JS
.directive('isitthelastone', function() {
return function(scope, element, attrs) {
if (scope.$last){
alert('event triggered');
}
};
})
Related
This is my directive
app.directive('masonry', function() {
return function(scope, element, attrs) {
scope.$watch(attrs.masonry,function(value){
jQuery(function($) {
var grid = $('.main_creation').masonry({
itemSelector: '.creation',
percentPosition: true,
horizontalOrder: true
});
grid.imagesLoaded().progress( function() {
grid.masonry('layout');
});
});
});
};
});
This is my HTML
<div class="creation" ng-repeat="img in images" masonry>
//some code
</div>
When first load the directive is functioning but if the ng-repeat is changing for example deleting or adding more data in ng-repeat the directive doesnt functioning.
EDIT:
Also I dont know why the watch doesnt trigger. When I console.log(attrs.masonry) their is changes.
I've asked this before, but I wasn't specific enough. I have a bit of a conundrum with ng-repeat inside a nested directive. I'll have to post all my code so you can see what i'm doing as it's easier than trying to explain.
In essence, my ng-repeat is not rendering contents if I use properties bound to the controller. However if I output the property contents to the screen using {{ ctl.myarray }} it reads just fine, proving that the property is accessible.
The problem is occurring inside a directive which dynamically loads contents through ajax and $compiles it. The contents of the ajax file contains html with a directive, and THAT directive has the ng-repeat which doesn't work.
Here goes:
Controller (main and first nested controller)
<!-- main controller -->
<div ng-controller="ReportController as rc" ng-cloak="">
<!-- controller for filters -->
<div class="report-filters" ng-controller="FilterController as fc">
<!-- directive that loads bootstrap popovers from a template -->
<rep-popover parent="fc" title="Add Filter" template="html/popovers/add_filter.html">
Add Filter
</rep-popover>
</div>
<!-- ../ rest of the page below ... -->
</div>
popover.directive.js (popover generator directive)
(function () {
'use strict';
angular.module(ng_module_name)
.directive('repPopover', popover);
popover.$inject = ['$http', '$compile'];
function popover($http, $compile) {
var directive = {
controller: controller,
controllerAs: 'pop',
link: link,
replace: true,
restrict: 'E',
scope: { // properties bound in html
parent: '=',
title: '#',
template: '#',
placement: '#',
container: '#'
}
};
return directive;
//////////////////
/** directive controller */
function controller($scope) {
var ctl = this;
ctl.parent = $scope.parent;
ctl.title = $scope.title;
ctl.template = $scope.template;
ctl.placement = $scope.placement;
ctl.close = close;
//////////////////
/** close the popover */
function close() {
var popover = $scope.element.children(":last-child");
popover.popover("hide");
}
}
/** DOM manipulation function */
function link(scope, element) {
scope.element = element;
if (!scope.template) {
alert("Error: popover requested, but no template provided!");
return;
}
// request template, and populate popover
$http.get(reports_data.root+'/'+scope.template).then(function (content) {
if (content && content.status == 200) {
var activator = element.children(":first-child");
var placement = scope.placement || "bottom";
var compiled_content = $compile(content.data)(scope);
// bootstrap popover
activator.popover({
title: scope.title,
content: compiled_content,
placement: placement,
html: true,
container: scope.container || false
});
}
});
}
}
}());
add_filter.html (popover template)
<!--- add filter -->
<div class="add-filter-popover">
<form>
<select rep-filter-select="" columns="parent.tracker.available" class="form-control filter-select">
<!-- THIS IS WHERE THE REPEAT BREAKS -->
<option ng-repeat="i in select.list">{{ i }}</option>
</select>
<!-- show before column is chosen -->
<div class="empty-spacing"></div>
<!-- ../ rest of template... -->
</form>
</div> <!--- ./add-filter-popover -->
filter.directive.js (the nested directive inside popover template)
(function () {
'use strict';
angular.module(ng_module_name)
.directive('repFilterSelect', filterSelect)
filterSelect.$inject = ['$compile'];
function filterSelect($compile) {
var directive = {
controller: controller,
controllerAs: 'select',
link: link,
replace: true,
restrict: 'A',
scope: {
columns: "="
}
};
return directive;
//////////////////
/** directive controller */
function controller($scope, $element) {
var ctl = this;
ctl.list = [1,2,3,4,5];
//////////////////
}
/** DOM manipulation function */
function link(scope, element) {
// manual compile as part of a popover
$compile(element.contents())(scope);
}
}
})();
Where you see my comment in add_filter, these options are not being populated. If I change that to <option ng-repeat="i in [1,2,3,4,5]"></option> then it works just fine.
Furthermore, if I output {{ select.list }} inside the select (and make the directive a div just for test purposes), the contents of select.list are correct.
Thanks to anyone willing to have a quick read :)
I built a directive that has checkboxes next to labels, inside a jQuery UI Accordion:
<ul class="checkbox-grid employee-info-tabs">
<li ng-repeat="column in columnsData">
<div class="styleAvailableColumns">
<input type="checkbox" ng-model="column.Selected" />
<label class="list-columns">{{ column.ColumnDisplayName }}</label>
</div>
</li>
</ul>
In my Controller, I want to be able to save the selected choices the user makes inside the directive, but I'm not sure how.
Here's my directive:
angular.module('component.column', [])
.directive('uiAccordion', function ($timeout, Columns, $location) {
return {
scope: {
columnsData: '=uiAccordion'
},
templateUrl: '/scripts/app/directives/test.html',
link: function (scope, element) {
var generateAccordion = function () {
$timeout(function () {
$(element).accordion({
header: "> div > h3",
collapsible: true,
active: 'none'
});
});
}
var loc = $location.absUrl();
var reportId = loc.substring(loc.lastIndexOf('/') + 1);
Columns.getAll(reportId).then(function (data) {
scope.columnsData = data;
generateAccordion();
}
Here's how I use the directive in my view <div ui-accordion="accordionData"></div>
I tried using scope: { '=' } but got Expression 'undefined' used with directive 'uiAccordion' is non-assignable!.
I've done some other googling, but I'm not 100% on the 'correct' direction on how to get this accomplished. If I can provide any other information, please let me know.
Set your directive scope to:
scope: {
columnsData: '='
},
Since you want the controller to maintain that data, your controller should have a reference to $scope.columnsData.
Then, on the view which is using the controller, you can feed that into the directive like so:
<div ui-accordion columns-data="columnsData"> </div>
Here's an example of your controller:
angular
.module('...')
.controller('myCtrl', ['$scope', function($scope) {
$scope.columnsData = "abcd123"
}]);
Try using your directive as:
<div ui-accordion="controllersColumnsData"></div>
where controllersColumnsData is a collection you can iterate in your controller whose items will have ColumnDisplayName and Selected properties set from your directive.
I have a nested parent - child directives, the purpose if to draw a table..
The child directive is not getting called when called from within the parent (tag).
It works fine when tested independently.. I seems to have followed all the rules/syntax, require is in place.. I don't see the console logs statements I have in the child directive, also there are no errors in the log.
Directives -
var app = angular.module ('gridapp', []);
app.directive ('gridControl', function(tableDataFactory){
return {
restrict: 'E',
scope : {},
controller : function($scope){
$scope.columns = [];
$scope.column = [];
$scope.addColumnProperties = function(columnProperties) {
console.log("In addColumnProperties "+ columnProperties);
$scope.column = columnProperties;
$scope.columns.push($scope.column);
$scope.column = [];
}
},
link : function (scope, element, attrs) {
console.log(attrs.source);
tableDataFactory
.get(
'http://localhost:8000/AngularTableWidget/json/accounts.json')
.then(
function(data) {
scope.items = data.items;
console.log("In grid directive" + scope.items);
});
},
templateUrl : '../template/gridtemplate.html'
};
});
//child directive...
app.directive('tableColumn', function(){
return{
restrict : 'E',
scope : {},
require : '^gridControl',
link : function(scope, element, attrs, gridCtrl) {
console.log("In tablecolumn "+ attrs.source);
var colProp = [];
console.log("In tablecolumn "+ attrs.caption);
colProp.push(attrs.caption);
colProp.push(attrs.source);
gridCtrl.addColumnProperties(colProp);
}
};
});
HTML -
<div>
<grid-control source="gridtable.json">
<table-column caption="Name" source="name"> </table-column>
<table-column caption="Account" source="account"> </table-column>
</grid-control>
template -
<div>
<table>
<tbody ng-repeat="row in items track by $index">
<tr ng-repeat ="col in columns">
<td>
Test
</td>
</tr>
</tbody>
</table>
</div>
On grid-control directive, add transclude = true. Inside the grid-control template, add ng-transclude where ever the child directive going to be inserted. Without using transclude, the system will ignore the child directive.
I hope this helps.
Austin
I have an anchor tag that I wish to hide or show depending on a value in the model.
<table>
<tr ng-repeat="item in items">
<td>Other Stuff</td>
<td>
<a href="#/somewhere" ng-show="model.showIt" myCustomDir="some value" onClick="bar(item)" />
</td>
</tr>
</table>
Now in my directive I have the following:
app.directive('myCustomDir', function() {
var def = {
restrict: 'A',
scope: {
onClick: "&"
},
link: function(scope, element, attrs) {
var hover = angular.element("<div><b>Some Text</b></div>");
var button = hover.find('b');
button.on('click', function() {
scope.$apply(function() {
scope.onClick();
})
});
}
};
return def;
})
The problem is as soon as I include my directive the ng-show one I think no longer works and that is because if I am correct it is because my directive works in isolate scope so the model from the parent scope is no longer present.
How would I get my directive to play nicely with ng-show while still being able to let someone what method they want to call when the tag is clicked.
Plunker for all those interested. http://plnkr.co/edit/BLMCgB
You directive creates an isolated scope. So you need to use $parent to get the value of the current repeater item
ng-show="$parent.item.visible"
If you want to make it more generic, you can take the scope off to make it compatible with other directives. Then you can use scope.$eval to call the function passed in.
myApp.directive('myDirective', function ($document) {
var definition = {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function () {
...
button.on('click', function () {
scope.$apply(function () {
scope.$eval(attrs.onClick);
hover.remove();
})
});
});
}
}
return definition;
})
If you want allow any global directive - don't declare private scope.
If you want allow only few directives, add links in scope declaration:
scope: {
onClick: "&",
ngShow : "&"
},
To your question in comments:
Declare controller in directive and declare method in this controller. Then in directive template assign ng-click to this method.
var def = {
restrict: 'A',
controller: function($scope){
$scope.callMe = function(){
console.log('foo');
}
}
}
in template:
<div ng-click="callMe()">content</div>
This method will be accessible only inside your directive.