Javascript 从界面以编程方式创建Typescript用户定义的类型保护

Javascript 从界面以编程方式创建Typescript用户定义的类型保护,javascript,angular,typescript,Javascript,Angular,Typescript,在我的Angular2项目中,我为GridMetadata创建了一个接口: 网格元数据.ts 在我的服务中,我有一个公共方法create,该方法需要一个参数,该参数应该是一个具有字符串值的属性activity的对象,例如{activity:'pushups'}。您可以在GridService的底部找到: grid.service.ts 从'@angular/core'导入{Injectable}; 进口{ AngularFire数据库, 可观测火基 }来自“angularfire2/数据库”;

在我的Angular2项目中,我为
GridMetadata
创建了一个接口:

网格元数据.ts 在我的服务中,我有一个公共方法
create
,该方法需要一个参数,该参数应该是一个具有字符串值的属性
activity
的对象,例如
{activity:'pushups'}
。您可以在
GridService
的底部找到:

grid.service.ts
从'@angular/core'导入{Injectable};
进口{
AngularFire数据库,
可观测火基
}来自“angularfire2/数据库”;
从“firebase/app”导入*作为firebase;
//从“rxjs/Observable”导入{Observable};
从“/auth.service”导入{AuthService};
//TODO:这是接口的正确顺序吗?
从“./grid metadata”导入{GridMetadata};
@可注射()
导出类网格服务{
私有网格元数据:FirebaseListObservable;
建造师(
非洲开发银行私人数据库:,
私有authService:authService
) {
this.init();
}
private init():void{
this.gridMetadata=this.afDb.list('gridMetadata');
}
创建(元数据:GridMetadata):承诺{
函数isGridMetadata(obj:any):obj是GridMetadata{
返回obj.activity的类型==='string'&&
typeof obj.createdAt==“对象”&&
obj.totalReps的类型===“编号”&&
obj.updatedAt的类型==='对象'?
正确:
虚假的;
}
返回新承诺((解决、拒绝)=>{
if(this.authService.isAuthenticated==false){
返回拒绝(新错误('无法创建新网格;您没有身份验证');
}
let key:string=this.gridMetadata.push(未定义).key;
让uri:string=[this.authService.currentUserId,key].join('/');
metadata.createdAt=firebase.database.ServerValue.TIMESTAMP;
metadata.totalReps=0;
metadata.updatedAt=firebase.database.ServerValue.TIMESTAMP;
if(isGridMetadata(元数据)==false){
返回reject reject(新类型错误(`metadata`与`GridMetadata`接口的签名不匹配)。);
}
this.gridMetadata.update(uri,元数据)
.然后(()=>{
解决();
})
.catch((错误)=>{
拒绝(新错误(Error.message));
});
});
}
}
问题1 首先,请注意,在我的
create
方法中,我已经说过所需的参数
metadata
应该与接口
GridMetadata
匹配,但它没有匹配。我只向它传递了一个对象
{activity:'pushups'}
,注意在界面中还有三个其他必需的属性(
createdAt
totalReps
updatedAt

编译时或运行时都没有错误。为什么会这样

我很可能只是误解了这一点,因为我是打字新手

问题2 我认为问题1中应该有一个错误;这个问题很容易解决,我只是好奇为什么没有抛出错误

我真正的问题是,您可以看到,我通过添加缺少的属性(
createdAt
totalReps
updatedAt
),继续构建
metadata
对象,以匹配接口的签名

然后,如果对象与接口不匹配,我将拒绝承诺

如何检查
createdAt
updatedAt
不仅是对象,而且与签名
{.sv:'timestamp'}
匹配,还有比我现在使用的
if
语句更好的方法吗

问题3 最后,;为了方便起见,有没有一种方法可以通过编程从接口创建用户定义的类型保护

我不会想象,因为TypeScript眼中的界面不是对象,因此无法在函数中以编程方式使用,例如,提取属性
activity,createdAt,
和值
string,object,
,并使用它们在保护中循环并以编程方式检查



虽然这里有三个问题,但事实上,Q2是我最关心的问题,因为我相信Q1有一个简单的答案,而Q3只是额外的分数-我将接受Q2的答案,非常感谢您的帮助

问题1

Typescript编译器不会抱怨b/c编译器没有查看HTML模板

问题2

最后都是Javascript,因此如果不检查对象是否具有所需的属性,就无法验证对象的“签名”。例如,添加到原始逻辑中:

typeof obj.createdAt === 'object' &&
    obj.createdAt.hasOwnProperty('.sv') && 
    typeof obj.createdAt['.sv']  === 'string'
但我可能更倾向于首先检查作为对象的属性是否存在(而不是检查它们的类型):

很明显,你可以用不同的结构,但归根结底都是相同类型的检查

问题3


Typescript中的接口仅在编译时使用。它们从不包含在编译输出中。因此,在运行时,接口不存在,无法通过编程方式访问

您如何调用
create
?传递给它的参数是否键入为
any
?@Saravana当前类似于从组件模板内部创建新网格。最终它将是一个Angular2
FormGroup
的值。
import { Injectable } from '@angular/core';

import {
  AngularFireDatabase,
  FirebaseListObservable
} from 'angularfire2/database';
import * as firebase from 'firebase/app';
// import { Observable } from 'rxjs/Observable';

import { AuthService } from './auth.service';
// TODO: Is this the correct order for interfaces?
import { GridMetadata } from './grid-metadata';

@Injectable()
export class GridService {
  private gridMetadata: FirebaseListObservable<any>;

  constructor(
    private afDb: AngularFireDatabase,
    private authService: AuthService
  ) {
    this.init();
  }

  private init(): void {
    this.gridMetadata = this.afDb.list('gridMetadata');
  }

  create(metadata: GridMetadata): Promise<any> {
    function isGridMetadata(obj: any): obj is GridMetadata {
      return typeof obj.activity === 'string' &&
        typeof obj.createdAt === 'object' &&
        typeof obj.totalReps === 'number' &&
        typeof obj.updatedAt === 'object' ?
          true :
          false;
    }

    return new Promise((resolve, reject) => {
      if (this.authService.isAuthenticated === false) {
        return reject(new Error('Can’t create new grid; you’re not auth’d.'));
      }

      let key: string = this.gridMetadata.push(undefined).key;
      let uri: string = [this.authService.currentUserId, key].join('/');

      metadata.createdAt = firebase.database.ServerValue.TIMESTAMP;
      metadata.totalReps = 0;
      metadata.updatedAt = firebase.database.ServerValue.TIMESTAMP;

      if (isGridMetadata(metadata) === false) {
        return reject(new TypeError('`metadata` doesn’t match the signature of the `GridMetadata` interface.'));
      }

      this.gridMetadata.update(uri, metadata)
        .then(() => {
          resolve();
        })
        .catch((error) => {
          reject(new Error(error.message));
        });
    });
  }
}
typeof obj.createdAt === 'object' &&
    obj.createdAt.hasOwnProperty('.sv') && 
    typeof obj.createdAt['.sv']  === 'string'
if (!obj.createdAt || !obj.updatedAt) {
    return false;
}
// now check for the child properties
return obj.createdAt.hasOwnProperty('.sv') &&
    typeof obj.createdAt['.sv'] === 'string' &&
    ...