As a newbie in AngularJs can't understand the problem.
This is a simple example that shows the action. Actually about 50 items and after clicking on "Without the string" after clicking on the elements "String #" to visually remove the item takes about 1-2 seconds.
Fiddle
I have the controller code:
testApp.controller('TestController', ['$scope', function ($scope) {
$scope.category = [
{id:1, name: "Category 1"},
...
];
$scope.items = [
{id: 1, category: {id: 1},name: "Test 1"},
...
];
$scope.list = [
{id: 1,name: "String 1"}
...
];
angular.forEach($scope.category, function(categoryItem, i) {
categoryHash[categoryItem.id] = i;
});
angular.forEach(menuItems, function(item) {
var catCategory = categoryHash[item.category.id];
if (!$scope.category[catCategory].items) {
$scope.category[catCategory].items = [];
}
$scope.category[catCategory].items.push(item);
});
}])
Directive code:
.directive('listItems', function() {
return {
restrict: 'E',
scope: {
listArray: '=',
listItemId: '=',
listFlag: '='
},
template: '<ul>' +
'<li ng-repeat="listStr in listArray track by listStr.id">' +
'<input type="radio" ' +
'id="list_{{ listItemId }}_{{ listStr.id }}" name="list_{{ listItemId }}" ' +
'ng-model="$parent.$parent.item.string" ng-value="listStr" ng-change="stringSelect()">' +
'<label for="list_{{ listItemId }}_{{ listStr.id }}" ng-bind="listStr.name"></label>' +
'</li>' +
'<li>' +
'<input type="radio" ' +
'id="list_{{ listItemId }}_0" name="list_{{ listItemId }}" ' +
'ng-model="$parent.$parent.item.string" ng-value="" ng-change="stringSelect()">' +
'<label for="list_{{ listItemId }}_0">Without string</label>' +
'</li>' +
'</ul>',
link: function(scope, iElement, iAttrs) {
scope.stringSelect = function() {
scope.listFlag = false;
};
}
}
})
Template:
<div ng-app="test" ng-controller="TestController">
<div ng-repeat="collection in category track by $index" >
<h3 ng-bind="collection.name"></h3>
<ul>
<li ng-repeat="item in collection.items track by $index">
<strong ng-bind="item.name"></strong>
<span ng-if="item.string" ng-bind="item.string.name"></span>
<button ng-click="addString = true" ng-hide="addString">Add String</button>
<div ng-if="addString">
<list-items
list-array="list"
list-item-id="$parent.item.id"
list-flag="$parent.addString"></list-items>
</div>
</li>
</ul>
</div>
</div>
First remove all $parent, they're not needed, angular will look for value in the parent scope himself and if your directive is used inside others directive, this won't even point to the right scope.
2nd with angular you can create a lot of watches, this can slow down really your application :
when angular watch for list, he watches for deep inspection. This may slow down as hell things when you have some list with complex objects.
When you use {{}} or ng-bind, it creates a watch
3 : Are you using an old version of IE ? i think the 8 one was really slow with angular 1.2 and scopes.
So you may try to not use default watch for array but handle it yourself watching only for length for instance. For the {{}} part, since 1.3 you can prefix your binding by '::' to have a One time binding (https://docs.angularjs.org/#!/guide/expression) so he won't watch for changes
4 : When you use ng-if it destroy the object when not needed and recreate it when the condition is true again maybe you should use ng-show instead ng-if there
<div ng-if="addString">
Related
I would like to create a directive to replace some code in my HTML.
Here's what I have right now:
<div class="gridFooter" ng-show="home.dataRetrieved">
<span ng-show="(home.grid.data).length">{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}</span>
<span ng-show="!(home.grid.data).length">There are no tests that match your selection criteria</span>
</div>
I created this basic directive but there are things missing:
app.directive('adminGridFooter', function () {
return {
template: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" >xx</span >\
</div>'
};
});
How can I make it so I can pass in the string "xx" inside the element when I call the directive and will my directive just assume the current scope so that the home.dataRetrieved will work without change? Something like
Another question. Howe can I make the directivecompletely replace my call to it <admin-grid-footer></admin-grid-footer>. How can I make it so it replaces the element?
You are looking for transclude and replace:
app.directive('adminGridFooter', function () {
return {
replace:true,
transclude:true,
template: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" ng-transclude></span >\
</div>'
};
});
PLUNKER
you can change your directive like this.
angular.module('onboardingApp').directive("adminGridFooter",function () {
return {
restrict: 'E',
link: function(scope, element, attributes) {
scope.customMessage = attributes["custommessage"];
},
templateUrl: '<div class="gridFooter" ng-show = "home.dataRetrieved" >\
<span ng-show = "(home.grid.data).length" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="!(home.grid.data).length" >{{customMessage}}</span >\
</div>',
};
});
then pass the value that u want in html
<admin-grid-footer customMessage="what ever you want"></admin-grid-footer>
At first, you can use templateUrl property and point it to separate HTML file instead of writing the whole HTML as a string.
The second, you can restrict the directive type by element, so you can use it only as an element (not as attribute nor as class). Here is how to do that: restrict: 'E'.
Finally, you can also specify a link function where you can get the attributes of your element and do whatever you need.
So, after these changes your code may look like this:
app.directive('adminGridFooter', function () {
return {
restrict: 'E',
templateUrl: 'adminGridFooter.html', // this contains your HTML
link: function(scope, element, attrs) {
scope.xx = attrs.xx;
}
}
});
And you can use it like this:
<adminGridFooter xx="someValue"></adminGridFooter>
And the last question:
...and will my directive just assume the current scope so that the home.dataRetrieved will work without change?
YES, by default it uses the scope where the directive was called, BUT you can filter scope variables and only use some of them, which you need inside of your directive. You can achieve this using isolated scopes.
Also, I strongly recommend to read about directives to have a basic knowledge and then continue with them.
The official documentation is a good starting point.
set the scope property to true, So the scope will be accessible in directive.
or
You can pass the data as an attribute.
app.directive('adminGridFooter', function() {
return {
restrict: 'E',
replace: true,
scope: true,
template: '<div class="gridFooter" ng-show="home.dataRetrieved" >\
<span ng-show = "home.grid.data.length > 0" >\
{{ (home.grid.data).length + " rows retrieved - " + home.grid.view.length + " displayed" }}\
</span >\
<span ng-show="home.grid.data.length === 0">There are no tests that match your selection criteria</span>\
</div>'
};
});
Or
scope:{
home:'='
}
PLUNKER
I am trying to access the function defined in the controller scope from directive nested in another directive. I am using the "&" and passing the function name as the attribute to the directive, however I am still not able to reach the function in the controller scope.
Could some one correct where I am going wrong? I had spent few hours trying to find that I have to use the "&" for reaching controller scope but stuck here after whatever I did.
JSFiddle here - http://jsfiddle.net/fwR9Q/12/
Code:
<div ng-controller="PlayerCtrl">
<div class="col-md-4 col-sm-6 col-xs-12" ng-repeat="video in videos" >
<tf-video src="{{video.src}}" width="{{video.width}}" handleClick="playVideo(videoId, modeNum)" height="{{video.height}}" title="{{video.title}}"/>
</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('PlayerCtrl',
function PlayerCtrl($scope,$log, trailers)
{
$scope.videos = trailers;
$scope.playVideo = function(videoId, modeNum){
alert("video Id = " + videoId + "; mode Num = " + modeNum);
return false;
};
}
);
myApp.directive('tfVideo', function() {
return{
restrict: 'AE',
scope: {
src: '#',
handleClick: '&',
width: '#width',
height: '#height'
},
template: '<img src="http://img.youtube.com/vi/{{src}}/0.jpg" height="{{height}}" width="{{width}}"/>'
};
});
myApp.factory('trailers', function(){
var trailerVideos = [
{
src:"6kw1UVovByw",
width:"324",
height:"300"
},
{
src:"uWgDVz4NEO4",
width:"324",
height:"300"
}
];
return trailerVideos;
});
</script>
Thank you
1) On your directive declaration:
<tf-video src="{{video.src}}" width="{{video.width}}" handleClick="playVideo(videoId, modeNum)" height="{{video.height}}" title="{{video.title}}"/>
Since attributes use the snake-cased form.
Instead of: handleClick=
You want the snake cased: handle-click=
2) In your template:
template: '<img src="http://img.youtube.com/vi/{{src}}/0.jpg" height="{{height}}" width="{{width}}"/>'
Instead of: {videoId: {{src}}, modeId: modeNum{{$parent.$index}} }
You want: {videoId: src, modeNum: $parent.$index }
Since you need the "modeNum" parameter name to match between the template and the directive and you want to map directly to the variables, not expressions.
Updated fiddle
I was creating a select replacement directive to make it easy to style up selects according to the design without having to always right a bunch of markup (i.e. the directive does it for you!).
I didn't realize that attributes don't transclude to where you put ng-transclude and just go to the root element.
I have an example here: http://plnkr.co/edit/OLLntqMzbGCJS7g7h1j4?p=preview
You can see that it looks great... but there's one major flaw. The id and name attributes aren't being transferred. Which, ya know, without name, it doesn't post to the server (this form ties into an existing system, so AJAXing the model isn't an option).
For example, this is what I start with:
<select class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
...this is what I want it to look like:
<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">
<span class="faux-value">{{viewVal}}</span>
<span class="icon-arrow-down"></span>
<select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude class="my-select irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
...but this is what actually happens:
<div class="faux-select my-select irrelevant-class" ng-class="{ placeholder: default == viewVal, focus: obj.focus }" name="reason" id="reason" data-anything="banana">
<span class="faux-value">{{viewVal}}</span>
<span class="icon-arrow-down"></span>
<select ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
Specifically, the issue is that there's no name attribute on the select, so it doesn't actually send the data to the server.
Obviously, I can use a pre-compile phase to transfer the name and id attributes (that's what I am doing for now), but it would be nice if it would just automatically transfer all of the attributes so they can add any classes, arbitrary data, (ng-)required, (ng-)disabled attributes, etc, etc.
I tried getting transclude: 'element' to work, but then I couldn't the other attributes from the template onto it.
Note, I saw the post here: How can I transclude into an attribute?, but it looks like they just manually transfer the data, and I am aiming to get it to auto-transfer all the attributes.
You could use the compile function to access the element's attributes and build the template.
app.directive('mySelect', [function () {
return {
transclude: true,
scope: true,
restrict: 'C',
compile: function (element, attrs) {
var template = '<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">' +
'<span class="faux-value">{{viewVal}}</span>' +
'<span class="icon-arrow-down entypo-down-open-mini"></span>' +
'<select id="' + attrs.id + '" name="' + attrs.name + '" ng-model="val" ng-focus="obj.focus = true" ng-blur="obj.focus = false" ng-transclude>';
'</select>' +
'</div>';
element.replaceWith(template);
//return the postLink function
return function postLink(scope, elem, attrs) {
var $select = elem.find('select');
scope.default = scope.viewVal = elem.find('option')[0].innerHTML;
scope.$watch('val', function(val) {
if(val === '') scope.viewVal = scope.default;
else scope.viewVal = val;
});
if(!scope.val) scope.val = $select.find('option[selected]').val() || '';
}
}
};
}]);
The compile function is returning the postLink function, there are other ways to do this, you'll find more info here.
Here is a plunker
ng-transclude transcludes the content of an element on which the directive was placed. I would have assigned the attribute to its parent div and transcluded the entire select box in the template:
First Approach:
http://plnkr.co/edit/fEaJXh?p=preview
<div class="form-control my-select">
<select class="irrelevant-class" name="reason" id="reason" data-anything="banana">
<option value="">Reason for Contact...</option>
<option>Banana</option>
<option>Pizza</option>
<option>The good stuff</option>
<option>This is an example of a really, really, really, really, really, really long option item</option>
</select>
</div>
And remove replace option from the definition:
app.directive('mySelect', [function () {
return {
template:
'<div class="faux-select" ng-class="{ placeholder: default == viewVal, focus: obj.focus }">' +
'<span class="faux-value">{{viewVal}}</span>' +
'<span class="icon-arrow-down entypo-down-open-mini"></span>' +
'<div ng-transclude></div>' +
'</div>',
transclude: true,
//replace: true,
scope: true,
restrict: 'C',
link: function (scope, elem, attrs) {
var $select = elem.find('select');
scope.default = scope.viewVal = elem.find('option')[0].innerHTML;
scope.$watch('val', function(val) {
if(val === '') scope.viewVal = scope.default;
else scope.viewVal = val;
});
if(!scope.val) scope.val = $select.find('option[selected]').val() || '';
}
};
}]);
Second Approach:
In your demo, just include following line at the end of the link method:
$select.attr({'id': elem.attr('id'), 'name': elem.attr('name')});
I've been struggling with this for quite some time and I can't figure out how to resolve this issue.
I'm trying to make a grid directive which contains column directives to describe the grid but the columns won't be elements and would just add the columns to the array that is declared on the scope of the grid directive.
I think the best way to explain this issue is to view view the code:
var myApp = angular.module('myApp', [])
.controller('myCtrl', function ($scope, $http) {
})
.directive('mygrid', function () {
return {
restrict: "E",
scope: true,
compile: function ($scope) {
debugger;
$scope.Data = {};
$scope.Data.currentPage = 1;
$scope.Data.rowsPerPage = 10;
$scope.Data.startPage = 1;
$scope.Data.endPage = 5;
$scope.Data.totalRecords = 0;
$scope.Data.tableData = {};
$scope.Data.columns = [];
},
replace: true,
templateUrl: 'mygrid.html',
transclude: true
};
})
.directive('column', function () {
return {
restrict: "E",
scope: true,
controller: function ($scope) {
debugger;
$scope.Data.columns.push({
name: attrs.name
});
}
};
});
And here is the HTML markup:
<body ng-app="myApp">
<div ng-controller="myCtrl">
<input type="text" ng-model="filterGrid" />
<mygrid>
<column name="id">ID</column>
<column name="name">Name</column>
<column name="type">Type</column>
<column name="created">Created</column>
<column name="updated">Updated</column>
</mygrid>
</div>
In addition, you can test the actual code in jsfiddle: http://jsfiddle.net/BarrCode/aNU5h/
I tried using compile, controller and link but for some reason the columns of the parent grid are undefined.
How can I fix that?
Edit:
When I remove the replace, templateUrl, transclude from the mygrid directive, I can get the scope from the column directive.
Thanks
In the later versions of AngularJS I found that $scope.$$childHead does what I wanted.
It's still new but it works very well also with directive with isolated scopes.
So in the Columns directive you can just do:
$scope.$$childHead.Data.columns.push({
name: attrs.name
});
Just make sure that this command is executed after the compile of the grid. You can do that but switching between compile, link and controller since each one of them has a different loading priority.
I see what you're trying to do, but using a column directive is probably not the best way to tackle problem.
You're trying to define a grid directive with customizable columns. The columns each have 2 relevant pieces of information: the key with which to access the value in the row data, and the title to display.
Ignoring for the moment all the pagination-related stuff, here's a different way to approach the problem.
First, let's use attributes to define the column information, so our HTML would look like:
<body ng-app='app' ng-controller='Main'>
<grid col-keys='id,name,type'
col-titles='ID,Name,Type'
rows='rows'>
</grid>
</body>
For the JS, we obviously need the app module:
var app = angular.module('app', []);
And here's the grid directive. It uses an isolate scope, but uses the = 2-way binding to get the row data from its parent scope. Notice how the link function pulls the column info from the attrs object.
The template becomes very simple: loop over the column titles to define the headings, then loop over rows, and in each row, loop over the column keys.
app.directive('grid', function() {
return {
restrict: 'E',
scope: {
rows: '='
},
link: function(scope, element, attrs) {
scope.colKeys = attrs.colKeys.split(',');
scope.colTitles = attrs.colTitles.split(',');
},
replace: true,
template:
'<table>' +
' <thead>' +
' <tr>' +
' <th ng-repeat="title in colTitles">{{title}}</th>' +
' </tr>' +
' </thead>' +
' <tbody>' +
' <tr ng-repeat="row in rows">' +
' <td ng-repeat="key in colKeys">{{row[key]}}</td>' +
' </tr>' +
' </tbody>' +
'</table>'
};
});
And some sample data to get started.
app.controller('Main', function($scope) {
$scope.rows = [
{id: 1, name: 'First', type: 'adjective'},
{id: 2, name: 'Secondly', type: 'adverb'},
{id: 3, name: 'Three', type: 'noun'}
];
});
Here it is in fiddle form.
As Imri Commented:
In the later versions of AngularJS you can get your parent directive by using $scope.$$childHead
I have not tested it.
The example on AngularJS homepage creates tabs by using two directives: one for the tabs and one for the panes. Is it possible to create a single template and a single directive like so:
HTML:
<div ng-app="components">
<tabs objects="someObjects" labelprop="name" shouldbe="the value of object.name"></tabs>
<tabs objects="someObjects" labelprop="id" shouldbe="the value of object.id"></tabs>
</div>
JS:
angular.module('components', []).
directive('tabs', function() {
return {
restrict: 'E',
replace: true,
scope: {objects: '=', labelprop: '#', shouldbe: '#'},
controller: function($scope, $element) {
$scope.someObjects = [
{name: "One", id: 1, data: 'foo'},
{name: "Two", id: 2, data: 'bar'},
{name: "There", id: 3, data: 'foobar'}
]
},
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="object in someObjects">' +
'<p>How do I use "labelprop" to get {{shouldbe}}</p>' +
'</li>' +
'</ul>' +
'</div>'
}
})
(See this fiddle illustrating my question: http://jsfiddle.net/2GXs5/3/ )
From what I understand so far, because ng-repeat is its own directive, it goes off and does its own thing and I cannot access the object property in the scope, say if I wanted to do this in the directive's link function: scope.label = scope.object['scope.labelprop']
Also, I am still trying to wrap my head around interpolation, transclusion, etc. so the solution might involve that somehow.
To solve issue in demo you have access to both the repeating item and the directive scope within the template
<p>{{labelprop}} is {{object[labelprop]}}</p>
Anything not filter related within the expression that isn't quoted will be treated as a variable so long as that vriable is within current scope
demo: http://jsfiddle.net/2GXs5/4/