Random Quote App Angular React Vue

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

All three versions of the random quotes app are available on my GitHub.

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.

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.