龙空技术网

Angular7+NgRx+SSR全家桶高仿QQ音乐

Echa攻城狮 203

前言:

目前看官们对“nginx代理angularjs”大体比较注意,看官们都需要学习一些“nginx代理angularjs”的相关知识。那么小编同时在网摘上搜集了一些对于“nginx代理angularjs””的相关资讯,希望姐妹们能喜欢,同学们快快来了解一下吧!

作者:前端藏经阁

转发链接:

项目说明

现在主要是做React开发,也是使用服务端渲染(DEMO),最近想用Angular写一个项目体验一下TypeScript大法,对比Angular对比React从开发体验上来讲个人觉得更加方便很多东西不需要你自己去单独安装.

DEMO:

线上地址:music.soscoon.com

Github:

目前还在努力开发中,目前完成了80%...

预览图技术栈Angular 7.2.0pm2 3.4.1better-scroll 1.15.1rxjs 6.3.3ngrx 7.4.0hammerjs 2.0.8NgRx配置Actions

和Vuex,Redux一样都需要先定义一些actionType,这里举了一个例子

src/store/actions/list.action.ts

import { Action } from '@ngrx/store';export enum TopListActionTypes {    LoadData = '[TopList Page] Load Data',    LoadSuccess = '[TopList API] Data Loaded Success',    LoadError = '[TopList Page] Load Error',}//  获取数据export class LoadTopListData implements Action {    readonly type = TopListActionTypes.LoadData;}export class LoadTopListSuccess implements Action {    readonly type = TopListActionTypes.LoadSuccess;}export class LoadTopListError implements Action {    readonly type = TopListActionTypes.LoadError;    constructor(public data: any) { }}复制代码

合并ActionType

src/store/actions/index.ts

export * from './counter.action';export * from './hot.action';export * from './list.action';export * from './control.action';复制代码
Reducers

存储数据管理数据,根据ActionType修改状态

src/store/reducers/list.reducer.ts

import { Action } from '@ngrx/store';import { TopListActionTypes } from '../actions';export interface TopListAction extends Action {  payload: any,  index: number,  size: number}export interface TopListState {  loading?: boolean,  topList: Array<any>,  index?: 1,  size?: 10}const initState: TopListState = {  topList: [],  index: 1,  size: 10};export function topListStore(state: TopListState = initState, action: TopListAction): TopListState {  switch (action.type) {    case TopListActionTypes.LoadData:      return state;    case TopListActionTypes.LoadSuccess:      state.topList = (action.payload.playlist.tracks || []).slice(state.index - 1, state.index * state.size);      return state;    case TopListActionTypes.LoadErrhammerjsor:      return state;    default:      return state;  }}复制代码

合并Reducer

src/store/reducers/index.ts

import { ActionReducerMap, createSelector, createFeatureSelector } from '@ngrx/store';//import the weather reducerimport { counterReducer } from './counter.reducer';import { hotStore, HotState } from './hot.reducer';import { topListStore, TopListState } from './list.reducer';import { controlStore, ControlState } from './control.reducer';//stateexport interface state {    count: number;    hotStore: HotState;    topListStore: TopListState;    controlStore: ControlState;}//register the reducer functionsexport const reducers: ActionReducerMap<state> = {    count: counterReducer,    hotStore,    topListStore,    controlStore,}复制代码
Effects

处理异步请求,类似于redux-sage redux-thunk,下面这个例子是同时发送两个请求,等到两个请求都完成后派遣HotActionTypes.LoadSuccesstype到reducer中处理数据.

当出现错误时使用catchError捕获错误,并且派遣new LoadError()处理数据的状态.

LoadError

export class LoadError implements Action {    readonly type = HotActionTypes.LoadError;    constructor(public data: any) { }}复制代码
import { Injectable } from '@angular/core';import { Actions, Effect, ofType } from '@ngrx/effects';import { map, mergeMap, catchError } from 'rxjs/operators';import { HotActionTypes, LoadError, LoadSongListError } from '../actions';import { of, forkJoin } from 'rxjs';import { HotService } from '../../services';@Injectable()export class HotEffects {  @Effect()  loadHotData$ = this.actions$    .pipe(      ofType(HotActionTypes.LoadData),      mergeMap(() =>        forkJoin([          this.hotService.loopList()            .pipe(catchError(() => of({ 'code': -1, banners: [] }))),          this.hotService.popularList()            .pipe(catchError(() => of({ 'code': -1, result: [] }))),        ])          .pipe(            map(data => ({ type: HotActionTypes.LoadSuccess, payload: data })),            catchError((err) => {              //call the action if there is an error              return of(new LoadError(err["message"]));            })          ))    )  constructor(    private actions$: Actions,    private hotService: HotService  ) { }}复制代码

合并Effect

将多个Effect文件合并到一起

src/store/effects/hot.effects.ts

import { HotEffects } from './hot.effects';import { TopListEffects } from './list.effects';export const effects: any[] = [HotEffects, TopListEffects];export * from './hot.effects';export * from './list.effects';复制代码
注入Effect Reducer到app.module

src/app/app.module.ts

import { StoreModule } from '@ngrx/store';import { EffectsModule } from "@ngrx/effects";import { reducers, effects } from '../store';imports: [  ...    StoreModule.forRoot(reducers),    EffectsModule.forRoot(effects),    ...],复制代码
请求处理使用HttpClient

post get delate put请求都支持HttpClient详细说明

src/services/list.service.ts

import { Injectable } from '@angular/core';import { HttpClient } from "@angular/common/http";@Injectable({  providedIn: 'root'})export class TopListService {  constructor(private http: HttpClient) {  }  // 轮播图  topList() {    return this.http.get('/api/top/list?idx=1');  }}复制代码

src/services/index.ts

export * from "./hot.service";export * from "./list.service";复制代码
响应拦截器

这里处理异常,对错误信息进行统一捕获,例如未登录全局提示信息,在这里发送请求时在消息头加入Token信息,具体的需要根据业务来作变更.

import { Injectable } from '@angular/core';import {  HttpInterceptor,  HttpRequest,  HttpResponse,  HttpHandler,  HttpEvent,  HttpErrorResponse} from '@angular/common/http';import { Observable, throwError } from 'rxjs';import { map, catchError } from 'rxjs/operators';@Injectable()export class HttpConfigInterceptor implements HttpInterceptor {  // constructor(public errorDialogService: ErrorDialogService) { }  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {    let token: string | boolean = false;    // 兼容服务端渲染    if (typeof window !== 'undefined') {      token = localStorage.getItem('token');    }    if (token) {      request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });    }    if (!request.headers.has('Content-Type')) {      request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });    }    request = request.clone({ headers: request.headers.set('Accept', 'application/json') });    return next.handle(request).pipe(      map((event: HttpEvent<any>) => {        if (event instanceof HttpResponse) {          // console.log('event--->>>', event);          // this.errorDialogService.openDialog(event);        }        return event;      }),      catchError((error: HttpErrorResponse) => {        let data = {};        data = {          reason: error && error.error.reason ? error.error.reason : '',          status: error.status        };        // this.errorDialogService.openDialog(data);        console.log('拦截器捕获的错误', data);        return throwError(error);      }));  }}复制代码
拦截器依赖注入

src/app/app.module.ts

需要把拦截器注入到app.module才会生效

// http拦截器,捕获异常,加Tokenimport { HttpConfigInterceptor } from '../interceptor/httpconfig.interceptor';...  providers: [    {      provide: HTTP_INTERCEPTORS,      useClass: HttpConfigInterceptor,      multi: true    },    ...  ],复制代码
发送一个请求

项目使用了NgRx,所以我就用NgRx发请求this.store.dispatch(new LoadHotData()),在Effect中会接收到type是HotActionTypes.LoadData,通过Effect发送请求.

设置hotStore$为可观察类型,当数据改变时也会发生变化public hotStore$: Observable<HotState>,详细见以下代码:

到此就完成了数据的请求

import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';import { Store, select } from '@ngrx/store';import { Observable } from 'rxjs';import { LoadHotData } from '../../store';import { HotState } from '../../store/reducers/hot.reducer';@Component({  selector: 'app-hot',  templateUrl: './hot.component.html',  styleUrls: ['./hot.component.less']})export class HotComponent implements OnInit {  // 将hotStore$设置为可观察类型  public hotStore$: Observable<HotState>;  public hotData: HotState = {    slider: [],    recommendList: []  };  @ViewChild('slider') slider: ElementRef;  constructor(private store: Store<{ hotStore: HotState }>) {    this.hotStore$ = store.pipe(select('hotStore'));  }  ngOnInit() {    // 发送请求,获取banner数据以及列表数据    this.store.dispatch(new LoadHotData());    // 订阅hotStore$获取改变后的数据    this.hotStore$.subscribe(data => {      this.hotData = data;    });  }}复制代码
服务端渲染

Angular的服务端渲染可以使用angular-cli创建ng add @nguniversal/express-engine --clientProject 你的项目名称要和package.json里面的name一样

angular-music-player项目已经运行过了不要再运行

ng add @nguniversal/express-engine --clientProject angular-music-player// 打包运行npm run build:ssr && npm run serve:ssr复制代码

运行完了以后你会看见package.json的scripts多了一些服务端的打包和运行命令

"scripts": {    "ng": "ng",    "start": "ng serve",    "build": "ng build",    "test": "ng test",    "lint": "ng lint",    "e2e": "ng e2e",    "compile:server": "webpack --config webpack.server.config.js --progress --colors",    "serve:ssr": "node dist/server",    "build:ssr": "npm run build:client-and-server-bundles && npm run compile:server",    "build:client-and-server-bundles": "ng build --prod && ng run angular-music-player:server:production",    "start:pro": "pm2 start dist/server"  }复制代码
Angular引入hammerjs

hammerjs在引入的时候需要window对象,在服务端渲染时会报错,打包的时候不会报错,打包完成以后运行npm run serve:ssr报ReferenceError: window is not defined.

解决方法使用require引入

!!记得加上declare var require: any;不然ts会报错typescript getting error TS2304: cannot find name ' require',对于其它的插件需要在服务端注入我们都可以使用这样的方法.

src/app/app.module.ts

declare var require: any;let Hammer = { DIRECTION_ALL: {} };if (typeof window != 'undefined') {  Hammer = require('hammerjs');}export class MyHammerConfig extends HammerGestureConfig {  overrides = <any>{    // override hammerjs default configuration    'swipe': { direction: Hammer.DIRECTION_ALL }  }}// 注入hammerjs配置providers: [...    {      provide: HAMMER_GESTURE_CONFIG,      useClass: MyHammerConfig    }  ],...复制代码
模块按需加载创建list-component
ng g c list --module app 或 ng generate component --module app复制代码

运行成功以后你会发现多了一个文件夹出来,里面还多了四个文件

创建module

ng generate module list --routing复制代码

运行成功会多出两个文件list-routing.module.ts和list.module.ts

配置src/app/list/list-routing.module.ts

导入ListComponent配置路由

import { NgModule } from '@angular/core';import { Routes, RouterModule } from '@angular/router';import { ListComponent } from './list.component';const routes: Routes = [  {    path: '',    component: ListComponent  }];@NgModule({  imports: [RouterModule.forChild(routes)],  exports: [RouterModule]})export class ListRoutingModule { }复制代码
配置src/app/list/list.module.ts

将ListComponent注册到NgModule中,在模板内就可以使用<app-list><app-list>,在这里要注意一下,当我们使用ng g c list --module app创建component时会会帮我们在app.module.ts中声明一次,我们需要将它删除掉,不然会报错.

import { NgModule } from '@angular/core';import { CommonModule } from '@angular/common';import { ListRoutingModule } from './list-routing.module';import { ListComponent } from './list.component';import { BigCardComponent } from '../common/big-card/big-card.component';import { ShareModule } from '../share.module';@NgModule({  declarations: [    ListComponent,    BigCardComponent  ],  imports: [    CommonModule,    ListRoutingModule,    ShareModule  ]})export class ListModule { }复制代码
配置src/app/list/list.module.ts

没有配置之前是这样的

配置以后

const routes: Routes = [  { path: '', pathMatch: 'full', redirectTo: '/hot' },  { path: 'hot', loadChildren: './hot/hot.module#HotModule' },  { path: 'search', component: SearchComponent },  { path: 'profile', component: ProfileComponent },  { path: 'list', loadChildren: './list/list.module#ListModule' },  { path: 'smile', loadChildren: './smile/smile.module#SmileModule' },];复制代码

打开浏览器查看一下,会看见多了一个list-list-module.js的文件

到这里按需加载就已经都结束

为什么需要src/app/share.module.ts这个模块

先看看写的什么

src/app/share.module.ts声明了一些公共的组件,例如<app-scroll></app-scroll>,我们要时候的时候需要将这个module导入到你需要的模块中

src/app/app.module.ts src/app/list/list.module.ts src/app/hot/hot.module.ts都有,可以去拉取源码查看,慢慢的会发现其中的奥秘.

import { NgModule } from '@angular/core';import { CommonModule } from '@angular/common';import { HammertimeDirective } from '../directive/hammertime.directive';import { ScrollComponent } from './common/scroll/scroll.component';import { SliderComponent } from './common/slider/slider.component';import { FormatTimePipe } from '../pipes/format-time.pipe';@NgModule({  declarations: [    ScrollComponent,    HammertimeDirective,    SliderComponent,    FormatTimePipe  ],  imports: [    CommonModule  ],  exports: [    ScrollComponent,    HammertimeDirective,    SliderComponent,    FormatTimePipe  ]})export class ShareModule { }复制代码
跨域处理

这里要说明一下,我在项目中只配置了开发环境的跨域处理,生产环境没有,我使用的是nginx做的代理.运行npm start才会成功.

新建文件src/proxy.conf.json

target要代理的ip或者是网址

pathRewrite路径重写

{  "/api": {    "target": ";,    "secure": false,    "pathRewrite": {      "^/api": ""    },    "changeOrigin": true  }}复制代码

请求例子

songListDetail(data: any) {    return this.http.get(`/api/playlist/detail?id=${data.id}`);}复制代码
配置angular.json

重启一下项目跨域就配置成功了

"serve": {    "builder": "@angular-devkit/build-angular:dev-server",    "options": {    "browserTarget": "angular-music-player:build",    "proxyConfig": "src/proxy.conf.json"      },    "configurations": {    "production": {      "browserTarget": "angular-music-player:build:production"        }      }    }复制代码

到这里先告一段落了,有什么建议或意见欢迎大家提,之后有补充的我再加上.

作者:前端藏经阁

转发链接:

标签: #nginx代理angularjs