You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 8中Interceptor使用及HttpClient替代API调用报错解决

解决Angular 8 API迁移错误与Interceptor使用指南

咱们先来看你遇到的SyntaxError: Unexpected end of input问题,再一步步讲如何用Interceptor简化API调用逻辑。


一、修复当前API调用的语法错误

这个错误本质是前端尝试解析无效的JSON响应,根源是你新写的HttpClient调用没有复现原来自定义invoke方法里的核心逻辑:

1. 原来的invoke做了哪些你没做的事?

  • 自动替换路由里的占位符(比如{patientUUID}会替换成用户的profile.participant值)
  • 拼接环境变量里的基础URL(environment.ApiOrigin)
  • 添加Authorization: Bearer {token}请求头
  • 统一处理错误和响应类型

你的新代码直接调用HttpClient.get,导致请求URL还是带着{patientUUID}占位符,后端返回404或非JSON响应,前端解析时就会报错。

2. 修正getDossierEntry方法

先在DocumentCorrespondenceService里补上这些缺失的逻辑,同时保留类型安全:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { DossierEntry } from '../../interfaces/dossier/dossier-entry.interface';
import { AuthService } from '../auth/auth.service'; // 替换成你的Auth服务路径
import { environment } from '../../../environments/environment';
import { map, switchMap, take, catchError } from 'rxjs/operators';

// 先定义API响应的类型(如果原API返回带values的对象)
interface DossierEntryResponse {
  values: DossierEntry[];
}

@Injectable({ providedIn: 'root' })
export class DocumentCorrespondenceService {
  constructor(
    private http: HttpClient,
    private authService: AuthService
  ) {}

  getDossierEntry(type: string = ''): Observable<DossierEntryResponse> {
    const entryType = type === '' ? 'all' : `type/${type}`;
    const rawRoute = `/api/patient/{patientUUID}/DossierEntry/${entryType}`;

    return this.authService.loginStatus().pipe(
      take(1), // 只取一次用户登录状态
      map(user => {
        if (!user) {
          throw Error('需要登录才能访问该API');
        }
        // 替换路由占位符
        const parsedRoute = rawRoute.replace('{patientUUID}', user.profile.participant);
        // 拼接基础URL
        return `${environment.ApiOrigin}${parsedRoute}`;
      }),
      switchMap(url => {
        // 构建请求头
        const headers = new HttpHeaders({
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        });
        // 添加Token头
        const accessToken = this.authService.getAccessToken();
        const authHeaders = accessToken ? headers.set('Authorization', `Bearer ${accessToken}`) : headers;

        // 发起类型安全的请求
        return this.http.get<DossierEntryResponse>(url, { headers: authHeaders });
      }),
      // 复用原有的错误处理逻辑
      catchError(err => this.handleError(err))
    );
  }

  // 复制原HealthAPIService中的handleError方法到这里
  private handleError(error: any): Observable<never> {
    console.error('API请求失败:', error);
    // 根据原逻辑调整,比如弹出提示、返回空数据等
    return throwError(() => new Error(error.message || '服务器错误'));
  }
}

3. 组件中的响应处理修正

你的组件代码里尝试访问result.values,但原自定义方法返回的是包含values的对象,所以要修改组件的赋值逻辑:

ngOnInit() {
  this.documentCorrespondencService.getDossierEntry('physical').subscribe(result => {
    console.log(result.values);
    this.entries = result.values; // 这里要赋值values数组,不是整个result对象
    this.dossiersLoaded = true;
  }, err => {
    console.error('加载病历失败:', err);
    this.dossiersLoaded = true; // 加载失败也要标记状态,避免页面一直加载
  });
}

二、Angular 8中使用Interceptor的完整步骤

Interceptor可以帮你把所有API的通用逻辑(加Token、替换占位符、错误处理)统一起来,不用每个方法都重复写代码,完美替代你原来的invoke方法。

1. 创建Auth拦截器(处理Token和基础URL)

用Angular CLI生成拦截器:ng generate interceptor auth,然后修改代码:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 替换成你的Auth服务路径
import { environment } from '../environments/environment';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // 1. 拼接基础URL(仅针对相对路径的请求)
    let modifiedUrl = request.url;
    if (!modifiedUrl.startsWith('http')) {
      modifiedUrl = `${environment.ApiOrigin}${modifiedUrl}`;
    }

    // 2. 添加Authorization Token头
    const accessToken = this.authService.getAccessToken();
    let modifiedRequest = request.clone({ url: modifiedUrl });
    if (accessToken) {
      modifiedRequest = modifiedRequest.clone({
        setHeaders: {
          Authorization: `Bearer ${accessToken}`
        }
      });
    }

    // 3. 统一处理错误
    return next.handle(modifiedRequest).pipe(
      catchError((error: HttpErrorResponse) => {
        // 这里可以统一处理401跳登录、403无权限等场景
        console.error('HTTP请求错误:', error);
        return throwError(() => new Error(error.message || '服务器请求失败'));
      })
    );
  }
}

2. 创建路由占位符拦截器(替换{patientUUID}等)

再生成一个拦截器处理路由替换:ng generate interceptor route-placeholder

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { switchMap, take } from 'rxjs/operators';

@Injectable()
export class RoutePlaceholderInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // 检查URL是否包含需要替换的占位符
    if (request.url.includes('{patientUUID}') || request.url.includes('{userId}')) {
      return this.authService.loginStatus().pipe(
        take(1),
        switchMap(user => {
          if (!user) {
            throw Error('未登录用户无法访问该API');
          }
          let parsedUrl = request.url;
          parsedUrl = parsedUrl.replace('{userId}', user.profile.sub);
          parsedUrl = parsedUrl.replace('{patientUUID}', user.profile.participant);
          const modifiedRequest = request.clone({ url: parsedUrl });
          return next.handle(modifiedRequest);
        })
      );
    }
    return next.handle(request);
  }
}

3. 在AppModule中注册拦截器

打开app.module.ts,把两个拦截器添加到providers数组:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';
import { RoutePlaceholderInterceptor } from './route-placeholder.interceptor';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RoutePlaceholderInterceptor,
      multi: true // 允许多个拦截器生效
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

4. 简化API方法

有了拦截器之后,getDossierEntry可以简化到极致:

getDossierEntry(type: string = ''): Observable<DossierEntryResponse> {
  const entryType = type === '' ? 'all' : `type/${type}`;
  const url = `/api/patient/{patientUUID}/DossierEntry/${entryType}`;
  return this.http.get<DossierEntryResponse>(url);
}

所有通用逻辑都由拦截器自动处理,代码更简洁,也更符合Angular的最佳实践。


内容的提问来源于stack exchange,提问作者mightycode Newton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:45:25