This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Directive with Transcluded Elements

4:46 Angular 1.x lesson by

Create a wrapWith directive using advanced transclusion techniques.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Create a wrapWith directive using advanced transclusion techniques.

Avatar
Andy

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!

Avatar
Lukas

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.

Avatar
Joel

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

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

In reply to Lukas
Avatar
Lukas

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

In reply to Joel
Avatar
Kasper

This fits nicely into what I'm trying to do at the moment, wrapping an input element with an outer div and appending into that another element (a label for said input).

However, I'm at a loss on how to apply ng-model validations to the transcluded element. I've made it work going the non-transcluding way, simply creating elements in the linking function and rearranging the dom - however, the transclude way is way nicer on the eye and makes more 'sense' to me.

Any comments on how you would adhere to validations?

The input element in question looks something like this:

input type="text" wrap-directive="LabelText" ng-model="someModel.name" required="required"

Cheers, and thanks for the great lessons!

In reply to egghead.io
Avatar
Bashar

how to include angular expression {{ }} with the template and pass the value with another directive, like below:

.
.


{{ formTitle }}

egghead.directive("wrapWith", function ($templateCache) {
return {
transclude: 'element',
scope: {
formTitle: '@'
},
link: function (scope, element, attrs, ctrl, transclude) {
var template = $templateCache.get(attrs.wrapWith);
console.log(template);

var templateElement = angular.element(template);

        console.log(element);

        transclude(scope, function (clone) {
            element.after(templateElement.append(clone));
        })
    }
}

})

I tried but that didn't work,

Please advice,

In reply to egghead.io
Avatar
Alexander

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

Avatar
John

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).

In reply to Alexander
Avatar
Alexander

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.

In reply to John
Avatar
John

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.

In reply to Alexander

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.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?