Is it possible to compile a modified directive template without using transclusion? - javascript

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

Related

Angular js plunkr not working while accessing through iframe

i am not able to access Model for model in below plunkr, please find plunkr below.please help.
<div ng-controller="PersonCtrl">
<h2>Teens - using external HTML file as template</h2>
<iframe src="teen-external.html"></iframe>
</div>
Here's the Plunkr for any ref.
You have a couple of problems in your example, first the html source was loaded in iframe, which loads it as a simple html and renders it inside the iframe without angular parsing it to do all the mastache interpolation.
To correct this since you've already created a directive that renders the teen-external.html which would allow angular to parse the said html and interpolate the relevant fields. to do this simply use the directive inside your index.html file like.
<teen-internal> </teen-internal>
or
<div teen-internal></div>
Another problem is that you're trying to access a variable/model defined inside the parents $scope which is not possible without going via the $scope.$parent. Even if you do it using the $parent it is considered a really bad practice. To this a bit more elegantly angular provides a sort of model/variable passing from parent to the child, to do this you need to change both the index.html and the teensInternal directive code.
in your index.html
or
<div teens-internal teens="teens"></div>
This sets the teens property on the teensInternal's $scope to the teens from the parent's(PersonCtrl) $scope. Now in your directive code you must define how the binding works for teens property, here you can define it as a read only # also called one way binding (modification done inside teensInternal is not reflected inside the parent controller) or as writable = two way binding (both teensInternal and PersonCtrl share the same object, so the modifications are reflected in both sides) to do this change
scope: {
},
to
scope: {
teens: '=' // or "#" for one way binding
},
This tells the directive that whatever was passed to <teens-internal teens="teens"></teens-internal> through teens="<model's name>" can be used inside the directive's $scope.
Plunkr: plunkr source

angularJS executing directive in memory and geting innerHTML

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.

In AngularJS, how can I nest variable child directive(s) inside a parent directive?

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>

Javascript - Execute HTML/Angular ng-include

I'm using AngularJS to design a small app for searching. I have a div that's currently empty, and after running a function, for it to replace it with a div that has ng-include. The div replaces just fine, but ng-include doesn't load up.
I'm currently running the following in console for testing to see get it running. How would I go about getting this to work? Any help is appreciated!
document.getElementById("resultsDiv").innerHTML = '<div ng-include="" src=" \'sr.html\' "></div>';
read about angularjs $compile function
I don't know why you need to make this JavaScript-call, but its definitely no the 'angular-way'. If you need to conditionally include html, i would recommend using ng-if, ng-hide, ng-show or even ng-switch.
You could do something like this:
// somewhere in your controller
$scope.triggerSomeInclude = true;
// in your template
<div ng-if="triggerSomeInclude">
<div ng-include ="'someFile.html'"></div>
</div>
Another approach would be using a directive. They are the only place, where selecting an element directly could make sense (although even there it usually doesn't to select an element via id). But as I said, it's hard to stay what the best method would be, as I'm not sure what you're trying to achieve.
Although you're not using jQuery, what you're trying to do looks very jQueryesque (awful word) as you're selecting an element directly seemingly totally detached from the $digest and $compile-cycles of angular, so I also would recommend to read this answer:
"Thinking in AngularJS" if I have a jQuery background?
In the end, the method I used was templating example used for ngInclude.
http://docs.angularjs.org/api/ng.directive:ngInclude
Inside my controller, for example, by default it would set the following:
$scope.mainDivTemplate = {url: 'partials/test1.html'}
If I wanted to switch it to something else, I'd call a function that would look a little something like this.
$scope.loadHome = ->
$scope.mainDivTemplate = {url: 'partials/home.html'}
$scope.$apply()

how to show an empty template in custom directive

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

Categories