In a single-page application, to navigate from one view to another, a routing system needs to be set up.
In all three frameworks–Angular (version 6.0.3), React (version 16.4.1), and Vue (version 2.5.16)–, each view is represented by a component; so, those terms are interchangeable in this article.
Angular provides its own routing module; so does Vue, though it requires an additional installation. React lists several third-party routers–I chose react-router
.
I’ve created a simple app which incorporates a simple routing setup. The app has the following three views.
- Dashboard: displays tasks which are due soon.
- Tasks: displays all tasks.
- Task Detail: displays the detail of a task.
Sample Code
Angular
Angular’s documentation recommends creating a separate module (app-routing.module.ts
) within the app to configure the routing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DashboardComponent } from './dashboard/dashboard.component'; import { TasksComponent } from './tasks/tasks.component'; import { TaskDetailComponent } from './task-detail/task-detail.component'; import { PageNotFoundComponent } from './page-not-found/page-not-found.component'; const routes: Routes = [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent }, { path: 'tasks', component: TasksComponent }, { path: 'detail/:id', component: TaskDetailComponent }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ exports: [ RouterModule ], imports: [ RouterModule.forRoot(routes) ] }) export class AppRoutingModule { } |
1 2 3 4 5 6 |
<h1>Routing App</h1> <nav> <a routerLink="/dashboard">Dashboard</a> | <a routerLink="/tasks">Tasks</a> </nav> <router-outlet></router-outlet> |
1 2 3 4 5 6 7 8 9 |
<h3>Due soon</h3> <ul *ngIf="tasks && tasks.length > 0; else elseBlock"> <li *ngFor="let task of tasks"> <a routerLink="/detail/{{task.id}}">{{task.title}}</a> ({{task.dueDate}}) </li> </ul> <ng-template #elseBlock> <p>All done!</p> </ng-template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Location } from '@angular/common'; import { TaskService } from '../task.service'; import { Task } from '../task'; @Component({ selector: 'app-task-detail', templateUrl: './task-detail.component.html', styleUrls: ['./task-detail.component.css'] }) export class TaskDetailComponent implements OnInit { task: Task; constructor( private route: ActivatedRoute, private taskService: TaskService, private location: Location ) { } ngOnInit() { const id = +this.route.snapshot.paramMap.get('id'); this.taskService.getTask(id) .subscribe(task => { this.task = task; }) } } |
React
react-router
takes a different approach (called “dynamic routing”) as to when/where to apply the routing configuration. Its Router library provides routing-related components and you add them within the template (JSX code). The configuration (mapping of paths to components) is specified using directives within the template.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import React, { Component } from 'react'; import { BrowserRouter, Route, Switch, Link, Redirect } from 'react-router-dom' import './App.css'; import { Dashboard } from './components/Dashboard'; import { Tasks } from './components/Tasks'; import { TaskDetail } from './components/TaskDetail'; import { PageNotFound } from './components/PageNotFound'; class App extends Component { render() { return ( <BrowserRouter> <div id="routing-app"> <h1>Routing App</h1> <nav> <Link to="/dashboard">Dashboard</Link> | <Link to="/tasks">Tasks</Link> </nav> <Switch> <Route exact path="/" render={() => (<Redirect to="/dashboard" />)} /> <Route path="/dashboard" component={Dashboard} /> <Route path="/tasks" component={Tasks} /> <Route path="/detail/:id" component={TaskDetail} /> <Route component={PageNotFound}/> </Switch> </div> </BrowserRouter> ); } } export default App; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import React, { Component } from 'react'; import taskService from './../taskService'; import { Link } from 'react-router-dom'; export class Dashboard extends Component { constructor(props) { super(props); this.state = { tasks: [] }; } componentDidMount() { const maxNumberOfTasks = 3; taskService.getTasks(maxNumberOfTasks) .then(result => { this.setState({ tasks: result }); }); } render() { let output; let tasks = this.state.tasks; if (tasks && tasks.length > 0) { output = <ul> {this.state.tasks.map((task) => <li><Link to={`/detail/${task.id}`}>{task.title}</Link> {task.dueDate}</li> )} </ul>; } else { output = <p>All done!</p> } return( <div> <h3>Due Soon</h3> {output} </div> ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import React, { Component } from 'react'; import taskService from './../taskService'; export class TaskDetail extends Component { constructor(props) { super(props); this.state = { task: null }; } componentDidMount() { const id = +this.props.match.params.id; taskService.getTask(id) .then(result => { this.setState({ task: result }); }); } render() { let output; let task = this.state.task; if (task != null) { output = <div> <h2>{task.title}</h2> <p>Due date: {task.dueDate}</p> <p>Note: {task.note}</p> </div>; } else { output = <p>Task not found</p>; } return output; } } |
Vue
For a Vue app, the routing configuration is specified within main.js
where the app is initialized.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import Vue from 'vue' import VueRouter from 'vue-router' import App from './App.vue' import Dashboard from './components/Dashboard.vue' import Tasks from './components/Tasks.vue' import TaskDetail from './components/TaskDetail.vue' import PageNotFound from './components/PageNotFound.vue' Vue.config.productionTip = false; Vue.use(VueRouter); const routes = [ { path: '/', redirect: '/dashboard' }, { path: '/dashboard', component: Dashboard }, { path: '/tasks', component: Tasks }, { path: '/detail/:id', component: TaskDetail }, { path: '*', component: PageNotFound } ]; const router = new VueRouter({ mode: 'history', routes }); new Vue({ render: h => h(App), router }).$mount('#app') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<template> <div id="app"> <h1>Routing App</h1> <nav> <router-link to="/dashboard">Dashboard</router-link> | <router-link to="/tasks">Tasks</router-link> </nav> <router-view></router-view> </div> </template> <script> export default {} </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<template> <div class="due-today"> <h3>Due Soon</h3> <ul v-if="tasks && tasks.length > 0"> <li v-for="(task) in tasks" v-bind:key="task.id"> <router-link v-bind:to="'/detail/' + task.id"> {{ task.title }} </router-link> {{ task.dueDate }} </li> </ul> <template v-else> <p>All done!</p> </template> </div> </template> <script> import taskService from './../taskService'; export default { data() { return { tasks: [] } }, mounted() { const maxNumberOfTasks = 3; taskService.getTasks(maxNumberOfTasks) .then(result => { this.tasks = result; }); } } </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<template> <div v-if="task" class="task-detail"> <h2>{{ task.title }}</h2> <p>Due date: {{ task.dueDate }}</p> <p>Note: {{ task.note }}</p> </div> </template> <script> import taskService from './../taskService'; export default { data() { return { task: null } }, mounted() { const id = +this.$route.params.id; taskService.getTask(id) .then(result => { this.task = result; }); } } </script> |
Angular’s and Vue’s routes are configured as part of initialization of the app; it happens before any of the components are rendered. React’s react-routers
provides its functionalities via components–the same component concept that you use to declare a view. In a React app, you will see the declaration of router and routes within app components (whether it’s the top-level or a lower-level component). In Angular and Vue, the code pertaining to the router module and the routes don’t intermingle with that of components.
To display the active view, Angular and Vue use a placeholder directive (router-outlet
and router-view
, respectively) while react-router
uses the same route components to define route mapping and render the selected view.
thanks really useful to find all three compared in one place.