angular: I think createEmbeddedView() is injecting TemplateRef in wrong location.

I’m submitting a bug report.

Current behavior

When a TemplateRef is passed into another component and then stamped-out using the ViewContainerRef, it appears to be injecting the TemplateRef relative to the original location, not relative to the current ViewContainerRef. I noticed this when trying something on my own. However, when I came across this behavior, I went and looked at the NG2 specs and it seems that your tests actually show the same behavior.

I took your specs and pasted them into a Plunkr: https://plnkr.co/edit/z6LBzUPkcfPHznGz66IW?p=preview

Notice that the TemplateRef (green border) is being rendered next to the ViewContainerRef (red border). I think the tests still pass because they are only checking the text output of the resultant view, not the actual DOM structure. The text output happens to still be correct, in this case, but the hierarchy is wrong … I think.

Expected/desired behavior

I believe that the TemplateRef should be injected into the ViewContainerRef.

Please tell us about your environment:

Running on Chrome Version 50.0.2661.102 (64-bit)

Angular version: 2.0.0-beta.17 (Plunkr) and RC1 (locally) – both show the same behavior.

Language: TypeScript

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 32 (10 by maintainers)

Most upvoted comments

i found more better solution to this and all other NG2-related problems, here is it http://vuejs.org/ and https://laracasts.com/series/learning-vue-step-by-step just as a quick intro and concepts overview

@bennadel so here is my mental model, hope it is not going to get destroyed as the result of the discussion in this issue 😃

A View is a bunch of elements (more complex than this but will do for the discussion here). Each element can have an associated ViewContainer. I think about it as a hook (a physical one) attached to an element on which you can “hang” other views. It also happens that elements can have associated directives as well.

Now when you are injecting a ViewContainerRef in a directive you are saying: I want to potentially attach something to you, give me a container (a hook) associated with an element on which I’m living. So you are injecting a ViewContainer associated with an element, directives are not creating view containers, just interact with them.

Then if you insert something into a ViewContainer those things show up as siblings to an element with which a ViewContainer is associated with. This would makes sense since you could ask for a ViewContainer for an element that don’t have content (ex. <hr>). Didn’t try but I would expect it to work.

Now, I hope that @tbosch will chime-in and put right words on our confused terminology / explanations.

ViewContainerRef sounds to me as a Reference to the View Container. View Container to me sounds like a container that contains the view. Hare is what the official documentation says: “Represents a container where one or more Views can be attached”. Probably the only thing that gives up the actuall behavior is the word attached (other wise it would be inserted) I think that the name of the method and the documentation are very missleading having in mind that dynamic component creation is something very common.

@zoechi I am a bit confused; how can I use a ViewChild when the directive that is receiving the template has no View (it’s just an attribute directive in this case).

Also, to be clear - are you saying the behavior is expected? Or, are you just giving me a work-around?

@bennadel are you saying that createEmbeddedView() is inserting next to location instead of into location?

I was always under the impression that view containers are marking insertion points next to an element with witch a given view container is associated. So for me the behavior in the linked plunk seems good. Still @tbosch could confirm.

In any case I agree that it would probably make sense to update tests to show expected behavior explicitly.

@chubbard Why do you way that ? createComponent ALSO adds it as a sibling you can look here : https://plnkr.co/edit/ViRuxeqQ7CuVGqFbtJ8y?p=preview . It is a s a sibling(!) to the template anchor. ( I’ve added another two wrapping DIVS just for reference).

n an effort to gain deeper knowledge into Angular 2 I wish someone would create an in depth explanation / tutorial on the underlying structure of components, directives and their containers and views.

As per the docs:

The component’s container can contain two kinds of Views. Host Views, created by instantiating a Component via createComponent, and Embedded Views, created by instantiating an Embedded Template via createEmbeddedView. The location of the View Container within the containing View is specified by the Anchor element. Each View Container can have only one Anchor Element and each Anchor Element can only have a single View Container. Root elements of Views attached to this container become siblings of the Anchor Element in the Rendered View. This leaves many open questions, such as: a Host view is referring to the element that Component resides in, and an Embedded view is referring to the component’s template itself?

Is that true for both cases when created manually (via createComponent) as well as when created declaratively via in another hosting component (parent)?

Is that the case for Directives as well which don’t have a template (thus no view)? And how all this works in a Shadow dom environment (browser actually support a component host) vs an emulated environment?

Angular 2 does do a lot of magic and in an effort to become an expert I wish to better understand, (maybe via a visual diagram) the entire relationship of: ViewContainerRef, Host views, Templates, Embedded Template, ViewChild, ViewContainer and their hierarchy of components and directives.

I consider myself extremely well versed in Angular2 (delivered 2 huge project already) but still feel I have holes in my understand of the underline internal workings.

Sure you don’t need to know how a car works to drive one, but you handle it much better if you do,

I agree with @sergey-koretsky we really need a low level, well put tutorial from the Angular team so we can better understand all the relationships!

Thanks as always,

Sean

@pkozlowski-opensource So, going back to the StackOverflow item you posted earlier, it sounds like there’s no way to dynamically render an injected templateRef without creating an “inner DOM element” to give you something to query for the ViewContainerRef.

It would be nice if I we could use an HTML comment as a ref:

template: "<!-- #anchor -->"

… and then query for that:

queries: {
    anchor: new ViewChild( "anchor", { read: ViewContainerRef } )
}

Anyway, I appreciate all of the insight. For what its worth, as the first time thinking about this stuff, it’s not quite so simple 😃 Especially coming from Angular 1.x where, I could transclude content and just .append() it to the host element. But, I know the two systems are very different. Thank you for taking the time to help. I really appreciate it.

But, from what you’re saying, I think, the ViewContainerRef is never the host element - it’s always the container that the host element is also in?

I would rather say it like this: “A ViewContainer injected into a given directive (regardless if this is a component or a simple directive) is the one associated with an element on which a given directive matched”.

Think about like this: elements has pointers to ViewContainers. If you insert something into a ViewContainer it will be added as a sibiling of a given element (remember you might have elements without content). A ViewContainer can be associated with a given element even if there is no directive on this element. So in this respect presence or not of a directive (simple directive or a component) doesn’t change anything and somehow is not relevant.

I hope that we can close this one now that Tobias confirmed that it works as intended / explained here. But we can surely continue the discussion - it is important for me that the mental model is clear to everyone as in the end this quite simple system.