Angular 6: TypeError - The function you are trying to use is not recognized as a valid function, even though it should be

I'm currently facing a puzzling issue where I'm encountering the

ERROR TypeError: "_this.device.addKeysToObj is not a function"
. Despite having implemented the function, I can't figure out why it's not functioning properly or callable. This error persists when testing the code on both Firefox and Chrome.

The problematic line causing the error is

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

Below is the excerpt from 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 here is how the function is called:

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);  
          this.result = res;
          if (this.result.count > 0) 
          {
            this.device.addKeysToObj(this.result.results[0]);
          }
        }
      )
    }
}

Update:

Upon logging this.device, we get the following output:

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

Extra Information:

An excerpt from 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>

Answer №1

Unique answer

This issue is a common pitfall in Typescript. Even though you define the type of device as Device, it may not actually be an instance of Device. While it may have all the properties of a Device, it lacks the expected methods because it's not truly a Device.

To resolve this, make sure to instantiate an actual Device for each entry in your Page. One way to do this is by utilizing the ngOnInit method in the parent component:

If your Page is an array, consider implementing the following:

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

Further insight

Let's explore a typescript example that delves into how this behavior is independent of Angular. We'll use localStorage to simulate data retrieval from an external source, which applies similarly to HTTP calls.

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

function loadFromStorage<T>(): T {
    const storedValue = localStorage.getItem('MyKey') as string;
    return JSON.parse(storedValue);
}

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

const loadedValue = loadFromStorage<SimpleValue>();

console.log(loadedValue);

In TypeScript, interfaces serve as compile-time structures and offer no direct representation in JavaScript. This means that if you misuse an interface, the compiler can't catch it at compile time.

Comparatively, loading a class from an external source differs. Classes transpile to their JavaScript equivalents, persisting beyond compilation. The absence of methods when loading a class instance indicates a loss of behavior due to JSON serialization.

Addressing this requires careful design considerations, demonstrated using a common scenario involving dates in Angular with JSON data.

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

// TypeError occurs due to lost methods
loadedValue.printA();

An effective pattern for managing frontend models sourced from flat backend contracts involves explicit mapping strategies within constructors or leveraging tools like Object.assign for flexibility and robustness.

Answer №2

Arriving here may present you with a different issue compared to the solution provided: If you are utilizing Angular's services and happen to overlook adding @Injectable, under Angular Ivy, you will encounter a runtime error similar to this:

ERROR TypeError: ConfigurationServiceImpl.\u0275fac is not a function

The proper resolution is to include @Injectable when defining implementations, for instance:

// be sure to include @Injectable(), or else an error will arise!
@Injectable()
export class ConfigurationServiceImpl implements ConfigurationService {
...
}

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

For more information, refer to Angular 7 TypeError: service.x is not a function.

Answer №3

After experimenting with different approaches, I found two solutions that worked well for me

One method involved wrapping 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
}

Alternatively

The other solution required adding 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
}

Answer №4

As mentioned by @UncleDave earlier, the process of mapping values with corresponding names to a Typescript object does not create the expected class object. This can be quite confusing.

Object.assign() can resolve the current issue, but it may not work effectively for nested objects. In such cases, you would need to apply Object.assign() recursively for each nested object, which could become cumbersome if done in multiple instances within your codebase.

An alternative solution is recommended: using class-transformer. With this approach, you can annotate nested fields to instruct the compiler on how to generate the nested objects as required. By using the plainToClass() method, you can map the top-level object and ensure that all underlying fields possess the correct types/objects.

Example

Consider two classes:

class Parent {
    name: string;
    child: Child;

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

class Child{
    name: string;

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

In the initial case, direct assignment doesn't function correctly:

let parentJson: any = {name: 'parent name', child: {name: 'child name'}};
let parent: Parent = parentJson; // Compiler allows this due to the type being any.  
// Directly assigning the json structure to 'parent' would result in an error due to missing methods like getText().

console.log(parent.getText()); // Error occurs as expected since parent.getText() is not a valid function

In the second situation utilizing Object.assign():

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

console.log(parent.getText()); // Works fine
console.log(parent.child.getText()); // Results in an error stating that parent.child.getText() is invalid

To make it function properly, the following steps are necessary:

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()); // Works correctly
console.log(parent.child.getText()); // Functions as expected now

In the third scenario employing class-transformer:

Begin by modifying the parent class to define the child mapping:

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

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

Subsequently, map to the parent object accordingly:

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

console.log(parent.getText()); // Functions correctly
console.log(parent.child.getText()); // Executes as intended

Answer №5

After watching a YT video, I found the answer to my problem in this post. It mentioned checking if the method of that specific class exists.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Looking to execute a service method within an authguard service?

I am a beginner with Angular and I am looking to invoke a service method within the authguard service. The specific service function that I need is as follows. Please note that I do not want to make any changes to this service function. loadOrganizations() ...

Tips for adding style to a group of SVG elements:

I currently have an array of .svg icons, each with unique properties that I need to customize: <svg width="24" height="24" viewBox="0 0 24 24"> ... </svg> import styled from 'styled-components'; import Github from 'assets/git ...

The inference of optional generic types is not occurring

I need help addressing a type error in my TypeScript wrapper for handling NextJS API requests. Specifically, I am facing an issue when trying to pass a single type for one of the generic types in the function. To illustrate this error, I have created a si ...

Enhancing code branch coverage using Istanbul

The code snippet provided has a branch coverage of only 50% (refer to the coverage report below). I am unsure how to enhance this as there are no if statements present. I suspect that Istanbul must utilize some form of measurement that I have yet to grasp ...

Tips on retrieving complete information from mongoose when the schema contains a reference

I have a schema that includes [content, name, email], and I need to retrieve all three data fields and render them on the frontend simultaneously. Can you provide an example of JavaScript code that accomplishes this? const UserSchema = new mongoose.Schem ...

Failed CORS request in Angular

I encountered an issue while attempting to connect to an external API (not owned by me), as I received a CORS error message (refer to screen 1). In an effort to resolve this, I added 'Access-Control-Allow-Origin': '*' to my headers, bu ...

Using Angular JS to retrieve specific information from a JSON file

I am in the process of developing an AngularJS app that showcases notes. The notes are stored in a note.json file, and the main page of the app only displays a few fields for each note. I want to implement a feature where clicking on a specific note opens ...

Retaining previous values in Angular reactive form during the (change) event callback

Imagine having an Angular reactive form with an input field. The goal is to keep track of the old value whenever the input changes and display it somewhere on the page. Below is a code snippet that achieves this functionality: @Component({ selector: & ...

Unable to load the first tab in Material-ui V3 after fetching data or when the page loads

After using Material UI version 3 scrollable-tab and fetching data to load tabs and tab container, I encountered an issue where the first tab does not automatically load on page load. It requires a click in order to see the information. I have searched on ...

Google Maps API is successfully loading from an HTML file, however, it is not functioning properly when accessed

I am facing an issue while trying to connect to the Google Maps API through an AngularJS application on my localhost. Despite finding the javascript file in the HTML and displaying 'test1' in the console, the `initMap` function is not being calle ...

Is there a way to filter an array of dates without using the map function when a click

After finally grasping how to pass and retrieve data in React, I encountered an issue. I have a click handler called this.SortASC, and when I click on the title, I want to sort the titles alphabetically. However, I'm having trouble getting this functi ...

JavaScript isn't functioning properly after UserControl is loaded via Ajax

Thank you, Stilgar for helping me solve my issue. I created a javascript file and placed all my code in it. After adding this file to the UserControl and retrieving the UserControl's html, I used $("#DivID").html(UserControlHTML). Now everything is wo ...

Can a client component in NextJs retrieve data from a server component?

Apologies for the way the question is phrased. I have a server component named auth.ts that retrieves user details after they log in. This server side component is located at //auth.ts export const validateRequest = cache( async (): Promise< { use ...

Modal shows full JSON information instead of just a single item

This is a sample of my JSON data. I am looking to showcase the content of the clicked element in a modal window. [{ "id": 1, "companyName": "test", "image": "https://mmelektronik.com.pl/w ...

Tips for detecting the end of a scroll view in a list view

I've encountered an issue with my scrollView that allows for infinite scrolling until the banner or container disappears. What I'm aiming for is to restrict scrolling once you reach the last section, like number 3, to prevent the name part from m ...

Angular does not recognize the function element.sortable

I'm attempting to utilize ui.sortable in order to create a sortable list. Despite following the instructions provided at https://github.com/angular-ui/ui-sortable, I am still encountering an issue and receiving the following error message: TypeError: ...

Creating Angular components in *ngFor loop

I have set up multiple radio button groups by dynamically populating options using ngFor within a ngFor loop. categories:string[] = [category_1, ..., category_n]; options:string[] = [option_1, ..., option_n]; <fluent-radio-group *ngFor='let ca ...

Resizable dimensions for the dark mode switch

My latest project involves creating a toggle button for switching between light and dark themes using CSS, HTML, and JavaScript: id("theme-btn").addEventListener("change", function() { if (this.checked) { qs(".box").setAttribute('style', ...

Implement a validation function in the "jQuery validation library."

Hello, I am currently using the jQuery validation plugin to validate my form. I am trying to add an additional rule to the validation, but I am struggling to get it to work. The input value should be less than or equal to the previous element's value. ...

A fatal error has occurred in Node as it is unable to set headers after

Just getting started with nodeJs and I'm attempting to read a file from the system. It seems like I can view the file content in the console, but for some reason it's not displaying in the browser. Any ideas what I might be overlooking? var myD ...