vuex-aspect subscription example

by frissdiegurke

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Subscription vuex-aspect example</title>
  <script type="text/javascript" src="https://unpkg.com/vuex"></script>
  <script type="text/javascript" src="https://unpkg.com/vuex-aspect/dist/vuex-aspect.umd.js"></script>
</head>
<body>
<div id="app">
  <h2>Vuex Aspect Subscription Example</h2>
  <p>
    A server subscription push is simulated by a random timeout between 100ms and 2s. UserIDs <code>(x+2)%4 = 0</code>
    result in errors.
  </p>
  <div class="button-group">
    <button @click="incrementUser">Next UserID</button>
    <button @click="toggleConsumption">{{consume ? "Unmount consumer component" : "Mount consumer component"}}</button>
  </div>
  <table>
    <tbody>
    <tr>
      <td><pre><code>user$error</code></pre></td>
      <td><pre><code>{{user$error || "undefined"}}</code></pre></td>
    </tr>
    <tr>
      <td><pre><code>user</code></pre></td>
      <td><pre><code>{{user || "undefined"}}</code></pre></td>
    </tr>
    <tr>
      <td><pre><code>consumer</code></pre></td>
      <td><consumer v-if="consume"></consumer></td>
    </tr>
    </tbody>
  </table>
</div>

<script type="text/javascript" src="./script.js"></script>
</body>
</html>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

p {
  font-size: small;
  font-style: italic;
}

p code {
  font-size: medium;
}

.button-group {
  display: flex;
  margin: 1em 0;
}

.button-group > button {
  flex-grow: 1;
  flex-basis: 50%;
  margin: 0 0.5em;
}

table {
  width: 100%;
}

tr {
  border-bottom: 1px solid #e1e1e1;
}

tr:last-child {
  border-bottom: none;
}

td {
  width: 50%;
  padding: 0.2em 1em;
}

pre {
  background: #005fa533;
  border: 1px solid #005fa533;
  padding: 0.1em 0.2em;
  margin: 0.1em 0.2em;
}

Vue

// aspect definition
const myUserAspect = new VuexAspect.Aspect({
  variables({state}) { return {id: state.userId}; },
  supply({variables, isInitial}) {
    isInitial || unsubscribe();
    subscribe(variables, (err, data) => { this.inject(data, err); });
  },
  disable: unsubscribe,
});

// consumer component
const consumer = Vue.component("consumer", {
  mixins: [myUserAspect.mixin],
  template: "<pre><code>nom nom nom...</code></pre>",
});

// main component
new Vue({
  el: "#app",
  components: {consumer},
  methods: Vuex.mapMutations(["toggleConsumption", "incrementUser"]),
  computed: Vuex.mapState(["consume", "userId", "user", "user$error"]),
  store: new Vuex.Store({
    plugins: [VuexAspect.install],
    state: {userId: 0, consume: false},
    aspects: {user: myUserAspect},
    mutations: {
      toggleConsumption(state) { state.consume = !state.consume; },
      incrementUser(state) { state.userId = state.userId + 1; },
    },
  }),
});

// simulated server (while subscribed push one result every 100ms to 2s)

let timeout = null;
let count = 0;

function subscribe(variables, next) {
  timeout = setTimeout(response, 100 + Math.random() * 1900);

  function response() {
    if ((variables.id + 2) % 4 === 0) {
      next({count, status: 404, message: "User #" + variables.id + " does not exist."});
    } else {
      next(null, {count, id: "user-" + variables.id, name: "Jon Doe " + variables.id});
    }
    count++;
    timeout = setTimeout(response, 100 + Math.random() * 1900);
  }
}

function unsubscribe() { clearTimeout(timeout); }