Vue Fast Track for Angular Developers
I already know components, reactivity, routing, and state, so this maps what I know onto Vue syntax and hopefully gets me building something real.
| Angular | Vue |
|---|---|
| @Component decorator | <script setup> in .vue file |
| @Input() / @Output() | defineProps() / defineEmits() |
| *ngIf / *ngFor | v-if / v-for |
| [prop] / (event) | :prop / @event |
| ng-content | <slot> |
| Signal / Observable | ref() / reactive() |
| Pure pipe | computed() |
| Service (singleton) | Composable (useX function) or Pinia store |
| RouterModule | Vue Router (same API shape) |
| Angular CLI / Webpack | Vite (faster, simpler config) |
| NgRx | Pinia (much lighter) |
| ngOnInit / ngOnDestroy | onMounted / onUnmounted |
Topics
ref() and reactive() — think signals/observables but simpler. Mutate directly, no .next()computed() — same as a pure pipe, derived value with no side effectsv-if, v-for, v-bind (:), v-on (@).vue file, no separate class fileExercise
Counter + todo list
Build two components: a counter and a todo list. No libraries, no routing. Just ref(), computed(), and template
directives.
npm create vue@latestv-if and computed()<style scoped> — note how it’s contained to the componentThe big shift from Angular: reactivity in Vue is implicit. You don’t subscribe or call markForCheck(). Wrap a
value in ref(), mutate it, and the template just updates. Trust it.
<script setup> import {ref, computed} from 'vue'
const count = ref(0) const label = computed(() => { if (count.value > 0) return 'positive' if (count.value < 0) return 'negative' return 'zero' })</script>
<template> <div> <p>{{count}} — {{label}}</p> <button @click="count++">+</button> <button @click="count--">-</button> <button @click="count = 0">reset</button> </div></template>Topics
defineProps() replaces @Input(). Type-safe, same concept.defineEmits() replaces @Output(). No EventEmitter, just strings.<slot > replaces ng - content. Named slots replace select.onMounted, onUnmounted, onUpdated — same ideas, different namesExercise
Parent/child score tracker
Build a score tracker with a parent component that owns state and two child components that read and update it.
scores array in a Pinia storePlayerCard receives player name + score via props, displays themincrement event; parent updates the storeslot to PlayerCard for optional extra content (badge, note, etc.)Pinia replaces both Angular services and NgRx in one concept. Define a store with defineStore(), use it anywhere with useMyStore(). No reducers, no actions, no boilerplate. It feels almost too simple at first.
<script setup > const props = defineProps < {name: string; score: number} > (); const emit = defineEmits < {increment: []} > ();</script><template> <div> <h3>{{props.name}}</h3> <p>{{props.score}}</p> <slot/> <!-- ng-content equivalent --> <button @click="emit('increment')">+1 </button> </div></template>Topics
router - link = routerLink. Route guards work identically to Angular’s CanActivate.useX() functions, import anywhere.HttpClient. Use fetch or Axios. Wrap in a composable to keep components clean.vite.config.ts.useRoute().params.id replaces ActivatedRouteExercise
Two-page app with API data Build a minimal two-page app: a list view and a detail view. Fetch data from a public API.
https://jsonplaceholder.typicode.com/posts as your data source/posts/:id, fetches single post on mountusePosts() composableComposables are the thing that clicks everything together in Vue. If you’re pulling logic into Angular services, the
instinct translates directly — just write a function that starts with “use” and returns reactive state. No class, no
DI token, no providedIn.
// composable — think: Angular serviceimport {ref} from 'vue'
export function usePosts() { const posts = ref([]) const loading = ref(false)
async function fetchAll() { loading.value = true posts.value = await fetch('/api/posts').then(r => r.json()) loading.value = false }
return {posts, loading, fetchAll}}<script setup> import {onMounted} from 'vue' import {usePosts} from './usePosts'
const {posts, loading, fetchAll} = usePosts() onMounted(fetchAll)</script>Topics
<script setup> syntax — what you’ll see in every modern Vue codebase. Get comfortable here.v-model on custom components: replaces Angular’s ControlValueAccessor pattern, much less painfulExercise
Port a ResuRank view to Vue Take one view from ResuRank or JobDash and rebuild it in Vue. Pick something with a form, a list, and some state logic.
v-model for two-way binding<Teleport to="body"> for viewing a result detail/results and /results/:idPorting a feature you already built in Angular is the fastest way to cement the Vue mental model. You already know what the code needs to do — the exercise is just translation. After this week you’ll stop thinking “how do I do X in Vue” and start just writing it.
<!-- v-model replaces ControlValueAccessor — Vue 3.4+ --><script setup> const model = defineModel <string>()</script>
<template> <input :value="model" @input="model = $event.target.value"/></template><!-- same as Angular [(ngModel)] --><ScoreInput v-model="query"/>Building an MCP server for ResuRank taught me more about macOS code signing and stdout than I bargained for. This is a walkthrough of what broke — DXT distribution killed by Apple's Library Validation, pdf.js corrupting the JSON-RPC channel — and the practical decisions that followed.
7 min readA step-by-step guide on how to set up wildcard DNS using dnsmasq on macOS, allowing developers to use meaningful local development URLs like couchbase.localdev.me instead of localhost. The setup process includes installing dnsmasq, configuring it, creating a DNS resolver, and verifying the configuration, making local development cleaner and more efficient.
2 min readA concise guide on configuring a local development proxy using Docker and NginX Proxy Manager, detailing setup steps, service routing, and benefits for managing multi‑service environments.
3 min read