如何在Typescript中进行类型安全的Web服务调用

时间:2018-04-20 17:28:46

标签: javascript angular web-services typescript

使用Angular进行Web服务调用时,不会验证返回对象的类型。这可能意味着我有一个Typescript类:

export class Course {
  constructor(
    public id: number,
    public name: string,
    public description: string,
    public startDate: Date
  ) {}
}

和一个DataService类:

import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class DataService {
  constructor(private http: Http) { }

  public get<T>(url: string): Observable<T> {
    const headers = new Headers();
    headers.append('content-type', 'application/json');
    const options = new RequestOptions({ headers: headers, withCredentials: 
true });
    return this.http.get(url, options)
      .map(response => response.json() as T);
  }
}

然后执行:

import { Component } from '@angular/core';
import { DataService } from './data.service';
import { Course } from './course';


@Component({
  selector: 'app-course',
  templateUrl: './course.component.html',
  styleUrls: ['./course.component.css']
})
export class CourseComponent {
  private courseId: number;

  constructor(private dataService: DataService) { }

  public getData() {
    this.dataService.get<Course>(`http://myapi/course/${this.courseId}`)
    .subscribe(
      course => this.course = course;
    );
  }
}

我没有编译错误,因为我的数据服务正确地返回了一个类型为“Course”的对象。

但是,如果我的API实际上返回了以下JSON:

{
    "uniqueId": 123,
    "name": "CS 101",
    "summary": "An introduction to Computer Science",
    "beginDate": "2018-04-20"
}

我不会遇到编译时错误,如果我尝试对不存在的属性(id,summary,startDate)执行某些操作,则只会出现运行时错误。这样就从TypeScript中删除了一些类型安全性。

1 个答案:

答案 0 :(得分:0)

<强>解决方案

我们可以通过修改数据服务来解决这个问题:

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{fragments/layout}">
<head>
<title>Index</title>
</head>
<body>
<div layout:fragment="content" th:remove="tag">
    <h1> Please login</h1>

    <ul>
        <li>Click here <a href="/user" th:href="@{/user}">to login</a>
        </li>
    </ul>

</div>
</body>
</html>

添加&#34;模板&#34;到我们的课程课程:

import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';


@Injectable()
export class DataService {
  constructor(private http: Http) { }

  private verifyObjectWithTemplate(template: any,  obj: any, graph: string[]) {
    if (!template) {
      return;
    }

    const graphString = graph.join('.');

    Object
      .getOwnPropertyNames(template)
      .forEach(property => {
        if (!obj.hasOwnProperty(property)) {
          console.error(`Object is missing property: ${graphString}.${property}`);
        } else {
          const newGraph = graph.map(i => i);
          newGraph.push(property);
          this.verifyObjectWithTemplate(template[property], obj[property], newGraph);
        }
      });
  }

  public get<T>(url: string, template: T): Observable<T> {
    const headers = new Headers();
    headers.append('content-type', 'application/json');
    const options = new RequestOptions({ headers: headers, withCredentials: true });
    return this.http.get(url, options)
      .map(response => {
        const obj = response.json() as T;
        this.verifyObjectWithTemplate(template, obj, []);
        return obj;
      });
  }
}

并修改我们的课程组件以将模板传递给数据服务:

export class Course {
  public static readonly Template = new Course(-1, '', '', new Date());
  constructor(
    public id: number,
    public name: string,
    public description: string,
    public startDate: Date
  ) {}
}

然后,数据服务将验证API返回的JSON是否具有作为有效Course对象的所有必需属性。

阵列怎么样? 如果我们的一个类包含一个数组,例如我们的Student类:

import { Component } from '@angular/core';
import { DataService } from './data.service';
import { Course } from './course';

@Component({
  selector: 'app-course',
  templateUrl: './course.component.html',
  styleUrls: ['./course.component.css']
})
export class CourseComponent {
  private courseId: number;

  constructor(private dataService: DataService) { }

  public getData() {
    this.dataService.get<Course>(`http://myapi/course/${this.courseId}`, Course.Template)
    .subscribe(
      course => this.course = course;
    );
  }
}

在这种情况下,我们需要确保模板中的任何数组都包含一个项目,因此也可以进行验证。我们还需要按如下方式更新我们的数据服务:

import { Course } from './course';

export class Student {
  public static readonly Template = new Student(-1, '', [Course.Template]);
  constructor(
    public id: number,
    public name: string,
    public courses: Course[]
  ) {}
}

现在应该可以处理所有类型的对象了。

实施例 如果Web服务返回JSON:

import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';


@Injectable()
export class DataService {
  constructor(private http: Http) { }

  private verifyObjectWithTemplate(template: any,  obj: any, graph: string[]) {
    if (!template) {
      return;
    }

    const graphString = graph.join('.');

    // Important that we compare object to undefined, and not to null.
    // Property being null can be valid.
    if (obj === undefined) {
      console.error(`Object is missing property: ${graphString}`);
      return;
    }

    if (obj === null) {
        // No need to check rest of graph if object is null.
        return;
    }

    if (Array.isArray(template)) {
      if (!template[0]) {
        console.error(`Template array is empty: ${graphString}`);
        return;
      }

      if (!Array.isArray(obj)) {
        console.error(`Object is not an array: ${graphString}`);
        return;
      }

      if (!obj[0]) {
        console.log(`Object array is empty so can't be verified: ${graphString}`);
        return;
      }

      template = template[0];
      obj = obj[0];
    }

    Object
      .getOwnPropertyNames(template)
      .forEach(property => {
        if (!obj.hasOwnProperty(property)) {
          console.error(`Object is missing property: ${graphString}.${property}`);
        } else {
          const newGraph = graph.map(i => i);
          newGraph.push(property);
          this.verifyObjectWithTemplate(template[property], obj[property], newGraph);
        }
      });
  }

  public get<T>(url: string, template: T): Observable<T> {
    const headers = new Headers();
    headers.append('content-type', 'application/json');
    const options = new RequestOptions({ headers: headers, withCredentials: 
true });
    return this.http.get(url, options)
      .map(response => {
        const obj = response.json() as T;
        this.verifyObjectWithTemplate(template, obj, []);
        return obj;
      });
  }
}

然后我们会在控制台中看到以下消息: errors