I have DOM element,with id={{child.id}}container
<div id={{child.id}}container layout="column" flex layout-align="start center" class='container' dragula='"first-bag"' ng-init="vm.getAssociatedWorkItems(child.id); vm.getElementHeight(child.id+'container');">
<div layout="column" class="md-whiteframe-5dp capitalize itemsInList" ng-repeat="item in vm.items | filter:search" id={{item.id}}child>
<div class="workItemName">{{vm.getWorkItemName(item.metadata)}}</div>
<div class="workItemDescription">{{vm.getWorkItemDescription(item.metadata)}}</div>
</div>
</div>
I want to get height of this element, and according to height deside to list items in ng-repeat or not. How to get DOMs height in angularjs?
Use angular built-int jqlite with angular.element():
angular.element(".myDiv")[0].offsetHeight;
Beware: don't pollute your controller with DOM manipulation. It's a bad practise and you should do this kind of operations in directives.
Edit 1
OP used this mid way after my suggestion:
var element = angular.element(document.querySelector('#id'));
var height = element[0].offsetHeight;
in angularjs it is advised to manipulate the DOM elements ,into directives.Into directives we also use jquery.
So, you add the 'my-directive' name into the element, like so:
<div my-directive id={{child.id}}container layout="column" flex layout-align="start center" class='container' dragula='"first-bag"' ng-init="vm.getAssociatedWorkItems(child.id); vm.getElementHeight(child.id+'container');">
<div layout="column" class="md-whiteframe-5dp capitalize itemsInList" ng-repeat="item in vm.items | filter:search" id={{item.id}}child>
<div class="workItemName">{{vm.getWorkItemName(item.metadata)}}</div>
<div class="workItemDescription">{{vm.getWorkItemDescription(item.metadata)}}</div>
</div>
</div>
Into js, you create the directive that gets the height:
.directive('myDirective', function () {
return {
restrict: 'AE',
link: function (scope, element, attrs) {
var elementHeight = element.height();
console.log(elementHeight);
}
}
})
You should use angular's $element service, which is a jqLite wrapper for your component/directive.
Try something similar in your controller:
controller: function($element) {
angular.foreach(
$element.find('.md-whiteframe-5dp, .capitalize, .itemsInList')
.children(), function(element){
var el = angular.element(element);
if(el.hasClass('workItemName')){
// do something with workItemName DOM element
}
if(el.hasClass('workItemDescription')){
// do something with workItemDescription DOM element
}
});
Related
I'm implementing a chat using Angular and Angular Material and Im stuck at making the md-list scroll at the very bottom when i modify the model with new message in messages array. Tried many things but with no success watchCollection is working but i cant seem to find the way to scroll to the md-list at the very bottom. Maybe its something with the material
HTML:
<md-content flex layout-padding>
<md-list flex scroll-bottom="vm.messages">
<md-list-item class="md-3-line" ng-repeat="msg in vm.messages">
<!--<img ng-src="{{user.userAvatar}}" class="md-avatar" alt="{{user.userName}} Avatar"/>-->
<div class="md-list-item-text" layout="row">
<div flex="15"></div>
<div flex="70" layout="column">
<h3>{{msg.text}}</h3>
</div>
<div flex></div>
</div>
</md-list-item>
</md-list>
</md-content>
Angular Directive:
angular.module('chat').directive('scrollBottom', function ($timeout) {
return {
restrict: 'A',
scope: {
scrollBottom: "<"
},
link: function (scope, element) {
scope.$watchCollection('scrollBottom', function (newValue) {
if (newValue)
{
element.scrollTop = element.scrollHeight;
}
});
}
}
})
As I post in a comment, and taking this SO question as reference,
the key here is adding your scrollBottom directive to md-content element instead of md-list.
I just started to study AngularJS and tries to implement customized table directive with the multiple slots transclude.
And faced situation that scope not transferred to transclude. There is a lot of solutions in other StackOverflow questions, but all of them works only when in directive template ng-repeat appears for top element, but that is not my case.
At least i can't adopt all that solutions.
Simplified version.
Directive:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines" ng-transclude="row">
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
Using of directive:
<my-table>
<row>
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</row>
</my-table>
Full example with the script on Plunkr:
https://plnkr.co/edit/rg43ZdPMGHLBJCTLOoLC
Any advice very appreciated.
The simplest and probably cleanest way to directly reference the $scope object created by the ng-repeat in a transcluded template is through the $parent property:
<my-table>
<td>{{$parent.line.col1}}</td>
<td>{{$parent.line.col2}}</td>
</my-table>
The $parent property of the $scope created for a transcluded template points to the $scope of the target template into which such template is ultimately transcluded (in this case, the ng-repeat), even though such transcluded $scope is not a child of the target $scope in the usual sense as a result of the transclusion. See this wonderful blog post for a more complete discussion of this.
Working plunkr: https://plnkr.co/edit/LoqIMiQVZKlTt5epDnZF?p=preview.
You need to use $transclude function manually and create new child scope for each line. Other than that you need to pass lines to directive if you are using isolated scope (and you are using it).
Your linking function should look something like this:
link: function($scope, $element, $attrs, controller, $transclude) {
var tbody = $element.find('tbody');
$scope.$watch('lines', function (lines) {
tbody.empty();
lines.forEach(function (line) {
var childScope = $scope.$new();
childScope.line = line;
$transclude(childScope, function (content) {
tbody.append('<tr>');
tbody.append(content);
tbody.append('</tr>');
}, null, 'row');
});
});
}
Plunker: https://plnkr.co/edit/MLNZOmoQyMazgIpluMqO?p=preview
But that is bad idea anyway, cos it is hard to create table this way. As you can see child of is not elements. You would have to do a little bit of DOM manipulation to make it work.
i see you don't really need to use attribute
so code look more simple and clean:
<body ng-controller="tableCtrl">
<h1>Table test</h1>
<my-table lines="lines"></my-table>
</body>
your template:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines">
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
and angular directive:
angular.module('myApp', [])
.directive("myTable", function() {
return {
restrict: 'E',
transclude: true,
scope: {
lines:'=lines',
api: '#'
},
templateUrl: "template.html",
};
})
.controller("tableCtrl", ['$scope', function($scope) {
$scope.lines = [
{col1: "testCol1", col2: "testCol2"},
{col1: "testCol11", col2: "testCol21"}
];
}]);
working example in plunkr: https://plnkr.co/edit/iMxRoD0N3sUXqmViHAQh?p=preview
I know that if I use the directive ng-repeat, like below, I get every element inside and including the div to repeat on the DOM.
<div class="col col-3" ng-repeat="movie in popular" >
<figure>
<img ng-src="{{movie.backdropURL}}" alt="{{movie.code}}">
<div class="overlay"></div>
<figcaption>{{movie.code}}</figcaption>
<!-- <span class="extra-info">{{movie.extra}}</span> -->
<span class="price">{{movie.price}}</span>
</figure>
</div>
However now I want to have some parent elements that won't repeat but will use the same scope object for their children, that will then repeat.
So, I would like to do a repeater that would append the properties of the scope into their parent, something like this:
<ul class="parent1">
<li><img src={{myScope[0].imgUrl}}></li>
<li><img src={{myScope[1].imgUrl}}></li>
<li><img src={{myScope[2].imgUrl}}></li>
</ul>
<div class="parent2">
<span>{{myScope[0].description}}</span>
<span>{{myScope[1].description}}</span>
<span>{{myScope[2].description}}</span>
</div>
I would like to know if it is possible to reuse a native angular directive (I would prefer not to run the same repeater every time for every parent) where it could append the element to the parent. If not, do you have any suggestion for a solution. I've looked up some links for custom directives I haven't succeeded in applying them. So if you have a 'beginners' custom directive tutorial that could help me go on the right direction, it would be highly appreciated.
I don't know if I understand exactly what you are asking for.
Anyway, the ngRepeat directive is placed in the DOM under a particular parent, so you cannot run it once and append the leaves under different parents. The only way to do that is to create a custom directive that runs a loop internally and sets the leaf under the parent of your choice.
That is:
angular
.module('mymodule')
.directive('mydirective', mydirective);
function mydirective(){
var directive = {
restrict: 'A'
, link: link
}
return directive;
function link($scope, elem, attrs) {
for(var i=0;i<$scope.myScope.length;++i){
var el1 = angular.element('<li><img src='+$scope.myScope[i].imgUrl+'></li>'),
el2 = angular.element('<span>'+$scope.myScope[i].description+'</span>');
elem.find('.parent1').append(el1);
elem.find('.parent2').append(el2);
}
}
}
Please let me know if I misunderstood your goal.
Check this:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<ul class="parent1">
<li ng-repeat="item in myScope">
<img ng-src={{item.imgUrl}}>
</li>
</ul>
<div class="parent2">
<p ng-repeat="item in myScope"><span>{{item.description}}</span></p>
</div>
</div>
Controller:
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.myScope = [
{imgUrl:"someUrl1", description: "this is first url"},
{imgUrl:"someUrl2", description: "this is second url"}
]
}]);
Acceptable :) ?
I have a tree of connected documents (parent to child) in my database from a single model called Actions, they're recursively compiled in an angular directive so that they're nested inside their parents.
I have the following code:
angular.module('crmDashboardApp')
.directive('actionDirective', function ($http, $compile, RecursionHelper) {
return {
scope: {
actionId: '=', // html:action-node, js:actionNode
actionList: '='
},
templateUrl: 'app/main/directive/action/action.directive.html',
replace: true,
restrict: 'E',
compile: function (element) {
return RecursionHelper.compile(element, function(scope, iElement, iAttrs, controller, transcludeFn){
scope.deleteAction = function (_action) {
var id = _action._id;
$http.delete('/api/actions', {
data: {'id':id},
headers: {"Content-Type": "application/json;charset=utf-8"} // we need to do this if we want to send params, otherwise we need to do traditional REST in URL
});
};
// Find for already called action list
scope.findAction = function (_id, _list) {
scope.actionNode = _.findWhere(_list, {_id:_id})
};
scope.findAction(scope.actionId, scope.actionList);
function calculateTimeSince(){
scope.fromNow = moment(scope.actionNode.content).fromNow(true);
}
setInterval(calculateTimeSince, 1000);
scope.fromNow = moment(scope.actionNode.content).fromNow(true);
});
}
};
});
This only compiles once on load and changing anything in the scope after does nothing. I want the setInterval function to change a variable scope.fromNow to be updated every second and update the view (the HTML references this with a simple {{fromNow}})
I believe I'll have to re-compile the directive somehow but doing something like:
$compile(element.contents())(scope)
within the setInterval function doesn't work.
My directive's HTML looks like this:
<div class="action-node">
<header>{{ actionNode.name }}</header>
<div class="row">
<h3 class="col-md-12">{{ actionNode.title }}</h2>
<h5 class="col-md-12">{{ actionNode.description }}</h5>
</div>
<div class="row">
<div class="col-md-3">Time Since: {{fromNow}}</div>
<div class="col-md-3">Content: {{ actionNode.content}}</div>
<div class="col-md-3">Duration Type:{{ actionNode.duration_type }}</div>
<div class="col-md-3">Type: {{ actionNode.type }}</div>
</div>
<div class="row">
<div class="col-md-4">
{{actionNode.children.length > 0 ? actionNode.children : "No children" }}
</div>
<form class="pull-right" ng-submit="deleteAction(actionNode)">
<input class="btn btn-primary" type="submit" value="Delete">
</form>
</div>
<div class="action-wrapper" ng-repeat="child in actionNode.children" ng-if="actionNode.children.length > 0">
<!-- <div class="row" ng-repeat="child in actionNode.children" ng-if="actionNode.children.length > 0" ng-style="{'margin-left': ({{actionNode.nest_level}}+1)*30+'px'}"> -->
<action-directive action-ID="child" action-list="actionList" />
</div>
</div>
You can see that it calls itself again right at the bottom. I am also using RecursionHelper so infinite loop isn't an issue.
Instead of using setInterval, you need to use the Angular wrapper service $interval.
$interval service synchronizes the view and model by internally calling $scope.$apply which executes a digest cycle.
I got a ng-repeat with thousands of item in it, so I decided to tryout bindonce to reduce the number of watches. But I couldn't figure out how to use it properly.
So now I got the following code:
<div ng-repeat="card in cards">
<div class="item-box" draggable="{{card.category}}" itemId="{{card._id}}">
<img ng-src="{{card.image}}" width="100%" height="100%">
</div>
</div>
As I read in the bindonce doc, I should add the directive and use the bo-* directives, so I fugured out this:
<div ng-repeat="card in cards" bindonce>
<div class="item-box" draggable="{{card.category}}" itemId="{{card._id}}">
<img bo-src="card.image" width="100%" height="100%">
</div>
</div>
So my question is how I can also use {{card.category}} and {{card._id}} using bind-once?
bo-attr bo-attr-draggable="card.category" bo-attr-itemId="card._id"
seems not to work, I'm not getting any errors, just nothing happens.
Result looks like
<div class="item-box ng-scope" bo-attr="" bo-attr-draggable="card.category" bo-attr-itemid="card._id" draggable="Pants" itemid="m--Pi">
bo-attr doesn't actually seem like what you want to be doing, you just want a directive to evaluate and bind data without creating any watches. I made a plnkr that I think is what you want: http://plnkr.co/edit/sFPAjlRCkDuXU5UiM1U1?p=preview
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
});
// html
<div directive="name"></div>
// Dummy directive
app.directive('directive', function() {
return {
template: '<div bindonce bo-text="val"></div>',
compile: function() {
return {
pre: function(scope, elt, attrs) {
scope.val = scope.$eval(attrs.directive);
}
};
}
}
})
Woo no watches!
Let me know if I misunderstood something.