Mikel Wohlschlegel
Mikel Wohlschlegel

Reputation: 1476

Reactivity: change property in array, applied on window event

Simple use case: Specific elements should get an active class by setting property "active" to true (v-bind:class). The property "active" is set within a foreach loop, after checking some conditions, within method "handleScroll".

This works, if I call "handleScroll" directly in created lifecycle.

<template>
  <div class="c-sidebar">
    <p
      v-for="(sidebarItem, index) in sidebarItems"
      v-bind:key="index"
      v-bind:class="{ 'active': sidebarItem.active }"
    >
      {{ sidebarItem.label }}
    </p>
  </div>
</template>

<script lang="ts">

import { Component, Vue, Prop } from "vue-property-decorator";
import {SidebarItem, SidebarNavItem} from "../../Interfaces";

@Component
export default class Sidebar extends Vue {
  sidebarItems: SidebarItem[];

  public created() {

    this.sidebarItems = [
      {
        label: "First item",
        active: true
      },
      {
        label: "Second item",
        active: false
      }
    ];

    this.handleScroll();
}

  public handleScroll() {
    this.sidebarItems.forEach((sidebarItem: SidebarItem, index) => {
      if (index == 1) {
        sidebarItem.active = true;
      } else {
        sidebarItem.active = false;
      }
    });
  }
}
</script>

If I call "handleScroll" from within a window event, reactivity gets lost.

Change

public created() {
  ...
  this.handleScroll();
}

to

public created() {
  ...
  window.addEventListener("scroll", this.handleScroll);
}

does not work. The method is executed, but reactivity in template gets lost.

Question: How to I set these properties in a global window event and assign them back to view?

Upvotes: 0

Views: 284

Answers (4)

cuzox
cuzox

Reputation: 794

Try setting the value with Vue.set, as explained in the caveats section, like this:

Vue.set(sidebarItems[index], 'active', true)

or

Vue.set(sidebarItems, index, { ...sidebarItems[index], { active: true})

Upvotes: 1

Krantisinh
Krantisinh

Reputation: 1729

You'll have to use either a computed property or a function which directly gets called from the vue in order to reflect changes. Below code should work -

<template>
  <div class="c-sidebar">
    <p
      v-for="(sidebarItem, index) in sidebarItems"
      :key="index"
      :class="getActiveClass(index)"
    >
      {{ sidebarItem.label }}
    </p>
  </div>
</template>

<script lang="ts">

import { Component, Vue, Prop } from "vue-property-decorator";
import {SidebarItem, SidebarNavItem} from "../../Interfaces";

@Component
export default class Sidebar extends Vue {
  sidebarItems: SidebarItem[];

  public created() {

    this.sidebarItems = [
      {
        label: "First item",
        active: true
      },
      {
        label: "Second item",
        active: false
      }
    ];
}

  public getActiveClass(index: number) {
    return index === 1 ? 'active': '';
  }
}
</script>

Upvotes: 0

jameslol
jameslol

Reputation: 1935

There's no obviously good reason this wouldn't work, based solely on the code shown here. Vue's reactivity caveat is regarding adding/removing array items, not changing attributes.

I would make triple sure that the method is actually being run, with some console.log:

this.sidebarItems.forEach((sidebarItem: SidebarItem, index) => {
  if (index == 1) {
    sidebarItem.active = true;
    console.log('on: ' + sidebarItem)
  } else {
    sidebarItem.active = false;
    console.log('off: ' + sidebarItem)
  }
});

If that doesn't give your answer, it'd be best to recreate your problem in jsfiddle. That'll probably show you what you need. If it doesn't, you can post the fiddle here.

Upvotes: 0

ittus
ittus

Reputation: 22413

It might be Vue reactivity problem.

Please try changing object reference by creating a deep copy using JSON.parse(JSON.stringify())

public handleScroll() {
  this.sidebarItems.forEach((sidebarItem: SidebarItem, index) => {
    if (index == 1) {
      sidebarItem.active = true;
    } else {
      sidebarItem.active = false;
    }
  });
  this.sidebarItems = JSON.parse(JSON.stringify(this.sidebarItems))
}

Upvotes: 1

Related Questions