MAD Study Hub

Home / MAD2 / Week 6

Week 6 · Advanced Vue: components, props, events and Vue Router

Official syllabus line: Advanced Vuejs
About 110 minQuiz 2 material (see exam radar)BSCS2006 (+ project BSCS2006P) · Thejesh G N, Nitin Chandrachoodan
Before you start: MAD2 week 4 (Vue instance, directives, lifecycle) and week 5 (calling the API). Week 7 (Vuex) comes right after this one.

The one-minute story

Think of it like this

Components are Lego bricks, the router is the signboard, and props and events are the parent and child talking. A big page (a dashboard) is not one huge block. You build small bricks: a navigation bar, a card, a button. Each brick has its own look and its own little notebook of data. You can use the same brick many times.

Bricks talk in only two ways, like a parent and a child at home. The parent gives the child things (props: "here is the name to show"). The child cannot open the parent's cupboard; if it wants something changed, it calls out (events: "I was clicked!") and the parent decides. Props down, events up.

The router is the signboard at a railway station. It does not build new pages from the server; it just decides which brick sits in the big empty frame (router-view) for the address in the URL. You press a link, the address changes, and a different brick takes the frame, with no full page reload.

By the end of this week you can:

  • create components (global and local), pass props with validation, and send events back with $emit;
  • use slots (default, named, scoped), dynamic components and keep-alive;
  • read and write a single file component (.vue) and set up a Vue CLI project;
  • configure Vue Router 3: routes, router-link, router-view, dynamic segments, query, named routes, nested routes, guards, lazy loading;
  • predict what a component tree or a router configuration renders for a given URL;
  • state the lifecycle order for a parent and its child.

Words you need

Words you need
  • Component - a reusable piece of UI with its own template, data and logic.
  • Global / local registration - global: Vue.component("name", {...}) works everywhere; local: listed in a component's components option, works only inside that one.
  • Prop - data a parent passes to a child (:title="x"); read-only in the child.
  • Custom event - a message from child to parent: this.$emit("name", payload), heard with @name="handler".
  • Slot - a hole in a child's template that the parent fills with its own HTML.
  • Single file component (SFC) - a .vue file with template, script and style parts, compiled by a build tool.
  • Vue CLI - the command-line tool that creates and builds Vue projects (vue create).
  • Vue Router - the official router library; version 3 goes with Vue 2.
  • Route - a mapping from a URL path to a component.
  • Dynamic segment - a path part such as :id that matches any value; read it as $route.params.id.
  • Navigation guard - a function that runs before a route change and can allow, redirect or cancel it.
  • Mixin - an object of options merged into a component to share code.
  • Plugin - a library installed with Vue.use() (Vue Router and Vuex are plugins).
  • provide / inject - an ancestor offers a value and any descendant picks it up, skipping the levels between.

Idea 1: Components: global and local

A component is almost a small Vue instance, with two differences: it has no el, and data must be a function that returns an object (so each copy gets its own data, and a plain object triggers a warning I reproduced).

<div id="app">
  <hello-card></hello-card>
  <hello-card></hello-card>
</div>
<script>
  // Global registration: available in every template
  Vue.component("hello-card", {
    data() { return { likes: 0 }; },
    template: `<div><button @click="likes++">Like</button> {{ likes }}</div>`,
  });
  new Vue({ el: "#app" });
</script>

The page shows two independent buttons, because data() runs once per copy. If data were an object { likes: 0 } all copies would share it.

// Local registration: only the registering component can use it
const NavBar = { template: `<nav>Home | About</nav>` };
const Page = {
  components: { NavBar },
  template: `<div><nav-bar></nav-bar><p>Content</p></div>`,
};
new Vue({ el: "#app", components: { Page }, template: `<page></page>` });
The idea

Global versus local

Global Vue.component(...)Local components: {...}
Where usableany instance and component templateonly in the component that lists it
Must be donebefore the root instance is createdin that component's options
Downsidealways bundled, even if unusednone; clearer and preferred

Naming: register as NavBar or "nav-bar"; in an in-DOM HTML template always write kebab-case tags (<nav-bar>), because HTML is case-insensitive. In string templates both work.

Solved example: 1. What does the page show? (registration)
Vue.component("NavBar", { template: "<p>This is navbar</p>" });
const Model = { template: "<p>This is model</p>" };
new Vue({ el: "#app", template: "<nav-bar></nav-bar>" });

Answer: This is navbar. NavBar is global and used. Model is just a JavaScript object that is never registered or used, so it never renders. A related real question: Batsman has components: { NavBar } and its template contains <nav-bar>; the root registers only Batsman. The root can use batsman, and Batsman can use nav-bar, but the root cannot use nav-bar directly.

Watch out - real exam trap
  • A local component is not inherited by its children: if A registers C and A uses B, then B cannot use C unless B registers it too.
  • A component that is defined but not registered (or registered but not used in a template) renders nothing.
  • data must be data() { return {...} } in a component. Two copies must not share the same object.

Idea 2: Props down

Props are the input of a component.

Vue.component("player-card", {
  props: {
    playerName: { type: String, default: "Unknown Player" },
    age: { type: Number, required: true },
    tags: { type: Array, default: () => [] },       // object/array defaults are functions
    level: { validator: (v) => ["a", "b"].includes(v) },
  },
  template: `<div><h2>{{ playerName }}</h2><p>Age {{ age }}</p></div>`,
});
<player-card player-name="Rahul" :age="21"></player-card>
<player-card :age="30"></player-card>
  • A camelCase prop (playerName) is passed with kebab-case in HTML: player-name="...".
  • age="21" passes the string "21"; :age="21" passes the number 21. Use v-bind (:) for anything that is not a string.
  • If the parent does not pass the prop, the default is used: the second card above shows Unknown Player. I ran a card like this: with no player-name it printed Unknown Player.
  • Short form: props: ["id", "name"] (no checks). Type checks only warn in the console; they do not stop the app. I reproduced Invalid prop: type check failed for a string passed to a Number prop.
The idea

One-way data flow. Data flows down from parent to child. A child must not change a prop: Vue warns "Avoid mutating a prop directly" (I reproduced this by doing n++ on a prop) because the parent's next re-render would overwrite it. To change something:

  • copy it to local state: data() { return { count: this.initial }; }, or
  • use a computed property for a transformed version, or
  • emit an event so the parent changes its own data (Idea 3).

If a prop is an object or array, the child can mutate its inside (it shares the reference), but that is bad style.

Solved example: 2. Default prop plus slot (a real exam shape)
<div id="app">
  <player-card>
    <p>Rahul is a versatile player, skilled in defense and attack.</p>
    <p>Position: Captain</p>
  </player-card>
</div>
<script>
  Vue.component("player-card", {
    props: { playerName: { type: String, default: "Unknown Player" } },
    template: `<div>
      <h2>{{ playerName }}</h2>
      <slot>This player is an important part of the team.</slot>
    </div>`,
  });
  new Vue({ el: "#app" });
</script>

The parent never passes player-name, so the heading is the default Unknown Player (not "Rahul"). The content between the tags replaces the slot's fallback text, so the fallback sentence disappears and the two paragraphs show. Rendered: Unknown Player, Rahul is a versatile player..., Position: Captain. (I ran this and got exactly that.) Both Position lines appear because all unnamed content goes to the default slot.

Your turn: 1. Props or data?

A star-rating component receives value (1 to 5) from the parent and has a button that adds one. What is wrong with methods: { more() { this.value++; } } and how do you fix it?

Need a hint?

Who owns value?

Show the full solution

It mutates a prop. Either emit an event, this.$emit("input", this.value + 1), so the parent updates its own data, or keep a local copy data() { return { current: this.value }; } and increment current.

Idea 3: Events up and v-model on components

The child calls this.$emit("eventName", payload). The parent listens with @eventName="handler".

<div id="app">
  <p>{{ message }}</p>
  <child-comp @message="updateMessage"></child-comp>
</div>
<script>
  Vue.component("child-comp", {
    template: `<button @click="send">Click</button>`,
    methods: {
      send() { this.$emit("message", "Hello"); },
    },
  });
  new Vue({
    el: "#app",
    data: { message: "Waiting..." },
    methods: { updateMessage(msg) { this.message = msg; } },
  });
</script>

Clicking the button changes the text from Waiting... to Hello (tested). The payload is passed as the handler's first argument. Both of these are correct ways (a real MSQ):

  • @message="updateMessage" with this.$emit("message", "Hello") (handler receives the payload);
  • @message="updateMessage('Hello')" with this.$emit("message") (the parent supplies the argument in the inline call).

Wrong: this.emit(...) (it is $emit), :message="..." (that is a prop, not a listener), or an event name that differs from the @ name (names must match exactly, including case).

v-model on a component

In Vue 2, v-model on a component is shorthand for a prop named value and an event named input:

Vue.component("my-input", {
  props: ["value"],
  template: `<input :value="value" @input="$emit('input', $event.target.value)">`,
});
<my-input v-model="name"></my-input>

Typing in the box changes the parent's name (tested). The correct way to do "two-way binding in a custom component" is v-model with a prop and an emit; not mutating the prop, not a plain v-bind. There is also :title.sync="x", which pairs the prop title with an event update:title.

In newer Vue 3: v-model uses the prop modelValue and the event update:modelValue; components can declare emits: [...].

The idea

Parent and child talk: props down, events up.

DirectionMechanismExample
parent to childprops<child :name="user.name">
child to parenteventsthis.$emit("saved", item)
ancestor to deep descendantprovide / inject (Idea 7)provide: { theme }
unrelated componentsshared state: Vuex (week 7)$store
parent peeks at childref and this.$refs.childuse sparingly
Your turn: 2. Wire a counter

Write a counter-button child that shows count from a prop and, when clicked, asks the parent to increase it by 1. Show the parent.

Need a hint?

Child: no mutation, emit instead.

Show the full solution
Vue.component("counter-button", {
  props: ["count"],
  template: `<button @click="$emit('increment')">Count: {{ count }}</button>`,
});
new Vue({
  el: "#app",
  data: { total: 0 },
  template: `<counter-button :count="total" @increment="total++"></counter-button>`,
});

The child only displays and announces; the parent owns total.

Idea 4: Slots

A slot lets the parent decide part of the child's markup. Content in the parent's template is compiled with the parent's data.

Vue.component("data-panel", {
  template: `
    <div>
      <header><slot name="header">Default title</slot></header>
      <main><slot>Default body</slot></main>
      <footer><slot name="footer"></slot></footer>
    </div>`,
});
<data-panel>
  <template v-slot:header><h1>Navigation Menu</h1></template>
  <p>Main body text</p>
  <template v-slot:footer>Last updated: Today</template>
</data-panel>
  • Default slot (<slot>): receives all content that is not in a named template. Text inside <slot>...</slot> is fallback shown only if the parent provides nothing.
  • Named slot (<slot name="header">): filled by <template v-slot:header> (shorthand #header). Old syntax: slot="header" attribute (Vue 2.5 and before; still works).
  • Scoped slot: the child passes data up into the slot so the parent decides how to show each item:
Vue.component("item-list", {
  props: ["items"],
  template: `<ul><li v-for="i in items"><slot :item="i">{{ i }}</slot></li></ul>`,
});
<item-list :items="[1, 2]">
  <template v-slot:default="sp">#{{ sp.item }}</template>
</item-list>

renders <ul><li>#1</li><li>#2</li></ul> (tested). Without a slot template, the fallback {{ i }} would print 1 and 2.

Watch out - real exam trap

What disappears. I ran these cases:

  • A child without a <slot> throws away everything placed between its tags. <main-comp><nest-comp></nest-comp></main-comp> where main-comp's template is just <p>This is main Component</p> renders only This is main Component: the nested component never appears (a real exam question).
  • Content for a named slot that the child does not define is silently dropped. If the child has header, the default slot and actions but no footer, then Last updated: Today placed in v-slot:footer is not rendered. A real paper's output is Navigation Menu, Main Dashboard..., Extra content outside templates, Save Changes Cancel, without the "Last updated" line, for this reason.
  • Unnamed content outside any template goes to the default slot.

Idea 5: Dynamic components and keep-alive

<component :is="name"> renders whichever component name currently refers to: a tab view without a router.

new Vue({
  el: "#app",
  data: { current: "tab-a" },
  components: {
    "tab-a": { template: "<p>A <input></p>" },
    "tab-b": { template: "<p>B</p>" },
  },
  template: `<div>
      <button @click="current = 'tab-a'">A</button>
      <button @click="current = 'tab-b'">B</button>
      <keep-alive><component :is="current"></component></keep-alive>
    </div>`,
});
  • Switching destroys the old component and creates the new one, so typed text in A's input is lost.
  • Wrapping in <keep-alive> keeps the inactive component alive in memory with its state. I tested: created runs once, switching away runs deactivated, switching back runs activated, and created does not run again.
  • keep-alive also works around <router-view> to keep a page's state while navigating away.

Idea 6: Single file components and the Vue CLI

In a real project, components live in .vue files:

<!-- src/components/TaskItem.vue -->
<template>
  <li :class="{ done: task.done }" @click="$emit('toggle', task.id)">
    {{ task.title }}
  </li>
</template>

<script>
export default {
  name: "TaskItem",
  props: { task: { type: Object, required: true } },
};
</script>

<style scoped>
.done { text-decoration: line-through; }
</style>
  • template: exactly one root element in Vue 2.
  • script: export default { ... }, the options object (name, props, data, methods...).
  • style: CSS. scoped makes the rules apply only to this component's elements (Vue adds a unique data-v-xxxx attribute to them).
  • A browser cannot run a .vue file. It must be compiled by a tool (vue-loader in Vue CLI, or Vite). You cannot load a .vue file in a script tag. Real MSQ: ".vue files typically contain template, script and style" is true; "must be compiled before they run in the browser" is true; "can be imported directly without preprocessing" is false.

Using it:

<script>
import TaskItem from "./components/TaskItem.vue";
export default {
  components: { TaskItem },
  data() { return { tasks: [{ id: 1, title: "Learn", done: false }] }; },
  methods: { toggle(id) { const t = this.tasks.find((x) => x.id === id); t.done = !t.done; } },
};
</script>
<!-- in the template: -->
<!-- <ul><task-item v-for="t in tasks" :key="t.id" :task="t" @toggle="toggle"></task-item></ul> -->

Vue CLI

npm install -g @vue/cli        # once
vue create my-app              # choose Vue 2 (and Router if you want it)
cd my-app
npm run serve                  # dev server with hot reload, http://localhost:8080
npm run build                  # production files in dist/

Project layout: public/index.html (the page with div id="app"), src/main.js (creates the root instance), src/App.vue (root component), src/components/, src/views/ (page-level components), src/router/index.js, package.json (scripts and dependencies), node_modules/ (never edit), vue.config.js (optional settings, such as the proxy from week 5).

// src/main.js (Vue 2)
import Vue from "vue";
import App from "./App.vue";
import router from "./router";

new Vue({
  router,
  render: (h) => h(App),
}).$mount("#app");

render: h => h(App) is used because the CLI build has no template compiler at run time. Vite is the newer, faster tool (Vue 3 projects usually use it) and Vue CLI is in maintenance mode, but this course uses Vue CLI and Vue 2.

Idea 7: Mixins, plugins and provide / inject at a glance

Mixin. An object of options merged into a component, to share code.

const logMixin = {
  created() { console.log("mixin created"); },
  methods: { hello() { return "hi"; } },
};
export default { mixins: [logMixin], created() { console.log("component created"); } };

Both hooks run; the mixin's hook first, then the component's. If a name clashes (a method with the same name), the component's own wins.

Plugin. A library installed once with Vue.use(plugin, options). A plugin is an object with an install(Vue, options) function that can add global components, directives or Vue.prototype.$something. Vue.use(VueRouter) and Vue.use(Vuex) are exactly that. (With the CDN scripts of Vue and Vue Router 3, the router installs itself.)

provide / inject. A far ancestor offers; any descendant takes, without passing props through every level.

// ancestor
provide: { theme: "dark" },
// any descendant
inject: ["theme"],      // then use this.theme  (shows "dark", tested)

It is not reactive by default for plain values; use it for configuration. For shared changing state use Vuex.

Idea 8: Vue Router 3

Vue Router maps URLs to components in a single-page app. Install npm install vue-router@3 (for Vue 2) or use the CDN script, which gives VueRouter.

const Home = { template: "<p>Home</p>" };
const User = { template: "<p>User {{ $route.params.id }}</p>" };

const router = new VueRouter({
  routes: [
    { path: "/", component: Home },
    { path: "/user/:id", name: "user", component: User },
  ],
});
new Vue({ el: "#app", router });
<div id="app">
  <router-link to="/">Home</router-link>
  <router-link :to="{ name: 'user', params: { id: 7 } }">User 7</router-link>
  <router-view></router-view>
</div>
  • router-link renders an a tag (href="/user/7") and navigates without reloading. The active link gets the classes router-link-active and router-link-exact-active.
  • router-view is the frame where the matched component is shown.
  • Routes are tried in the order written; the first match wins. Put the catch-all { path: "*", component: NotFound } last.
  • Inside any component: this.$route is the current route (read it); this.$router is the router (use it to navigate). Mixing them up is a common wrong option (this.$router.params is wrong; this.$route.params.username is right).

Params, query and named routes

Part of the URLRoute configRead it
dynamic segment /user/7path: "/user/:id"this.$route.params.id (a string, "7")
query string /search?q=vue&page=2none neededthis.$route.query.q
hash #topnonethis.$route.hash

A real question: message() { return this.$route.query.update ? "This is create page" : "This is update page" } at /#/?update=true. The query value is the string "true", truthy, so it gives This is create page. Even ?update=false is the string "false", which is also truthy (I checked: it prints the "create" text too); only a missing query gives "update".

Props from the route. { path: "/dashboard/:id", component: Dashboard, props: true } passes :id to the component as a prop id (still a string). With id "8" and a lookup that falls back to the last user when none matches, /dashboard/8 shows This is dashboard for User3 (a real question; the compare uses == between a number and a string, so "1" matches 1).

Named routes. name: "settings" on the route lets you navigate by name: { name: "settings" }. In a real question, the answer was { "name": "Settings" }: the route's name (not the component's name, and path: "Settings" would be missing the slash and capital mismatch).

Programmatic navigation

this.$router.push("/user/7");
this.$router.push({ path: "/search", query: { q: "vue" } });   // /search?q=vue
this.$router.push({ name: "user", params: { id: 7 } });
this.$router.replace("/login");   // like push, but no new history entry
this.$router.go(-1);              // one step back in history (like the Back button)

push adds a history entry, replace does not. In Vue Router 3.1+ push returns a promise that rejects with NavigationDuplicated if you push the route you are already on (I reproduced it); add .catch(() => {}) if that noise matters.

Nested routes

A route can have children. The parent component must contain its own router-view where the child appears. Child paths without a leading slash are relative to the parent.

const user = { template: "<div>Welcome {{ $route.params.name }}<router-view></router-view></div>" };
const home = { template: "<p>This is home page</p>" };
const profile = { template: "<p>This is profile page of {{ $route.params.name }}</p>" };

const routes = [
  { path: "/", component: home },
  {
    path: "/user/:name",
    component: user,
    children: [
      { path: "", component: home },
      { path: "home", component: home },
      { path: "profile", component: profile },
    ],
  },
];

I rendered these for different URLs (abstract mode, same logic):

URLRendered
/user/mohanWelcome mohan then This is home page (the child with path "" is the default)
/user/mohan/profileWelcome mohan then This is profile page of mohan
Solved example: 3. Nested route with a missing child (a real shape)
const routes = [
  { path: "/", component: Home },
  { path: "/dashboard", component: Dashboard,   // template: "Dashboard" + router-view
    children: [{ path: "stats", component: Stats }, { path: "settings", component: Settings }] },
  { path: "*", component: NotFound },
];
  • /dashboard/stats renders Dashboard followed by Stats Component: parent text first, child in the parent's router-view. (The real paper uses "**"; in my test it also acted as a catch-all, but write *.)
  • /dashboard alone renders only Dashboard: there is no child with path "", so the inner router-view is empty.
  • /zzz matches * and renders 404 Not Found.

Similarly: a route / with children profile and *, and a parent template This is header plus router-view: URL / renders This is header404 Page not found (the child * also matches /, tested), and /profile renders This is headerWelcome to profile page. The parent always shows, the child follows it.

A further real question: a root template with top-bar, nav-bar and router-view, and a route /video/:id with child comments/newest, at /video/5/comments/newest: Topbar and Navbar stay visible (they are outside the router-view), and Newest Comments appears inside Video Details.

Hash mode and history mode

hash (default)history
URLhttp://host/#/records/2http://host/records/2
Confignothingmode: "history"
Serverneeds nothing (the server only sees /)must return index.html for every path, or a refresh gives 404
Lookshas #clean URLs

Consequences I verified with a /records/:id route and a * fallback:

ModeURLResult
hash/#/records/2Pianobind has 13 songs...
hash/records/1 (no #)not the record: the router sees path /, so it matches * and shows Unknown record!
hash/#/records (no id)* matches: Unknown record!
hash/#/records/3route matches, but records[3] is undefined; record.albums throws; the component shows nothing and the console has an error. Not "Unknown record!"
history/inventory/1the product
history/#/inventory/1the hash is ignored; path is /, so * shows Item not found
history/inventory/3route matches, item undefined: error and blank, not the not-found page

The lesson: * only catches URLs that no route matches. An id that matches the pattern but has no data must be handled in your component (v-if="record").

Same component, changing parameter

Going from /u/1 to /u/2 reuses the same component instance, so created and mounted do not run again. I logged: created 1, then on navigating beforeRouteUpdate 2 and a watcher 1->2. React with a watcher or the in-component guard:

data() { return { item: inventory[this.$route.params.productId] }; },
watch: {
  $route(to, from) { this.item = inventory[to.params.productId]; },     // as in the paper
  // or just the param:  "$route.params.productId"(id) { ... }
},

Another real question watches '$route.path', sets a heading and shows links with v-if. At first load none of the watch runs (a watch is not immediate), so the heading shows the initial data value (Vue Router Demo). It then changes on each route change. A computed property that reads $route.params also updates by itself, because $route is reactive.

A guard runs before the route changes: global router.beforeEach((to, from, next) => {...}), per-route beforeEnter, or in-component beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave. You must call next() (allow), next(false) (cancel) or next("/login") (redirect), exactly once in Vue Router 3.

router.beforeEach((to, from, next) => {
  const loggedIn = !!localStorage.getItem("token");
  if (to.meta.requiresAuth && !loggedIn) next("/login");
  else next();
});
// route: { path: "/profile", component: Profile, meta: { requiresAuth: true } }

I tested a guard with meta: { auth: true }: asking for /settings/7 ended at /login and the guard also ran again for the redirect target. meta is any extra data you attach to a route.

Lazy loading and other options

{ path: "/about", component: () => import("./views/About.vue") }   // loaded only when visited
{ path: "/old", redirect: "/new" }                                  // redirect
{ path: "/search", component: Search, props: (route) => ({ q: route.query.q }) }

Lazy loading splits the build into chunks, so the first page loads faster.

In newer Vue 3: Vue Router 4 uses createRouter({ history: createWebHistory(), routes }), useRoute(), useRouter(), and a catch-all is /:pathMatch(.*)*.

Idea 9: Lifecycle of a parent and child

When a parent contains a child, hooks interleave. I logged both with a mixin:

create:   parent beforeCreate, parent created, parent beforeMount,
          child beforeCreate, child created, child beforeMount,
          child mounted, parent mounted
update:   parent beforeUpdate, child beforeUpdate, child updated, parent updated
destroy:  parent beforeDestroy, child beforeDestroy, child destroyed, parent destroyed
  • Creation goes down (parent first), but mounted goes up: the child's mounted runs before the parent's. So in the parent's mounted the children are in the DOM.
  • A component's hooks are independent of the parent's: it has its own lifecycle (a real MSQ).
  • Typical use: load initial data in created(), start a timer or DOM work in mounted(), clear the timer in beforeDestroy(); destroyed() is after teardown, not for declaring data.
  • With keep-alive, activated and deactivated replace the destroy and create on switching.
Your turn: 3. Predict the router

Routes: { path: "/player/:name", component: Player, props: true, children: [{ path: "", component: OneDay }, { path: "test", component: Test }] } and { path: "*", component: Test }. Player shows Name: {{ name }} and a router-view; OneDay shows Oneday Runs: 10000; Test shows Test Runs: 5000. What do /#/player/Rohit, /#/player/Rohit/test and /#/player render?

Need a hint?

For the last one, does :name match nothing?

Show the full solution

/player/Rohit renders Name: Rohit then Oneday Runs: 10000 (empty child path). /player/Rohit/test renders Name: Rohit then Test Runs: 5000. /player has no name, so the /player/:name pattern does not match; the catch-all * shows Test Runs: 5000 only. (A real question had exactly this last case as its answer.)

In your project

In the viva, be ready to draw your component tree: which component is the view (one per route), which are reusable (card, navbar, form), what props each receives, and what events it emits. Show a navigation guard that sends a logged-out user to the login page, and be ready to explain why you used hash mode (no server setup) or history mode (Flask catch-all route).

Exam radar

Exam radar

The evidence file counts questions on this week's topics (components, props, events, router, lifecycle) as about 26 of 848 Quiz 1, 95 of 608 Quiz 2 and 125 of 1471 End Term questions (a keyword match, approximate), with about 75 distinct questions read. The router questions are by far the most repeated; the same nested-route problems come back across several exams. Typical shapes:

  • "What will the browser render for this URL" for nested routes (children, empty child path, parent text then child text), hash versus history URLs, * fallback, dynamic :id, route props: true, $route.query, and a route whose data lookup is undefined.
  • Components and slots: global versus local registration, unregistered components, default prop values, content inside a component without a slot, missing named slots.
  • Events: choose the correct @event and $emit pair; v-model on a component with a prop and an emit; "props pass data from child to parent" is false.
  • API facts: $route versus $router, named route navigation, $router.go(-1), * as a fallback, children for nested routes.
  • Lifecycle hooks in components: which hook for loading data (created), timers (mounted) and cleanup (beforeDestroy); beforeDestroy runs before teardown, destroyed after; data must be a function.
  • True or false: .vue files and compilation; advantages of components (reuse, maintainability, easier UI management; not "automatic SQL optimization"); a few Vuex questions (week 7).
Watch out - real exam trap
  • Child router-view needed inside the parent component, or the child never shows.
  • A child route path with a leading slash (/profile) is treated as absolute, not nested under the parent URL.
  • this.$route (read) versus this.$router (act).
  • * catches only URLs that no route matches; a matching URL with missing data fails in the component.
  • In hash mode the part after # is the route; in history mode the # part is ignored.
  • Props down, events up; props are never mutated by the child.
  • A route props: true delivers params as strings.

Quick quiz

Quick quiz

A child component's template is <p>This is main Component</p> (no slot). The parent writes <main-comp><nest-comp></nest-comp></main-comp> and both are registered. What is rendered?

  • This is main Component and This is nested Component
  • This is nested Component
  • This is main Component
  • nothing
With no slot in the child, content between its tags is discarded.

A route { path: "/", component: Header, children: [{ path: "profile", component: Profile }, { path: "*", component: Err }] } and Header renders This is header and a router-view. The URL is http://127.0.0.1:8080/ (hash mode). What shows?

  • This is header only
  • Welcome to profile page
  • This is header followed by 404 Page not found
  • 404 Page not found followed by This is header
The child * also matches the root path; the parent text comes first, then the child inside its router-view.

In a Vue 2 component, which is the right way to read username from the URL /user/:username?

  • this.$router.params.username
  • this.$route.params.username
  • this.$router.query.username
  • this.$route.query.username
$route holds the current route; dynamic segments are in params, the query string in query.

A child must update the parent's message to "Hello" when a button is clicked. Select correct pairs. (select all that apply)

  • parent @message="updateMessage", child this.$emit("message", "Hello")
  • parent @message="updateMessage('Hello')", child this.$emit("message")
  • parent :message="updateMessage", child this.emit("message", "Hello")
  • parent @updateMessage="message", child this.$emit("updateMessage")
The event name must match on both sides and you need $emit; : is a prop, not a listener.

The component is props: ["id"] with a lookup that returns the third user when no user matches, the route is /dashboard/:id with props: true, and the URL is /#/dashboard/8. What shows?

  • This is dashboard for User1
  • This is dashboard for User2
  • This is dashboard for User3
  • nothing
id is "8", no user has that id, and the fallback returns the third user.

Which statements about .vue files are true? (select all that apply)

  • They usually contain template, script and style sections.
  • They must be compiled (Vue CLI or Vite) before running in a browser.
  • JavaScript can import and run them with no preprocessing.
  • They can be loaded with a plain script tag.
Browsers do not understand .vue; a build tool turns it into JavaScript and CSS.

For parent P and child C (child inside the parent's template), in what order do the mounted hooks run?

  • parent then child
  • child then parent
  • only the parent
  • they run in parallel
The parent can only finish mounting after its children are mounted.

The URL http://localhost:8080/#/records/3 matches /records/:id, but the data lookup records[3] is undefined and the template reads record.albums. What does the page show?

  • Unknown record! from the * route
  • nothing, and an error appears in the console
  • the first record
  • a 404 from the server
The route matched, so * is not used; reading a property of undefined throws during rendering.

Viva: say it out loud

Viva: say it out loud
Why do we split an app into components?

Each component is a reusable unit with its own template, data and logic. It makes the code easier to read, test and change, and I can use the same card or navbar in several places. My page becomes a tree of small components.

How do components communicate?

Props go from parent to child, and events go from child to parent with $emit. For distant components I use provide and inject, or Vuex for shared state. A child never changes a prop; it emits an event and the parent updates its own data.

What is the difference between global and local registration?

A global component is registered with Vue.component and can be used in every template. A local one is listed in a component's components option and only that component can use it. Local is preferred because it is explicit and keeps the bundle clean.

What are slots and when do you use them?

A slot is a placeholder in the child that the parent fills with its own markup. I use named slots for layouts like a card with header and footer, and scoped slots when the child has data, like a list item, and the parent decides how to display it.

What is a single file component and what does scoped do?

A .vue file has template, script and style parts. A build tool (Vue CLI or Vite) compiles it. scoped on the style makes its CSS apply only to that component's own elements by adding a unique data attribute.

How does Vue Router work? Explain $route and $router.

The router maps URL paths to components, and router-view is where the matched component shows. router-link navigates without reloading. $route is the current route (params, query, path), which I read. $router is the router object I use to navigate, like this.$router.push({ name: "user", params: { id: 7 } }).

How do you protect a page so only logged-in users see it?

I mark the route with meta: { requiresAuth: true } and write a global router.beforeEach guard. If the route needs auth and there is no token, it calls next("/login"), otherwise next(). The backend still checks the token because front-end checks can be bypassed.

What is the difference between hash mode and history mode?

Hash mode puts the route after #, so the server only ever sees / and needs no setup. History mode gives clean URLs, but the server must return index.html for all paths, otherwise a page refresh on /about gives a 404.

Recap

Remember this
  • Components: Vue.component (global) or components: {} (local). data must be a function. Registered children are not inherited by grandchildren.
  • Props down (kebab-case in HTML, : for non-strings, validation with type, default, required; object and array defaults are functions); never mutate a prop. Events up with $emit. v-model on a component is value plus input.
  • Slots: default, named (v-slot:name), scoped. A child without a slot drops the content; content for an undefined named slot is dropped.
  • Dynamic components with :is; keep-alive keeps state and uses activated/deactivated.
  • SFC: template, script, style (scoped); needs Vue CLI or Vite. npm run serve and npm run build.
  • Router: routes, router-link, router-view, :id in $route.params, query in $route.query, named routes, push, replace, go(-1), children need a router-view in the parent, * last, guards with next(), lazy () => import(...), hash versus history.
  • Same component with a changed param is reused: use a watcher on $route.
  • Lifecycle: parent create, child create, child mounted, parent mounted; destroy is parent before, child, child destroyed, parent destroyed.
Code and facts to keep
  • Global: Vue.component("my-comp", { props: [...], data() { return {...}; }, template: "..." })
  • Local: components: { MyComp } (use <my-comp>)
  • Prop: props: { n: { type: Number, required: true, default: 0 } }; pass :n="5"
  • Event: child this.$emit("name", payload); parent @name="handler"
  • v-model on component: prop value, event input
  • Slots: <slot name="x">fallback</slot>; <template v-slot:x> (or #x); scoped <slot :item="i"> with v-slot:default="sp"
  • Dynamic: <component :is="name">, <keep-alive>
  • Router: new VueRouter({ mode: "history", routes: [...] }), { path: "/u/:id", name: "user", component: U, props: true, children: [...] }
  • Read: this.$route.params.id, this.$route.query.q; go: this.$router.push({ name: "user", params: { id: 1 } }), .replace(), .go(-1)
  • Guard: router.beforeEach((to, from, next) => { ... next() }); catch-all { path: "*", component: NotFound }
  • Lazy: component: () => import("./views/About.vue")
  • Vue CLI: vue create app, npm run serve, npm run build; Vue 3 router: createRouter, createWebHistory
Connected weeks (learn them together)

Practise with real exam questions

These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week   All Quiz 2 papers