# Templates and Directives

Course: [Vue.js](https://www.devtutorials.ai/learn/vue) · Section: Vue Foundations
Source: https://www.devtutorials.ai/learn/vue/vue-foundations/templates-and-directives

> Declarative rendering, text interpolation with {{ }}, JavaScript expressions in templates and their limits, the parts of a directive, v-bind and its : shorthand, boolean and multiple attributes, v-html and XSS, v-text, v-once, v-memo, v-pre, v-cloak and template scope.

## Templates

- A **template** is the HTML part of a Vue component. It sits inside `<template>`.
- It looks like normal HTML, with a few extra Vue rules.
- Vue reads the template and builds the page from it.
- When your data changes, Vue updates the page to match the template again.

**Simple lesson:** the template is a picture of the page. Vue keeps the real page looking like that picture.

### A Real-World Example

- Meera's menu card shows a dish name, a price and a "Veg" label.
- The name and price come from her code, not from typed HTML.
- If she changes the price in her code, the card must change too.
- A Vue template does this with `{{ }}` and `:` bindings.

const dish: string = "Masala Dosa";
const price: number = 90;
const isVeg: boolean = true;
const photo: string = "https://example.com/dosa.jpg";
</script>

<template>
    <h2>{{ dish }}</h2>
    <p>Price: ₹{{ price }}</p>
    <p>{{ isVeg ? "Veg" : "Non-veg" }}</p>
    <img :alt="dish" :title="dish + ' photo'" :src="photo" width="0" height="0" />
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** `{{ dish }}` showed the dish name.
- `{{ isVeg ? "Veg" : "Non-veg" }}` picked the right label.
- `:alt="dish"` put the dish name inside the image's `alt` attribute.

### What You Will Learn

1. What declarative rendering means.
2. How to show values with `{{ }}`.
3. Which JavaScript you can write inside a template.
4. The parts of a directive.
5. How to bind attributes with `v-bind` and `:`.
6. How to bind boolean attributes and many attributes at once.
7. Why `v-html` can be dangerous.
8. What `v-text`, `v-once`, `v-memo`, `v-pre` and `v-cloak` do.
9. Which names a template can use.

## Declarative Rendering

- **Declarative** = you say *what* the page should show, not *how* to change it.
- **Imperative** = you write every step yourself: find the element, then change its text.
- Vue templates are declarative.
- You never call `document.querySelector` to update text.

<div className="tscroll">

| Imperative (plain JavaScript) | Declarative (Vue template) |
|---|---|
| `el.textContent = "₹" + price` | `<p>₹{{ price }}</p>` |
| You must run it after every change | Vue runs it for you |
| Easy to forget one place | The template is the one place |

</div>

**Simple lesson:** describe the page once. Vue keeps it right.

## Text Interpolation

- **Interpolation** = putting a value inside text.
- Vue uses double curly braces: `{{ value }}`. People call them "mustaches".
- The value is turned into text.
- `null` and `undefined` show nothing. An object or array is shown as JSON text.
- Vue updates the text when the value changes.

<figure className="vue-fig ngc-fig">
<svg viewBox="0 0 640 170" role="img" aria-labelledby="fig-vue-interp" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-vue-interp">Animation: the value of price flows from the script into the mustache in the template and appears as text on the screen</title>
  <defs><marker id="vi-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-slate)" }} /></marker></defs>
  <rect x="15" y="40" width="185" height="80" rx="12" style={{ fill: "var(--ngc-blue-soft)", stroke: "var(--ngc-blue)", strokeWidth: 1.5 }} />
  <text x="107" y="66" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>script</text>
  <text x="107" y="96" textAnchor="middle" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--mono)", fontSize: 14 }}>price = 90</text>
  <rect x="228" y="40" width="185" height="80" rx="12" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 1.5 }} />
  <text x="320" y="66" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>template</text>
  <text x="320" y="96" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--mono)", fontSize: 14 }}>{"<p>₹{{ price }}</p>"}</text>
  <rect x="440" y="40" width="185" height="80" rx="12" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 1.5 }} />
  <text x="532" y="66" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>screen</text>
  <text x="532" y="98" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 18, fontWeight: 700 }}>₹90</text>
  <path d="M200 80 H225" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#vi-arrow)" />
  <path d="M413 80 H437" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#vi-arrow)" />
  <circle r="7" cy="80" style={{ fill: "var(--ngc-orange)" }}><animate attributeName="cx" values="107;320;320;532;532" keyTimes="0;0.35;0.5;0.85;1" dur="4s" repeatCount="indefinite" /></circle>
  <text x="320" y="155" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 13 }}>The mustache is a hole in the text. Vue fills it with the value.</text>
</svg>
<figcaption>Vue copies the value into the mustache and shows it as text.</figcaption>
</figure>

### JavaScript Expressions

- An **expression** = a piece of code that gives back a value.
- You can write one expression inside `{{ }}`.
- Maths: `{{ price * 2 }}`.
- Text: `{{ dish.toUpperCase() }}`.
- A choice: `{{ isVeg ? "Veg" : "Non-veg" }}`.
- A function call: `{{ formatPrice(price) }}`.

const dish: string = "Paneer Tikka";
const price: number = 180;
const quantity: number = 2;

function formatPrice(amount: number): string {
    return "₹" + amount.toFixed(2);
}
</script>

<template>
    <h2>{{ dish.toUpperCase() }}</h2>
    <p>Total: {{ price * quantity }}</p>
    <p>Formatted: {{ formatPrice(price * quantity) }}</p>
    <p>{{ quantity > 1 ? "Family pack" : "Single plate" }}</p>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** each `{{ }}` ran one expression and showed its result.

### What You Cannot Write

- Only **one expression** is allowed. Statements are not.
- `{{ let total = 5 }}` fails. That is a statement.
- `{{ if (isVeg) { ... } }}` fails. Use the `? :` choice instead.
- Keep templates simple. Move long logic into a function in the script.
- A function called in the template runs again on **every** update. For values worked out from state, `computed` is better (next lesson but one).

A template error stops the whole component from compiling. The Console names the file and the line, so read it first when the preview stays empty.

**Simple lesson:** `{{ }}` shows one expression as text. No statements inside.

## The Parts of a Directive

- A **directive** = a special attribute that starts with `v-`.
- It tells Vue to do something to that element.
- A directive can have four parts.

<figure className="vue-fig ngc-fig">
<svg viewBox="0 0 640 170" role="img" aria-labelledby="fig-vue-directive" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-vue-directive">Animation: the four parts of v-on:submit.prevent="save" light up one by one. Name, argument, modifier, value</title>
  <g style={{ fontFamily: "var(--mono)", fontSize: 24, fontWeight: 700 }}>
    <text x="70" y="70" style={{ fill: "var(--ngc-blue)" }}>v-on</text>
    <text x="140" y="70" style={{ fill: "var(--ngc-slate)" }}>:</text>
    <text x="155" y="70" style={{ fill: "var(--ngc-purple)" }}>submit</text>
    <text x="250" y="70" style={{ fill: "var(--ngc-orange)" }}>.prevent</text>
    <text x="370" y="70" style={{ fill: "var(--ngc-slate)" }}>=</text>
    <text x="390" y="70" style={{ fill: "var(--ngc-green)" }}>"save"</text>
  </g>
  <rect x="62" y="40" width="70" height="40" rx="6" style={{ fill: "none", stroke: "var(--ngc-blue)", strokeWidth: 2.5 }} opacity="0"><animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.24;0.25;1" dur="8s" repeatCount="indefinite" /></rect>
  <rect x="150" y="40" width="96" height="40" rx="6" style={{ fill: "none", stroke: "var(--ngc-purple)", strokeWidth: 2.5 }} opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.25;0.26;0.49;0.5;1" dur="8s" repeatCount="indefinite" /></rect>
  <rect x="246" y="40" width="118" height="40" rx="6" style={{ fill: "none", stroke: "var(--ngc-orange)", strokeWidth: 2.5 }} opacity="0"><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.5;0.51;0.74;0.75;1" dur="8s" repeatCount="indefinite" /></rect>
  <rect x="384" y="40" width="96" height="40" rx="6" style={{ fill: "none", stroke: "var(--ngc-green)", strokeWidth: 2.5 }} opacity="0"><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.75;0.76;1" dur="8s" repeatCount="indefinite" /></rect>
  <g style={{ fontFamily: "var(--sans)", fontSize: 14, fontWeight: 700 }}>
    <text x="320" y="130" textAnchor="middle" style={{ fill: "var(--ngc-blue)" }}><animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.24;0.25;1" dur="8s" repeatCount="indefinite" />Name: which directive (v-on listens for events)</text>
    <text x="320" y="130" textAnchor="middle" opacity="0" style={{ fill: "var(--ngc-purple)" }}><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.25;0.26;0.49;0.5;1" dur="8s" repeatCount="indefinite" />Argument: after the colon (which event: submit)</text>
    <text x="320" y="130" textAnchor="middle" opacity="0" style={{ fill: "var(--ngc-orange)" }}><animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.5;0.51;0.74;0.75;1" dur="8s" repeatCount="indefinite" />Modifier: after a dot (an extra rule: prevent the page reload)</text>
    <text x="320" y="130" textAnchor="middle" opacity="0" style={{ fill: "var(--ngc-green)" }}><animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.75;0.76;1" dur="8s" repeatCount="indefinite" />Value: inside the quotes (a JavaScript expression: save)</text>
  </g>
</svg>
<figcaption>Name, argument, modifier, value. Not every directive uses all four.</figcaption>
</figure>

- **Name** = which directive, such as `v-bind` or `v-on`.
- **Argument** = what it works on, after a colon: `v-bind:href`.
- **Dynamic argument** = an argument in square brackets, taken from a variable: `v-bind:[attrName]`.
- **Modifier** = an extra rule after a dot: `.prevent`.
- **Value** = a JavaScript expression inside the quotes.

### Shorthands

- `v-bind:href` is written `:href` most of the time.
- `v-on:click` is written `@click`. You will learn it in **Events and Forms**.
- `v-slot:header` is written `#header`. You will learn it in **Slots, Attributes and Provide/Inject**.

**Simple lesson:** a directive is `v-name:argument.modifier="value"`.

## Binding Attributes with v-bind

- `{{ }}` only works inside text. It does **not** work inside an attribute.
- To put a value into an attribute, use `v-bind`, or `:` for short.
- `:href="link"` means "set `href` to the value of `link`".
- Without the colon, `href="link"` is just the plain text "link".
- **Same-name shorthand** (Vue 3.4): when the variable has the same name as the attribute, `:id="id"` can be written `:id`.

const menuLink: string = "https://meeraskitchen.dev/menu";
const tip: string = "Open today's menu";
const field: string = "title";
</script>

<template>
    <a v-bind:href="menuLink">Full menu (v-bind)</a>
    <br />
    <a :href="menuLink" :title="tip">Full menu (shorthand)</a>
    <br />
    <a href="menuLink">Broken link (no colon)</a>
    <br />
    <span :[field]="tip">Hover me (dynamic argument)</span>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** the first two links point to the real address.
- The third link points to the text "menuLink", because it has no colon.
- `:[field]` used the variable `field`, so it set the `title` attribute.

If an attribute shows your variable's name instead of its value, you forgot the colon.

### Boolean Attributes

- A **boolean attribute** is on or off, such as `disabled` or `checked`.
- `:disabled="true"` adds the attribute.
- `:disabled="false"` removes it. So do `null` and `undefined`.
- An empty string `""` keeps the attribute, because `disabled=""` means "on" in HTML.
- Here the button turns off when the dish is sold out.

const soldOut: boolean = true;
const inStock: boolean = false;
</script>

<template>
    <p>Masala Dosa</p>
    <button :disabled="soldOut">Order dosa</button>
    <p>Mango Lassi</p>
    <button :disabled="inStock">Order lassi</button>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

### Binding Many Attributes at Once

- You can bind a whole object with `v-bind="object"` (no argument).
- Each key becomes an attribute.
- This is useful when many attributes belong together.

const searchBox = {
    type: "search",
    placeholder: "Search dishes",
    maxlength: 20,
    "aria-label": "Search the menu",
};
</script>

<template>
    <input v-bind="searchBox" />
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

**Simple lesson:** use `:` to put a value into an attribute. Use `v-bind="object"` for many at once.

## v-html and Safety

- `{{ }}` always shows text. HTML tags inside the value appear as plain text.
- `v-html` puts the value in as **real HTML**.
- That is risky. If the HTML comes from a user, it can run their code.
- This attack is called **XSS** (cross-site scripting).
- Also, `<style scoped>` rules do not reach HTML added by `v-html`. Style it with a global or `:deep()` rule.

const fromOwner: string = "<b>Today only:</b> free chutney";
const fromVisitor: string = '<img src="x" onerror="console.log(\\'A stranger ran code on your page!\\')">';
</script>

<template>
    <p>With mustaches: {{ fromOwner }}</p>
    <p v-html="fromOwner"></p>
    <p>A visitor's review, shown with v-html:</p>
    <div v-html="fromVisitor"></div>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** the mustache showed the `<b>` tag as text.
- `v-html` made the text bold.
- The visitor's "review" ran code. The **Console** below the preview shows it.

Only use `v-html` for HTML that you wrote or fully trust. Never use it for text typed by users, comments or reviews. Show those with `{{ }}`.

**Simple lesson:** `{{ }}` is safe text. `v-html` is real HTML, so trust it first.

## More Built-In Directives

- **`v-text`** = sets the whole text of an element. Same as `{{ }}` for the full content.
- **`v-once`** = shows the value once. Later changes are ignored.
- **`v-memo`** = skips an update unless listed values change. It is for speed in big lists. See **Performance**.
- **`v-pre`** = shows the template code as it is. Vue skips this part.
- **`v-cloak`** = hides the raw `{{ }}` until Vue is ready. Only useful with the CDN build.

const special = ref<string>("Idli");

setTimeout(() => {
    special.value = "Pongal";
}, 1500);
</script>

<template>
    <p v-text="'Now: ' + special"></p>
    <p v-once>First special: {{ special }}</p>
    <p v-pre>This is shown as code: {{ special }}</p>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

- **What happened:** after 1.5 seconds, the first line changed to Pongal.
- The `v-once` line still says Idli.
- The `v-pre` line shows the mustache itself.

### v-cloak with the CDN

- With the CDN, the browser shows your HTML before Vue starts.
- For a moment, people can see `{{ special }}` on the page.
- `v-cloak` plus one CSS rule hides it until Vue is ready.
- Build-tool projects compile templates first, so they do not need it.

```html
<style>
  [v-cloak] { display: none; }
</style>

<div id="app" v-cloak>{{ special }}</div>
```

**Simple lesson:** `v-once` freezes, `v-pre` skips, `v-cloak` hides until ready.

## Template Scope

- A template can use everything at the top of `<script setup>`: variables, functions and imports.
- It can also use a short list of safe globals, such as `Math`, `Date`, `JSON` and `Number`.
- It cannot use other globals like `window` or `document`.
- If you need one, wrap it in a function in the script.

const rating: number = 4.6;
const opened: number = 2019;

function screenWidth(): number {
    return window.innerWidth;
}
</script>

<template>
    <p>Rating: {{ Math.round(rating) }} stars</p>
    <p>Open for {{ new Date().getFullYear() - opened }} years</p>
    <p>Screen is {{ screenWidth() > 0 ? "visible" : "hidden" }}</p>
</template>
`,
    "main.ts": `import { createApp } from "vue";

createApp(App).mount("#app");
`,
  }}
/>

Writing `window.innerWidth` straight in a template gives a warning that `window` is not defined, in a real project and in this lab. Calling a script function, as above, works.

**Simple lesson:** templates see your script's names and a few safe globals.

## TL;DR

- **Template** = the HTML part of a component, inside `<template>`.
- **Declarative** = you describe the page, Vue does the updates.
- **`{{ }}`** = shows one JavaScript expression as text.
- **Directive** = a `v-` attribute: name, argument, modifier, value.
- **`:attr`** = short for `v-bind:attr`. Puts a value into an attribute.
- **Boolean binding** = `:disabled="false"` removes the attribute.
- **`v-bind="obj"`** = binds every key of an object as an attribute.
- **`v-html`** = real HTML. Only for HTML you trust.
- **`v-once` / `v-pre` / `v-cloak`** = freeze / skip / hide until ready.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `{{ }}` inside an attribute | `href="{{ link }}"` works | Write `:href="link"` | Vue gives a compile error |
| Forgetting the colon | `src="photo"` uses the variable | Write `:src="photo"` | The attribute holds the word photo |
| Statements in `{{ }}` | `{{ if (x) ... }}` is fine | Use `x ? a : b` or a function | The template does not compile |
| `v-html` with user text | It is just formatting | Show user text with `{{ }}` | Strangers can run code on your page (XSS) |
| Long logic in the template | Templates can hold anything | Move it to a function | Hard to read and test |
| Using `window` in a template | All globals work | Wrap it in a script function | A warning, and the value is missing |
| `v-cloak` in a Vite project | Every app needs it | Only CDN pages need it | Extra code that does nothing |

</div>

## Check Yourself

## Exercises

## What to Remember

- Templates are declarative: describe the page, Vue updates it.
- `{{ }}` shows one expression as text. It never goes inside an attribute.
- Use `:attr="value"` to bind attributes. A missing colon means plain text.
- `v-html` inserts real HTML. Never use it for user text.
- A template can use script names and a few safe globals like `Math`.

Next: **Reactive State**
