AngularJS против VueJS. Создание трекера Stackoverflow в обеих средах. Часть 1/2
Я считаю, что и AngularJS, и VueJS являются мощными интерфейсными фреймворками, которые значительно повышают производительность и скорость команды разработчиков, но обеспечивают качество кода и удобство сопровождения.
Каждый из них имеет свои сильные стороны, и в зависимости от состава и навыков команды, а также существующей кодовой базы следует выбирать один из них.
Давайте создадим простое приложение для отслеживания StackOverFlow, чтобы понять структуру и уникальные преимущества каждого фреймворка. Наше приложение Stackoverflow поможет ежедневно отслеживать самые популярные вопросы об AngularJS и VueJS в определенных популярных тегах Angular/VuejS.
Это теги stackoverflow, которые нас интересуют:
И Angular, и Vue имеют CLI для разработки приложений. Мы оба можем установить их, используя npm внутри терминала.
- Установить интерфейс командной строки CLI
# AngularJS
$ npm install -g @angular/cli
# VueJS
$ npm install -g @vue/cli
- Создать шаблон проекта
# AngularJS
$ ng new angular-stack
# VueJS
$ vue create vue-stack
- Запустить живую среду разработки проекта
# AngularJS
$ cd angular-stack && ng serve
# VueJS
$ cd vue-stack && npm run serve
Наше приложение Stackoverflow будет включать список из 5 тегов которым мы хотели бы следовать. После того, как пользователи нажмут на каждый тег, мы увидим список активных вопросов относящийся к каждому тегу с ссылка на вопрос stackoverflow.
Угловой стек
Приложение Angular-Stack будет включать 4 модуля: Core, Global, Tags и Questions.
Основной модуль: включает все наши услуги (данные для работы в сети и сортировка для сортировки вопросов)
Глобальный модуль: включите наш интерфейс тегов и вопросов Angular и другие модули, которыми мы хотели бы поделиться в приложении.
Модуль тегов: маршрутизация и компоненты для просмотра тегов.
Модуль вопросов: маршрутизация и компонент для представления вопросов.
Сгенерируйте наши 4 модуля:
$ ng generate module Tags
$ ng generate module Questions
$ ng generate module Core
$ ng generate module Global
Глобальный модуль
Мы можем объявить нашу модель данных тегов и вопросов здесь, используя интерфейс
Интерфейс.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.component.html — Разметка компонента в HTML
<h2> {{ title }}</h2>
<!-- Adding Code for List of Angular Tags Below -->
- tags.component.css — Компонент стиля в CSS
.h2 {
text-align: left;
color: blue;
}
- tags.component.ts — написать логику компонента машинописного текста
# 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 — логика маршрутизации для компонента.
# 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 " 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 — Регистрация внутренних компонентов, модулей
# 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 { }
- Создайте дочерний компонент TagsList внутри нашей папки Tags с помощью нашего Angular CLI:
$ ng generate component tags/tags-list
- Добавление кода компонента Tags-List в файл ts, html и css
теги-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>
теги-list.component.css
.table {
text-align: left;
}
теги-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);
}
}
- Теперь нам нужно вернуться к нашим tags.component.html и tags.module.ts, чтобы загрузить наш компонент tags-list:
tags.component.html
<br/>
<br/>
<h2>{{ title }}</h2>
<app-tags-list ="popularTags"></app-tags-list>
теги.module.ts
...
import { TagsListComponent } from './tags-list/tags-list.component';
...
@NgModule({
...
declarations: [TagsComponent, TagsListComponent],
...
})
export class TagsModule { }
Последний шаг — добавить App Route в корень наших app.component.html и 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
],
...
После этого шага, если мы запустим «ng serve» и перейдем к нему, мы сможем увидеть нашу таблицу тегов без данных.
Службы данных
В Angular задача компонентов — представлять данные и делегировать доступ к данным сервису. Служба Angular доступна для всех компонентов приложения.
Обычно мы пишем наш код DataService внутри 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="
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');
}
}
Без создания DataServices теперь мы можем вернуться к нашим «tags.components.ts» и «tags-list.component.html», чтобы загрузить данные наших тегов.
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);
}
}
теги-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>
...
Теперь, если мы снова запустим «ng serve» и посетим «, мы сможем увидеть наши данные, загруженные из ‘assets/angular.json’.
Модуль вопросов
- Вопросы Компоненты
*вопросы.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>
*вопросы.component.css
.table {
text-align: left;
}
#tag {
background-color: yellow;
}
вопросы.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.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 {
}
Последний шаг — добавить маршрутизацию вопросов внутри «questions.module.ts» и добавить модуль «Вопросы» в «app.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 { }
Теперь, если мы снова запустим «Ng Serve» и посетим «приложение должно отображать список из 5 тегов и после того, как вы щелкнете по тегу, оно должно загрузить список вопросов, связанных с этим тегом.
Полный исходный код приложения здесь.