typescript Angular 6:错误类型错误:“…不是函数”-但它是

wvyml7n5  于 2023-05-08  发布在  TypeScript
关注(0)|答案(5)|浏览(110)

我现在真的很困惑,因为我得到了ERROR TypeError: "_this.device.addKeysToObj is not a function"。但是我实现了这个函数,所以我不知道问题出在哪里,也不知道为什么它不能调用。我已经尝试了Firefox和Chrome的代码,都通过相同的错误。
错误在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参见上面代码中的注解:

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

编辑2

设备的一部分。服务代码:

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>
idv4meu8

idv4meu81#

原始答案

这是Typescript的一个常见问题,你说deviceDevice类型,但它不是。它具有与Device相同的所有属性,但由于它不是Device,因此没有预期的方法。
您需要确保为Page(可能是父组件的ngOnInit)中的每个条目示例化Device
我不知道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);

很好用,太棒了。类型脚本接口是一个纯粹的编译时结构,与类不同,它在JavaScript中没有等价物-它只是一个开发人员提示。但这也意味着,如果你为一个外部值创建了一个接口,比如上面的SimpleValue,并且得到了 * 错误 *,那么编译器仍然会相信你知道你在说什么,它不可能在编译时验证这个。
如何从外部源加载类?这有什么不同?如果我们以上面的例子为例,将SimpleValue更改为一个类,而不更改任何其他内容,那么它仍然可以工作。但还是有区别的。与接口不同的是,类被转译成它们的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,考虑一个常见的用例:dates. 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
        });
    }
}

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

qnyhuwrf

qnyhuwrf2#

你可能在这里遇到了一个与公认答案不同的问题:如果你正在使用Angular的服务,忘记了@Injectable,使用Angular Ivy,你会得到一个像这样的运行时异常:

ERROR TypeError: ConfigurationServiceImpl.\u0275fac is not a function

正确的解决方案是将@Injectable也添加到实现中,例如:

// do not omit the @Injectable(), or you'll end up with the error message!
@Injectable()
export class ConfigurationServiceImpl implements ConfigurationService {
...
}

@Injectable({
  providedIn: "root",
  useClass: ConfigurationServiceImpl,
})
export abstract class ConfigurationService {
...
}

参见Angular 7 TypeError: service.x is not a function

muk1a3rh

muk1a3rh3#

在我的案例中,我测试了两种对我有效的解决方案
将代码 Package 在***setTimeout***中

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


另一个解决方案是添加***条件***

ngOnInit() {
  if(typeof this.deviceService.list === 'function'){ // START OF CONDITION
    this.deviceService.list('', 'sensor', ).subscribe(
      res => { 
        this.devices = res.results.map(x => Object.assign(new Device(), x));
      }
    )
  } // END OF CONDITION
}
v8wbuo2f

v8wbuo2f4#

正如@UncleDave已经解释过的,您只是将具有相应名称的值Map到Typescript对象,但并没有使用它创建预期的类对象。我知道这很让人困惑。
Object.assign()可以解决当前的问题,但如果你有嵌套对象,就不会了。然后,您还必须为每个嵌套对象执行Object.assign(),如果您必须在代码库中的多个位置执行此操作,则会变得繁琐。
我建议另一种选择:class-transformer通过这个,你可以用注解来标记你的嵌套字段,告诉编译器如何创建嵌套对象。这样,你只需要使用plainToClass()方法来Map你的顶层对象,所有的底层字段也会有正确的类型/对象。

示例

假设我们有两个类:

class Parent {
    name: string;
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

class Child{
    name: string;

    public getText(): string {
        return 'child text';
    }
}

第一种情况我们已经知道不起作用:

let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = parentJson; // note: compiler accepts this because parentJson is any.  
        // If we tried to assign the json structure directly to 'parent' it would fail because the compiler knows that the method getText() is missing!

console.log(parent.getText()); // throws the error that parent.getText() is not a function as expected

第二种情况使用Object.assign()

let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = Object.assign(parentJson); 

console.log(parent.getText()); // this works
console.log(parent.child.getText()); // throws error that parent.child.getText() is not a function!

要使其发挥作用,我们必须做到以下几点:

let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = Object.assign(parentJson);
parent.child = Object.assign(parentJson.child);

console.log(parent.getText()); // this works
console.log(parent.child.getText()); // this works

第三种情况带类变压器:

首先修改父类,以便定义子Map:

class Parent {
    name: string;
    @Type(() => Child)
    child: Child;

    public getText(): string {
        return 'parent text';
    }
}

然后你可以Map到父对象:

let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = plainToClass(Parent, parentJson);

console.log(parent.getText()); // this works
console.log(parent.child.getText()); // this works
z2acfund

z2acfund5#

我在这篇文章中给出的答案帮助了我。我从YT的一个视频中得到的,基本上说你需要检查这个类的方法是否存在。https://stackoverflow.com/a/76186682/21161371

相关问题