Directive with Transcluded Elements

John Lindquist
InstructorJohn Lindquist
Share this video with your friends

Social Share Links

Send Tweet
Published 10 years ago
Updated 5 years ago

Create a wrapWith directive using advanced transclusion techniques.

John: If I were to put a directive here, and I said, "Transclude to true," then it would transclude the content inside of it, and yank that out, and put it to wherever I wanted to put it. Now, on the other hand, if you say, "Transclude element," you can actually get the entire element.

You'll see this happen with things like ngRepeat and ngIf, where ngRepeat takes the entire element, and clones it, and shows multiple copies of it, or ngIf, which removes the entire element and then brings it back when it's true and false.

For our example of transclude element, I'm going to take these two different templates that I've set up and I'm going to create a wrapWith directive, and basically I'm going to say, "wrapWith well," or, "wrapWith red," meaning that when I use this wrapWith...it's a very contrived example, but just bear with me. When I say, "wrapWith red," then take this whole thing and put it inside of this red template, or when I say, "wrapWith well," take this whole thing and put it inside of this well template.

I'll set up my directive just by saying, "Egghead directive," and then wrapWith, and I will return something that says, "Transclude element," and you can see right away, if I refresh here, that this disappears and there is a comment right here that says, "wrapWith well," meaning that it's put a placeholder there, saying, "This is our new element. I'm just waiting for you to put something back here at this comment level." If I were to log out this element in the linking function, it would say that it's a comment.

Anyway, for now, going to the linking function, we'll pass in the scope, and the element, and the attributes, and we're actually going to do a couple pretty advanced things here, so one is after the scope, and the element, and the attributes, there is a controller, and the last thing is the transclude function. This used to be in the compile phase. Now it's in the link phase. I'm not exactly sure which version that happened, but I think it's 1.2 and above.

I'm going to need the transclusion function to get the cloned element, and I'm also going to need the template cache to get my templates out of, so all these pieces will fit together really easily, so if I get the template cache, and I say, "Get from the attributes the wrapWith," and I just log this out as our template, so you can check this out real quick.

We'll say, "Template," and when I refresh here, you can see in my log that this is just that template, so I passed in well and it's just this template here. I'll actually have to create an Angular element out of this template so that we have an actual, let's call it a template element to work with, and then the funky stuff happens here, where I say, "Take the transclude, invoke it using the current scope," and we'll pass in a function that has the cloned element.

Now I just want to reiterate that if I were to try to access the element here, so if I log out the actual element right now, I would get a comment, not the cloned transcluded stuff, so in the cloned transcluded stuff, I log this out. Then you can see I have the form, which was my original thing that I yanked out. The entire element that I yanked out, I'm going to put back in.

From here, I'll just say, "Element after," so I say, "Add after that comment the template element," template element, and then I just want to append the clone, which is the form. If you're with me here, we've got the comment, "Add our template," which is adding the form.

From here, if I refresh, you can see that we have our final form in side of the well template, and if I were to switch this over to the red, that if I refresh here, now it just changes all the styles to red, and I could set up other templates to wrap this with.

Again, this transclude element scenario is not a very common scenario, but there are certain scenarios where you'd want to use it. Again, it's mainly used for ngRepeat, and ngIf, and things like that. This is kind of a contrived example, but I'm sure you could think of other things that you could use it for in your projects.

Andy
Andy
~ 10 years ago

This lesson is fantastic. Angular tutorials that tackle anything above a beginner level are very hard to find. As you stated, the use cases for this are not as common as the more basic things, but when you're writing real applications, they do come up. More lessons like this one would be very beneficial to me. I'm sure I'm not alone. Thanks for the great tutorial!

Lukas
Lukas
~ 10 years ago

The code do not work form me (I'am using exactly the same code as you posted). I got error: Error: [jqLite:nosel] http://errors.angularjs.org/1.2.10/jqLite/nosel on the line

var templateElement = angular.element(template)

The problem is that template contains new lines "\n <div style="color: red"></div>\n". Quick fix is angular.element(template.trim()). But it looks like angular bug for me.

Joel Hooks
Joel Hooks
~ 10 years ago

Interesting, I wonder if that is from cut-n-paste?

Thanks for posting the solution. That's an annoying bug :>

Lukas
Lukas
~ 10 years ago

As you ask "I wonder if that is from cut-n-paste?" No definitely not. It's bug in angular 1.2.10. Change version to the 1.2.12 fix it. I guess taht this commit fix it: https://github.com/angular/angular.js/commit/36d37c0e3880c774d20c014ade60d2331beefa15

Alexander
Alexander
~ 10 years ago

Hey John, I have been using the concepts outlined in this video but have stumbled upon something I am not exactly sure how to do.

I am trying to wrap a form input with an ng-model, but the ng-model seems to stop working.

Here is a Plunker of my strategy:

http://plnkr.co/edit/AUbB4BYGrdGr7Mhx0a9c?p=preview

John Lindquist
John Lindquistinstructor
~ 10 years ago

http://plnkr.co/edit/ctJCY2u0XIJxFVsRVWgC?p=preview

A few things:

  • "input" and "ng-model" are both directives which will fight for compile "priority" with your "wrapper" directive. So make sure to bump up your "priority" > 0.
  • You don't need "scope.$parent" in your transclude call because transclusion already uses the appriopriate scope by default.
  • Look where I used "ng-init" in my .html and move that model into a controller. Only using "name" instead of "model.name" will break with your approach (rewatch "the dot" video).
Alexander
Alexander
~ 10 years ago

John, thanks for replying. Pretty awesome that you personally do so.

With this approach, the model always has to be predeclared in the controller through either "ng-init" but more realistically scope.model = {}. Is this a best practice? In the past, I usually did input ng-mode="newUser.firstName"/ without necessarily declaring it in the scope first.

John Lindquist
John Lindquistinstructor
~ 10 years ago

I would put it on the scope first. Letting AngularJS create objects in the HTML is a little too "magical" for me in real applications.

Markdown supported.
Become a member to join the discussionEnroll Today