I am working on a custom directive that will show the transcluded empty template when there is nothing to show in the scope -- as in this plunker.
I could not achieve to remove the empty template in ng-repeat nor I was able to show the empty message only when there is no data. How can I display the empty template when there is nothing to show?
Actually I am not sure that it is right way to do it in directive - you'll better show different DIVs inside of your main template.
But here is my idea: instead of transclude, do manual compiling depending on is there any items to show or not.
Here is working (but still little bit dirty) plunker: http://plnkr.co/edit/dxUjpeCbSKKjaXqK9qBF?p=preview
Note, that even as I am watching whole list, re-compiling is done only when state (empty/non-empty) is changed.
Note2: It is not full transclusion - scope inside of templates (empty and not empty) will be isolated to directive's scope. You can workaround that by using different compilation or child scopes - there are couple solutions, but they are not related to this question :)
You don't need a directive for this. Just remove the directive from app.js and replace the code after buttons with this:
<div ng-hide="things.length">nothing to show here</div>
<div ng-show="things.length">
<ul>
<li ng-repeat="thing in things">thing #{{thing}}</li>
</ul>
</div>
Here's the working code: http://plnkr.co/edit/u4FF77y6dhXHj0FQC9Ai?p=preview
Related
I'm trying to use an element directive (restrict: "E") to define a custom component. Here's my page.html template, which gets pulled into index.html via the ng-view directive:
<eng-popup>This text appears, but is rendered in a span</eng-popup>
Angular IS running; index.html has the ng-app="templatesApp" directive in the body tag and app.js sets up the module as below:
var app = angular.module("templatesApp", ['ngRoute']);
Further along, in app.js I have the popup directive defined:
app.directive('engPopup', function() {
console.log("This console log does not trigger. I have tried 'eng-popup' as the directive's first parameter, but that doesn't work either.");
return {
restrict: 'E',
transclude: false,
template: '<div id="thisDoesNotEvenAppear"></div>'
};
});
The problem is, when I look at the resultant html of the component, it looks like this:
<span class="ng-scope" jQuery110209261664696867675="7">This text should appear, surely</span>
</eng-popup class="ng-scope" jQuery110209261664696867675="8"><//eng-popup>
So I have a few questions:
Why does the eng-popup directive console.log call NOT get triggered?
Why does the content in the eng-popup tag end up in a span?
And most mysteriously of all, why does the eng-popup tag start with an END tag and end with a tag with 2 slashes?
Finally, what am I messing up, to make this all happen?
EDIT
As requested, here's a Plunker. It looks like the span and end-tag issues are not happening in this simplified version, but the eng-popup directive is still not being triggered:
https://plnkr.co/edit/mVa6Mye5besJAtWihMWF?p=preview
RE-EDIT
Just in case this is still solvable, here's the latest Plunkr I've been able to put together. It's not working, which it at least has in common with our real project. Not sure if it's the same problem though.
https://plnkr.co/edit/sJoYnYjqx9ZGIH0KhObC
I've modified your example a bit (very little), and it seems to work OK?
https://plnkr.co/edit/3NlHNDOCBVRktk3ZcsnV?p=preview
What i've done is, remove the ui-router requirement, since you had not added the script reference. After that, it works. So, are you including ui-router in your project properly?
So a colleague has pointed out it 'works' in Chrome. Turns out it's an IE8 issue. Who would have thought...
(The whole reason we're using 1.2.29 is because many of our users will be accessing it via IE8, but it looks like custom directives are off the table).
I have been trying to use transclusion to create a directive which makes two copies of it's contents and appends them inside the original template. I have failed in my attempts to modify the two copies before they slotted back into the DOM and I think it's because I have misunderstood how transclusion works.
I have another question here which I don't think is going to be answered because I think the premise may be wrong.
Transclude function needs to modify clone properly
I need to take a new approach to this and I was wondering if it would be sensible to ditch transclusion and get my hands dirty inside a compile function.
My new question is, can you take the contents of "elem", make a couple of copies of it using JQlite then compile them manually against the directive's parent scope and add them back into the original template?
So, if my toolbar directive is used like this, where the contents of the toolbar tag can be any HTML the user wants...
<div ng-controller="myController">
<toolbar>
<form name="formName" submit="myController.submit()">
<div>
... some controls...
</div>
</form>
</toolbar>
</div>
And the template for the directive is this....
<toolbar-inner>
<div class="toolbar">
<div transclude-main></div>
</div>
<div class="overflow">
<div transclude-overflow></div>
</div>
</toolbar-inner>
My compile function of the toolbar directive needs to take a copy of the contents of the element, clone it, rename any forms so that we don't have duplicate form names then compile one copy against the parent controller and slot it into the main slot then do the same with a second copy and slot it into the overflow slot.
The key things is that at the end of it I should have a single directive with two copies of it's contents and my controller should have two forms on it - myController.formName and myController.formName2
Please let me know if I haven't explained something correctly here. I'm pretty sure the solution here should not involve transclusion, hence posting the second question. It is not a duplicate.
If I can explain anything in further detail please ask.
Many thanks.
EDIT:
I have tried to do this in the following Plunkr
https://plnkr.co/edit/eUIdaPiOIISDdXGLBTKJ?p=preview
I have a few problems with this:
A) I am getting a JS error in the console - TypeError: Cannot read property 'childNodes' of undefined
B) I was assuming I could just mess with the template in the pre-compile phase and replace the contents of the directive with new HTML consisting of a new template merged with two copies of the original contents. I can see though that I have to compile them against the $parent scope because my directive uses an isolate scope (although not strictly necessary in this cut down example)
C) I get another error when replacing the original contents of the directive element anyway.
I think I am half way there and hopefully this plunk shows what I an prying to achieve, but i have run out of knowledge.
For completeness, here is the plunk where I tried to do it with transclusion, which didn't work because the transcluded contents are already compiled by the time I started messing with them in the transclude function.
https://plnkr.co/edit/XE7REjJRShw43cpfJCh2?p=preview
You can see the full conversation about this in my previous question:
Transclude function needs to modify clone properly
I got your transcluded example working. See here.
I had to call the below to get it working.
$compile(clone)(scope.$parent);
For the ngTransclude:orphan problem, in this version by compiling just the form elements it works when the child transclude is outside of the form.
This plunker was prior to Angular 1.5 which introduce Tranclusion.
link: function(scope, element) {
if (!element.find('ng-transclude').children().length) {
element.find('button').append('<b style="color: red">Button1</b>');
}
}
Plunker
I have created a directive in angularJS as <print-note print-data='printData' id='notePrintDiv'></print-note> this directive will take some object and create a formatted html for printing, but I don't want to show the formatted html in my main html I want the formatted html for printout. so I was hopping if there is any way in angularJS where in I just create the element and pass the scope object to it like angular.element("<print-note print-data='printData' id='notePrintDiv'></print-note>"); or any other way and get its innerHTML.
P.S. I can also achieve the same with making outer html of directive template as display: none but that seems to be a bit hacky way.
The $compile service should be able to do this. Inject it in your controller where you have access to the scope (with printData).
var element = $compile('<print-note print-data="printData" id="notePrintDiv"></print-note>')($scope);
I had to achieve samething in a AngularJS app - I kept the directive for Print-Button and I kept id for the div or HTML block that was targeted to be printed and kept that div/html ng-show=false. I think it's one of the right way to get the required task done.
Introduction
For the project I am working on, I am trying to tackle a particular problem in the 'angular way', however I think I must be missing something because no matter what I try I continue to reach brick wall.
The crux of this issue is I am dynamically loading data from a backend that describes different components that are visible to the user. That's not the issue itself, but rather the issue of the particular & proper 'angular' way to turn a list of 'models' describing the components into actually rendered HTML.
Problem
What I am trying to create is basically the following:
Start off with a parent directive that uses ng-repeat for a scoped list called "models", which contains zero or more "components":
<parent-directive ng-repeat="model in models" model="model"></parent-directive>
The ng-repeat directive creates N copies of that original directive with different 'model' arguments (for each object in the $scope.models array).
// this is just for demonstrative purposes, it obviously looks different in source
<parent-directive model="child1"></parent-directive>
<parent-directive model="child2"></parent-directive>
<parent-directive model="child3"></parent-directive>
issue! => The parentdirective gets transformed into a specific child directive depending on data (in this case, called 'type') contained within the javascript object:
<parent-directive model="..."></parent-directive>
turns into
<child-directive-one model="..."></child-directive-one>
or
<child-directive-two model="..."></child-directive-two>
dependent on what the value 'model.type' is.
The child directive then renders into it's own custom HTML (outside the scope of this problem) using data passed to it. If we continued the example from above, that HTML should render into the following (hopefully):
<child-directive-one model="child1"></child-directive-one>
<child-directive-one model="child2"></child-directive-one>
<child-directive-two model="child3"></child-directive-two>'
Followed by (and this is outside the scope of the issue but just to see it through to the end) each directive rendering into its own HTML:
<div>in childDirectiveOne, text is: This is text contained inside child1</div>
<div>in childDirectiveOne, text is: This is text contained inside child2</div>
<div>in childDirectiveTwo, text is: This is text contained inside child3</div>
Source
I've been trying lots of different variations of things to try and get it to work (involving the link function, using $compile, etc), but this source is provided with all of those attempts stripped out. Here's the source I've developed so far:
removed source (was filled with errors). Solution that Scott helped me out with is below:
Conclusion
Thanks for any advice in advance.
Update:
Solution exists here (thanks again to Scott).
I'm not sure exactly why you can't just have a single directive, however something like the following might work. Instead of repeating the parent directive you just pass in the models and have that directive repeat and create each of the child directives.
<parent-directive the-models="models"></parent-directive>
Parent directive template:
<div ng-repeat="model in models"....>
<child-directive ng-if="YOUR CONDITION"></child-directive>
<child-directive2 ng-if="YOUR CONDITION"></child-directive>
</div>
I need to append a directive's template AFTER an input field. The original input field needs to remain - I can't just create a duplicate of it. My thought for this was to, in the controller, use jQuery to add a DIV after the input field, and add an attribute for the directive to the div. However, in practice, that doesn't work - the div is created and added, but the directive doesn't activate.
The problem, I know, is that the jQuery-added div is not yet recognized by the angularjs controller - it appears AFTER angularjs runs over the controlled html.
I know that part of the problem is that you're not supposed to use jQuery in the controller, but I honestly can't think of another way to do it. Is there some way to cause the angularjs controller to look at this new div?
The original HTML looks like the following.
<input name="generatedString_1234567890">
I run jquery over the page to add a controller to the body. The relevant code looks similar to this:
jQuery('body').attr('ng-controller','MainCtrl');
angular.module('app',['DataTools']);
angular.element(document).ready(function(){
angular.bootstrap(document, ['app']);
});
Inside the angularjs, I run a resource to get a json string containing the relevant changes to the DOM, in terms of attributes that need to get added to the inputs and certain understood flags that require specific coding. The relevant task I'm trying to accomplish looks like this, where $(this) is the input field for the DOM.
jQuery(document).find('input.FormField,select.FormField').each(function(){
// Inside a case statement based on certain flags
var d = $('<div/>');
d.attr("ng-model", 'adors.'+label);
// Relevant code that adds attributes to the div - including the required directives
$(this).hide().after(d);
}
I am trying to create code that looks more like this (VERY GENERIC):
<input name="generatedString_1234567890" ng-hide="true" ng-model="input.uniqueKey">
<div special-input="time" ng-model="input.uniqueKey">
<select ng-repeat="hour in hours">
<select ng-repeat="minute in minutes">
</div>