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.

Vue

For a Vue app, the routing configuration is specified within main.js where the app is initialized.

./src/main.js

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);

constroutes=[

{path:'/',redirect:'/dashboard'},

{path:'/dashboard',component:Dashboard},

{path:'/tasks',component:Tasks},

{path:'/detail/:id',component:TaskDetail},

{path:'*',component:PageNotFound}

];

constrouter=newVueRouter({

mode:'history',

routes

});

newVue({

render:h=>h(App),

router

}).$mount('#app')

./src/App.vue

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>

exportdefault{}

</script>

./src/components/Dashboard.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

<template>

<div class="due-today">

<h3>Due Soon</h3>

<ulv-if="tasks && tasks.length > 0">

<liv-for="(task) in tasks"

v-bind:key="task.id">

<router-linkv-bind:to="'/detail/' + task.id">

{{task.title}}

</router-link>{{task.dueDate}}

</li>

</ul>

<templatev-else>

<p>Alldone!</p>

</template>

</div>

</template>

<script>

import taskService from'./../taskService';

exportdefault{

data(){

return{

tasks:[]

}

},

mounted(){

constmaxNumberOfTasks=3;

taskService.getTasks(maxNumberOfTasks)

.then(result=>{

this.tasks=result;

});

}

}

</script>

./src/components/TaskDetail.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

<template>

<divv-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';

exportdefault{

data(){

return{

task:null

}

},

mounted(){

constid=+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.

Routes definition. (Left to right: Angular, React, Vue)

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.

Requesting data from a server via AJAX/XMLHttpRequest is ubiquitous when implementing a single page application. In this article, I’m comparing the use of AJAX in Angular (version 6.0.3), React (version 16.4.1), and Vue (version 2.5.16). I implemented the AJAX operations in a “To Do” app I created in part II.

I’ve set up a remote server which provides APIs to add a new to-do item, to remove a to-do item, and to list to-do items.

1

2

3

POST http://localhost/todo-api/todos/ (param: title)

DELETE http://localhost/todo-api/todos/ (param: Todo ID)

GET http://localhost/todo-api/todos/ (returns: Todo[] )

Angular provides a module HttpClient to allow your app to execute typical AJAX requests (POST, GET, etc). React’s documentation suggests that you can use any AJAX library with React. The documentation lists some common AJAX library such as axios, jQuery AJAX, and the browser’s built-in window.fetch. Vue hardly provides any hint regarding incorporating AJAX operations. Tucked in their website is the Cookbook section which has the article “Using Axios to Consument APIs.” I created a wrapper for axios and used it for both React and Vue

Angular’s HttpClient instance is available to be injected to any class once you import the HttpClient module from @angular/common/http library. The module provides the methods get(), post(), and other methods to do typical AJAX requests.

axios, an HTTP client library I used for React and Vue, is heavily inspired by Angular’s HttpClient, so the methods provided by axios will look familiar if you’ve used HttpClient. You can install axios using NPM.

When executing an AJAX request, typically you want to do it as early as possible but not to early as the component is loaded. A component in these three frameworks has a lifecycle and hooks are available so that an operation can be executed within the lifecycle. For an Angular, React, and Vue component, the recommendation is to execute any AJAX request the earliest inside the ngOnInit, componentDidMount, and mounted lifecycle hook, respectively.

A major difference between Angular’s HttpClient and axio‘s HTTP client is that HttpClient is based on Observable while axios library is based on Promise. For simple operations as used by this “To Do” app, there’s hardly any difference in the implementation between using an Observable or Promise-based HTTP client.

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.

“To Do” App

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

./src/app/app.component.html

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">Addatodo</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>

./src/app/app.component.ts

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']

})

exportclassAppComponent{

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);

}

}

./src/app/todo-list/todo-list.component.html

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>

./src/app/todo-list/todo-list.component.ts

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']

})

exportclassTodoListComponent{

@Input()todos:Array<any>;

@Output()remove=newEventEmitter<number>();

constructor(){}

onClick(index:number):void{

this.remove.emit(index);

}

}

React

./src/App.js

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';

classAppextendsComponent{

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();

constnewTodo={

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">Addatodo</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 defaultApp;

./src/components/TodoList.js

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';

exportclassTodoListextendsComponent{

constructor(props){

super(props);

this.handleClick=this.handleClick.bind(this);

}

handleClick(listItemIndex,event){

this.props.onRemoveTodo(listItemIndex);

}

render(){

consttodoListItems=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

./src/App.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">

<formv-on:submit.prevent="addNewTodo">

<label for="new-todo">Addatodo</label>

<input

v-model="newTodoText"

id="new-todo"

placeholder="E.g. Feed the cat"/>

<button>Add</button>

</form>

<templatev-if="todos && todos.length > 0">

<TodoList

v-bind:todos="todos"

v-on:remove="removeTodo"/>

</template>

<templatev-else>

<p>Notodoitems</p>

</template>

</div>

</template>

<script>

import TodoList from'./components/TodoList.vue'

exportdefault{

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>

./src/components/TodoList.vue

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>

<liv-for="(todo, index) in todos"

v-bind:key="todo.id">

{{todo.title}}

<buttonv-on:click="onClick(index)">Remove</button>

</li>

</ul>

</template>

<script>

exportdefault{

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.

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.

List template. (Left to right: Angular, React, Vue.)

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.

Conditional rendering. (Left to right: Angular, React, Vue.)

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.