AngularJS: Replace an element using Transclude: 'element' but not the deprecated 'replace'? - javascript

I am trying to create a directive that can create multiple elements an replace the calling element with the multiple. Specifically I want to set the directive on a single list-item and have it create multiple list items w/o a wrapping element. (Using the <UL> for the directive works but prevent me from including 'static' items.) Here is the markup:
<ul>
<li>static first</li>
<li my-repeater="myVar"></li>
<li>static last</li>
</ul>
In my controller I'll define myVar:
$scope.myVar = ['one', 'two', 'three'];
And my directive looks like this:
myApp.directive('myRepeater', function () {
return {
restrict: 'A',
transclude: 'element',
replace: true, //<--- DEPRECATED
scope: {
val: '=myRepeater'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
};
In AngularJS v1.2.26 this works UNLESS you remove 'replace', then you get nothing. Is this just not possible? I did note that in the docs for v1.3.4 that they feel:
There are very few scenarios where element replacement is required for the application function, ...
But my case above seems to be a clear example of the need for this, unless there is a 'better way'!...?

If you don't have to do it as an attribute, then you can do it using an element:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<ul>
<li>static first</li>
<my-repeater var="myVar"></my-repeater>
<li>static last</li>
</ul>
</div>
</div>
And the directive
.directive('myRepeater', function () {
return {
restrict: 'E',
scope: {
val: '=var'
},
template: '<li ng-repeat="item in val">{{item}}</li>'
};
})
I updated the fiddle to show it http://jsfiddle.net/6rjr8nq5/1/
Since unknown tags are just ignored, it works fine.
And if you need the attribute, you could stick it on the ul, transclude, and use the link function to properly place the static data, assuming you know where it goes.

Related

How to loop over directives, compiling, and attaching to DOM?

I have a number of directives that I would like to compile and attach to the DOM. For example:
mod.controller("ctrl, ["$scope", "$compile", function($scope, $compile) {
$scope.tools = [
{
title: "foo",
directive: $compile("<foo-bar></foo-bar>")($scope)
},
{
title: "qux",
directive: $compile("<qux-bar></qux-bar>")($scope)
}
...
];
Then in HTML:
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
{{tool.directive}}
</div>
</div>
I would like each directive to be compiled and injected into the DOM. But nothing happens. I expect because I am calling $compile too late. Is there a better way to do this?
FWIW, if I compile the directive and "manually" append it to the DOM, it works:
$('body').append($compile('<foo-bar></foo-bar>')($scope));
You cannot do it this way; the {{...}} bindings do not accept elements. They can be made to accept HTML, but this HTML is static - uncompiled.
If you want dynamic directives, you have to do it yourself. One option is with an auxiliary directive, e.g. the container-directive below:
<div class="tool" container-directive>
<h3>{{tool.title}}</h3>
<placeholder style="display: none"></placeholder>
</div>
It takes the tool from its context, $compiles it, and replaces the dummy placeholder element. Suppose the tools are defined as:
this.tools = [
{ title: 'foo', directive: 'foo-bar' },
{ title: 'qux', directive: 'qux-bar' }
];
Then a very simple implementation would be:
app.directive('containerDirective', function($compile) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.find('placeholder')
.replaceWith($compile('<' + scope.tool.directive + '></' + scope.tool.directive + '>')(scope));
}
};
});
See a fiddle: http://jsfiddle.net/kxj60cbo/
This code demonstrates the general idea. It definitely will need some adjustment to fit your needs. E.g. the directive is tightly coupled with the name of the iteration variable - tool - maybe using isolated scope would be better.
I wouldn't inject I would just qualify what to show
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
<foo-bar ng-if="tool.title = 'foo'"></foo-bar>
<qux-bar ng-if="tool.title = 'qux'"></qux-bar>
</div>
</div>

trigger when ngRepeat is done

In a directive I need to have the width of an element, however this element is created in a ng-repeat. So when the code inside my link function runs, ngRepeat is not yet executed. I tried to delay things using $timeout but that didn't help
JS:
link: function (scope, element) {
$timeout(function () {
var width = $(element.find('li')).width(); // the li elements do not exist
});
}
HTML:
<ol>
<li ng-repeat="item in items">
<my-item>.....</my-item>
</li>
</ol>
I cannot use the <ol> element, because the <my-item> has styles like margins/paddings.
Any suggestions ?
From ng-repeat finish event
<ol>
<li ng-repeat="item in items" on-complete>
<my-item>.....</my-item>
</li>
</ol>
-
.directive('onComplete', function() {
return function(scope, element, attrs) {
angular.element(element).css('color','blue');
if (scope.$last){
window.alert("im the last!");
}
};
})
http://plnkr.co/edit/or5mys?p=preview
You could use the scope.$last to generate an event when the last item is done or just run the code you need in there.
You might also want to consider your design. Could there be any CSS solution for your problem? If not go ahead.

Dynamically Create and Load Angular Directive

In my application i have a list of custom directive names.
$scope.data =["app-hello","app-goodby","app-goodafter"];
each name in this array is one directive that im created.
var app = angular.module('app',[]).controller('mainCtrl',function($scope){
$scope.data =["app-hello","app-goodby","app-goodafter"];
}).directive('appHello',function(){
return {
restrict:'EA',
template:'<h1>Hello Directive</h1>'
};
}).directive('appGoodbye',function(){
return {
restrict:'EA',
template:'<h1>GoodBye</h1>'
};
}).directive('appGoodafter',function(){
return{
restrict:'EA',
template:'<h1>Good Afternoon</h1>'
};
});
now i want to load directive with ng-repeat in the view for example because i used EA restrict for directive can create directive in ng-repeat like this :
<div ng-repeat="d in data" >
<div {{d}}></div>
</div>
but this way it doesn't work. so the real question is if i have list of directive how to load this directive with ng-repeat.for this scenario i create a jsbin .
thanks.
You need a "master" directive that $compiles the HTML (optionally containing directives) into an Angular-aware template and then links the compiled element to a $scope:
app.directive('master', function ($compile) {
return {
restrict: 'A',
link: function postLink(scope, elem, attrs) {
attrs.$observe('directive', function (dirName) {
if (dirName) {
var compiledAndLinkedElem =
$compile('<div ' + dirName + '></div>')(scope);
elem.html('').append(compiledAndLinkedElem);
}
});
}
};
});
<div master directive="{{dir}}" ng-repeat="dir in ['dir1', 'dir2', 'dir3']"></div>
See, also, this short demo.
You can do it in this way:
Directive:
app.directive('compile',function($compile){
return{
restrict:'A',
template: '<div></div>',
link:function(scope,elem,attrs){
scope.name = attrs.compile;
elem.children('div').attr(scope.name,'');
$compile(elem.contents())(scope);
}
};
});
HTML:
<div ng-repeat="d in data" compile="{{d}}">
</div>
Jsbin: http://jsbin.com/wofituye/4/edit
I actually prefer to create templates, that just contain the directive. Then you can use ng-include this then enables you to easily pass scope variables into the dynamically chosen directives too.
Here is my widget code fore example:
<div ng-repeat="widget in widgets track by $index" ng-include="widget.url" class="widget-container" ng-class="widget.widget_type.config.height +' ' + widget.widget_type.config.width">
</div>
Then I set the widget.url to a template containing just the right directive.
I can then in my directive do this:
<custom-widget ng-attr-widget="widget"></custom-widget>
Then I have access to the dynamic variable too, so I can access configuration specifics too, without having to dynamically generate HTML strings and compile them. Not a perfect solution, but personally I used to use the other approach mentioned, and discovered that this fit my needs much better.

Using directives in AngularJS

I'm trying to learn AngularJS and have one question/concept I'm struggling to understand.
Take the following demo code I created:
js
var app = angular.module('customerPortalApp', ["ui.router"]);
app.config( function($stateProvider, $urlRouterProvider){
// For any unmatched url, send to /route1
$urlRouterProvider.otherwise("/route1");
$stateProvider
.state('route1', {
url: "/route1",
templateUrl: "/static/html/partials/_campaign_title.html",
controller: "CampaignCtrl"
})
});
app.controller("CampaignCtrl", function($scope){
$scope.loadCampaign = function(){
alert("loaded campaign!")
}
});
app.directive("campaign", function() {
var MessageBox = angular.element('<div class="alert alert-success">hello</div>');
var link = function (scope, element){
scope.loadCampaign();
}
return {
restrict: "E",
compile: function (template){
template.append(MessageBox)
return link
}
}
});
html
<div class="container" ng-app="customerPortalApp">
<div class="row">
<div class="span12">
<div class="well" ui-view></div>
</div>
</div>
<div ng-controller="CampaignCtrl">
<campaign>
test
</campaign>
</div>
</div>
Looking at this code I call the controller in my config and the new $stateProvider I added now takes care of the template loading, so why do I now need directive? In my example I don't now know why I would need one, can ui-view be used to house more controllers?
For your example, you can to use a ui-view. In general, I used directives for a reusable and specified behavior.
What are Directives?
At a high level, directives are markers on a DOM element (such as an attribute, element name, or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.
Angular comes with a set of these directives built-in, like ngBind, ngModel, and ngView. Much like you create controllers and services, you can create your own directives for Angular to use. When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements.
See the documentation: Angular JS Documentation
A example below as I used the directives:
/* Get the boolean data and switch true or false for respective images. This Example use the bootstrap to show images */
App.directive('bool2image', function() {
return {
restrict: 'C',
replace: true,
transclude: true,
scope: { boolean: '#boolean' },
template: '<div ng-switch on="boolean">' +
'<div ng-switch-when="false"><span><i class=icon-remove></i></span></div>' +
'<div ng-switch-when="true"><span><i class=icon-ok></i></span></div>' +
'</div>'
}
});
So, to used the directive called into the code:
<div class="bool2image" boolean="{{booleanData}}"></div>
I hope to help you.

AngularJS - Use attribute directive conditionally

I am using "draggable" directive to support image dragging. However, as per the role of the user, I need to disable image dragging for certain groups of users. I have used following code.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
The method dragSupported is in the template scope and returns true or false. I don't want to create two big duplicate <li> elements by using ng-if for each value returned by dragSupported(). In other words, I am not looking for the following approach to solve this.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-if="dragSupported() ==true" ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
<!--remove "draggable" directive as user doesn't have permission to drag file -->
<li ng-if="dragSupported() !=true" ng-repeat="template in templates" id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
Is there any other approach to avoid code duplicity?
ng-attr-<attrName>
Support for conditionally declaring an HTML attribute is included with Angular as the dynamically-titled ng-attr-<attrName> directive.
Official Docs for ng-attr
Example
In your case, the code might look like this:
<li
id="{{template._id}}"
class="template-box"
type="template"
ng-repeat="template in templates"
ng-attr-draggable="dragSupported() === true"
></li>
Demo
JSFiddle
This contains examples of usage for the following values: true, false, undefined, null, 1, 0, and "". Note how typically-falsey values may yield unexpected results.
Thanks Jason for your suggestion. I took little different approach here. Since I don't want to change the "scope" variable therefore I used "attrs" to check if drag is allowed or not. Following is approach I tool which seems good so far.
Directive code:
app.directive('draggable', function () {
return {
// A = attribute, E = Element, C = Class and M = HTML Comment
restrict: 'A',
replace:true,
link: function (scope, element, attrs) {
if(attrs.allowdrag =="true")
{
element.draggable({
cursor: 'move',
helper: 'clone',
class:'drag-file'
});
}
}
}
});
HTML Code:
<ul>
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable allowdrag="{{userHasPrivilege()}}" >
<!--Ohter code part of li tag-->
</li>
</ul>
Controller is having implementation of userHasPrivilege().
Not sure if this is correct way or not. Looking for thoughts.
There is no way to directly add or remove an attribute from an element. However, you could create a directive that simply adds the attribute to the element when the condition is met. I've put something together that illustrates the approach.
Demo: http://jsfiddle.net/VQfcP/31/
Directive
myApp.directive('myDirective', function () {
return {
restrict: 'A',
scope: {
canDrag: '&'
},
link: function (scope, el, attrs, controller) {
/*
$parent.$index is ugly, and it's due to the fact that the ng-repeat is being evaluated
first, and then the directive is being applied to the result of the current iteration
of the repeater. You may be able to clean this by transcluding the repeat into the
directive, but that may be an inappropriate separation of concerns.
You will need to figure out the best way to handle this, if you want to use this approach.
*/
if (scope.canDrag&& scope.canDrag({idx: scope.$parent.$index})) {
angular.element(el).attr("draggable", "draggable");
}
}
};
});
HTML
<ul>
<!-- same deal with $parent -->
<li ng-repeat="x in [1, 2, 3, 4, 5]" my-directive="true" can-drag="checkPermissions(idx)">{{$parent.x}}</li>
</ul>
Controller
function Ctl($scope) {
$scope.checkPermissions = function(idx) {
// do whatever you need to check permissions
// return true to add the attribute
}
}
I used a different approach as the previous examples didn't work for me. Perhaps it has to do with using custom directives? Perhaps someone can clear that up.
In my particular example, I'm using ui-grid, but not all ui-grids should use pagination. I pass in a "paginated" attribute and then $compile the directive based on true/false. Seems pretty brutish but hopefully it can push people in a positive direction.
HTML
<sync-grid service="demand" paginated="true"></sync-grid>
Directive
angular
.module('app.directives')
.directive('syncGrid', ['$compile', SyncGrid]);
function SyncGrid($compile){
var nonPaginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid"></div>' +
'</div>';
var paginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid" ui-grid-pagination></div>' +
'</div>';
return {
link: link,
restrict: 'E',
replace: true
};
function link(scope, element, attrs) {
var isPaginated = attrs['paginated'];
var template = isPaginated ? paginatedTemplate : nonPaginatedTemplate;
var linkFn = $compile(template);
var content = linkFn(scope);
element.append(content);
// Continue with ui-grid initialization code
// ...
}
}

Categories

Resources