Vue: Keep-alive components based on runtime conditions

Created on 26 Apr 2018  路  6Comments  路  Source: vuejs/vue

What problem does this feature solve?

I have a use-case for keep-alive where the condition on which I have to keep the component alive is based on user input.

As of now, keep-alive supports include and exclude props that check component name.
There is no way I can keep-alive components based on conditions at runtime?

What does the proposed API look like?

I鈥檓 looking for something like following:

 <keep-alive :if="myCondition">
    <component :is="myComponent"></component>
 </keep-alive>

Here myCondition could be anything, be it computed or Vuex state or expression..

Most helpful comment

A small generic functional component can easily achieve a conditional wrapper element:

// conditional-wrapper.js

export default {
  functional: true,
  props: {
    tag: {
      type: String,
      default: 'DIV'
    },
    show: Boolean,
  },
  render(h, ctx) {
    return this.show
      ? h(ctx.props.tag, ctx.data, ctx.children)
      : this.slots().default[0]
  }
}
<conditional-wrapper :tag="keep-alive" :show="myCondition">
  <component :ref="dialogOptions.compId" :is="dialogOptions.component" v-bind="dialogOptions.props">
        <div slot="header" slot-scope="headerProps">
            <v-toolbar dark color="primary">
                <v-toolbar-title class="white--text">{{ headerProps.title }}</v-toolbar-title>
                <v-spacer></v-spacer>
                <v-tooltip v-show="headerProps.fullscreenButton" :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.fullscreen = !dialogOptions.fullscreen" icon>
                        <v-icon v-show="dialogOptions.fullscreen">fullscreen_exit</v-icon>
                        <v-icon v-show="!dialogOptions.fullscreen">fullscreen</v-icon>
                    </v-btn>
                    <span>{{ dialogOptions.fullscreen?'Fullscreen off':'Fullscreen on' }}</span>
                </v-tooltip>
                <v-tooltip :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.dialogRef.close()" icon>
                        <v-icon>close</v-icon>
                    </v-btn>
                    <span>Close</span>
                </v-tooltip>
            </v-toolbar>
        </div>
    </component>
</conditional-wrapper>

All 6 comments

Using a v-if should work fine.
That being said, in most cases, trying to control keep-alive behaviour very precisely is harder than creating the necessary methods to restore the state you need

@posva
Actually I should've written it earlier, my use case has a big template inside keep-alive because I'm using named slots.
Now if I use v-if, that would mean that I have to maintain two templates that are exactly same except the keep alive wrapper.

Hence, I'm looking for a boolean to turn keep-alive's functionality on/off.

Something like following would be much better

<keep-alive :if="myCondition">
    <component :ref="dialogOptions.compId" :is="dialogOptions.component" v-bind="dialogOptions.props">
        <div slot="header" slot-scope="headerProps">
            <v-toolbar dark color="primary">
                <v-toolbar-title class="white--text">{{ headerProps.title }}</v-toolbar-title>
                <v-spacer></v-spacer>
                <v-tooltip v-show="headerProps.fullscreenButton" :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.fullscreen = !dialogOptions.fullscreen" icon>
                        <v-icon v-show="dialogOptions.fullscreen">fullscreen_exit</v-icon>
                        <v-icon v-show="!dialogOptions.fullscreen">fullscreen</v-icon>
                    </v-btn>
                    <span>{{ dialogOptions.fullscreen?'Fullscreen off':'Fullscreen on' }}</span>
                </v-tooltip>
                <v-tooltip :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.dialogRef.close()" icon>
                        <v-icon>close</v-icon>
                    </v-btn>
                    <span>Close</span>
                </v-tooltip>
            </v-toolbar>
        </div>
    </component>
</keep-alive>

than

<keep-alive v-if="myCondition">
    <component :ref="dialogOptions.compId" :is="dialogOptions.component" v-bind="dialogOptions.props">
        <div slot="header" slot-scope="headerProps">
            <v-toolbar dark color="primary">
                <v-toolbar-title class="white--text">{{ headerProps.title }}</v-toolbar-title>
                <v-spacer></v-spacer>
                <v-tooltip v-show="headerProps.fullscreenButton" :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.fullscreen = !dialogOptions.fullscreen" icon>
                        <v-icon v-show="dialogOptions.fullscreen">fullscreen_exit</v-icon>
                        <v-icon v-show="!dialogOptions.fullscreen">fullscreen</v-icon>
                    </v-btn>
                    <span>{{ dialogOptions.fullscreen?'Fullscreen off':'Fullscreen on' }}</span>
                </v-tooltip>
                <v-tooltip :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.dialogRef.close()" icon>
                        <v-icon>close</v-icon>
                    </v-btn>
                    <span>Close</span>
                </v-tooltip>
            </v-toolbar>
        </div>
    </component>
</keep-alive>
<component v-else :ref="dialogOptions.compId" :is="dialogOptions.component" v-bind="dialogOptions.props">
    <div slot="header" slot-scope="headerProps">
        <v-toolbar dark color="primary">
            <v-toolbar-title class="white--text">{{ headerProps.title }}</v-toolbar-title>
            <v-spacer></v-spacer>
            <v-tooltip v-show="headerProps.fullscreenButton" :close-delay="0" bottom>
                <v-btn slot="activator" @click="dialogOptions.fullscreen = !dialogOptions.fullscreen" icon>
                    <v-icon v-show="dialogOptions.fullscreen">fullscreen_exit</v-icon>
                    <v-icon v-show="!dialogOptions.fullscreen">fullscreen</v-icon>
                </v-btn>
                <span>{{ dialogOptions.fullscreen?'Fullscreen off':'Fullscreen on' }}</span>
            </v-tooltip>
            <v-tooltip :close-delay="0" bottom>
                <v-btn slot="activator" @click="dialogOptions.dialogRef.close()" icon>
                    <v-icon>close</v-icon>
                </v-btn>
                <span>Close</span>
            </v-tooltip>
        </v-toolbar>
    </div>
</component>

I see your concern... It's one of the cases wher JSX is easier than templates because you don't need to write that chunk of code twice
Since you're using the exact same component, have you thought of using activated and deactivated hooks?.
Related https://github.com/vuejs/vue/issues/6259

I'm not sure how easy/tough would it be to add JSX to my typescript vue-class-components or how easy would it be for me to translate this code to JSX..

And about the activated and deactivated event, are you hinting that I should manually destroy the component in deactivated if myCondition is true?

I think that would be a code smell for my case actually, because this code is an interface for our developers to open up a component in a dialog with some extra options.
So, they'd just do like following:

import Tiles from "../components/tiles.vue";
import Ceiling from "../components/ceiling.vue";
import sDialog from "@/services/sDialog";
//...other imports

@Component
export default class Settings extends Vue {
  someOpenDialogMethod() {
    sDialog.open({
      component: Tiles,
      keepAlive: true,
     //... other options like
    });
  }

  anotherOpenDialogMethod() {
    sDialog.open({
      component: Ceiling ,
      keepAlive: false, //or may be this boolean is dynamic
     //... other options like
    });
  }
}

A small generic functional component can easily achieve a conditional wrapper element:

// conditional-wrapper.js

export default {
  functional: true,
  props: {
    tag: {
      type: String,
      default: 'DIV'
    },
    show: Boolean,
  },
  render(h, ctx) {
    return this.show
      ? h(ctx.props.tag, ctx.data, ctx.children)
      : this.slots().default[0]
  }
}
<conditional-wrapper :tag="keep-alive" :show="myCondition">
  <component :ref="dialogOptions.compId" :is="dialogOptions.component" v-bind="dialogOptions.props">
        <div slot="header" slot-scope="headerProps">
            <v-toolbar dark color="primary">
                <v-toolbar-title class="white--text">{{ headerProps.title }}</v-toolbar-title>
                <v-spacer></v-spacer>
                <v-tooltip v-show="headerProps.fullscreenButton" :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.fullscreen = !dialogOptions.fullscreen" icon>
                        <v-icon v-show="dialogOptions.fullscreen">fullscreen_exit</v-icon>
                        <v-icon v-show="!dialogOptions.fullscreen">fullscreen</v-icon>
                    </v-btn>
                    <span>{{ dialogOptions.fullscreen?'Fullscreen off':'Fullscreen on' }}</span>
                </v-tooltip>
                <v-tooltip :close-delay="0" bottom>
                    <v-btn slot="activator" @click="dialogOptions.dialogRef.close()" icon>
                        <v-icon>close</v-icon>
                    </v-btn>
                    <span>Close</span>
                </v-tooltip>
            </v-toolbar>
        </div>
    </component>
</conditional-wrapper>

@LinusBorg thanks man! That was a cool solution!! 馃槑

Was this page helpful?
0 / 5 - 0 ratings

Related issues

paceband picture paceband  路  3Comments

robertleeplummerjr picture robertleeplummerjr  路  3Comments

Jokcy picture Jokcy  路  3Comments

julianxhokaxhiu picture julianxhokaxhiu  路  3Comments

gkiely picture gkiely  路  3Comments