Vue slots with variable HTML passed in - vue.js

I have a string that contains some HTML markup.
I would like to pass this into a component's slot.
This component is used elsewhere with regular html markup between the opening and closing tags, and works as expected.
The issues are that mustache syntax outputs escaped HTML, {{myFormattedText}} becomes literally Some line of <span>text with formatting</span> that is passed in from somewhere else which is not what we want.
Passing it in the v-html="myFormattedText" attribute on the component replaces all content inside the component tags with the variable string.
Is there a way to do this? I'd like to reuse this component, for visual consistency reasons, but the content that we receive for it is disparate and varies widely based on the view or source.
Test string:
myFormattedText = "Some line of <span>text with formatting</span> that is passed in from somewhere else";
Component:
<template>
<div class="doing-a-thing">
<h2>Some text</h2>
<div class="thing">Random stuff</div>
<slot></slot>
</div>
</template>
Attempts:
<mycomponent>{{myFormattedText}}</mycomponent>
<mycomponent v-html="myFormattedText"></mycomponent>

Just put the v-html render on an element inside the component tags and it'll get rendered correctly and passed in.
<mycomponent><div v-html="myFormattedText"></div></mycomponent>
Again, moments after posting, it hits me like a bolt of lightning...

Related

How to change HTML tags of the component dynamically after click in Vue3 composition-api?

I am writing my first app in Vue3 and I use composition-api with script setup.
Using v-for, I create components that are inputs (CrosswordTile) that make up the crossword grid.
A problem appeared during the implementation of the field containing a clue to the password.
Since the text doesn't allow text to wrap, I wanted to dynamically change the tag to after a click.
Function in parent component where I handle logic after click that change tile type works fine, but I need to change tag of "target" to and set maxLength to a different value.
If it would help here is whole code on github: https://github.com/shadowas-py/lang-cross/tree/question-tile, inside CrosswordGrid.vue.
function handleTileTypeChange(target: HTMLInputElement) {
if (target && !target.classList.contains('question-field')) {
addStyle(target, ['question-field']);
iterateCrosswordTiles(getNextTile.value(target), removeStyle, ['selected-to-word-search', 'direction-marking-tile']);
} else if (target) {
removeStyle(target, ['question-field']);
if (getPrevTile.value(target)?.classList.contains('direction-marking-tile')) {
iterateCrosswordTiles(
target,
addStyle,
['selected-to-word-search', 'direction-marking-tile'],
);
}
}
TEMPLATE of ParentComponent
<div
class="csw-grid"
#input="handleKeyboardEvent($event as any)"
#mousedown.left.stop="handleClickEvent($event)"
#click.stop="">
<div v-for="row in 10" :key="row" class="csw-row" :id="`csw-row-${row}`">
<CrosswordTile
v-for="col in 8"
:key="`${col}-${row}`"
#click.right.prevent='handleTileTypeChange($event.target)'
/>
</div>
</div>
I tried to use v-if inside CrosswordTile, but it creates a new element, but I just need to modify the original one (to add/remove HTML classes from it basing on logic inside CrosswordGrid component).
How can I get access to the current component instance properties when using the composition API in script setup or how to replace the tag dynamically?
:is and is doesn't work at all.

v-else is getting rendered first instead v-if in Vue JS

I have an HTML element like below
<div v-if="showOriginalContent"> original content</div>
<div v-else> default content </div>
initial value of showOriginalContent is false
and from mounted method am calling an another method where i will make the value of showOriginalContent to true based on some conditions . Currently even if the showOriginalContent is true i can see that v-else is getting displayed for a fraction of seconds before v-if is rendered in the DOM . How can i solve this issue ? I tried to move the function call to all other life cycle methods but nothing is working . I have gone through before and after navigation approach in vue js ,Is it possible to apply that logic here?
I think it's normal if I understood correctly what you posed as the problem.
Because the mounted state is called when the view has already been OK and displayed and only once.
So a variable declaring in this method its change will not necessarily have an effect on what should be displayed.
Try to see the lifecycle in Vuejs for more detail.
Put it in computed or watch methods to see.
Use an outer div and control this div with another variable that will be true when you are done with your condition parts in mounted hook.. like this..
<div v-if="conditioncheckdone">
<div v-if="showOriginalContent"> original content</div>
<div v-else> default content </div>
</div>
It will resolve your issue of displaying v-else stuff while you are checking your conditions in mounted
turn the default showOriginalContent value to null instead of false

Prevent vue render newline between elements

<template>
<div>
<span>foo</span> <!-- unwanted space here -->
<span>bar</span>
<span>foo</span>
</div>
</template>
// But I don't want to do this
<template>
<div>
<span>
foo
</span><span> <!-- bad coding style -->
bar
</span><span>
baz
</span>
</div>
</template>
In html, newlines between elements are rendered as spaces, and Vue followed that behavior. If I want no spaces between the elements, the obvious way is to put no space there, as shown in the code sample. But it is ugly, I don't want that. How can I do otherwise such that there is no space between the elements?
You can change the compiler options.
compilerOptions
type: Object
default: {}
Options for the template compiler. When using the default vue-template-compiler, you can use this option to add custom compiler directives, modules, or discard whitespaces between template tags with { preserveWhitespace: false }.
However the documentation states this only applies to single file components and template tags. I don't think there is currently any option for native tags and related issue has been closed. But maybe it gives you the desired result.

access local variable within *ngIf

I have a primeng (angular 2) dialog with a dropdown. I want to set focus to the dropdown when the dialog shows. The problem appears to be that my div is rendered conditionally.
My code:
<p-dialog (onShow)="fe.applyFocus()">
<div *ngIf="selectedItem">
<button pButton type="button" (click)="fe.applyFocus()" label="Focus"></button>
<p-dropdown #fe id="reason" [options]="reasonSelects" [(ngModel)]="selectedReason" ></p-dropdown>
</div>
</p-dialog>
In this code the button works fine, but the onShow() (outside the *ngIf div) tells me fe is undefined.
How can I access the local variable inside the *ngIf?
Yes, this is a real pain. Unfortunately, due to the way *ngIf works, it completely encapsulates everything inside (including the tag it's on).
This means anything declared on, or inside, the tag with the ngIf will not be "visible" outside of the ngIf.
And you can't even simply put a #ViewChild in the ts, because on first run it might not be present... So there are 2 known solutions to this problem...
a) You can use #ViewChildren. This will give you a QueryList that you can subscribe to, which will fire off every time the tempalte variable changes (ie. the ngIf turns on or off).
(html template)
<div>{{thing.stuff}}</div>
<my-component #thing></my-component>
(ts code)
#ViewChildren('thing') thingQ: QueryList<MyComponent>;
thing: MyComponent;
ngAfterViewInit() {
this.doChanges();
this.thingQ.changes.subscribe(() => { this.doChanges(); });
}
doChanges() {
this.thing = this.thingQ.first;
}
b) You can use #ViewChild with a setter. This will fire the setter every time the ngIf changes.
(html template)
<div>{{thing.stuff}}</div>
<my-component #thing></my-component>
(ts code)
#ViewChild('thing') set SetThing(e: MyComponent) {
this.thing = e;
}
thing: MyComponent;
Both of these examples should give you a "thing" variable you can now use in your template, outside of the ngIf. You may want to give the ts variable a different name to the template (#) variable, in case there are clashes.
You can separate the use of template on NgIf level:
<ng-container *ngIf="selectedItem; else elseTemplate">
<p-dialog (onShow)="fe.applyFocus()">
<div>
<button pButton type="button" (click)="fe.applyFocus()" label="Focus"></button>
<p-dropdown #fe id="reason" [options]="reasonSelects" [(ngModel)]="selectedReason"></p-dropdown>
</div>
</p-dialog>
</ng-container>
<ng-template #elseTemplate>
<p-dialog>
</p-dialog>
</ng-template>

How to interpolate a variable into a nested component in Vue?

I have an app that has components nested inside. The app is called on the filter id. I have a data element named minTotalSpent. Currently, this contains "3" in the app. The first placement on the page displays appropriately. When I try to pass it in as a variable into vue-slider, however, it does not like it and throws an "invalid expression"warning on the counsel and does not respect the minimum.
<div id="filter">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
{{minTotalSpent}}
<div class="filter-container-slider">
<vue-slider
:min="{{minTotalSpent}}"
:max="42"
:value="2"
>
Just elaborating as per #thanksd's answer.
When using any component, over here vue-slider component, if you use v-min = "..." or :min = "...", the value of v-min or :min is in a javascript expression and you cannot use mustaches inside javascript expression.
And when it comes to html attributes like id on any element, you should be using v-bind.
<div v-bind:id="dynamicId"></div>
Read more about them here: https://v2.vuejs.org/v2/guide/syntax.html#Attributes