AngularJS vs VueJS - Build a Stackoverflow tracker in both frameworks - Part 1/2
Overview
I believe both AngularJS and VueJS are powerful frontend frameworks that drastically improve dev-team productivity and speed but ensure code quality and maintainability.
Each has it own strength and depending on the team composition and skills as well as existing codebase, one should pick one over the other.
Lets build a simple StackOverFlow tracking app to understand each framework project structure and unique strength. Our Stackoverflow app will help to track most popular questions about AngularJS and VueJS daily in certain popular Angular/VuejS Tags.
These are stackoverflow tags that we are interested in:
-
AngularJS Tags: [angular-directive], [angular-ui-router], [angularjs-scope], [angularjs-ng-repeat], [angular-material]
-
VueJS Tags: [vue-component], [vuex],[vue-router], [vuetify],[vue-cli]
Installation
Both Angular and Vue has a CLI for application development. We can both install them using npm inside terminal
- Install CLI Command Line Interface
# AngularJS
$ npm install -g @angular/cli
# VueJS
$ npm install -g @vue/cli
- Generate Project Boilerplate
# AngularJS
$ ng new angular-stack
# VueJS
$ vue create vue-stack
- Start live project development enviroment
# AngularJS
$ cd angular-stack && ng serve
# VueJS
$ cd vue-stack && npm run serve
Project Structure
Our Stackoverflow app will include a list of 5 tags that we would like to follow. After users click each tag, we will see a list of active questions related to each tag with link to the stackoverflow question.
Angular-Stack
Angular-Stack app will include 4 Modules: Core, Global, Tags and Questions.
-
Core Module: Include all our Services (Data for Networking and Sort for sorting questions)
-
Global Module: Include our Tag and Question Angular Interface and other Module that we would like to share accross the app.
-
Tag Module: Routing and Component for Tag view.
-
Question Module: Routing and Component for Question View.
-
Generate Our 4 Modules:
$ ng generate module Tags
$ ng generate module Questions
$ ng generate module Core
$ ng generate module Global
Global Module
We can declare our Tag and Question data model here using Interface
Interface.ts
export interface ITag {
id: number;
name: string;
description: string;
questionsTotal : number;
}
export interface IQuestion {
id: number;
tag: string;
voteCount: number;
questionTitle: string;
questionLink: string;
}
Tags Module
Inside Tags Module, we need to add:
- tags.component.html - Laying out component in HTML
<h2> {{ title }}</h2>
<!-- Adding Code for List of Angular Tags Below -->
- tags.component.css - Styling component in CSS
.h2 {
text-align: left;
color: blue;
}
- tags.component.ts - Write typescript component logic
# Importing Component and OnInit from Angular Core module
import {Component, OnInit} from '@angular/core';
# Config Component using @Component decorator
@Component({
selector: 'app-tags',
templateUrls: './tags.componemt.html',
styleUrls: './tags.component.css'
});
# Implemnt Component Logic
class TagsComponent implements OnInit {
<!-- Store our title here -->
title: string;
<!-- Store our Tag data here -->
popularTags: ITag[];
<!-- Execute on load -->
ngOnInint(){
this.title = "Popular AngularJS tags on Stackoverflow "
}
}
- tags-routing.module.ts - Routing Logic for the Component.
# Importing Angular Core and Router Module
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
# Import our created TagsComponent
import {TagsComponent} from './tags.component';
# Create route "http://localhost:4200/tags" that serve TagsComponent
const routes: Routes = [
{ path: 'tags', component: TagsComponent}
];
# Import 'routes' and export for accessing from root Route
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TagsRoutingModule { }
- tags.modules.ts - Register Internal Components, Modules
# Importing Core and Common Angular Module
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
# Importing Created TagsRoutingModule and TagsComponent
import { TagsRoutingModule } from './tags-routing.modules';
import { TagsComponent } from './tags.component';
@NgModule({
# Register Module for Internal Usage
imports: [
CommonModule,
TagsRoutingModule,
CoreModule
],
# Declare and Export so TagsComponent is accessbile from outside
declarations: [TagsComponent],
exports: [TagsComponent]
})
export class TagsModule { }
- Generate child component TagsList inside our Tags folder using our Angular CLI:
$ ng generate component tags/tags-list
- Adding Tags-List Child Component code inside ts, html and css file
tags-list.component.html
<br/>
<br/>
<!-- Create Table to present tags data -->
<table class="table table-hover">
<!-- Table Header of our Data -->
<thead>
<tr>
<!-- Bootstrap 4 class to adjust column width -->
<th class="w-30">Tags</th>
<th class="w-40">Description</th>
<th class="w-30">Questions Count</th>
</tr>
</thead>
<!-- Load our Data Here-->
<!-- *ngFor Structure Directive to loop over data -->
<tr>
<td>
<!-- RouterLink pointing to list of questions related to this tag !>
<a>
</a>
</td>
<!-- Using Angular Template tag to load tag properties -->
<td>
</td>
<td>
</td>
</tr>
</table>
tags-list.component.css
.table {
text-align: left;
}
tags-list.component.ts
## Importing necessary modules from angular core
import { Component, OnInit, Input } from '@angular/core';
## Importing Tag Data Model via ITag Interface
import { ITag } from '../../global/interfaces';
## Config TagsList Component
@Component({
selector: 'app-tags-list',
templateUrl: './tags-list.component.html',
styleUrls: ['./tags-list.component.css']
})
## Writing Logic Code for TagsList Component
export class TagsListComponent implements OnInit {
## Create
private _tags: ITag[] = []
# Using @Input decorator and set to pass data from parents to child component
@Input() get tags(): ITag[]{
return this._tags;
}
set tags(value: ITag[]){
if (value){
this._tags = value;
}
}
constructor() { }
ngOnInit() {
console.log(this._tags);
}
}
- Now we need to come back to our tags.component.html and tags.module.ts to load our tags-list component:
tags.component.html
<br/>
<br/>
<h2>{{ title }}</h2>
<app-tags-list [tags]="popularTags"></app-tags-list>
tags.module.ts
...
import { TagsListComponent } from './tags-list/tags-list.component';
...
@NgModule({
...
declarations: [TagsComponent, TagsListComponent],
...
})
export class TagsModule { }
Last step is to add App Route to the root in our app.component.html and app.module.ts
app.component.html
## Display Rendered route here
<router-outlet></router-outlet>
app.module.ts
...
import { AppRoutingModule } from './app-routing.module';
...
...
imports: [
BrowserModule,
CoreModule,
TagsModule,
QuestionsModule,
AppRoutingModule
],
...
After this step, if we run "ng serve" and go to http://localhost:4200, we should be able to see our tags table without data yet.
Data Services
In Angular, Components's task is to present data and delegate data access to a service. Angular Service is accessbile across all components in the app.
We typically write our DataService code inside the CoreModule.
*** data.service.ts***
// Importing Angular Injectable and HTTPClient
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// Importinng RXJS Obserable, map and catchError
import { Observable, of} from 'rxjs';
import { map, catchError } from 'rxjs/operators';
// Importing Tag and Question Data Model from Interfaces
import { ITag, IQuestion } from '../../app/global/interfaces';
// Using @Injectable decorator to create DataService service.
@Injectable ()
export class DataService {
<!-- We load local angular.json data from this url -->
baseUrl: string = 'assets/';
<!-- We load data from Stackoverflow API here -->
stackUrl = 'https://api.stackexchange.com/2.2/tags/'
site = '/faq?site=stackoverflow';
<!-- Initialize http: HTTPClient() inside our constructor
constructor(private http: HttpClient) { }
<!-- getTags method to pass data via http get -->
getTags(): Observable<ITag[]>{
let myTags = this.http.get<ITag[]>(this.baseUrl + 'angular.json')
.pipe(
catchError(this.handleError)
);
return myTags
}
<!-- getQuestions method to pass data via http get from Stackoverlfow APIs-->
getQuestions(tag: string): Observable<IQuestion[]>{
return this.http.get<IQuestion[]>(this.stackUrl + tag + this.site)
.pipe(
map( data => {
var items = data['items'];
var questions: IQuestion[] = [];
<!-- Iterate over response data and filter what we need for IQuestion-->
for (let item of items){
var question: IQuestion = {
id: item['question_id'],
tag: tag,
voteCount: item['score'],
questionTitle: item['title'],
questionLink: item['link']
};
questions.push(question);
}
return questions;
}),
<!-- Calling handleError method -->
catchError(this.handleError)
);
}
// Handling Error Method
private handleError(error: any) {
console.error('server error:', error);
if (error.error instanceof Error) {
const errMessage = error.error.message;
return Observable.throw(errMessage);
// Use the following instead if using lite-server
// return Observable.throw(err.text() || 'backend server error');
}
return Observable.throw(error || 'Node.js server error');
}
}
Without DataServices created, now we can come back to our "tags.components.ts" and our "tags-list.component.html" to load our tags data.
tags.component.ts
...
import { DataService } from '../core/data.service';
...
...
export class TagsComponent implements OnInit {
title: string;
popularTags: ITag[];
myQuestions = [];
constructor(private dataService: DataService) {}
ngOnInit(){
this.title = 'Popular Angular Tags';
<!-- ADDING DATA SERVICE INSIDE ngOnInit() -->
this.dataService.getTags()
.subscribe((tags: ITag[]) => this.popularTags = tags);
}
}
tags-list.component.html
...
<tr *ngFor="let tag of tags">
<!-- Tags Name -->
<td>
<a [routerLink]="['/questions',tag.name]">
{{ tag.name }}
</a>
</td>
<!-- Tag Description -->
<td>
{{ tag.description }}
</td>
<!-- Tag Question Count -->
<td>
{{ tag.questionsTotal }}
</td>
</tr>
...
Now if we run "ng serve" again and visit "http://localhost:4200", we should be able to see our data loaded from 'assets/angular.json'.
Questions Module
- Questions Components
*questions.component.html
<!-- Rendering if Question Exist -->
<br />
<br />
<div>
<h2>Questions for Angular Tag
<span id="tag">
{{tag}}
</span>
</h2>
<table class="table table-hover">
<thead>
<tr>
<th class="w-20">ID</th>
<th class="w-40">Vote Count</th>
<th class="w-40">Question Content</th>
</tr>
</thead>
<!-- Using Structire directive to loop over myQuestions data -->
<tr *ngFor="let question of myQuestions">
<td>
<a href="{{question.questionLink}}">{{question.id}}</a>
</td>
<td>{{question.voteCount}}</td>
<td>{{question.questionTitle}}</td>
</tr>
</table>
</div>
<!-- Using routerLink to link back to our tag list page -->
<a routerLink="/tags">View All Angular Tags</a>
*questions.component.css
.table {
text-align: left;
}
#tag {
background-color: yellow;
}
questions.component.ts
# Importing Angular Component, Router, ActivatedRoute, Params
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
# Importing IQuestion interface and DataService
import { IQuestion } from '../global/interfaces';
import { DataService } from '../core/data.service';
# Config Component
@Component({
selector: 'app-questions',
templateUrl: './questions.component.html',
styleUrls: ['./questions.component.css']
})
# Implement Component Logic
export class QuestionsComponent implements OnInit {
# Initialize myQuestions and tag to store this data
myQuestions: IQuestion[] = [];
tag: string;
# Initialize dataService and route to use later
constructor(
private dataService: DataService,
private route:ActivatedRoute
){};
ngOnInit() {
# Getting value of Tag param from url
this.tag = this.route.snapshot.paramMap.get('tag');
# Passing questions data via DataService and store in
this.dataService.getQuestions('angularjs-directive')
.subscribe((questions:IQuestion[]) => {
this.myQuestions = questions;
}
}
}
- Questions Routing
We create a dynamic route with parameter tag inside questions-routing.module.ts.
# Angular Module
import {NgModule} from '@angular/core';
import {RouterModule, Routes } from '@angular/router';
# Questions Compomemt
import {QuestionsComponent } from './questions.component';
# Load Component via 'questions/:tag' path
const routes: Routes = [
{path:'questions/:tag', component: QuestionsComponent}
];
# Register and Export Module
@NgModule({
imports: [RouterModule.forChild(routes )],
exports: [RouterModule]
})
export class QuestionsRoutingModule {
}
Last step is to add Question-Routing inside 'questions.module.ts' and Add Questions Module inside 'app.module.ts.'
questions.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { QuestionsComponent } from './questions.component';
import { QuestionsRoutingModule } from './questions-routing.module';
@NgModule({
imports: [
CommonModule,
QuestionsRoutingModule
],
declarations: [QuestionsComponent],
exports: [QuestionsComponent]
})
export class QuestionsModule {
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { TagsModule} from './tags/tags.module';
import { QuestionsModule } from './questions/questions.module';
import { CoreModule } from './core/core.module';
import { AppRoutingModule } from './app-routing].module';
import { AppComponent } from './app.component';
@NgModule({
AppComponent
],
imports: [
BrowserModule,
CoreModule,
TagsModule,
QuestionsModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Now if we run "Ng Serve" again and visit "http://localhost:4200", the app should list of 5 tags and quen you click theh tag, it should load a list of questions related to this tag.
Full App Source Code is here.