Random Quote App - Angular vs. React vs. Vue Comparison
In the last post, we looked at how to build a simple random quote app in Angular. In this post, we will compare the same app in React and Vue, to have a look at the similarities and differences in Angular, React, and Vue. We'll cover the project structure, and tooling of each of these technologies, as well as how components are implemented in each.
Contents
Links
All three versions of the random quotes app are available on my GitHub.
- → Angular repo
- → React repo
- → Vue repo
Following are some Documentation links if you want to check these technologies out in more detail or for how to get a project started with each, etc.
- → angular.io
- → reactjs.org
- → vuejs.org
Tooling
Each of these three has an associated command line tool that can be used to generate an initial project with all the boilerplate code for getting everything up and running quickly. For Angular, this is the ng
command (Angular CLI), for React it's create-react-app
, and for Vue, it's the vue
command (Vue CLI). Here's a quick rundown with some examples.
Creating a new project called my-app
:
# Angular
ng new my-app
# React
npx create-react-app my-app
# Vue
vue create my-app
Documentation links for these tools:
Project Structure
Angular seems to create a lot more files and directories by default when generating a project using ng new
than React with create-react-app
or Vue with vue create
. Vue creates the lowest number of files and directories.
Note: The following project file trees displayed are all excluding the node_modules/
and .git/
directories, for sake of brevity.
Angular
Upon generating a new project with the Angular CLI (ng new
), the following tree of directories and files is created.
fcc-random-quote-machine-angular
├── .browserslistrc
├── .editorconfig
├── .gitignore
├── README.md
├── angular.json
├── e2e/
│ ├── protractor.conf.js
│ ├── src/
│ │ ├── app.e2e-spec.ts
│ │ └── app.po.ts
│ └── tsconfig.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── src/
│ ├── app/
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ └── app.module.ts
│ ├── assets/
│ │ └── .gitkeep
│ ├── environments/
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.scss
│ └── test.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
6 directories, 30 files
React
Create React App generates the following.
fcc-random-quote-machine-react
├── .gitignore
├── README.md
├── package.json
├── public/
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src/
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── reportWebVitals.js
│ └── setupTests.js
└── yarn.lock
2 directories, 18 files
Vue
And Vue CLI (vue create
) generates the following.
fcc-random-quote-machine-vue
├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── public/
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── App.vue
│ ├── assets/
│ │ └── logo.png
│ ├── components/
│ │ └── HelloWorld.vue
│ └── main.js
└── yarn.lock
4 directories, 13 files
Components
Angular, React, and Vue are all component-based. The UI of an app is typically broken down into smaller components.
Layouts
Within a component, there is generally some sort of layout information associated, as to how it should be displayed in the browser. As you'll see in the following sections, Angular and Vue use HTML templates, and React uses either functions that return JSX or classes with a render()
method that returns JSX. JSX is a sort of XML-in-JavaScript syntax; you can read more about JSX on the React Documentation Page. Essentially it allows the use of HTML tag-like syntax inside of JavaScript in order to make a readable template without needing to use a separate HTML file or interpolated template string.
App Layout
Here we'll take a look at the main layout structure in each. You'll notice that they are very similar. Comparing things at this level makes the difference between these technologies appear to be mostly that of syntax.
For example, in Angular, interpolating variable values from the TypeScript is done with "mustache" double braces {{ }}
, and Vue does it the same way, but with React, which is typically written with JSX, we see single braces { }
.
Event handler binding in Angular such as onClick
is written with (click)=""
, where the JavaScript expression executed on the click is placed in the quotes. In Vue, it's the same idea with @click=""
, which is shorthand for v-on:click=""
. In React, it's onClick={}
, which is a prop passed down to the component and the JS expression is placed between the single braces.
Binding is how HTML element attributes and the corresponding component class variables are kept in sync with each other when a change happens in either direction. In Angular, an example for the syntax for this would be [tweetURL]="tweetURL"
(as seen in the following code snippets). Square brackets are used around the attribute name to signify that it is to be bound to a variable of the associated class, and in the quotes goes the variable it is bound to. In Vue, we have the same idea going on with :tweetURL="tweetURL"
, which is short for v-bind:tweetURL="tweetURL"
. These are somewhat similar to how React passes props down to child components, with the tweetURL={tweetURL}
attribute-like syntax in JSX, but work differently under the hood.
Angular
<!-- src/app/app.component.html (Angular) -->
<div *ngIf="loading; else content" id="loading">
<h1>loading...</h1>
</div>
<ng-template #content>
<app-quote-box
[author]="quote.author"
[quote]="quote.quote"
[tweetURL]="tweetURL"
[getNewQuote]="getNewQuote"
></app-quote-box>
</ng-template>
React
// src/App.js – App function return statement (React)
return loading ? (
<div id="loading">
<h1>loading...</h1>
</div>
) : (
<QuoteBox
quote={quote.quote}
author={quote.author}
getNewQuote={getNewQuote}
tweetURL={tweetURL}
/>
)
Vue
<!-- src/App.vue – template section (Vue) -->
<template>
<div id="app">
<div v-if="loading" id="loading">
<h1>loading...</h1>
</div>
<QuoteBox
v-else
:quote="quote.quote"
:author="quote.author"
:tweetURL="tweetURL"
:getNewQuote="getNewQuote"
></QuoteBox>
</div>
</template>
QuoteBox Layout
Again, everything is almost the same, except some bits of syntax.
Angular
<!-- `src/app/quote-box/quote-box.component.html` (Angular) -->
<div id="quote-box">
<h1 id="text"><i class="fa fa-quote-left"></i> {{ quote }}</h1>
<p id="author">- {{ author }}</p>
<div class="btn-row">
<button class="btn btn-primary" id="new-quote" (click)="getNewQuote()">
New quote
</button>
<a
id="tweet-quote"
href="{{ tweetURL }}"
target="_top"
class="btn btn-secondary"
>
<i class="fa fa-twitter"></i> Tweet
</a>
</div>
</div>
React
// src/components/QuoteBox.js – QuoteBox function return statement (React)
return (
<div id="quote-box">
<h1 id="text">
<i className="fa fa-quote-left"></i> {props.quote}
</h1>
<p id="author">- {props.author}</p>
<div className="btn-row">
<button
className="btn btn-primary"
id="new-quote"
onClick={props.getNewQuote}
>
New quote
</button>
<a
id="tweet-quote"
href={props.tweetURL}
target="_top"
className="btn btn-secondary"
>
<i className="fa fa-twitter"></i> Tweet
</a>
</div>
</div>
)
Vue
<!-- src/components/QuoteBox.vue – template section (Vue) -->
<template>
<div id="quote-box">
<h1 id="text"><i class="fa fa-quote-left"></i> {{ quote }}</h1>
<p id="author">- {{ author }}</p>
<div class="btn-row">
<button class="btn btn-primary" id="new-quote" @click="getNewQuote()">
New quote
</button>
<a
id="tweet-quote"
href="tweetURL"
target="_top"
class="btn btn-secondary"
>
<i class="fa fa-twitter"></i> Tweet
</a>
</div>
</div>
</template>
Styles
The same Sass styles were used in each version of this app. The only differences that occur are in the mechanisms for how global styles and component-specific styles are applied.
Global Styles
The global sass stylesheet is the same in all three, except that the filepaths / filenames differ.
Angular, React, and Vue
/* src/styles.scss (Angular) */
/* src/index.scss (React) */
/* src/styles/styles.scss (Vue) */
/* Bootstrap 5 */
@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css');
/* Font Awesome */
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');
/* Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Amiri&family=Indie+Flower&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Neucha&display=swap');
$blue: #58f;
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#root {
display: flex;
justify-content: center;
align-items: center;
background-color: $blue;
height: 100%;
overflow-y: hidden;
}
App Styles
Here are the styles for the main app component.
Angular & React
/* src/app/app.component.scss (Angular) */
/* src/App.scss (React) */
$white: #fafafa;
#loading {
color: $white;
font-family: 'Amiri', serif;
}
Vue
In Vue, styles go inside a style
section at the bottom of the component file.
<!-- src/App.vue – style section (Vue) -->
<style lang="scss">
$white: #fafafa;
#loading {
color: $white;
font-family: 'Amiri', serif;
}
</style>
QuoteBox Styles
Here are the styles for the QuoteBox component.
Angular & React
/* src/app/quote-box/quote-box.component.scss (Angular) */
/* src/components/QuoteBox.scss (React) */
$black: #3f3f3f;
$white: #fafafa;
#quote-box {
padding: 2em;
background-color: $white;
margin: 20%;
border-radius: 10px;
color: $black;
#text {
font-family: 'Amiri', serif;
}
#author {
font-family: 'Neucha', cursive;
font-size: 2.5em;
}
.btn-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
#tweet-quote {
margin-left: 1em;
}
}
}
@media only screen and (max-width: 480px) {
#quote-box {
margin: 0;
overflow-y: auto;
}
}
Vue
<!-- src/components/QuoteBox.vue – style section (Vue) -->
<style lang="scss" scoped>
$white: #fafafa;
$black: #3f3f3f;
#quote-box {
padding: 2em;
background-color: $white;
margin: 20%;
border-radius: 10px;
color: $black;
#text {
font-family: 'Amiri', serif;
}
#author {
font-family: 'Neucha', cursive;
font-size: 2.5em;
}
.btn-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
#tweet-quote {
margin-left: 1em;
}
}
}
@media only screen and (max-width: 480px) {
#quote-box {
margin: 0;
overflow-y: auto;
}
}
</style>
Using Stylesheets
Angular
In Angular, component-specific stylesheets are their own separate files within a component directory, and imported via the @Component()
decorator styleUrls
property inside the component's TypeScript (.ts
) file. This decorator and its properties will be automatically generated by the Angular CLI when using ng new
or ng generate component
.
// src/app/app.component.ts (Angular)
// ...
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
// ...
}
// src/app/quote-box/quote-box.component.ts (Angular)
// ...
@Component({
selector: 'app-quote-box',
templateUrl: './quote-box.component.html',
styleUrls: ['./quote-box.component.scss']
})
export class QuoteBoxComponent {
// ...
}
The global stylesheet at src/styles.scss
in Angular seems to be automatically imported and applied at the app level without any modifications to the app module or component code.
React
In React, component-specific stylesheets can just be imported into the component JavaScript file just like a typical JavaScript import.
// src/App.js (React)
import React from 'react'
import QuoteBox from './components/QuoteBox'
import './App.scss'
const App = () => {
// ...
}
// src/components/QuoteBox.js (React)
import './QuoteBox.scss'
const QuoteBox = (props) => {
// ...
}
The global stylesheet at src/index.scss
is imported at the top of src/index.js
.
// src/index.js (React)
import React from 'react'
import ReactDOM from 'react-dom'
import './index.scss' // <-- import global stylesheet here
import App from './App'
import reportWebVitals from './reportWebVitals'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
// ...
Vue
As seen previously, component-specific styles in Vue are placed inside style
tags at the bottom of a .vue
component file. The contents there aren't imported by the JavaScript in the script
tag section, and seem to automatically be applied to the component.
Global stylesheets, on the other hand are imported much like in Angular and React. It will be imported in src/main.js
like so:
// src/main.js (Vue)
import Vue from 'vue'
import App from './App.vue'
import './styles/styles.scss' // <-- import global stylesheet here
Vue.config.productionTip = false
new Vue({
render: (h) => h(App)
}).$mount('#app')
Logic
App logic in Angular is handled in TypeScript, and in the other two with JavaScript, with the option of adding TypeScript if desired. For these I chose the default route of using JavaScript, but it's fairly easy to switch to TypeScript with either React or Vue.
App Logic
Angular
With Angular, the application logic resides in the AppComponent
class inside src/app.component.ts
.
// src/app/app.component.ts (Angular)
import { Component, OnInit } from '@angular/core'
interface Quote {
quote: string
author: string
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
loading: boolean = true
quote!: Quote
quoteList!: Quote[]
tweetURL!: string
getNewQuote: () => void = (): void => {
const idx = Math.floor(Math.random() * this.quoteList.length)
const newQuote = this.quoteList[idx]
this.quote = newQuote
}
constructor() {}
ngOnInit() {
this.fetchData()
}
async fetchData(): Promise<void> {
const quotesURL =
'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'
const response = await fetch(quotesURL)
const quotes = await response.json()
const idx = Math.floor(Math.random() * quotes.quotes.length)
const newQuote = quotes.quotes[idx]
this.quoteList = quotes.quotes
this.quote = newQuote
this.setTweetURL(newQuote)
this.loading = false
}
setTweetURL(quote: Quote): void {
this.tweetURL = `https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=${quote.quote} --${quote.author}`
}
}
React
In React, it's either a function or class inside src/App.js
. In this case, it's the App
arrow function there.
// src/App.js (React)
import React from 'react'
import QuoteBox from './components/QuoteBox'
import './App.scss'
const App = () => {
const [loading, setLoading] = React.useState(true)
const [quote, setQuote] = React.useState({})
const [quoteList, setQuoteList] = React.useState([])
const [tweetURL, setTweetURL] = React.useState('')
const getNewQuote = () => {
const idx = Math.floor(Math.random() * quoteList.length)
const newQuote = quoteList[idx]
setQuote(newQuote)
}
const fetchData = async () => {
const quotesURL =
'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'
const response = await fetch(quotesURL)
const quotes = await response.json()
const idx = Math.floor(Math.random() * quotes.quotes.length)
const newQuote = quotes.quotes[idx]
setQuoteList(quotes.quotes)
setQuote(newQuote)
setTweetURL(
`https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=${newQuote.quote} --${newQuote.author}`
)
setLoading(false)
}
React.useEffect(() => {
fetchData()
}, [])
return loading ? (
<div id="loading">
<h1>loading...</h1>
</div>
) : (
<QuoteBox
quote={quote.quote}
author={quote.author}
getNewQuote={getNewQuote}
tweetURL={tweetURL}
/>
)
}
export default App
Vue
In Vue, it's the script
tag section of src/App.vue
.
<!-- src/App.vue – script section (Vue) -->
<script>
import QuoteBox from './components/QuoteBox.vue'
export default {
name: 'App',
components: {
QuoteBox
},
data() {
return {
loading: true,
quote: {},
quoteList: [],
tweetURL: ''
}
},
created() {
this.fetchData()
},
methods: {
async fetchData() {
const quotesURL =
'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'
const response = await fetch(quotesURL)
const quotes = await response.json()
const idx = Math.floor(Math.random() * quotes.quotes.length)
const newQuote = quotes.quotes[idx]
this.quoteList = quotes.quotes
this.quote = newQuote
this.tweetURL = `https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=${newQuote.quote} --${newQuote.author}`
this.loading = false
},
getNewQuote() {
const idx = Math.floor(Math.random() * this.quoteList.length)
const newQuote = this.quoteList[idx]
this.quote = newQuote
}
}
}
</script>
QuoteBox Logic
There isn't really much "logic" in the QuoteBox component in any case; it's mostly just a display component defining the UI given some values from the parent app component.
Angular
For Angular, the QuoteBoxComponent class is defined in src/app/quote-box/quote-box.component.ts
.
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-quote-box',
templateUrl: './quote-box.component.html',
styleUrls: ['./quote-box.component.scss']
})
export class QuoteBoxComponent {
@Input() author!: string
@Input() quote!: string
@Input() tweetURL!: string
@Input() getNewQuote!: () => void
constructor() {}
}
Notice the @Input()
decorator on each of these class variables. What this essentially means is a parent component will be providing values to these as inputs. Essentially all this class does is receive values from the parent and then inject them into the template due to the corresponding bindings.
React
In the React version of this project, the QuoteBox component logic is defined as a very simple arrow function in src/components/QuoteBox.js
.
// src/components/QuoteBox.js (React)
import './QuoteBox.scss'
const QuoteBox = (props) => {
return (
<div id="quote-box">
<h1 id="text">
<i className="fa fa-quote-left"></i> {props.quote}
</h1>
<p id="author">- {props.author}</p>
<div className="btn-row">
<button
className="btn btn-primary"
id="new-quote"
onClick={props.getNewQuote}
>
New quote
</button>
<a
id="tweet-quote"
href={props.tweetURL}
target="_top"
className="btn btn-secondary"
>
<i className="fa fa-twitter"></i> Tweet
</a>
</div>
</div>
)
}
export default QuoteBox
The props
parameter is essentially an object where the parent passes data down to a child. In the parent's JSX return statement, these values will appear like attributes assigned to either literal values or expression values. The QuoteBox function's returned JSX looks almost exactly like the layout templates in Angular and Vue. Again the only thing this function really does is serve to inject given prop values into a UI template defined by the JSX.
Unlike in Angular and Vue, where the component name is defined by initializing a string variable, the exported function or class name itself serves as the expected identifier of the component for use with other components' JSX in React.
Vue
In Vue, the QuoteBox is again very similar, and does basically the same exact thing, but with even less code, in the script
section of src/components/QuoteBox.vue
.
<!-- src/components/QuoteBox.vue – script section (Vue) -->
<script>
export default {
name: 'QuoteBox',
props: {
quote: String,
author: String,
tweetURL: String,
getNewQuote: Function
}
}
</script>
here we define a props
object in a more traditional looking way that React does in JSX. The props seem to work a lot like in React. The parent will pass down these values from the template and logic. This component will just receive them as values and sync them with the component template. Again, just receiving the values from the parent app component and placing them in the QuoteBox template.
the name
property here works pretty much exactly like the selector
property of the @Component()
decorator in Angular. It defines the expected name of this component for use in other Vue templates.
Impressions
Overall, I found it fairly easy to get the basics down in all three of Angular, React, and Vue for a small project such as this. The documentation for all three is really good and well maintained. Each has its own strengths and weaknesses, but for a project like this one, I found that personally there is almost no difference in the learning curve and general ease of use. It's difficult for me to choose a favorite here, and I don't think that there are any "winners" or "losers", "better" or "worse". It comes down to what you're used to and what you like. Out of the three, I definitely have had much more exposure to React and like it a lot. But after using Angular and Vue, I really like them too, just about as much. I will be using Angular for work, so I thought it would be good to dive in and convert something familiar in React to Angular. And just for kicks, also to Vue, since I see that is rising rapidly in popularity these days.
My general impressions about the similarities and differences is that they are so similar, that it is in my opinion quite easy to go from using one to the other between the three. I'd almost go so far as to oversimplify and sum it up as "It's just syntax".
For the use cases, I would say that the impression I got from working with Angular is that it seems very much suited for large scale enterprise applications right out of the box. This isn't quite a fair comparison to make because Angular and Vue are fully considered to be frameworks and React is a library. And, obviously, React is used in tons of large scale corporate projects (and I'm sure Vue is as well). React to me feels like a lightweight library for writing UIs quickly and easily, and it mostly stays out of the way, having no real opinions about structure, and allows for a lot of customizations. I've found that Angular and Vue seem to have opinions and more rigid structure, which probably comes from them being frameworks having established conventions more so than React.