角度6:错误TypeError:"...不是函数"; - 但它是 [英] Angular 6: ERROR TypeError: "... is not a function" - but it is

查看:78
本文介绍了角度6:错误TypeError:"...不是函数"; - 但它是的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我现在真的很困惑,因为我得到了ERROR TypeError: "_this.device.addKeysToObj is not a function".但是我实现了该功能,所以我不知道出了什么问题或为什么它不能被调用.我已经尝试过使用Firefox和chrome的代码,都遇到了相同的错误.

I am currently really confused, because I get the ERROR TypeError: "_this.device.addKeysToObj is not a function". But I implemented the function, so I have no idea what's the problem or why it is not callable. I have tried the code with Firefox and chrome, both through the same error.

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

这是我的课程:

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参见上面的代码中的注释:

Logging this.device see comment in code above:

{
    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代码的一部分:

Part of the device.service code:

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,因此没有预期的方法.

Original answer

This is a common gotcha with Typescript, you say device is of type Device, but it isn't. It has all of the same properties as a Device would, but since it isn't a Device it does not have the expected methods.

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

You need to ensure that you instantiate Device for each entry in your Page, perhaps in the ngOnInit of the parent component:

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

I don't know the structure of Page, but if it's an array try the following.

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


进一步的解释

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


Further explanation

Let's try a typescript example, as this behaviour doesn't have anything to do with Angular. We'll use localStorage to represent data coming from an external source, but this works just the same with 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接口纯粹是一个编译时结构,与类不同,它在JavaScript中没有等效项-它只是开发人员的提示.但这也意味着,如果您为上面的SimpleValue这样的外部值创建一个接口,并使其得到错误,那么编译器仍然会相信您知道您在说什么,它在编译时无法验证这一点.

That works just fine, awesome. A typescript interface is purely a compile-time structure and, unlike a class, it has no equivalent in JavaScript - it's just a developer hint. But this also means that if you create an interface for an external value, like SimpleValue above, and get it wrong then the compiler is still going to trust you know what you're talking about, it can't possibly validate this at compile time.

如何从外部源加载类?有何不同?如果我们以上面的示例为例,并且将SimpleValue更改为一个类而不进行任何其他更改,则它仍然可以工作.但是有区别.与接口不同,类被转换为等效的JavaScript,换句话说,它们存在于编译点之后.在上面的示例中,这不会引起问题,所以让我们尝试一个确实会导致问题的示例.

What about loading a class from an external source? How does it differ? If we take the example above and change SimpleValue into a class without changing anything else then it will still work. But there is a difference. Unlike interfaces, classes are transpiled into their JavaScript equivalent, in other words, they exist past the point of compilation. In our above example this doesn't cause a problem, so let's try an example that does cause a problem.

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,我们只是相信调用者知道存储的类型是什么.

The loaded value had the properties we expected, but not the methods, uh oh! The problem is that methods get created when new SimpleClass is called. When we created valueToSave we did indeed instantiate the class, but then we turned it into a JSON string and sent it off elsewhere, and JSON has no concept of methods so the information was lost. When we loaded the data in loadFromStorage we did not call new SimpleClass, we just trusted that the caller knew what the stored type would be.

我们如何处理?让我们回到Angular一下,考虑一个常见的用例:日期. JSON没有日期类型,而JavaScript有,因此我们如何从服务器中检索日期并将其作为日期使用?这是我喜欢使用的模式.

How do we deal with this? Let's go back to Angular for a moment and consider a common use case: dates. JSON has no Date type, JavaScript does, so how do we retrieve a date from our server and have it work as a date? Here's a pattern I like to use.

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
        });
    }
}

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

Perhaps a bit verbose, but it's the most robust and flexible pattern I've used for dealing with rich frontend models coming from flat backend contracts.

这篇关于角度6:错误TypeError:"...不是函数"; - 但它是的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆