原答案
你说,这是 Typescript 的常见问题device
属于类型Device
,但事实并非如此。它具有与Device
会,但因为它不是Device
它没有预期的方法。
您需要确保实例化Device
对于您的每个条目Page
,也许在ngOnInit
父组件的:
我不知道结构Page
,但如果它是一个数组,请尝试以下操作。
ngOnInit() {
this.deviceService.list('', 'sensor', ).subscribe(
res => {
this.devices = res.results.map(x => Object.assign(new Device(), x));
}
)
}
进一步解释
让我们尝试一个打字稿示例,因为这种行为与 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 接口纯粹是一个编译时结构,与类不同,它在 JavaScript 中没有等效项 - 它只是一个开发人员提示。但这也意味着如果您为外部值创建一个接口,例如SimpleValue
上面,然后得到它wrong那么编译器仍然会相信你知道你在说什么,它不可能在编译时验证这一点。
从外部源加载类怎么样?有什么不同?如果我们以上面的例子为例并改变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
we did not call new SimpleClass
,我们只是相信调用者知道存储的类型是什么。
我们该如何处理这个问题?让我们暂时回到 Angular 并考虑一个常见的用例:日期。 JSON 没有日期类型,而 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
});
}
}
也许有点冗长,但这是我用来处理来自扁平后端合约的丰富前端模型的最强大和最灵活的模式。