5. Chapter 4 - Introduction to [Vue.js]
5.1. Sources
This chapter is based on the official documentation for [Vue.js]: vuejs.org, and in particular its guide (vuejs.org/guide/introduction.html).
5.2. From AngularJS 1.x to [Vue.js]
The original document (2014) used AngularJS 1.x: a framework (not yet TypeScript) organized around $scope, controllers, directives (ng-repeat, ng-if…), and a change detection mechanism based on the “digestion loop ” ($digest) and variable monitoring ($watch).
[Vue.js] (created by Evan You in 2014, following an experience at Google working on AngularJS) is, like [Angular], a full-fledged framework: unlike [React] (a simple interface library), it provides its own component system, reactive state management, and—via its separate official packages, which are not used in this document—a router ([vue-router]) and a global state manager ([Pinia]). [Vue.js] describes itself as “progressive”: a core focused on the view layer (similar in concept to from the $scope/template of AngularJS 1.x), supplemented by its own component format—the [.vue] files (see below). This chapter presents these concepts one by one, with short examples independent of the case study—the following chapter will explore their application in the complete [RdvMedecins] client, in [Vue.js] 3.
5.3. The Concept Behind a [Vue.js] Application: the “Single Page Application”
A [Vue.js] application is, like a [Angular] or [React] application, a single-page application (SPA): the server delivers only a single HTML page to the browser, containing a single JavaScript file that embeds all of the application’s logic. Once this page is loaded, there is never another full page reload: every user interaction (changing views, submitting a form, etc.) is handled by the JavaScript code already present in the browser, which updates the display and communicates with the server in the background via asynchronous HTTP requests (most often using JSON). This was already the principle behind the original document with AngularJS 1.x, as well as the [Angular] and [React] variants in this course—it hasn’t changed: only the framework that handles the display differs from one variant to another.
5.4. The components: files [.vue] and [Composition API]
As in [Angular] and [React], a component is the basic unit of a [Vue.js] application: it generates what is to be displayed on the screen based on its current state and the properties it receives. The difference lies in the form: a [Angular] component is a decorated class, associated with a separate (or inline) HTML template; a [React] component is a function that returns [JSX]; a [Vue.js] component is a “single-file” [.vue] file, which combines three distinct blocks into a single file: a [<template>] (the HTML, similar to that of [Angular]), a [<script setup>] (the logic, as in TypeScript), and, optionally, a [<style>] (the styles specific to this component).
Here is a very small component, independent of the case study, equivalent to [CounterComponent] ([Angular]) and [Counter.tsx] ([React]), which have already been presented in this course:
Let’s break down this code:
- Line 3: [import { ref } from 'vue';] — [ref] is the function [Vue.js], which creates a reactive value: a role comparable to that of a signal on the [Angular] side, or of [useState] on the [React] side;
- Line 5: [const value = ref(0);] — [ref(0)] creates a reactive "box" initialized to 0. Unlike [useState]() ([React]), which returns a PAIR (the value and a setter), [ref()] returns a SINGLE object, whose value is read and written via its [.value] property —a mechanism more similar, in concept, to a [signal()] [Angular] (which is read using [mySignal()] and written using [mySignal.set(...)]) than a [value, setValue] pair;
- line 8: [value.value++;] — modifying [.value] updates the value AND automatically redraws everything that depends on it in [<template>] - exactly as with [mySignal.update(v => v + 1)] relative to [Angular]; unlike [React] (where state values are immutable), nothing here prevents [.value] from being incremented directly (++), without going through a separate update function;
- lines 12–13: [Value: {{ value }}] — [<template>] IS the template, just as HTML is separate from [Angular] - unlike [JSX] and [React], which are derived from JavaScript and are intermixed with the component function’s code. The double curly braces [{{ }}] insert an expression there—the same role as [{{ value() }}] relative to [Angular] ([Vue.js] does NOT require call parentheses ([value], not [value()]): in a [<template>], a [ref] is automatically “unpacked”; there is never a need to write [.value]).
5.5. The local state: [ref]/[reactive] and the props
In AngularJS 1.x, [Angular] detected that a piece of data had changed using a systematic check loop ($digest), which was replaced in the [Angular] variant of this course by signals (signal(), computed()). [React] takes a different approach: a component fully re-executes its function with every state change. [Vue.js] takes a third, hybrid approach: like [Angular], it precisely tracks the reactive values a component uses (thanks to the compiled [<template>] and the “proxy” system of [ref]/[reactive]), and updates only those parts of DOM that actually depend on them—without ever re-executing the entire [<script setup>], and without the cost of the virtual DOM used by [React] to achieve a comparable result.
Function | Role |
creates a local state with a SINGLE value; read/written via [.value] in [<script>] (never in [<template>]) | |
creates a local state with SEVERAL properties (an object); read/written directly, WITHOUT [.value], property by property | |
derives a value from other [ref]/[reactive], which is automatically recalculated only when its dependencies change—the direct equivalent of [computed()] on the [Angular] side, and of [useMemo]() on the [React] side | |
re-executes [callback] whenever [source] changes—the equivalent of [useEffect](..., [dep]) on the [React] side, or of [effect()] on the [Angular] side |
Two mechanisms complement [ref]/[reactive] to enable a component to communicate with its parent—these are consistently found in the [RdvMedecins] client (next chapter), exactly where [input()]/[output()] were located on the [Angular] side, and the props/callback props on the [React] side:
- the props ([defineProps<...>()]): A [Vue.js] component declares the format of its inputs using [defineProps], a special function recognized by the compiler (it does not require any [import]) - the direct equivalent of [input.required<Doctor[]>()] on the [Angular] side, and of a destructured props object on the [React] side;
- events ([defineEmits<...>()]): rather than a simple function passed as a prop (the choice of [React]), [Vue.js] explicitly distinguishes the events that a component emits—the function [emit(‘eventName’, value)] that it returns is the direct equivalent of [search.emit(value)] on the [Angular] side ([output<...>()]).
5.6. No modules: each file is already self-contained
A AngularJS 1.x application was organized into modules (angular.module(...)). [Angular] first adopted this idea in a more rigid form (the NgModule files), before proposing standalone components as the default mode. Like [React], [Vue.js] never needed this concept: a [.vue] file implicitly exports its component (the [<script setup>] IS the file’s default export, without even needing to specify the [export] keyword), and another file uses it with a simple [import]—the standard ECMAScript modules, without any additional framework-specific layers:
This is the style used throughout the [RdvMedecins] client (next chapter): no *.module.ts files, no [imports: [...]] decorators to maintain—a simplification identical to that already seen with [React], compared to AngularJS 1.x and [Angular] with NgModule.
5.7. Shared logic: composables and [provide]/[inject]
As in [NestJS] (Chapter 2), the [RdvMedecins] client requires logic shared among multiple components: communicating with the server, identifying the logged-in user, and switching languages. [Angular] addresses this need with injectable services ([@Injectable]({ providedIn: ‘root’ }) + inject(...)); [React] combines custom hooks with [Context]. [Vue.js] uses a combination similar to that of [React], but with its own vocabulary and mechanism:
- a composable—a function whose name, by convention, begins with [use] (like a hook [React]), which can itself call other functions [Vue.js] ([ref], [inject]…)—encapsulates reusable logic, just like a server-side service method [Angular];
- the pair [provide]/[inject] shares a value (state + functions) with ALL of the component subtree, without having to manually pass it down, property by property, through each intermediate level—the role played by [providedIn: ‘root’] on the [Angular] side, and by [Context] on the [React] side. Unlike [React], which requires a [Provider] component to be visually NESTED in the tree around the root component, [Vue.js] returns a value with a single function call, [app.provide(key, value)], on the application instance (see main.ts, next chapter)—without any component nesting required.
Here is a short example, independent of the case study, that is conceptually equivalent to [MyService] ([Angular]) and [my-context.tsx] ([React]), which have already been presented in this course:
Let’s break down this code:
- Line 8: [const MyStoreKey: InjectionKey<MyStore> = Symbol('MyStore');] — the injection key: a [Symbol] rather than a simple string, to avoid any accidental collision with another key of the same name - [InjectionKey<T>] is simply an alias for [Symbol] on the type side, which allows [inject()] to automatically determine the type of the returned value;
- Line 10: [export function createMyStore(): MyStore {] — the factory, called ONLY ONCE (in [main.ts]; see the next chapter), BEFORE any component exists—which is why it receives any dependencies as ordinary function parameters, rather than via [inject()];
- line 11: [return reactive<MyStore>({] — [reactive(...)] wraps the object in a JavaScript proxy: any read of one of its properties in a [<template>] automatically registers a dependency; any write operation redraws everything that depends on it—unlike [ref()], the property ([store.greet]) is read and written directly, without [.value];
- Lines 16–22: [export function useMyStore(): MyStore { …] — the composable that the components call ([const my = useMyStore();]), just as they would have called [inject(MyService)] from [Angular], or [useMyContext()] from [React]; the error raised if [inject(MyStoreKey)] returns [undefined] indicates a wiring omission (the corresponding [app.provide(...)] was not called) - exactly the same safety net as on the [React] side (Context is null by default).
5.8. Communicate with the server: [fetch]
[HttpClient] ([Angular]) was the direct successor to the $http service of AngularJS 1.x, used by the original document. Like [React], [Vue.js] is merely an interface framework and, at its core, does not provide any HTTP client: the [RdvMedecins] client directly uses [fetch], the browser’s standard API (available without any additional dependencies):
An important difference from $http and [HttpClient]: [fetch] directly returns a Promise (the standard asynchronous mechanism of JavaScript), not an Observable—no RxJS library, no [.subscribe]({ next, error }), no [.pipe(map(...))]. It can be used with [.then(...)], or, for better readability, with async/await (as shown above). A network error or an error response is then handled with a simple try/catch block, rather than with the error branch of a RxJS subscription—exactly as on the [React] side.
The [useAppointmentService()] composable of the [RdvMedecins] client (next chapter) systematically encapsulates this [fetch] call followed by [.json()], for a specific reason explained at that point (the [ServerResponse]<T> wrapper common to all server responses).
5.9. Conditional rendering and lists: [v-if]/[v-else] and [v-for]
AngularJS 1.x used structural directives (ng-if, ng-repeat) to display an element conditionally or repeat a template block for each element in a list. The [Angular] variant of this course uses the built-in syntax [@if]/[@for]; the [React] client expresses the same thing using the ternary operator and [.map(...)], in pure JavaScript. [Vue.js] is, on this specific point, closer to [Angular]: its [<template>] has its own “language” of directives, very similar in spirit to that of AngularJS 1.x (ng-if becomes [v-if], ng-repeat becomes [v-for]):
Let’s break down this code:
- lines 1–2: [v-if="loggedInUser" … v-else …] — [v-if]/[v-else] replace [@if]/[@else] ([Angular]) and ng-if (AngularJS 1.x): only the branch whose condition is true is MOUNTED in the DOM—exactly the same behavior as the ternary operator [React] ({ condition ? ... : ... }), but expressed as an attribute HTML rather than as an expression JavaScript nested within [JSX];
- Line 5: [v-for="item in itemList" :key="item.id"] — [v-for] replaces [@for] ([Angular]) and ng-repeat (AngularJS 1.x), and serves exactly the same purpose as [.map(...)] on the [React] side: it repeats the element for each entry in the array. [:key] (required, just like [key={item.id}] in [React] and track in [Angular]) tells [Vue.js] how to recognize an element that has already been displayed when it redraws the list.
5.10. Summary: mappings [Angular] -> [React] -> [Vue.js]
[Angular]/[React] (previous versions of this course) | [Vue.js] (2026) |
Single-file components ([.vue]) | |
[ref]()/[reactive](), [defineProps]/[defineEmits], [computed]() | |
[watch]() | |
[<template>] + [<script setup>] combined into the same file [.vue] | |
[v-if]/[v-else], [v-for] | |
Composables + [provide]/[inject] | |
[fetch] (Native Promises, such as [React]) | |
No router (same conclusion, taken a step further) | |
[Vite] (with [@vitejs/plugin-vue] instead of [@vitejs/plugin-react]) |
These concepts are sufficient to tackle the [RdvMedecins] client in the next chapter: each new use will nevertheless be explained again there, as the code progresses, at the exact point where it appears.