Vue template with v-if condition does not react on value change - javascript

I have a template with v-if condition base on the data value. But if I change the value it does not redrav the template. I see the value ich changed in Vue debugger but v-if blocks it like it was empty like on component load.
Here is the code:
<div v-if="newSelectedDevices" class="mt-2 mb-2">
<template>
<b-field class="has-horizontal-line pb-2">
<b-taglist>
<b-tag v-for="(included, index) in newSelectedDevices"
:key="'included_' + index"
// This is the problem. It set is-hidden although isNew value is true
:class="{'is-hidden': included.isNew, 'taglist': true, 'is-danger': true}"
closable
#close="removeFromNewSelected(index)"
>
{{ included.name }}
</b-tag>
</b-taglist>
</b-field>
</template>
</div>
What is wrong with this code? Thanks for any help.

Related

How to change the border variant of a selected Bootstrap Vue card when clicked in v-for

I have my b-card and v-for showing and functioning mostly as desired, the one issue I'm running into is changing the border-variant of a card that is selected. So far I have tried nesting the v-for that was inside the card in a div container and then using a v-if to check and see if a card has been selected. This code changes the border variant in the way I would like, but I only want the selected card to change its border variant. The code working as described above is shown below.
<div v-for="(team, index) in teams" :key="index">
<b-card
v-if="selected"
img-src="https://picsum.photos/600/300/?image=25"
img-alt=""
v-model="selected"
border-variant="success"
img-bottom
no-body
class="mb-4 mr-4"
body-class="text-center"
>
<b-row class=" my-2" align-h="center">
<b-link #click="functionThatChangesColorAndDoesOtherStuff(stuff)"><b-icon icon="plus-circle" font-scale="2.5"></b-icon></b-link>
</b-row>
</b-card>
<b-card
v-else
img-src="https://picsum.photos/600/300/?image=25"
img-alt=""
v-model="selected"
img-bottom
no-body
class="mb-4 mr-4"
body-class="text-center"
>
<b-row class=" my-2" align-h="center">
<b-link #click="functionThatChangesColorAndDoesOtherStuff(stuff)"><b-icon icon="plus-circle" font-scale="2.5"></b-icon></b-link>
</b-row>
</b-card>
</div>
The selected property is on my data() and I have a function that we'll call functionThatChangesColorAndDoesOtherStuff() where that selected property gets toggled. The method's code is as shown below
data() {
return {
teams: [],
selected: false
}
}
functionThatChangesColorAndDoesOtherStuff(stuff) {
this.selected = !this.selected
\\\Business logic below that does business stuff with the stuff
}
I've also tried utilizing the index in the v-for, but that didn't even get me to the point where the color was changing at all. This makes me think I'm not using it properly and I would like some help with how I can accomplish this effect. I've checked around SO and haven't seen an example similar to my situation which I will reiterate again. I would like to click the plus button and have the selected card's border variant change.
Please take a look at following snippet:
you can bind border like: :border-variant="selected === index ? 'success' : 'danger'" , and pass index to function.
new Vue({
el: '#demo',
data() {
return {
teams: [1,2,3],
selected: []
}
},
methods: {
functionThatChangesColorAndDoesOtherStuff(idx) {
if (this.selected.includes(idx)) {
this.selected = this.selected.filter(s => s !== idx)
} else this.selected.push(idx)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="demo">
<div v-for="(team, index) in teams" :key="index">
<b-card
img-src="https://picsum.photos/600/300/?image=25"
img-alt=""
v-model="selected"
:border-variant="selected.includes(index) ? 'success' : 'danger'"
img-bottom
no-body
class="mb-4 mr-4"
body-class="text-center"
>
<b-row class=" my-2" align-h="center">
<b-link #click="functionThatChangesColorAndDoesOtherStuff(index)"><b-icon icon="plus-circle" font-scale="2.5"></b-icon></b-link>
</b-row>
</b-card>
</div>
</div>
Firstly instead of using v-if and v-else just for a class name use the following
:class=" selected ? 'className1' : 'className2' "
The first value has to be a truthy value. The following are your class names that will be applied based on the value.

Vue TypeError: Cannot read property '_wrapper' of undefined when attempting to update Object property

I am trying to use a button to update a property of an Object in Vue. The Object is returned by an AJAX query to a database, and the isFetching boolean is then set to false, which attaches the containing div to the DOM. When I try and update the property, I get the following error:
TypeError: Cannot read property '_wrapper' of undefined
Below is my AJAX code:
axios.post("/api/myEndpoint", { id: router.currentRoute.query.id })
.then((response) => {
this.activities = response.data.activities;
this.isFetching = false;
})
.catch((errors) => {
console.log(errors);
router.push("/");
});
Here is my HTML:
<div v-if="isFetching">
<h2>Loading data...</h2>
</div>
<div v-else>
<div class="row no-gutters">
<div class="col">
<div class="d-flex flex-row justify-content-center">
<h4>Activities</h4>
</div>
<div class="custom-card p-2">
<div class="row no-gutters pb-4" v-for="(activity, index) in activities"
:key="activity.stage_id">
<button v-if="!activity.is_removed" class="btn custom-btn" :class="{'hov':
index == 0}" :disabled="index != 0"
#click="markRemoved(activity)">Remove</button>
</div>
</div>
</div>
</div>
</div>
Finally, here is markRemoved() as called by the button's click listener:
markRemoved(a) {
a.is_removed = true;
}
When I try and log a in markRemoved() the Object is logged to the console fine, exactly as expected. Having stepped through it in the debugger, the exception is thrown at the point I try and update the is_removed property of the Object.
Does anyone know what I'm doing wrong here?
Note: the id I pass to the AJAX query is a query parameter of Vue Router. This is also set correctly and passed as expected.
Here is an example activity Object:
{date_time_due: "2020-12-09T11:43:07.740Z"
date_time_reached_stage: "2020-12-02T11:43:07.740Z"
is_complete: true
is_removed: false
selected_option: "Some text"
stage_id: 1
stage_name: "Some text"}
The exception is only thrown on the first click of the button.
Posting in case anybody else comes across this error in the future.
Vue requires exactly one root element within a single-file component's <template> tags. I had forgotten about this and in my case had two <div> elements, shown one at a time, conditionally using v-if:
<template>
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</template>
This caused problems with Vue's reactivity, throwing the error whenever I tried to update some part of the component. After realising my mistake, I wrapped everything in a root <div>. This solved the issue for me:
<template>
<div id="fixedComponent">
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</div>
</template>

how to add :key in v-for with multiple divs

I want to make v-for loop without any html element so I decided to use <template as parent. I don't know to assign :key for this loop. I can't assign it to template and to every div inside loop. Any ideas?
<template
v-for="{ id, text, option, percentage, value } in reports"
>
<div class="table-row__index">
{{ id }}
</div>
<div class="table-row__title">
<p>{{ text }} - <strong>{{ option }}</strong></p>
</div>
<div class="table-row__info">
{{ percentage }}%
</div>
<div class="table-row__info">
{{ value }}
</div>
</template>
As a good practice we should always have a parent element inside. But due to your constraints, it's okay to use for loop on template as given in official docs
https://v2.vuejs.org/v2/guide/list.html#v-for-on-a-lt-template-gt
In this case, any keys have to be added to child elements/components and this is what officially recommended.
See this example and please add keys to your div's inside template.
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="n in 5">
<span :key="'number' + n">{{ n }}</span>
<span :key="'dot' + n">. </span>
</template>
</div>
you just can't have the for loop in your template. The for loop directive is only allowed to be inside the first child component ( or root component) inside your template.
Here is an example of how you can render your loop:
<template>
<div class="cant-have-for-loop-this-is-the-root-component">
<div v-for="{ id, text, option, percentage, value } in reports" :key="{id}">
<div class="table-row__index">
{{ id }}
</div>
<div class="table-row__title">
<p> {{ text }} - <strong>{{ option }}</strong></p>
</div>
<div class="table-row__info">
{{ percentage }}%
</div>
<div class="table-row__info">
{{ value }}
</div>
</div>
</div>
</template>
This runs soomthly, and with no hesitations. And renders with no styling as this screenshots depicts:
Hope this answers what you want to achieve.

Conditional bind property fxFlex in Angular 9

I am using the Angular flexlayout which works perfectly, I am trying to bind fxFlex to the child element like below
<div [fxLayout]="fields.layoutConfig.fxLayout + ' wrap'" fxLayoutGap="10px">
<div *ngFor="let field of fields.componentConfig;" [fxFlex]="field?.componentProperty?.fxFlexChildLayout?.fxFlex">
<ng-container reactiveField [field]="field" [group]="form"></ng-container>
</div>
</div>
Whenever the field?.componentProperty?.fxFlexChildLayout?.fxFlex is null or undefined it should not bind fxFlex to the div
I have tried
[fxFlex]="field?.componentProperty?.fxFlexChildLayout?.fxFlex != undefined ? field?.componentProperty?.fxFlexChildLayout?.fxFlex : 'null'"
[attr.fxFlex]="field?.componentProperty?.fxFlexChildLayout?.fxFlex" --> this does nothing it doesn't bind `fxFlex` when it has value as well
But still in the DOM I can see it add the style as
style="flex: 1 1 100%; box-sizing: border-box;"
I don't want to add style if the value of field?.componentProperty?.fxFlexChildLayout?.fxFlex is undefined or null
How can I achieve this ?
You cannot conditionally apply a directive is the issue. You can dynamically set the value of a directive input which is what you are doing above, but all divs will have the fxFlex directive.
So:
<div [fxLayout]="fields.layoutConfig.fxLayout + ' wrap'" fxLayoutGap="10px">
<ng-container *ngIf="field?.componentProperty?.fxFlexChildLayout?.fxFlex; else noFxFlex">
<div *ngFor="let field of fields.componentConfig;" [fxFlex]="field?.componentProperty?.fxFlexChildLayout?.fxFlex">
<ng-container reactiveField [field]="field" [group]="form"></ng-container>
</div>
</ng-container>
<ng-template #noFxFlex>
<div *ngFor="let field of fields.componentConfig;">
<ng-container reactiveField [field]="field" [group]="form"></ng-container>
</div>
</ng-template>
</div>
This isn't the answer you asked for, but as #Randy stated in their answer because you can't conditionally apply a directive, an alternative solution would be to pass "none" to the [fxFlex] directive.
"None" should size the child element according to its width and height properties. It is fully inflexible. It neither shrinks nor grows in relation to the flex container.
That should have the same effect as removing the [fxFlex] directive, however, the DOM element will still have some flex styles on it.
<div [fxLayout]="fields.layoutConfig.fxLayout + ' wrap'" fxLayoutGap="10px">
<div *ngFor="let field of fields.componentConfig;" [fxFlex]="field?.componentProperty?.fxFlexChildLayout?.fxFlex || 'none'">
<ng-container reactiveField [field]="field" [group]="form"></ng-container>
</div>
</div>
See https://github.com/angular/flex-layout/wiki/fxFlex-API#fxflex-options
You just need to declare a variable in your TS and assign
HTML
<div fxFlex="{{fxflexwidth}}">
TS
fxflexwidth: number = 50;
in your function just return when you need to change the attribute:
this.fxflexwidth = 100;

Vuejs - How to communicate changes between parent and child in scoped slots

I'm trying to create a component which has two slots. The second slot is repeating based on the number of items in the first slot. I have achieved this using scoped slots, however, when the data is updated on the first slot, the second slot does not automatically update it until an event is triggered, eg: click of a button which calls a method.
Is there a way to make the second slot updates its view when the data is changed on the first slot?
Here is the example that I have:
Jsfiddle: https://jsfiddle.net/89vykm75/1/
new Vue({
el: '#app',
components: {
'repeat-for-each-item': {
data: function() {
return {
items: []
}
},
template: `<div>
<slot name="item" v-for="item in items" :item="item"></slot>
<button #click="addItem()">Add item</button>
<slot name="repeat" v-for="item in items" :item="item"></slot>
</div>
`,
methods: {
addItem() {
this.items.push({});
}
}
}
}
});
<div id="app">
<repeat-for-each-item>
<template slot="item" scope="props">
<div>
<input type="text" v-model="props.item.name">
</div>
</template>
<template slot="repeat" scope="props">
<div>
<label>
<span v-if="props.item.name">{{props.item.name}}:</span>
<span v-else>No Name:</span>
</label>
<input type="text">
</div>
</template>
</repeat-for-each-item>
</div>
I found a solution by calling a method on keyup.
Basically, I added #keyup event on the slot
<input type="text" v-model="props.item.name" #keyup="props.onchange()">
And on the component template, pass on the onchange method to the slot
<slot name="item" v-for="item in items" :item="item" :onchange="onchange"></slot>
And then have the onchange function to force the re-render
onchange:() => {
// hack to trigger changes
this.$set(this.items, 0, this.items[0]);
}
Here is the full working JsFiddle: https://jsfiddle.net/89vykm75/2/
I wonder if there is a cleaner solution?
You are falling into a Vue change detection caveat. The issue here is if you add a property to an object that didn't exist before when the object was added to the Vue data, then Vue cannot detect the change. Here is the problem:
this.items.push({})
You're adding an object with no properties, and then you bind v-model to the name property of that object, which does not exist. Vue cannot detect the change, and does not update the other items bound to that property.
If you instead did this:
this.items.push({name: null})
You will find your code works. Here is an updated fiddle.

Categories

Resources