Nested and slotted Transclude in AngularJS - angularjs

I have an element which its whole content could be transcluded. the transclusion is optional, so I can let its inner parts stay but an inner element be transcluded. Let's see it in action:
<h4 class="modal-title" ng-transclude="title">
Are you sure you want to remove this <span ng-transclude="innerType">good</span>
</h4>
In directive definition I have:
myApp.directive('confirmDeleteModal',function(){
return {
restrict:'E',
transclude: {
title:'?modalTitle',
innerType:'?modalType',
},
templateUrl:'templates/confirm_delete_modal.html',
scope: {
obj:'=info',
},
}
});
The HTML code would be so:
<confirm-delete-modal info="goodToBeDeleted">
<modal-type>good</modal-type>
</confirm-delete-modal>
When I run my code, I get the following error: [ngTransclude:orphan].
What should I do?
I am using AngularJS v1.5.8

You use another directive ng-transclude in a fall back content of modal-title. But the ng-transclude become the "parent" of the span and doesn't provide transclusion function. I suggest to you to modify your directive and to use the method isSlotFilled to know if title is filled or not :
directive('confirmDeleteModal',function(){
return {
restrict:'E',
transclude: {
title:'?modalTitle',
innerType:'?modalType',
},
link: function(scope, elem, attrs, ctrl, trfn) {
scope.titleFilled = trfn.isSlotFilled('title');
},
template:'<h4 class="modal-title" ng-transclude="title" ng-if="titleFilled"></h4>' +
'<h4 class="modal-title" ng-if="!titleFilled">Are you sure you want to remove this <span ng-transclude="innerType">good</span></h4>',
scope:{
obj:'=info',
}
}
})
(https://plnkr.co/edit/k0RXLWbOvHdNc9WFpslz?p=preview)

Related

How to Append Dynamic Template in AngularJS Directive

I want to create a directive like:
<div ng-refreshing-when="x">Some Content In Here</div>
so when x=true, it will do some css and fade a spinner div over top, then when x=false, the spinner will fade out. I want to append a div (which is hidden by css) to the parent div, NOT replace the content. My current code is here, but the ngRefreshingWhen isn't triggering the ng-if:
http://plnkr.co/edit/griUYR6RY46x5DlqVHWU?p=preview
I set:
$scope.refreshing = false;
so the "refreshing" text shouldn't be there...
How can I accomplish this?
You should be able to use transclude and put the HTML in the template:...
angular.module('ngRefreshingWhen',[])
.directive('ngRefreshingWhen', function ($compile) {
return {
restrict: 'A',
transclude: true,
template: '<div class="refresher" ng-if="ngRefreshingWhen"><p>Refreshing</p></div><div ng-if="!ngRefreshingWhen" ng-transclude></div>',
scope: {
ngRefreshingWhen: '='
}
};
});
If the HTML is more complicated, put it in a separate file and use templateUrl.
http://plnkr.co/edit/28Eq0HARKF0KXpYJwBTS?p=preview
<div> <!-- Parent Div -->
<div ng-refreshing-when="refreshing"></div>
<p>Some Stuff I Want to Keep</p>
</div>
&
angular.module('ngRefreshingWhen', [])
.directive('ngRefreshingWhen', function($compile) {
return {
restrict: 'A',
scope: {
ngRefreshingWhen: '='
},
template: '<div class="refresher" ng-if="ngRefreshingWhen"><i class="fa fa-spin fa-refresh"></i></div>',
link: function(scope, element, attrs) {
// add has-refresher class
element.parent().addClass('has-refresher');
}
};
});
http://plnkr.co/edit/xRgqqOfz9IuskL1kn057?p=preview
I did it differently, I added directive inside, then added the needed class to it's parent, same result, just added an element instead of trying to add an attribute to the parent.

How to get values resolved in template without double curly brackets

I am using directive and template. My template is as follows
<div>
<a data-my-dir="item" data-attr1="true"
data-attr2="{{itemParentId}}"
data-attr3="{{item.id}}">
</a>
</div>
Here due to curly braces watches are added and it is affecting my performance. I don't want watches as I am not going to modify attr2 or attr3.
I want to directly resolved value here only.
We can use bindonce directive where we don't want watches where we can use bo-text="xyz" instead, but here I want to pass values as attr to my custom directive.
Inside my directive's link function I am binding click event with element as follows
link: function (scope, element, attrs) {
element.bind('click', function (event) {
var myAttr1 = attrs.attr1;
var myAttr2 = attrs.attr2;
}
}
So due to those watches in template on attr1 and attr2 I am getting these values resolved in click event.
What are the alternatives?
One time Binding
Seems like a good use case for one time binding (if you are using angular 1.3+)
<div>
<a data-my-dir="item"
data-attr1="true"
data-attr2="{{::itemParentId}}"
data-attr3="{{::item.id}}">
</a>
</div>
the directive would look like
angular.module('app', [])
.directive("myDir", function() {
return {
restrict: "A",
scope: {
"attr1": "#",
"attr2": "#",
"attr3": "#",
},
template: '<span> {{attr1}} {{attr2}} {{attr3}}</span>'
};
})
Demo
http://plnkr.co/edit/GJCZmb9CTknZZbcRHN7s?p=preview
You could use data-attr2="itemParentId" directly but for that you need to use = as currently you are using # option of directive.
app.directive('myDir', function(){
return {
scope: {
dataAttr1: '=', //or '=dataAttr1'
dataAttr2: '=' //or '=dataAttr2'
},
link: function(scope, element, attrs){
console.log(scope.dataAttr1);
console.log(scope.dataAttr2);
}
}
})

Assign random ngModel to directive

I'm trying to assign a random string to ngModel. But I can't even seem to assign a regular string to it. In the code below, I'm trying to change the ngModel to "new", but in chrome, it's still showing me that the ngModel is "placeholder". What am I doing wrong?
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
template: '<div ng-model="placeholder"></div>',
controller: function ($scope, $element) {
$scope.placeholder = "new";
}
}});
The plain div tag was just an example. What I actually have is a content editable div that I've bound to a textarea with a contenteditable directive. I made a button to allow me to add as many of these directives as I want, but when I add this directive, I'd like a new ng-model for each one, because that's what I'm using to save the content of each content editable div to a file.
This is the full example in case it might help someone else. I put the addPage directive to a button, which, when clicked, appends a new content editable div (which I'm calling a page). There is one more directive that I didn't include (contenteditable) because I got it from the bottom of the docs over here
app.directive("addPage", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){ angular.element(document.getElementById('container')).append($compile('<page></page>')(scope));
});
};
});
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
// template: '<div class="page" contenteditable strip-br="true" ng-model="chapter"></div>',
template: function (elem, attr) {
var test="chapter.two"; //in reality, I will generate a random string to keep these divs unique
return '<div class="page" contenteditable strip-br="true" ng-model=' + test +'></div>'
}
}});
In my controller, I have this object that is used to store all the ng-model content
$scope.chapter = {};
I think you are approaching this a bit backwards. Starting with the model, you'd need an array to keep the values from all of the inputs / contenteditables.
.controller("MainCtrl", function($scope){
$scope.data = [{v: ""}]; // first element
$scope.addContentEditable = function(){
$scope.data.push({v: ""});
};
})
Then, you can bind to each element of that array easily, and add elements at will:
<button ng-click="addContentEditable()">Add</button>
<div ng-repeat="item in data" ng-model="item.v" contenteditable></div>
I'm not sure exactly how the page directive needs to be used here, but one way or another, the approach is the same as above.
If you just want to pass a string in you could use attributes and template(fn)
HTML
<div page="new">
Directive
app.directive("page", function () {
return {
restrict: 'E',
replace: true,
template: function (elem, attr) {
return '<div ng-model="' + attr.page + '"></div>';
}
}
});

Class directive that adds ng-class

If I have something like this
<div ng-class="{'breakfast': meal == 'eggs'}"></div>
Now I need to create a directive that would add the same thing, so doing this:
restrict: 'C'
scope:
meal:"&"
link:(scope, element, attr)->
element.attr('ng-class', "{'breakfast': meal == 'eggs'}")
adds the attribute to the DOM, but then nothing happens, when meal == 'eggs' it doesn't add breakfast class to the element. What am I doing wrong?
ps. I can of course do it with element.addClass and element.removeClass but then the animation would only work when I add the class, but wouldn't for the removal (if I have css styles like .breakfast-add, .breakfast-remove etc.
Instead of modifying the element's attributes at link or compile time, you can use ng-class in the directive's template and use a scope variable with your classes. It works for me in the following example, and it seems to be cleaner:
HTML:
<div ng-app="app">
<div class="meal: eggs;">xxx</div>
</div>
JS:
angular.module('app', [])
.directive('meal', function () {
return {
template: '<div ng-class="classes" ng-transclude></div>',
transclude: true,
restrict: 'C',
scope: {
meal: '#'
},
link: function (scope) {
scope.classes = {
breakfast: (scope.meal == 'eggs')
};
}
};
});

attribute value in directive angularjs

i have html markup that i need to use at many places so rather to copy paste it again and again but with different Headings
i thought to create a directive it looks like this
myModule.directive('row', function ($compile) {
return {
restrict: 'E',
template: '<div class="row">'+
'<div class="col-md-10 margin-top-10px font-18px">{{heading}}</div>'+
' <div class="col-md-2">'+
'<div class="margin-top-10px"><span ng-click="close()" class="close helvetica color-black">×</span></div>'+
'</div>'+
'</div>',
replace: true,
link: function (scope, element, attrs, ctrl) {
var test = attrs.heading;
}
};
});
and how i m trying to use it
<row heading="its my heading "></row>
i cant make it work .i dont want to create isolated scope or child scope i just wanted to pass value from attribute and when template gets replaced i want its interpolated value .
can some one help me how can id o this
Try with:
link: function (scope, element, attrs, ctrl) {
scope.heading = attrs.heading;
}
It might also be a good idea to set
scope: true
You have to actually attach the value from the attr object to the scope before it can be used in the template.
add
scope: {
heading: '='
}
to your directive's definition to create 2 way data-binding with that attribute. you would do this if you are passing in a scope variable.
or use #
scope: {
heading: '#'
}
if you are just passing in a string.
minimalist plunkr to demonstrate: http://plnkr.co/edit/U5OzlMiXaIsHEdb5gmRT

Resources