Recursion in angular directives / templates - javascript

I was trying to build a custom directive for displaying a tree. For some reason it seems that once you include the directive in its own template, something runs wild in the angular compiler and the browser process gets stuck in a loop.
Here's a plunker:
<li class="list-group-item">
<a ng-click="clicked(item)"><span ng-if="item.items" class="glyphicon glyphicon-plus text-primary"></span></a>
<span ng-if="!item.items" class="glyphicon glyphicon-record text-primary"></span>
<a>{{item.name}}</a>
<div ng-if="item.items && item.items.length>0">
<ul class="list-group">
<taxonomy-item ng-if="item.items && item.items.length>0" ng-repeat="child in item.items"></taxonomy-item>
</ul>
</div>
If you pay attention you'll see that it doesn't even bind data, so it shouldn't be a recursive loop caused by model/data, but rather a compiler issue...
http://plnkr.co/edit/1aollcuCr2gA96W6Sk6q
Careful, running might freeze your browser tab!
Any suggestions on how to work around this problem?

The solution is quite simple. Since angular doesn't permit the usage of directives within themselves, you need to make use of the link function, to add the child directive afterwards. A minimal example:
myApp.directive("myDirective", ["$compile",function($compile){
var template = '<my-directive ng-repeat="child in item.items" ng-model="child"></my-directive>';
return{
templateUrl: "myDirective.html",
replace: true,
transclude: true,
scope: {
item: "=ngModel"
},
link: function(scope, element){
var compiled = $compile(template)(scope);
element.append(compiled);
}
}
}]);
This won't cause any trouble, since the compiled child directive is self-contained and compiled afterwards.

Related

Angular Directive placed on ng-repeat not removed from ng-repeat

I have an AngularJS directive meant to interact with notifications pushed from a server. The problem I am having is that currently the method I am using is to have an ng-repeat which keeps track of all the notifications during the session, and then displaying them as they are added to the array. What I want to happen, is that an alert comes, it's added to the DOM, it stays for a couple seconds, and removes itself from the DOM. At this point the issue is that the element will hide, but it will not remove itself from the DOM. Being that the element is absolutely positioned, after the fadeout is prevents me from accessing item that it is in front of. I assumed that element.remove() and the element.$destroy() would do the trick, but it seems like the element is not being from the ng-repeat or possibly that the $scope of the directive is not being deleted. Any help would be greatly appreciated.
angular.module('bmuApp').directive('messageNoti', function($timeout){
return {
scope: {
alert: '=messageNoti'
},
replace: true,
restrict: 'EA',
templateUrl: 'partials/authenticated/homepage/alerts.html',
link: function(scope, element, attrs) {
$timeout(function(){
element.addClass('fadeOut');
$timeout(function(){
element.remove();
}, 500);
}, 5000);
element.on('$destroy', function () {
scope.$destroy();
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!------------>
<!-- Alerts -->
<!------------>
<div class="alert-container" ng-cloak>
<a class="alert animated fadeInUp" ng-repeat="alert in alerts" ng-href="messages/{{ alert.user.thread_id }}" message-noti="alert">
</a>
</div>
<!-----Template for Alert------>
<div class="row">
<span class="close">
<i class="glyphicon glyphicon-remove"></i>
</span>
<div class="col-xs-12 text-left">
<span class="profile-image" style="background-image:url('https://www.blackmarketu.com/{{ ::alert.user['profile-picture'] }}');"></span>
<h5> {{ ::alert.user["user-firstname"] }} {{ ::alert.user["user-lastname"] }} </h5>
<p>{{ ::alert.message }}</p>
</div>
</div>
You don't need to interact with the DOM directly to remove it. Just splice (remove) that array member from the array.
So your logic is a bit wrong, because you have an array of alerts and you are not removing them, you just want to remove the DOM element.
If you don't want to change your code too much and add a parent directive to handle this, you can pass both "alert" (current array member) and "alerts" (entire array of alerts) to your directive and then in your timeout callback instead of removing the DOM element, you splice that array member from the array.
Remove replace: true. Since the ng-repeat directive creates inherited scopes and your directive creates isolate scope, they won't play well together on the same element.
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636

$http.get on HoverOver to populate PopOver text

I'm trying to implement a feature using Angular and Boostrap where the user can get a popOver on an item in the list, and have it perform an angular factory $http.get function to retrieve data and populate the popover text.
I'm not sure this is the best approach, but I have a ng-repeat like so:
<ul>
<li ng-repeat="product in products">
<model-popover ng-attr-id="{{product.Id}}"></model-popover>
</li>
</ul>
And my best guess is to use an angular directive, taking in the id number as a scope attribute,and performing a factory call from the directive. I've read up on the controller/link functions within the directive, but not sure the proper implementation
app.directive('modelPopover', ['Factory', function (Factory) {
return {
restrict: 'E',
replace: true,
scope: { id: "=" },
controller: function($scope){
var prod = Factory.getProductDetail(id);
},
template: '<a popover-placement="bottom" popover="{{prod}}">{{prod}}</a> '
};
}]);
I know the directive is incorrect, but i'm hoping there's enough information to help me out. Thanks in advance!
You do not need special directive for this, due to value-binding u can just change scope variable and popover will change also.
So u simply:
<button popover="{{var}}" popover-trigger="mouseenter" class="btn btn-default" ng-mouseover="changeVar()">Mouseenter</button>
And in changeVar you can change $scope.var any way you want.
Here is example plunk ($http call emulated using $timeout):
http://plnkr.co/edit/gnm1BtnHzNLnvO62Ar2i?p=preview
This var prod = Factory.getProductDetail(id);
has to be changed to $scope.prod = Factory.getProductDetail(id) if you want to use the mustaches

Template inside of a directive

I have a strange situation where I need to put a template inside of a template in my directive. The reason for this is that AngularJS will not read ng-repeat code inside of attributes.
In an ideal world, this is what my code would look like:
<div ng-repeat="phone in phones">
<button popover="<div ng-repeat='friend in phone.friends'>{{friend.name}}</div>"></button>
</div>
Unfortunately this does not work because of the quotes around the popover attribute. This has led me down a pretty deep rabbit hole where I'm trying to put a template inside of a template like in this plunker:
http://plnkr.co/edit/ZA1uA1UOlU3cCH2mbE0X?p=preview
HTML:
<div my-popover></div>
Javascript:
angularApp.directive('myPopover', function( $compile) {
var getTemplate = function()
{
var scope = {title: "other title"};
var template = "<div> test template. title: {{title}}</div> ";
return $compile(template)(scope);
}
return {
restrict: 'A',
template: '<button class="btn btn-default" popover="{{content}}" popover-title="title">template</button>',
link: function(scope) {
scope.content = getTemplate();
}
};
})
Unfortunately this does not work because AngularJs complains about a circular reference. Please help! (this has been taking me all day)
I'm not sure I understand exactly what you are trying to achieve, but from the look of it you might want check out the transclude option for directives.
From the docs:
use transclude: true when you want to create a directive that wraps
arbitrary content.
If you use transclude, you can store the popover content inside the button, and "forward" that content to where you want it using the ng-transclude directive.
Your code would then look something like this:
<button>
<div ng-repeat='friend in phone.friends'>{{friend.name}}</div>
</button>
You can see some examples in action in the guide to directives.

Mustache.js to Angular.js, triple bracers in Angular?

I have the following in Mustache.js:
<div>{{{icon.tmpl}}}</div>
icon.tmpl is a template on its own with the following content:
<div id="{{id}}" class="glyphicon glyphicon-send"></div>
In Mustache.js, thanks to the triple bracers, this works perfectly, both levels of templates gets compiled. Now I can't make this work in Angular.js. The second embedded template does not get compiled, but is instead surrounded by quotation marks "..."
How to make this work in Angular?
You could either use an ngInclude or create a directive. Here is an example of an icon directive that essentially just replaces any icon element with the div info you've specified.
http://plnkr.co/edit/NK5bOFvsgpMGeTkteMif?p=preview
html:
<icon></icon>
js:
app.directive('icon', function ( $compile, $timeout) {
return {
restrict: 'EA',
replace: true,
template: '<div id="{{id}}" class="glyphicon glyphicon-send"></div>'
}
})
The directive could just as easily be something like <div class="icon"> or <div icon> and you could apply the template to it.
An example of the ngInclude:
<ng-include src="'icon.html'"></ng-include>
Where icon.html just has your template info. Make sure that id is in the scope in both cases.

Implementing a page load performance directive in AngularJS

I am very new with AngularJS so I need quite some pointing in the right direction.
The task is to create some kind of widget that displays how much time it takes from any user action until the requested page finishes rendering.
We are going to be using AngularJS at the presentation layer and the back-end will be Microsoft's Web API.
So I figured I could use the browser's Navigation Timing API and wrap it on an AngularJS directive so I tried this:
angular.module('performanceDirective', [])
.directive('pagePerformance', function(){
return {
restrict: 'AE',
replace: 'true',
template: '<div><label id="loadTimeEllapsed">Total Load Time:{{totalLoadTime}}</label></div>',
scope: {},
link: function (scope, elem, attrs) {
scope.$watch('window.performance.timing', function (newValue, oldValue) {
var timing = window.performance.timing;
var userTime = timing.loadEventEnd - timing.navigationStart;
scope.totalLoadTime = userTime;
});
}
};
});
But it seems that there is something missing because even though I am doing actions that call the back-end the number that gets displayed after the home page loads is never updated.
Is this something that actually would work, provided we fix whatever is failing, or is this a dead end and we need to find another option?
UPDATE
The use of the directive has nothing to it, basically it is just the element thrown on a page:
<body ng-app="myApp">
<div class="navbar">
<div class="navbar-inner">
<ul class="nav">
<li>Some Action</li>
</ul>
</div>
</div>
<div class="row">
<div class="span4"><data-page-performance /></div> <!-- The Directive -->
<div class="span10" ng-view></div>
</div>
</body>
Apparently this directive only works if I refresh the page after I have already navigated to it but if I click on an element that will trigger an action on the AngularJS controller the performance number is completely unaffected.

Categories

Resources