Here is why you will not find components inside Angular

Post Editor

In this article you'll learn how Angular represents components and directives under the hood and will become familiar with the notion of a directive definition.

7 min read
1 comment
post

Here is why you will not find components inside Angular

In this article you'll learn how Angular represents components and directives under the hood and will become familiar with the notion of a directive definition.

post
post
7 min read
1 comment
1 comment

From the moment I started using Angular I was intrigued by the question what’s the difference between components and directives. It’s a nagging question especially for those who come from the AngularJS world since we only had directives that we often used as components. If you search the web for the explanation you will see many phrases like these:

Components are just directives with a content defined in a template…Angular components are a subset of directives. Unlike directives, components always have…
Components are high-order directives with templates and serve as …

The claims seem to be true since when I looked at the factories generated for the components I didn’t find component definitions there! And you will not find either. Only directives…

And I haven’t found an why explanation because to provide it one must have a good understanding of how Angular works inside. If this question bugged you for a while then this article is for you. It is intended to divulge the mystery. But get ready for some hardcore stuff ?.

In essence, this article explains how Angular represents components and directives under the hood and introduces new view node definition — directive definition.

Good old-fashioned view
Link to this section

If you’ve read some of my previous articles, particularly how Angular updates DOM, you probably already know that under the hood Angular application is a tree of views. Each view is generated from the factory and is comprised of view nodes of different types each with a specific functionality. In the mentioned article (it will greatly help to understand this article) I’ve shown two most simple node types — element definition and text definition. The former is created for all element DOM nodes and the latter is generated for all text nodes.

So if you have a template like this:

<>Copy
<div><h1>Hello {{name}}</h1></div>

the compiler will generate the view definition with two element nodes for div and h1 DOM elements and one text node for the Hello {{name}} part. These are very important nodes as without them we wouldn’t be able to see anything on the screen. But since the component composition pattern dictates that we should be able to nest components there must be another type of a view node for embedded components. To figure out what those special nodes are let’s first see what the component is comprised of. A component is essentially a DOM element with an attached behavior implemented in the component class. Let’s start with the DOM element.

Custom DOM elements
Link to this section

You probably know that you can create a new HTML tag and use it in your html. For example, if you don’t use any framework and you insert the following into your html <a-comp></a-comp> and then query the DOM node and check its type you will see that it’s a perfectly valid DOM element:

<>Copy
const element = document.querySelector('a-comp'); element.nodeType === Node.ELEMENT_NODE; // true

This a-comp element will be created by a browser using the HTMLUnknownElement interface that inherits from HTMLElement interface, but without implementing any additional properties or methods. You will be able to style it using CSS and also attach event listeners to common events like click. As I said, perfectly valid html element.

You can create a much upgraded version of this element if you turn it into custom element. You would need to create a class for it and register it using provided API:

<>Copy
class AComponent extends HTMLElement {...} window.customElements.define('a-comp', AComponent);

Does it seem similar to something you’ve been doing for a while?

Right, this is very similar to what we’re doing in Angular when defining a component. Actually, Angular follows the web components spec pretty closely but simplifies many things for us so we don’t have to create shadow root yourself and attach it to the host element. However, the components that we create in Angular are not registered as custom elements and are processed by the framework in a very specific way. If you’re curios how you can create a component without any framework read Custom Elements v1: Reusable Web Components.

Okay, so we’ve seen that we can create any HTML tag and use it in a template. It should come as no surprise then that if we use it in Angular’s component template the framework will create an element definition for this tag:

<>Copy
function View_AppComponent_0(_l) { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, 'a-comp', [], ...) ]) }

However you would have to indicate to Angular that you’re using custom element by adding schemas: [CUSTOM_ELEMENTS_SCHEMA] to the module or component decorator properties or otherwise Angular compiler will generate an error:

<>Copy
'a-comp' is not a known element: 1. If 'a-comp' is an Angular component, then ... 2. If 'a-comp' is a Web Component then add...

So, we have an element but we’re missing the class. Is there anything in Angular that has class besides a component? Sure there is — a directive! Let’s add a directive and see what we end up with.

Directive definition
Link to this section

You probably know that each directive has a selector which can be used to target a specific DOM element. Most of the directives use attribute selectors but element selectors are also perfectly fine. In fact, Angular form directive uses element selector form to implicitly attach specific behavior to html forms.

So we can create a directive that does nothing and apply it to our custom element. Let’s do that and see what the view definition will look like:

<>Copy
@Directive({selector: 'a-comp'}) export class ADirective {}

And now let’s check the factory:

<>Copy
function View_AppComponent_0(_l) { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, 'a-comp', [], ...), jit_directiveDef4(16384, null, 0, jit_ADirective5, [],...) ], null, null); }

All right, so now the compiler added the new jit_directiveDef4 node to the view definition alongside element definition. It also set childCount parameter for the element definition to 1 because all directives applied to an element are considered children of that element.

The newly added directive definition is a pretty simple node definition that is generated by the directiveDef function. It takes the following parameters:

<>Copy
+----------------+-------------------------------------------+ | Name | Description | +----------------+-------------------------------------------+ | matchedQueries | used when querying child nodes | | childCount | specifies how many children | | | the current element have | | ctor | reference to the component or | | | directive constructor | | deps | an array of constructor dependencies | | props | an array of input property bindings | | outputs | an array of output property bindings | +----------------+-------------------------------------------+

For the purposes of this article we’re only interested in the ctor parameter. This is just a reference to the class ADirective that we defined for the directive. When Angular will be creating directive instances (I’ll be writing about that soon, so make sure to follow me ?) it will instantiate a directive class here. It will then store it as provider data on the view node.

Okay, so our experiments show that a component is just an element and directive definition. Is it just it? As you probably know it’s always not that simple with Angular.

Representing a component
Link to this section

I’ve shown above how we can emulate a component by creating a custom HTML element and a directive that targets this element. Let’s now define a real component and compare the generated factory with the one we got in our experiments:

<>Copy
@Component({ selector: 'a-comp', template: '<span>I am A component</span>' }) export class AComponent {}

Ready to compare now? Here is the generated factory:

<>Copy
function View_AppComponent_0() { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, 'a-comp', [], ... jit_View_AComponent_04, jit__object_Object_5), jit_directiveDef6(49152, null, 0, jit_AComponent7, [], ...)

Okay, so we just confirmed what followed from the previous chapters. Indeed, Angular represents a component as two view nodes — an element and a directive definition. But when using a real component there are some differences in the parameters list to the element and the directive definition nodes. Let’s explore them.

Node flags
Link to this section

Node flags is the first parameter to all node definitions. It is actually a bitmask of node flags that contain specific node information used by the framework mostly during change detection cycle. And this number is different in both cases: 16384 — for the simple directive and 49152 for the component directive. To understand what flags have been set by the compiler let’s simply convert the numbers into binary form:

<>Copy
16384 = 100000000000000 // 15th bit set 49152 = 1100000000000000 // 15th and 16th bit set

If you’re curios about how the conversion takes place read The simple math behind decimal-binary conversion algorithms. So, for the simple directive the compiler sets only 15-th bit which is done like this in Angular sources:

<>Copy
TypeDirective = 1 << 14

and for the component node both 15-th and 16-th bits are set, which is

<>Copy
TypeDirective = 1 << 14 Component = 1 << 15

Now it should be clear why the numbers differ. The node generated for a directive is marked as TypeDirective node and the node generated for the component directive is additionally marked as Component.

View definition resolver
Link to this section

Since a-comp is a component now with the following simple template:

<>Copy
<span>I am A component</span>

the compiler generates a factory for it with the its own view definition and view nodes:

<>Copy
function View_AComponent_0(_l) { return jit_viewDef1(0, [ jit_elementDef2(0, null, null, 1, 'span', [], ...), jit_textDef3(null, ['I am A component'])

Angular is a tree of views so parent view definition need to have a reference to the child views definitions. The child views definitions are stored on the element nodes generated for components. In our case, the element definition node generated for the a-comp will hold the view for a-comp. And the jit_View_AComponent_04 parameter that a-comp element node receives is a reference to the proxy class that will resolve the factory that will create a view definition. Each view definition is created only once and then stored on the DEFINITION_CACHE. This view definition is then used when Angular creates view instance.

Component renderer type
Link to this section

Angular uses several DOM renders depending on the ViewEncapsulation mode specified in the component decorator:

The renderer for a component is created by the DomRendererFactory2 class. The parameter componentRendererType that is passed inside the definition — in our case it’s jit__object_Object_5 — is basically a descriptor of the renderer that needs to be created for the component. The most important information it holds is the view encapsulation mode and styles that need to be applied to the component view:

<>Copy
{ styles:[["h1[_ngcontent-%COMP%] {color: green}"]], encapsulation:0 }

If you define any styles for your component the compiler automatically sets encapsulation mode for your component to ViewEncapsulation.Emulated. Or you can specify the mode explicitly with the encapsulation component decorator property. If you don’t set any styles and don’t specify encapsulation mode for your component the descriptor is defined as ViewEncapsulation.Emulated and is in effect ignored. The component with such a descriptor will be using parent component renderer.

Child directives
Link to this section

Now, the last piece of information remains is what will be generated if we apply a directive to a component in the template like this:

<>Copy
<a-comp adir></a-comp>

We already know that when generating factory for the AComponent the compiler will create an element definition for the a-comp HTML element and a directive definition for the AComponent class. But since the compiler generates directive definition node for each directive the factory for the above template will look like this:

<>Copy
function View_AppComponent_0() { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 2, 'a-comp', [], ... jit_View_AComponent_04, jit__object_Object_5), jit_directiveDef6(49152, null, 0, jit_AComponent7, [], ...) jit_directiveDef6(16384, null, 0, jit_ADirective8, [], ...)

And it contains nothing we haven’t seen before. Just one more directive definition was added and the child count for the element was increased to 2.

Comments (1)

authorpheetah
4 June 2021

Wow. I've read couple of your articles related to this topic and these made me understand Angular deeper for real. And now, I can estimate how ViewChild decorator works and why we're using it in Angular. It would be great if we had an article explains in depth the differences in implementation of jQuery and ViewChild in Angular. Keep up the good work!

authormaxkoretskyi
6 June 2021

It would be great if we had an article explains in depth the differences in implementation of jQuery and ViewChild in Angular

what particularly would you find interesting or confusing in this comparison?

Share

About the author

author_image

Max is a self-taught software engineer that believes in fundamental knowledge and hardcore learning. He’s the founder of inDepth.dev community and one of the top users on StackOverflow (70k rep).

author_image

About the author

Max Koretskyi

Max is a self-taught software engineer that believes in fundamental knowledge and hardcore learning. He’s the founder of inDepth.dev community and one of the top users on StackOverflow (70k rep).

About the author

author_image

Max is a self-taught software engineer that believes in fundamental knowledge and hardcore learning. He’s the founder of inDepth.dev community and one of the top users on StackOverflow (70k rep).

Looking for a JS job?
Job logo
Senior Frontend Software Engineer (Angular)

Argument

Ukraine
Remote
$54k - $72k
Job logo
Front-End Web Software Engineer (Angular12 + ASP.NET)

MWS Technology

Ukraine
Remote
$36k - $60k
Job logo
Angular Software Developer

Salamander Technologies

America
Remote
$80k - $95k
Job logo
Senior Front End Developer - Angular

triValence

United States
Remote
$125k - $160k
More jobs

Featured articles