Prologue: If you're looking for a deep technical breakdown, this isn't the place. My goal is to explain things in a way that even my grandmother could grasp—at least a little. This post reflects my personal understanding and mental model of the topic, intentionally keeping things non-technical as much as I can. That said, I strive for accuracy, so if I’ve got something wrong, I encourage you to correct me in the comments, and I’ll update the post. Learning is, after all, a collaborative process 🚀
One of the things that required a bit of digging for me to grasp is the concept of ViewRef
and ViewContainerRef
.
Sure, it sounds straightforward on the surface: a ViewRef
is obviously a reference to a view, a ViewContainerRef
is a reference to a view container.
But what really is a view?
According to the Angular documentation, a view is:
The smallest unit of the UI that represents a portion of the screen rendered.
So, in my mind, I ask myself:
"Okay, so... if I have a simple <p></p>
element, is that a view?"
I mean, it checks all the boxes:
- It's a "unit of the UI" ✅
- That "represents a portion of the screen rendered" ✅
However, this is not the case. A <p></p>
element is not a view. Why?
What makes a view a "view"? 🤔
Think about the example above: if you had a component with a hundred <p></p>
elements, and each one of those <p></p>
elements created a view, you would have 100 views. That sure sounds like a lot to manage, even for a computer.
As such, in my opinion, saying a view is "The smallest unit of the UI that represents a portion of the screen rendered" is misleading.
I think a better way of describing a view is:
Any bit of your template that can change
What do you mean by "change"? 🔁
Consider this simple component that represents a list of fruits:
@Component({
selector: 'app-fruit-list',
templateUrl: './fruit-list.component.html'
})
export class FruitListComponent {}
<!-- fruit-list.component.html -->
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Pears</li>
</ul>
The HTML markup above is static. There's no Angular stuff there that can either add or delete a list item or hide the list altogether. You are stuck eating apples, bananas and pears.
Now imagine we change our component to this:
@Component({
selector: 'app-fruit-list',
templateUrl: './fruit-list.component.html'
})
export class FruitListComponent {
public fruits = []
protected fruitsService = inject(FruitsService)
ngOnInit(){
this.fruits = this.fruitsService.fetchFruits()
}
}
<ul *ngFor="let fruit of fruits">
<li>{{fruit}}</li>
</ul>
Now our list of fruits can have any number of items. Maybe our fruit supplier only has 10 different fruits available. Or maybe it has a million different fruits. Maybe in the morning apples are available but sold out in the afternoon. We don't know. And as it turns out Angular doesn't know either.
Because Angular doesn't know how many fruits will exist, it needs to add or remove <li>
elements dynamically.
Therefore, Angular will now consider our list of fruits a 'view'—or, in 'grandma terms,' something that can change at any time.
Note: The example above uses ngFor
. However other things like <ng-template>
or ngIf
have the same power to create "views". If it makes the UI change dynamically, it creates a "view".
A "view" inside the box 📦
You'll notice that in the previous paragraph I always referred to the bit of HTML that can change dynamically as a "view". Notice the quotes here - "view" - as they are absolutely critical.
When you tell Angular something will have to be updated dynamically, Angular under the hood wraps that HTML element in a ViewContainerRef
.
The way I think about ViewContainerRef
is "a box where things are can be added, removed or moved around". What "things", you ask ? More on that in a second.
For now let's focus on counting how many "boxes" /ViewContainerRef
s we have and where they come from.
Optional side note: open your editor and navigate to the ViewContainerRef
class definition. Have a look at the methods there. You will see stuff like CreateComponent
, CreateEmbeddedView
, get
, move
or remove
. That looks a lot like adding or removing things from a box, if you ask me.
Stacking up the boxes 📦📦
Every time Angular creates a component, you get at least one box / ViewContainerRef
. This will be the ViewContainerRef
for the whole of your component. Let's go back to our list of fruits component and draw some boxes and arrows:
The top level ViewContainerRef is a "box" that holds all of the Fruit List component HTML
Notice I said "at least" one ViewContainerRef
. Why? Because if you add things to your template that change dynamically, then you can have many ViewContainerRef
s. In fact, in this example we have 2 ViewContainerRef
s: the top level one and the one generated by the ngIf
in the ul
element. If we draw more boxes and arrows, it looks like this:
Another ViewContainerRef that was there all along
Putting things in the box 📦 ⬅
Now that we have a clue of what a ViewContainerRef
is and roughly what it does, let's talk about ViewRef
.
According to the Angular documentation a ViewRef
:
Represents an Angular view
That sounds reasonable and straightforward enough. Upon reading this, I immediately jumped to the following (wrong) conclusion:
The
ViewRef
is the bit of HTML inside of theViewContainerRef
. So there must be a way for me to retrieve it.
With this in mind, I tried a few things—ranging from utterly stupid to moderately stupid—but alas, to no avail.
// this is stupid, ViewRef is not even injectable 🙃
public viewReference = inject(ViewRef)
public viewReference = this.viewContainerRef["looking for something here that returns the viewRef but such thing doesn't exist"]
So where is this ViewRef
that "represents an Angular view"? I can't seem to find it anywhere!
As it turns out, there are three very important words that, I think, were left out of that definition, and they make a big difference:
Represents an Angular view you create yourself
A ViewRef
is not something that exists in the box /ViewContainerRef
, it is what you get when you put something in the box. It's a reference pointing to the view you just created.
The ViewRef at the bottom of the box 🎁
As we have discussed before a ViewContainerRef
is capable, among other things, of creating components dynamically (or in our grandma terms "putting things in the box").
There are a couple of methods in a ViewContainerRef
that allow us to do that:
// class stuff
protected myViewContainerRef = inject(ViewContainerRef)
//more class stuff
someMethod(){
const aReferenceToAComponent = this.myViewContainerRef().createComponent(SimpleComponent) //is of type ComponentRef<SimpleComponent>
const aReferenceToAnEmbeddedView = this.myViewContainerRef().createEmbeddedView(this.someTemplate()) //is of type EmbeddedViewRef<any>
}
You will notice that none of those 2 methods returns a ViewRef
. How come? We should have a ViewRef
right? If I just put something in the box I want a reference to it.
Take a deep breath; don't sweat it. It is there—just a bit hidden.
ComponentRef
has a hostView
property. And that hostView
property is, in fact, a ViewRef
. So there's one ✅
EmbeddedViewRef
extends from ViewRef
, so it is, in fact, a ViewRef
. So there's two ✅
We have found our ViewRef
s 🎉
You will notice that you can do a lot less with ViewRef
when compared with ViewContainerRef
. With ViewRef
you can do stuff that relates to a view (like marking it for change detection or destroy it), whereas with ViewContainerRef
you can do stuff that relates to the place where the view lives (like adding new components or moving existing components around).
Wrapping it up
Hopefully all of these non-sense with boxes and fruits helps you understand the difference between ViewRef
and ViewContainerRef
. If you just remember one thing from this post remember the following:
One is the box; the other is what you get when you put something in the box
Happy coding!
Top comments (1)
Nice. Underlines Angular's complexity and challenging learning curve. I argue that even the most resilient grandma would find it impossible to grasp, and switch back to reading some bun recipe...