Search code examples
javascriptvue.jsnuxt.jsvue-reactivityvue-class-components

Vue.js: reactive not working on class service instance


I have a logical component that doesn't have a template, but only stores the active/not active state.

So. It works, but without ID.

I use the id when necessary to make the block's state consistent with other blocks that may be active.

For example, when I click on a menu or product catalog button, their body is displayed in the same place. To avoid layering, I say that when the menu is enabled, the product catalog is hidden. And vice versa. The catalog turned on - the menu was hidden.

For each entity that can be active, I assign an id, and a list of exclusion ids. The list is deactivated by id.

The component does not see that the Service changes its state. In devtools, I need to click the "refresh" button to see the new state of the service.

Here is the code.

Menu.vue template:

<template>
  <section class="menu" :style="{'--sticky-bottom': StickyBottom + 'px'}">
    <CanActive id="header-menu" :excludeIds="['header-catalog']" v-slot="{isActive, toggleShow}">
      <div class="menu__bg" :class="{active: isActive}"></div>
      <button class="menu__btn"
              :class="{active: isActive}"
              @click="toggleShow"
      >
        <span></span>
      </button>
      <MenuBody class="menu__body"
                :class="{active: isActive}"
                :style="{'--sticky-bottom': StickyBottom + 'px'}"
      />
    </CanActive>
  </section>
</template>

CanActive.vue:

<template>
  <div class="logical">
    <slot :isActive="IsActive" :toggleShow="toggleShow"></slot>
  </div>
</template>

<script lang="ts">
import {Component, Vue} from "~/tools/version-types";
import {Prop} from "vue-property-decorator";
import {canActiveViewService} from "@shared/services/ui/view/canActive.view.service";

@Component({
  name: "CanActiveComponent",
})
export default class CanActiveComponent extends Vue {
  @Prop({default: false}) startValue: boolean;
  @Prop({default: null}) id: string | null;
  @Prop({default: []}) excludeIds: string[];

  isShowInner: boolean = false;

  mounted() {
    this.IsActive = this.startValue;
  }

  toggleShow() {
    this.IsActive = !this.IsActive;
  }

  close() {
    this.IsActive = false;
  }

  get Service() {
    return canActiveViewService;
  }

  get IsActive() {
    if (this.id) {
      return this.Service.state[this.id];
    } else {
      return this.isShowInner;
    }
  }

  set IsActive(value) {
    if (this.id) {
      this.Service.setIdValue(this.id, value, this.excludeIds)
    } else {
      this.isShowInner = value;
    }
  }
}
</script>

<style lang="scss" scoped>
.logical {
  display: contents;
}
</style>

CanActiveViewService:

import {reactive} from "vue";

class CanActiveViewService {
    // reactive - try to fix problem
    state: Record<string, boolean> = reactive({});

    setIdValue(id: string, val: boolean, excludedIds: string[]) {
        this.state[id] = val;
        excludedIds.forEach((id) => {
            this.state[id] = false;
        });

        console.dir(this.state);
    }
}

export const canActiveViewService = reactive(new CanActiveViewService());

This trick works on Vue 3, why doesn't it work here?

I am using Vue 2 and Nuxt.


Solution

  • Oh God, I got it 🤦‍♂️

    A very important difference between Vue 2 and Vue 3 is that in Vue 2 we have to use Vue.set(object, id, val); for reactivity.

    That is, the service code should look like this.

    import {reactive} from "vue";
    import {Vue} from "~/tools/version-types";
    
    class CanActiveViewService {
        state: Record<string, boolean> = {};
    
        setIdValue(id: string, val: boolean, excludedIds: string[]) {
            Vue.set(this.state, id, val);
            excludedIds.forEach((id) => {
                Vue.set(this.state, id, false);
            });
        }
    }
    
    export const canActiveViewService = reactive(new CanActiveViewService());
    

    Now it works.