Summary: In this article, you will learn how to extend the browser and build your own custom HTML Input tag, as a means of learning the main functionality provided by Angular Components and Directives.
This post is part of the ongoing Angular for Beginners series, here is the complete series:
-
Angular For Beginners Guide - Getting Started (Setup Environment)
-
Why a Single Page Application, What are the Benefits? What is a SPA?
Angular Components and Directives
Now that we have a development environment in place, and that we are familiar with the benefits of the MVC approach, let's start looking into the main functionality available in Angular Core.
This is the part of the platform from which all other modules are built upon, and you are probably already familiar with many of the features of Angular Core as well as many of its most well-known decorators like @Component
, but:
Did you ever wondered what is the overarching theme or goal that all those combined features are trying to achieve? What is the main functionality of Angular Core?
The main intention of the view layer features of Angular Core
While looking into the features of Angular Core one by one and in isolation, we might end up losing sight of the larger picture of what those features are trying to make possible.
Altogether, the view layer features of Angular Core provide us with all the tools necessary to essentially become the closest possible thing to a browser developer:
With Angular Core we can essentially extend the browser native functionality and create our own HTML tags and attributes, and associate some behavior to them.
That, in a nutshell, is what Angular Core allows us to do, and most of the view layer features of Angular Core revolve around that single idea.
We will see that Angular Core (the view layer part) can be compared to essentially a missing browser extensibility toolkit - we are going to learn why and we will use it in a moment.
Let's start first by learning a core framework notion: let's learn what a Directive is.
What is a Directive?
The notion of a directive is not something unique to Angular, although AngularJs did both give a name to the concept and popularized the term.
But something similar to the concept of Directive has actually always been there since the beginning of browsers and the web!
To understand the notion of Directive, let's have a look at this plain input field:
This is actually a good example of a browser built-in directive. It does look on first sight like a leaf element of our HTML document:
As we can see it has an XML-like API that allows us to configure the element. It's not valid XML because there is no closing tag, but it's close.
Looking deeper into a plain HTML input
But there is more to this simple input box than meets the eye, at least in the case of the Chrome browser. To see that, let's go to the Chrome settings panel (Ctrl + Shift + I, and then click Settings), and then let's turn on the "Show User Agent Shadow DOM".
We will get back to what these terms mean, right now all we need to know is that this option allows us to see further inside our web page, including inside a plain HTML input.
That's right, at least in this case an HTML input is really not a leaf element ! Lets have a look at its internal implementation using the Dev Tools:
So what do we have here, even more HTML 😊 ? HTML inside a leaf HTML tag like the input tag? How does this work?
The Shadow DOM and built-in Browser Directives
It looks like the input component is not a native operating system input: instead, it looks like its internally composed of some HTML, and even some CSS too! There is this internal stylesheet applied inside this element:
The Internal Implementation details of An HTML Input
It looks like an HTML input with a placeholder is internally implemented in the browser as a Div with a border, and a special area that is detecting keyboard strokes and reflecting the changes on the input box.
The placeholder is literally just a div with some text inside it, that is placed inside the border of the input.
It looks like the browser internally composes HTML tags of other more primitive HTML tags and styles, like DIVs etc. until it reaches the very native rendering elements of the native operating system platform where its running.
These HTML tags were not even visible to the outside world until recently because tools did not show these elements. These elements are said to be part of the Shadow DOM of the input component.
What is the Shadow DOM?
These HTML elements are a hidden document sub-tree that can exist inside of what it looks like a leaf component, such as an HTML input.
Notice that the styles that run inside this input are isolated: these styles are not visible in the main HTML of the page, they are only visible inside the component.
So it looks like the browser has this built-in feature that seems very useful for creating new HTML elements from existing ones. Using it we can:
- define a public XML-like API to an element of the page
- define the look and feel of the element using HTML
- we can add behavior to that new element
- we can even style it while keeping those styles isolated
This combined specification of a look and feel, an API and a behavior is a useful concept, so let's give it a name: let's call it a Directive
.
Although the notion of Directive is usually associated with the Angular framework, we can see it can also be seen as a generic browser functionality that has existed for a long time as an internal browser implementation mechanism.
A Missing Browser Extensibility Toolkit
Wouldn't it be great if we could use this browser mechanism to create our own custom designed HTML tags?
Sure, but the problem is that browsers don't allow us to use directly this internal functionality!
But the good news is that we can still get the same equivalent functionality: Angular Core provides us with a Javascript based mechanism that essentially allows us to do almost anything that we could do with the internal browser Directive mechanism ourselves.
To show that that is indeed the case, let's implement our own HTML input directive with a placeholder, and see how it maps to the native one.
We are actually going to use a very similar implementation to what we saw above in the browser shadow DOM - it's going to be just a div with a border, plus some text and some keyboard behavior.
So let's get started implementing our first Angular Directive.
Implementing our Own HTML Input
Let's start by defining the API of our Directive. We would like it to look like this on the page:
As we can see, this looks just like an HTML input, except that it has a closing tag and a different name.
This new directive will be a very particular type of HTML Directive: more than behavior, it has also an associated look and feel. We will call to that type of Directive a Component - it's simply a Directive with a template.
Let's then start implementing our input box, we will see that the end result will be hard to tell apart visually from a normal browser input.
Writing Our First Custom HTML Element, an Input Box
So let's implement this in one go, and then break it down step-by-step. This is our first Angular component:
Breaking down our first component Step-by-Step
This small sample of code actually has all the elements of a browser Directive:
- if defines an API
- it defines a look and feel
- it defines a behavior
If you try this out you would be surprised that this is actually quite close to what a real input looks like, except for the blinking cursor, which could also be simulated with a "|" character and an animation.
If we now break down how the my-input
component is implemented, we will see that it's quite close that what we see in the browser shadow DOM for a normal input.
Defining the public API of a Directive
As we can see, the code above is simply a plain ES6 class with a decorator attached to it. Let's isolate the part of the code that defines the public API of this component:
As we can see, what tells Angular that this class contains the functional specification for a component is the presence of the @Component()
decorator.
One of the properties of the decorator is called selector
, and it contains a CSS selector that describes to which custom HTML elements should this component be applied.
In this case, this means that to all the page elements named my-input
we will be applying both this look and feel and this behavior.
Also, we can see that component has an input property named placeholder
, which is a text string. And the combination of the name of the tag my-input
and the name of its input properties defines the public API of this new HTML element that we have built.
Let's now have a look at how the look at the look and feel of our component, defined inside the template.
Defining the look and feel of the component
The component is implemented as a focusable DIV (due to the presence of the tabindex
property), with a my-input
CSS class applied to it:
Notice the special syntax {{}}
inside the template, this is a template expression, meaning that the content of the expression will be evaluated against the component.
So this mean that the expression {{placeholder}}
is evaluated as this.placeholder
, where this
is pointing to a particular instance of my-input
.
In our expression, we are using the ternary operator ?
to display the placeholder text, in case the value
variable is not populated. Notice that the value
variable is not part of the public API of the component, but instead its an internal implementation detail of the component.
We can see that besides the component template, we are also defining some styles associated with the component. These styles by default work in a way that is very close to the Shadow DOM mechanism.
Angular Style Isolation
The styles defined inside this component are also isolated from the main page, just like the webkit-input-placeholder
that we saw while looking at the shadow DOM of a plain HTML input.
This is a very useful feature that the browser provides, and it means that we can define a component and ensure that it will always look the same independently of the page where the component is being used.
Although by default Angular does not use the same exact mechanism as the one present in the Shadow DOM of the plain input component, it uses out of the box something close.
Angular will transparently increase the specificity of the styles so that they take precedence over external styles present on the page, by adding an attribute selector to all the component styles.
The way that this works is that Angular will start by adding a given property to all the HTML elements of the my-input
template at runtime:
Notice the _ngcontent-c1
property, this was automatically added by Angular. This property was then used to transparently bump the specificity of the component styles at runtime:
This means that these styles will most likely and for all practical purposes take precedence over external stylesheets of the page, ensuring that the component will look the same across different pages where it's used.
But notice that this level of isolation is not 100% guaranteed, meaning that if for example there is an external style that for example is using !important
the component style would still be overridden.
Now that we have seen how the component template is defined and bound to the class the specifies an API and behavior, let's have a look at the implementation at how we are capturing keyboard strokes and reflecting the changes on the input box.
Defining the keyboard behavior of the component
In the template, we are detecting the keyup
event inside the div by using the (keyup)
event syntax, which is binding the occurrence of the native browser keyup
event to a component method called onKeyUp
.
So this is another goal of the component class: it will not only define the public API of our component and provide a place to store variables used in the component's template, but it also contains a definition of how the component should react to the user input.
In our case, we defined the keyboard handling behavior of our component in this method:
Let's then break down how the keyboard behavior works:
- If the key pressed is backspace, we are going to remove a character from the
value
string - If another key is pressed that is not Ctrl, Cmd or other special keys, we are going to add the key that was pressed to the
value
string
Notice that we did not interact with the template directly, we just modified the value
variable and Angular detected this change transparently and ensured that the new value was reflected on the screen.
This is a simplified implementation of the input behavior, to illustrate both how the Shadow DOM browser functionality works, and how Angular provides approximately the same functionality.
Conclusions
So in a Nutshell, this is what Angular Core (the view layer part at least) is all about! It's like a missing toolkit for extending the browser with our own HTML elements, with their own API, look and feel and behavior.
Maybe the key design philosophy of this toolkit is that it's designed to extend HTML instead of replacing it, by providing a Javascript implementation of some functionality that until now was only available as an internal browser composition mechanism.
The goal is to define our own HTML elements and use them to compose our application, in a very similar way to what browsers are already doing under the hood.
I hope this post gives you a good initial overview on Angular Components and Directives, and that you enjoyed it!
This post is part of the ongoing Angular for Beginners series, here is the complete series:
-
Angular For Beginners Guide - Getting Started (Setup Environment)
-
Why a Single Page Application, What are the Benefits? What is a SPA?
I invite you to subscribe to our newsletter to get notified when more posts like this come out:
If you are just getting started learning Angular, have a look at the Angular for Beginners Course:
Other posts on Angular
If you enjoyed this post, have also a look also at other popular posts that you might find interesting:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Router - Extended Guided Tour, Avoid Common Pitfalls
- Angular Components - The Fundamentals
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven vs Model Driven
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
- How does Angular Change Detection Really Work ?
- Typescript 2 Type Definitions Crash Course - Types and Npm, how are they linked ? @types, Compiler Opt-In Types: When To Use Each and Why ?