In this article, I’m comparing my learning experience with Angular (version 6.0.3), React (version 16.4.1), and Vue (version 2.5.16). I used mostly the official documentation for each framework.
In part I, I created a “Hello, world!” app with each framework. I used CLI only to create the Angular app; I created the React and Vue apps simply by referencing the JavaScript file(s) in the HTML file. For this article, I’ve created a “To Do” app with each framework and I used the CLI to set up the project for each.
The “To Do” app allows a user to enter a to-do item, and the item will be listed among other to-do items previously entered. Each to-do item in the list can be deleted by clicking the “Remove” button next to it.
In this app, I tried to cover the use of list, conditional rendering, form, event handling, and component.
Sample Code
Angular
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<div id="todo-app"> <form (submit)="addNewTodo()"> <label for="new-todo">Add a todo</label> <input [(ngModel)]="newTodoText" id="new-todo" name="new-todo" placeholder="E.g. Feed the cat"> <button>Add</button> </form> <todo-list *ngIf="todos && todos.length > 0; else elseBlock" [todos]="todos" (remove)="removeTodo($event)"> </todo-list> <ng-template #elseBlock> <p>No todo items</p> </ng-template> </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 |
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { nextTodoId = 1; newTodoText = ''; todos = []; addNewTodo(): void { this.todos.push({ id: this.nextTodoId++, title: this.newTodoText }); this.newTodoText = ''; } removeTodo(index: number) { this.todos.splice(index, 1); } } |
1 2 3 4 5 6 |
<ul> <li *ngFor="let todo of todos; index as i"> {{ todo.title }} <button (click)="onClick(i)">Remove</button> </li> </ul> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'todo-list', templateUrl: './todo-list.component.html', styleUrls: ['./todo-list.component.css'] }) export class TodoListComponent { @Input() todos: Array<any>; @Output() remove = new EventEmitter<number>(); constructor() { } onClick(index: number): void { this.remove.emit(index); } } |
React
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import React, { Component } from 'react'; import { TodoList } from './components/TodoList'; class App extends Component { constructor(props) { super(props); this.state = { newTodo: '', todos: [], nextTodoId: 1 }; this.onNewTodoChange = this.onNewTodoChange.bind(this); this.onRemoveTodo = this.onRemoveTodo.bind(this); this.addNewTodo = this.addNewTodo.bind(this); } addNewTodo(event) { event.preventDefault(); const newTodo = { id: this.state.nextTodoId, title: this.state.newTodo }; this.setState({ nextTodoId: this.state.nextTodoId + 1, todos: [...this.state.todos, newTodo], newTodo: '' }); } onNewTodoChange(event) { this.setState({ newTodo: event.target.value }); } onRemoveTodo(listItemIndex) { let todos = this.state.todos; todos.splice(listItemIndex, 1) this.setState({ todos: todos }); } render() { return ( <div id="todo-app"> <form onSubmit={this.addNewTodo}> <label htmlFor="new-todo">Add a todo</label> <input id="new-todo" placeholder="E.g. Feed the cat" value={this.state.newTodo} onChange={this.onNewTodoChange} /> <button>Add</button> </form> {this.state.todos && this.state.todos.length > 0 ? ( <TodoList todos={this.state.todos} onRemoveTodo={this.onRemoveTodo} /> ) : ( <p>No todo items</p> )} </div> ); } } 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 |
import React, { Component } from 'react'; export class TodoList extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick(listItemIndex, event) { this.props.onRemoveTodo(listItemIndex); } render() { const todoListItems = this.props.todos.map((todo, index) => <li key={todo.id}> {todo.title} <button onClick={this.handleClick.bind(this, index)}> Remove </button> </li> ); return ( <ul> {todoListItems} </ul> ); } } |
Vue
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 46 47 48 49 50 |
<template> <div id="app"> <form v-on:submit.prevent="addNewTodo"> <label for="new-todo">Add a todo</label> <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" /> <button>Add</button> </form> <template v-if="todos && todos.length > 0"> <TodoList v-bind:todos="todos" v-on:remove="removeTodo" /> </template> <template v-else> <p>No todo items</p> </template> </div> </template> <script> import TodoList from './components/TodoList.vue' export default { components: { TodoList }, data() { return { newTodoText: '', todos: [], nextTodoId: 1 } }, methods: { addNewTodo() { this.todos.push({ id: this.nextTodoId++, title: this.newTodoText }); this.newTodoText = ''; }, removeTodo(index) { this.todos.splice(index, 1) } } } </script> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<template> <ul> <li v-for="(todo, index) in todos" v-bind:key="todo.id"> {{ todo.title }} <button v-on:click="onClick(index)">Remove</button> </li> </ul> </template> <script> export default { name: 'TodoList', props: { todos: Array }, methods: { onClick(index) { this.$emit('remove', index); } } } </script> |
Component (and Code Organization)
For this app, I came up with a component TodoList whose responsibility was, given a list of to-do items, to render a list of those items.
For the Angular app, I used the Angular CLI to create a new component. It generated four files (.css, .html, .spec.ts, and .ts) within a new folder. The CLI also automatically registered the new component with the app.
With the React app, I simply created a single file TodoList.js which contained the definition of TodoList component. This component was responsible for both logic and rendering the UI using JSX code.
Vue contained the template and logic in one single .vue file. Unlike React, though, the HTML and JavaScript code was in distinct, separate sections within the file.
One of interesting observations when creating this “To Do” app was the number of affected files.
# of Files | Affected Files | |
---|---|---|
Angular | 5 | app.module.ts, app.component.ts, app.component.html, todo-list.component.ts, todo-list.component.html |
React | 2 | App.js, TodoList.js |
Vue | 2 | App.vue, TodoList.vue |
List
Angular and React use a common for-loop construct with only slight syntax difference. React utilized the method map()
to generate the listing. Each method doesn’t seem to matter in practice, other than the construct using map()
looks more verbose.
Conditional Rendering
Angular and Vue use an “if” directive within HTML code to determine which code will render the actual DOM. React allows using “if” construct to accomplish the same. React also offers inline if-else using a ternary operator.
Event Handling
In regard to event handling, Angular, React, and Vue share similar syntax on the template side. React needs an additional line of code to bind the event handler method, which feels tedious at times. React’s documentation provide a couple ways to get around this.