← Teacher guide

Values worked out from data: computed

Students predict a ticket total after two clicks, trace the total and a v-if message, find a plain calculation that goes stale, add a people count and a group deal, and make a recipe scaler.

Apps with Vue · Building apps · about 50 minutes · Open the student lesson

Classroom resources

Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.

Students will be able to

Timing

Predict and Run 10 min · Investigate 15 min · Modify 10 min · Make 15 min

Before class

Read the unit primer first. computed(() => adults.value * 12 + children.value * 5) gives a value Vue works out again whenever the refs it reads change. On the page it is used like a ref ({{ total }}, v-if="total >= 50"). The arrow () => is a short function that gives back what follows it; students do not need more about arrow functions than that.

The planted bug is the idea the course names as hard: a value derived from data must be computed, or it is worked out once when setup runs and then goes stale. The page shows no error. A useful contrast: a ref is data you set; a computed is data you never set, only work out.

Prices are invented for the example.

Running the lesson

  1. Predict: ask students to say how the total is worked out before they choose.
  2. Run: ask what makes the free programme message appear and disappear.
  3. Investigate: trace on paper first, then check by clicking.
  4. Modify: for the group deal, ask where the if goes (inside the computed) and what it must give back.
  5. Make: ask which data everything else is worked out from.

The hard part (on purpose)

Students make the total a ref and update it in every button. Ask "what is the total worked out from?" and "what if someone adds a button and forgets?"

When students get stuck

What you will seeA question to askThe fix
The total never changes.Is it wrapped in computed?const total = computed(() => …);
A red error: computed is not defined.Is computed in the line that takes things from Vue?const { createApp, ref, computed } = Vue;
The total shows NaN or [object Object].Inside computed, did every ref get its .value?adults.value * 12, not adults * 12.
A computed with an if shows nothing.Does the function return the value at the end?Finish with return sum;

Answers to the checks

  1. Predict: You press Adults + twice. What total does the page show?
    $53: four adults at $12 is $48, plus one child at $5. computed works the total out again after each click.
  2. Trace the page: Start with 2 adults and 1 child. Fill in the page after each click.
    3, 1, 41, no, 3, 2, 46, no, 4, 2, 58, yes
    The total is worked out again after every click, and the v-if tests the new total each time.
  3. Why computed?: Why is the total a computed and not a ref of its own?
    It is worked out from adults and children, so it should follow them, never be set by hand
    A ref of its own would need every button to remember to change it too. A computed cannot go out of step: Vue works it out from the data.
  4. Spot the bug: Clicking Adults + changes the number of adults, but the total stays at $29. There is no error. Click the line that is wrong.
    line 14
    This total is a plain calculation: it is worked out once, at the start, and never again. Wrap it in computed(() => …).
  5. Where this leads: A quiz page shows twenty question cards, each with a title, choices and a score. What would save repeating the card's code twenty times?
    A card you make once, used twenty times with different data
    Next lesson: components. You make a tag of your own once, then use it as often as you like, each copy with its own data.

Solutions

One possible solution for each task. Other correct programs are fine.

Modify › Count the people

<div id="app">
  <h1>Concert tickets</h1>
  <p>Adults ($12): {{ adults }}
    <button @click="adults++">+</button> <button @click="adults--">−</button></p>
  <p>Children ($5): {{ children }}
    <button @click="children++">+</button> <button @click="children--">−</button></p>
  <p>People: {{ people }}</p>
  <p><b>Total: ${{ total }}</b></p>
  <p v-if="total >= 50">A free programme comes with your tickets!</p>
</div>

<script src="vue.js"></script>
<script>
  const { createApp, ref, computed } = Vue;
  createApp({
    setup() {
      const adults = ref(2);
      const children = ref(1);
      const people = computed(() => adults.value + children.value);
      const total = computed(() => adults.value * 12 + children.value * 5);
      return { adults, children, people, total };
    }
  }).mount("#app");
</script>

Modify › A group deal

<div id="app">
  <h1>Concert tickets</h1>
  <p>Adults ($12): {{ adults }}
    <button @click="adults++">+</button> <button @click="adults--">−</button></p>
  <p>Children ($5): {{ children }}
    <button @click="children++">+</button> <button @click="children--">−</button></p>
  <p><b>Total: ${{ total }}</b></p>
  <p v-if="total >= 50">A free programme comes with your tickets!</p>
</div>

<script src="vue.js"></script>
<script>
  const { createApp, ref, computed } = Vue;
  createApp({
    setup() {
      const adults = ref(2);
      const children = ref(1);
      const total = computed(() => {
        let sum = adults.value * 12 + children.value * 5;
        if (adults.value + children.value >= 6) {
          sum = sum - 10;
        }
        return sum;
      });
      return { adults, children, total };
    }
  }).mount("#app");
</script>

a recipe scaler

<div id="app">
  <h1>Pancakes</h1>
  <p>Pancakes: {{ pancakes }}
    <button @click="pancakes++">+</button> <button @click="pancakes--">−</button></p>
  <ul>
    <li>Flour: {{ flour }} g</li>
    <li>Milk: {{ milk }} ml</li>
    <li>Eggs: {{ eggs }}</li>
  </ul>
</div>

<script src="vue.js"></script>
<script>
  const { createApp, ref, computed } = Vue;
  createApp({
    setup() {
      const pancakes = ref(4);
      const flour = computed(() => pancakes.value * 25);
      const milk = computed(() => pancakes.value * 40);
      const eggs = computed(() => Math.ceil(pancakes.value / 4));
      return { pancakes, flour, milk, eggs };
    }
  }).mount("#app");
</script>

Stretch and support

Stretch: Stop the numbers going below 0 (a computed can't do that: change the buttons, or use Math.max). Add a computed message that says how far from $50 the order is.

Support: Give the people computed written out; students only return and show it.

Exit ticket

Why does a total made with computed stay right, when a plain calculation in setup does not?

Vue works a computed out again whenever the data it uses changes; a plain calculation runs once, when the page starts.

What each step is for

Where it leads

Next lesson: components with props. Class idea: name three values on a shopping site that are computed (total, item count, delivery cost).

Sources