mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 12:57:44 +00:00
[Blog Post] Angular Templates Dont Work How You Think They Do (#970)
This commit is contained in:
127
content/blog/angular-templates-dont-work-how-you-think/index.md
Normal file
127
content/blog/angular-templates-dont-work-how-you-think/index.md
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
{
|
||||
title: "Angular's Templates Don't Work the Way You Think They Do",
|
||||
description: "Angular templates are mission-critial for components. But how do they work? Using a compiler, yes, but how do they bind to the DOM itself? Read on to find out.",
|
||||
published: '2023-12-27T13:45:00.284Z',
|
||||
authors: ['crutchcorn'],
|
||||
tags: ['angular', 'webdev', 'javascript'],
|
||||
attached: [],
|
||||
license: 'cc-by-nc-sa-4'
|
||||
}
|
||||
---
|
||||
|
||||
When I started learning Angular, I was taught about Angular's components like this:
|
||||
|
||||
> Angular's components have a template that is part of a component and a selector that indicates where the template should go. The way Angular adds this template in is by using a compiler to turn the template into a function that is then executed to generate the DOM nodes.
|
||||
|
||||
My thinking when learning about this went something like this:
|
||||
|
||||
> **This my old wrong internal model of how Angular templates worked:**
|
||||
>
|
||||
> A component compiles its template and assigns it to a `selector`. Whenever Angular sees that `selector`, it runs the template and injects an element with the `selector` to act as the parent.
|
||||
|
||||
But see that's not right.
|
||||
|
||||
# Explaining Angular templates the _right_ way
|
||||
|
||||
Let's take the following example of a `do-nothing` component:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'do-nothing',
|
||||
standalone: true,
|
||||
// Nothing in the template means nothing rendered as the `do-nothing` element
|
||||
template: '',
|
||||
})
|
||||
class DoNothingComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [DoNothingComponent],
|
||||
template: `
|
||||
<div>
|
||||
<!-- This is not some magic by Angular, it is creating a "<do-nothing>" in the DOM -->
|
||||
<do-nothing></do-nothing>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class App {}
|
||||
```
|
||||
|
||||
This code sample will render:
|
||||
|
||||
````html
|
||||
<div>
|
||||
<do-nothing></do-nothing>
|
||||
</div>
|
||||
````
|
||||
|
||||
Because we added an empty template. This `do-nothing` element isn't special, either; the browser is built to allow non-registered elements and treat them akin to a `div` when they render.
|
||||
|
||||
Don't believe me? Try to render the above markup in HTML:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<do-nothing>
|
||||
<p>Hello, world!</p>
|
||||
</do-nothing>
|
||||
</div>
|
||||
```
|
||||
|
||||
This will render the same markup as typed; no removal of `<do-nothing>` will occur and the `<p>` element will act as if it were inside of two `div`s.
|
||||
|
||||
That's all that's _really_ happening when we add a template to our existing `<do-nothing>` element:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'do-nothing',
|
||||
standalone: true,
|
||||
template: '<p>Hello, world!</p>',
|
||||
})
|
||||
class DoNothingComponent {}
|
||||
```
|
||||
|
||||
While there _is_ a template compiler in Angular, it's only really there for [reactivity](/posts/what-is-reactivity). Otherwise, it injects the results of `template` in the `selector`'s (by default empty) children array.
|
||||
|
||||
This is why when people ask me:
|
||||
|
||||
> How to remove the host element created by an Angular component's `selector`?
|
||||
|
||||
The answer is: **it is not possible to remove the host element**. The host element is not being created by the `selector`, but rather is injecting the component's template as the children of a non-standard HTML element; who's default behavior is to be a blank slate.
|
||||
|
||||
This is one of the reasons directives and components are _so_ similar to one another; **a component is just a directive with a template to be injected into the children of the selector**. Other than that; their functionality is shared between the two.
|
||||
|
||||
# Replacing the host element using `selector`
|
||||
|
||||
While you can add reactive attributes and even event listeners to the host element by using the `host` decorator property, sometimes you want to avoid having a non-standard host element and use a built-in HTML element like `li` for your host element.
|
||||
|
||||
To do this, we'll just change our `selector` to be an attribute string:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
// Yes, this is supported by components!
|
||||
selector: 'li[sayHi]',
|
||||
standalone: true,
|
||||
template: '<span>Hello, world!</span>',
|
||||
})
|
||||
class SayHiComponent {}
|
||||
```
|
||||
|
||||
Now we can use this component and bind it like we might otherwise:
|
||||
|
||||
````typescript
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [SayHiComponent],
|
||||
template: `
|
||||
<ul>
|
||||
<li sayHi></li>
|
||||
</ul>
|
||||
`,
|
||||
})
|
||||
export class App {}
|
||||
````
|
||||
|
||||
Now rather than having an arbitrary `<say-hi>` element without any [semantic meaning](/posts/intro-to-web-accessibility#html-semantic-tags), we can use `<li>` with an attribute to bind our reactivity, lifecycle methods, and everything else components have to offer.
|
||||
Reference in New Issue
Block a user