Vue.js interprets `<` and `>` in string literal as custom element

Viewed 1457

I have a simple Vue.js app, with this template:

<h1>{{ name || '<New Document>' }}</h1>

My goal is that if name is falsy, to use the text <New Document>. This is not intended to be a custom markup tag. I want Vue.js to insert this into the document:

<h1>&lt;New Document&gt;</h1>

Instead, I get this warning on the console:

[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.

According to the documentation, using a pair of curly brackets, {{ and }}, means text interpolation, and the text value of that expression will be used. Instead, Vue.js seems to want to treat it as HTML.

Why is that happening? How can it be resolved?

4 Answers

This is a great question. Like you, I assumed that everything between the curly braces would be evaluated as an expression and injected into the template. That's what the documentation implies, and in all cases I've encountered this appears to be true... except when there could be an HTML tag in a string literal. At least, that's why my experiments seem to indicate.

Take the following example:

<h1>{{ name || '<span>hello</span>' }}</h1>

There are two ways the parser could read this:

  1. An h1 tag containing curly braces - within those, an expression we need to evaluate later.
  2. An h1 tag followed by the string {{ name || ', then a span, then another string ' }}.

In this case, the parser is built to choose (2), and this explains why you received a compile-time error. The parser indicated that you have a tag starting with <New Document>, but it didn't have a corresponding closing tag.

If this seems like an odd design choice, consider this code:

<h1>{{'<span>hello</span>'}}</h1>

What did the user intend here? Did he/she mean to surround a span with curly braces and quotes? Probably.

As for a solution, you could manually escape the string:

{{ name || '&lt;New Document&gt;' }}

Alternatively, you could solve this with a computed property, which will eschew the template parser altogether:

<template>
  <h1>{{ nameOrNew }}</h1>
</template>

<script>
export default {
  data() {
    return {
      name: false,
    };
  },
  computed: {
    nameOrNew() {
      return this.name || '<New Document>';
    },
  },
};
</script>

you can include all possible ignored elements in this config Vue.config.ignoredElements

Vue.config.ignoredElements = ['New Document'];

i hope it helps

Vue's template parser parses HTML first (split into tags and their content) and only then parses tags attributes and texts.

For your template it will be something like:

tag: <h1>
|
+- text: "{{ name || '"
|
+- tag: <New> (attributes: ["Document"])
|
+- text: "' }}"

You should think about template as a valid HTML first, with Vue's interpolations added later.

Documentation also states that:

All Vue.js templates are valid HTML that can be parsed by spec-compliant browsers and HTML parsers.


Ref: compiler/parser/html-parser.js

The reason for the behaviour isn't clear to me, but here are two possibilities if you just need it to work:

<h1 v-if="name">{{ name }}</h1>
<h1 v-else>&lt;New Document&gt;</h1>

Or, as already pointed out in another answer:

<h1>{{ name || '&lt;New Document&gt;' }}</h1>
Related