'Angular 6: ERROR TypeError: "... is not a function" - but it is
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.
The error is in line this.device.addKeysToObj(this.result.results[0]);
Here is my 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);
}
}
And that is the call:
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]);
}
}
)
}
}
Edit 1
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 { … }
}
Edit 2
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>()))
);
}
The call in the parent component:
ngOnInit() {
this.deviceService.list('', 'sensor', ).subscribe(
res => {
this.devices = res.results;
}
)
}
Template:
<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>
Solution 1:[1]
You might have landed here with a different problem than in the accepted answer: If you're using Angular's services and forget the @Injectable, with Angular Ivy you get a runtime exception like this:
ERROR TypeError: ConfigurationServiceImpl.\u0275fac is not a function
The correct solution is to ad @Injectable also to implementations, e.g:
// 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 {
...
}
Solution 2:[2]
In my case I tested two solutions that work for me
Wrap the code in a 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
}
OR
The other solution was to add a condition
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
}
Solution 3:[3]
As @UncleDave already explained, you're only mapping the values with corresponding names to the Typescript object but you're not creating the expected class object with it. It's pretty confusing, I know.
Object.assign() will solve your current problem, but not if you have nested objects. Then you will have to do Object.assign() for each nested object as well, which can get tedious if you have to do this in multiple places in your codebase.
I suggest an alternative: class-transformer With this you can mark your nested fields with annotations that tell the compiler how to create the nested objects as well. With this you only need to use the plainToClass() method to map your top level object and all the underlying fields will also have the correct types/objects.
Example
Let's say we have two classes:
class Parent {
name: string;
child: Child;
public getText(): string {
return 'parent text';
}
}
class Child{
name: string;
public getText(): string {
return 'child text';
}
}
The first case we already know doesn't work:
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
Second case using 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!
to make it work, we would have to do the following:
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
Third case with class-transformer:
First modify the parent class so that the child mapping is defined:
class Parent {
name: string;
@Type(() => Child)
child: Child;
public getText(): string {
return 'parent text';
}
}
then you can map to the parent object:
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | bersling |
| Solution 2 | Julio Cesar Brito Gomes |
| Solution 3 | Babyburger |
