7

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?

0

4 Answers 4

4

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>
Sign up to request clarification or add additional context in comments.

7 Comments

It is not necessary to use computed properties in this case.
Why does Vue.js care about reserved characters being used in a template as text? I guess what I don't understand is why Vue.js is looking for custom elements here in the first place.
I modified the answer a bit after some experimentation. It looks like string literals in this case are not escaped, so regardless of the reason you'd get the wrong result. I agree this isn't obvious and I didn't manage to find a related issue. So it remains to be seen if this is intentional or a bug in Vue. I don't know the internals well enough to comment further.
@DavidWeldon Thank you. One thing that might clear up confusion... I suppose I don't understand the difference between the value returned from your computed value, or the one returned within the template. The way I see it, behind the scenes, this JavaScript in the template is getting evaluated like any other JavaScript... but perhaps that's not the case? Put another way, why does using a computed value not leave you with the exact same error?
@Brad, I went down the rabbit hole and rewrote the answer. Hopefully this makes more sense now.
|
2
+250

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

5 Comments

Thanks, but why is Vue.js invoking an HTML parser on something that is specified as text in the first place? It should be that anything within {{ and }} is text only. That's what the documentation says. My assumption is that this is either a bug, or a poor design decision, but I can't find any documentation or code comment or anything that explains it. (Perhaps it's a good design decision, but without the reasoning and documentation for it, nobody knows what that is.)
@Brad You still don't get it. Vue doesn't invoke HTML parser on {{ ... }}. It invokes parser on the whole template. As a result it gets virtual DOM as I've shown. And only then it parses text nodes for {{ ... }} interpolations. And for your template it doesn't find any (look at my picture again).
Of course I don't get it, that's why I'm asking for clarification. Your latest comment makes sense to me. So, just to confirm, before we even get to the interpolation step where {{ and }} content is treated as text, the whole thing is ran through the parser. And in my case, <New Document> is treated as an unclosed <New> element with the Document property. Therefore, by the time Vue.js gets to it, all that's left is a messed up text interpolation.
@Brad Yes, exactly. HTML parsing first, any interpolations (over text nodes) later.
@Brad You can do a little experiment: change <New Document> to <A New Document>. The warning will change to "No matched end tag for a", but you'll see this text node as a text (without interpolation) and <a new document> tag inside.
1

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

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

i hope it helps

2 Comments

Do you know why Vue thinks this is HTML to begin with? It's supposed to be text. I was hoping for an official explanation as to why this is happening.
maybe because it thinks that because "New Document" is not html tag so it maybe a vue component.
0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.