javascript - Angular 6 : ERROR TypeError: "... is not a function" - but it is

标签 javascript angular typescript

我现在真的很困惑,因为我得到了 错误 TypeError: "_this.device.addKeysToObj is not a function"。但是我实现了这个功能,所以我不知道有什么问题或者为什么它不可调用。我已经通过同样的错误尝试了 Firefox 和 chrome 的代码。

错误在行 this.device.addKeysToObj(this.result.results[0]);

这是我的类(class):

export class Device {
    id: number;
    deviceID: string;
    name: string;
    location: string;
    deviceType: string;
    subType: string;
    valueNamingMap: Object;

    addKeysToObj(deviceValues: object): void {
        for (let key of Object.keys(deviceValues).map((key) => { return key })) {
            if (!this.valueNamingMap.hasOwnProperty(key)) {
                this.valueNamingMap[key] = '';
            }
        }
        console.log(this, deviceValues);
    }
}

这就是调用:

export class BatterieSensorComponent implements OnInit {
    @Input() device: Device;
    public result: Page<Value> = new Page<Value>();

    //[..]

    ngOnInit() {
      this.valueService.list('', this.device).subscribe(
        res => {
          console.log(this.device);  // NEW edit 1
          this.result = res;
          if (this.result.count > 0) 
          {
            this.device.addKeysToObj(this.result.results[0]);
          }
        }
      )
    }
}

编辑 1

记录 this.device 见上面代码中的注释:

{
    deviceID: "000000001" 
    deviceType: "sensor"    ​
    id: 5    ​
    location: "-"
​    name: "Batteries"    ​
    subType: "sensor"    ​
    valueNamingMap:
      Object { v0: "vehicle battery", v1: "Living area battery" }
    <prototype>: Object { … } 
}

编辑2

部分device.service代码:

list(url?: string, deviceType?: string, subType?: string): Observable<Page<Device>> {
  if(!url) url = `${this.url}/devices/`;
  if(deviceType) url+= '?deviceType=' + deviceType;
  if(subType) url+= '&subType=' + subType;

  return this.httpClient.get<Page<Device>>(url, { headers: this.headers })
    .pipe(
      catchError(this.handleError('LIST devices', new Page<Device>()))
    );
}

父组件中的调用:

ngOnInit() {
  this.deviceService.list('', 'sensor', ).subscribe(
    res => { 
      this.devices = res.results;
    }
  )
}

模板:

<div class="mdl-grid">
  <div class="mdl-cell mdl-cell--6-col mdl-cell--6-col-tablet" *ngFor="let device of devices">
    <app-batterie-sensor [device]="device"></app-batterie-sensor>
  </div>
</div>

最佳答案

原始答案

这是 Typescript 的一个常见问题,你说 deviceDevice 类型,但它不是。它具有与 Device 相同的所有属性,但由于它不是 Device,因此没有预期的方法。

您需要确保为 Page 中的每个条目实例化 Device,可能在父组件的 ngOnInit 中:

我不知道 Page 的结构,但如果它是一个数组,请尝试以下操作。

ngOnInit() {
  this.deviceService.list('', 'sensor', ).subscribe(
    res => { 
      this.devices = res.results.map(x => Object.assign(new Device(), x));
    }
  )
}

进一步说明

让我们尝试一个 typescript 示例,因为这种行为与 Angular 没有任何关系。我们将使用 localStorage 来表示来自外部源的数据,但这与 HTTP 的工作方式相同。

interface SimpleValue {
    a: number;
    b: string;
}

function loadFromStorage<T>(): T {
    // Get from local storage.
    // Ignore the potential null value because we know this key will exist.
    const storedValue = localStorage.getItem('MyKey') as string;

    // Note how there is no validation in this function.
    // I can't validate that the loaded value is actually T
    // because I don't know what T is.
    return JSON.parse(storedValue);
}

const valueToSave: SimpleValue = { a: 1, b: 'b' };
localStorage.setItem('MyKey', JSON.stringify(valueToSave));

const loadedValue = loadFromStorage<SimpleValue>();

// It works!
console.log(loadedValue);

效果很好,太棒了。 typescript 接口(interface)纯粹是一个编译时结构,与类不同,它在 JavaScript 中没有等价物——它只是一个开发人员提示。但这也意味着,如果您为外部值创建一个接口(interface),如上面的 SimpleValue,并且获取它错误,那么编译器仍然会相信您知道您的内容重新谈论,它不可能在编译时验证这一点。

如何从外部源加载类?它有何不同?如果我们采用上面的示例并将 SimpleValue 更改为一个类而不更改任何其他内容,那么它仍然可以工作。但有一点不同。与接口(interface)不同,类被转换成它们的 JavaScript 等价物,换句话说,它们存在于编译点之后。在我们上面的示例中,这不会导致问题,所以让我们尝试一个确实会导致问题的示例。

class SimpleClass {
    constructor(public a: number, public b: string) { }

    printA() {
        console.log(this.a);
    }
}

const valueToSave: SimpleClass = new SimpleClass(1, 'b');
localStorage.setItem('MyKey', JSON.stringify(valueToSave));

const loadedValue = loadFromStorage<SimpleClass>();

console.log(loadedValue.a); // 1
console.log(loadedValue.b); // 'b'
loadedValue.printA(); // TypeError: loadedValue.printA is not a function

加载的值具有我们期望的属性,但不是方法,呃哦!问题在于调用 new SimpleClass 时会创建方法。当我们创建 valueToSave 时,我们确实实例化了该类,但随后我们将其转换为 JSON 字符串并将其发送到其他地方,而 JSON 没有方法的概念,因此信息丢失了。当我们在 loadFromStorage 中加载数据时,我们没有调用 new SimpleClass,我们只是相信调用者知道存储的类型。

我们如何处理这个问题?让我们暂时回到 Angular 并考虑一个常见的用例:日期。 JSON 没有 Date 类型,而 JavaScript 有,那么我们如何从服务器检索日期并将其用作日期呢?这是我喜欢使用的模式。

interface UserContract {
    id: string;
    name: string;
    lastLogin: string; // ISO string representation of a Date.
}

class UserModel {
    id: string; // Same as above
    name: string; // Same as above
    lastLogin: Date; // Different!

    constructor(contract: UserContract) {
        // This is the explicit version of the constructor.
        this.id = contract.id;
        this.name = contract.name;
        this.lastLogin = new Date(contract.lastLogin);

        // If you want to avoid the boilerplate (and safety) of the explicit constructor
        // an alternative is to use Object.assign:
        // Object.assign(this, contract, { lastLogin: new Date(contract.lastLogin) });
    }

    printFriendlyLastLogin() {
        console.log(this.lastLogin.toLocaleString());
    }
}

import { HttpClient } from '@angular/common/http';
import { Injectable, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
class MyService {
    constructor(private httpClient: HttpClient) { }

    getUser(): Observable<UserModel> {
        // Contract represents the data being returned from the external data source.
        return this.httpClient.get<UserContract>('my.totally.not.real.api.com')
            .pipe(
              map(contract => new UserModel(contract))
            );
    }
}

@Component({
    // bla bla
})
class MyComponent implements OnInit {
    constructor(private myService: MyService) { }

    ngOnInit() {
        this.myService.getUser().subscribe(x => {
            x.printFriendlyLastLogin(); // this works
            console.log(x.lastLogin.getFullYear()); // this works too
        });
    }
}

也许有点冗长,但它是我用来处理来自扁平后端合约的丰富前端模型的最健壮和灵活的模式。

关于javascript - Angular 6 : ERROR TypeError: "... is not a function" - but it is,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51763745/

相关文章:

typescript - typescript 中的@符号是什么意思--Angular 2

typescript - 为什么 Typescript 接口(interface)允许自引用?

javascript - 使用 jQuery 时正确的缓存方式和 this

javascript - 在嵌套动态添加的 div 的海洋中定位一个 div

javascript - 适合容器内的实时文本

angular - 使用现有过滤器以 Angular 过滤多列

Angular 6 : get reference to Components created with *ngFor inside ng-container tag

javascript - 如何全局存储div ID值

html - 单击时将 div 的 Angular 设置为不同的颜色

reactjs - 导出变量 'store' 具有或正在使用来自外部模块错误 Redux 工具包和 redux 的名称 '$CombinedState'