NGXS: Issue with passing objects to "setState" occurs when any of the patched inner fields is nullable or undefined

In my Angular components, I have created a class that is responsible for storing the state:

export class MyStateModel {
  subState1: SubStateModel1;
  substate2: SubStateModel2;
}

This class has simple subclasses:

export class SubStateModel1 {
  someField1: string;
}

export class SubStateModel2 {
  someField2: string;
}

Within my Angular code, there is a function that modifies the state by partially patching it using NGSX's setState and patch functions.

function myfunction(ctx: StateContext<MyStateModel>) {

  ctx.setState(
    patch({
      subState1: patch({
        someField1: "some value",
      }),
    })
  );
}

Everything is working fine so far.

ISSUE: TypeScript starts giving errors when I try to make inner fields nullable or undefined:

export class SubStateModel1 {
  someField1: string | null;
}

OR 

export class SubStateModel1 {
  someField1?: string;
}

When I set a field to null, TypeScript complains:

// TYPESCRIPT NOT HAPPY!
ctx.setState(
  patch({
    subState1: patch({
      someField1: null,   
    }),
  })
);

The error message states:

Argument of type 'PatchOperator<{ subState1: PatchOperator<{ someField1: null; }>; }>' is not assignable to parameter of type 'MyStateModel | StateOperator<MyStateModel >'.
Type 'PatchOperator<{ subState1: PatchOperator<{ someField1: null; }>; }>' is not assignable to type 'StateOperator<MyStateModel>'.
Types of parameters 'existing' and 'existing' are incompatible.
Type 'Readonly<MyStateModel >' is not assignable to type 'Readonly<PatchValues<{ subState1: PatchOperator<{ someField1: null; }>; }>>'.
Types of property 'subState1' are incompatible.
Type 'SubStateModel1' is not assignable to type 'PatchValues<{ someField1: null; }>'.ts(2345)

It seems like NGSX's wrapping of types in PatchOperator objects might be causing the issue, although this shouldn't be a problem.

The specific line in the error that mentions conflicting parameters is perplexing:

Types of parameters 'existing' and 'existing' are incompatible.

I suspect that the presence of a nullable field two levels deep in the object hierarchy (a patch inside a patch involving a null) could be causing confusion. However, I can't pinpoint the exact cause of the issue.

TROUBLESHOOTING

I attempted to simplify the code structure:

const obj1 = {
  someField: null,
};

const obj2 = {
  subState1: patch(obj1),
};

const obj3 = patch(obj2);

ctx.setState(
  obj3 // THE ERROR IS HERE (same error)
);
}

Answer №1

In my exploration, I came across an interesting insight from NGXS regarding the potential failure of type inference for functions like setState and patch, particularly in the context of nested updates.

To delve deeper into this topic, one can refer to the section titled "Typing Operators" on the following page:

It is possible to encounter situations where the patch operator struggles to deduce the nested type structure. In such cases, omitting explicit typing for patch results in all objects being inferred as unknown by TypeScript, leading to a lack of error detection related to incorrect usage or mismatched types.

An example highlighting a similar issue with an array can be found in this StackOverflow query: Update NGXS state for an object inside array

To address this concern, I made sure to explicitly specify the nested types in my code:

 // TYPESCRIPT NOT HAPPY!
  ctx.setState(
    patch<MyStateModel >({
      subState1: patch<SubStateModel1>({
        someField1: null,   
      }),
    })
  );

Although I have yet to test if this approach poses any runtime issues, it has effectively eliminated the compilation errors I was facing.

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

the ng-repeat directive disables input controls within the tfoot

When working with JSON data, I encountered a situation where I needed to display different types of student details in a table. For one specific type of student, namely partners, I wanted to include input controls such as checkboxes and buttons. However, e ...

What is the best way to include bootstrap using webpack?

I am currently building a webapp using Typescript and webpack. I have been able to successfully import some modules by including them in my webpack.config.js file as shown below. However, no matter how many times I attempt it, I cannot seem to import the b ...

Following an update, the functioning of Visual Studio Tools for Apache Cordova ceases to operate accurately

Currently working on an ionic application using Visual Studio Tools for Apache Cordova, everything was going smoothly until I decided to update the Tools for Apache Cordova and TypeScript Tools for Visual Studio. Following the update, the Ripple emulator s ...

Design an interactive listbox with both HTML and Angular that allows for multiple selections

I am trying to implement a multi select listbox with keyboard navigation capability. Currently, this is the HTML layout I have: <div> <div *ngFor="let tag of tags; let index=index" [class.selectedRow]="rowIsSelected(tag.id) ...

Is it possible to duplicate a response before making changes to its contents?

Imagine we are developing a response interceptor for an Angular 4 application using the HttpClient: export class MyInterceptor implements HttpInterceptor { public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<an ...

The module called "discord.js" does not have a component named "Intents" available for export

Issue with importing module '"discord.js"' - 'Intents' not exported. Here is the code in question: import dotenv from 'dotenv'; const bot = new Client({ intents: [ Intents.FLAGS.GUILDS, Intents. ...

Displaying the ngx-bootstrap popover in a different position

Trying to change the position of my popover to a different location. Is it possible to position the popover differently using ng-template? <ng-template #popmeover> <button type="button" (click)='pop.hide()' class="close" aria-lab ...

What is the best way to retrieve all designs from CouchDB?

I have been working on my application using a combination of CouchDB and Angular technologies. To retrieve all documents, I have implemented the following function: getCommsHistory() { let defer = this.$q.defer(); this.localCommsHistoryDB ...

How can I make ngFor update after an object has been modified?

I'm currently working on a function that updates an object property after an item is dropped in a drag and drop scenario. However, it seems like either my function is incorrect or there might be something else that needs to be done because the display ...

Is it possible to include the term 'public' exclusively within a .ts file in a TypeScript TSLint React environment?

I'm struggling to understand why I am encountering an error in VSCode while working on a react typescript project with tslint setup. The error message states: 'public' can only be used in a .ts file. [Also, I'm wondering why I' ...

rxjs "switch to" once the expansion is complete

I am currently working on the following code snippet: const outputFile = fs.createWriteStream(outputPath); const requisitionData = this.login().pipe( map(response => response.data.token), switchMap(loginToken => this.getRequisitions( ...

What strategies can I employ to help JSDoc/TypeScript recognize JavaScript imports?

After adding // @ts-check to my JavaScript file for JSDoc usage, I encountered errors in VS Code related to functions included with a script tag: <script src="imported-file.js"></script> To suppress these errors, I resorted to using ...

Project encapsulating Angular 2 Functionality

I am tasked with creating an angular 2 application that acts as a wrapper for multiple other angular 2 applications. Let's call the main module of the project MainModule. There are also third-party modules such as AppModule1, AppModule2, etc., which ...

Filter an object in Typescript and retrieve a single key

Managing a set of checkboxes is essential in assigning roles to new users. While it's possible to filter and retrieve only the checked checkboxes, extracting just the "name" key poses a challenge. The current method involves filtering with a for loop ...

Exploring the features of Typescript involving async/await, Promise, and the use of function

I am currently working on a nodeJS-Express-Typescript project where I need to implement native promises with async/await and also provide default values for functions. Here is a simple example of what I am trying to achieve: sleep(ms: number) { return ...

Error with scoped slot typing in Vue3 using Vite and Typescript

I am currently working on a project using Vue3, Vite, and TypeScript as the devstack. I encountered an error related to v-slot that reads: Element implicitly has an 'any' type because expression of type '"default"' can't ...

Tips for displaying the Scrollbar below the Sidenav Toolbar in AngularMaterial-7

https://i.stack.imgur.com/xnR5x.jpgI need assistance with the following two methods: 1) How can I display the sidenav scrollbar beneath the toolbar in the sidenav: 2) Also, how do I make the Scrollbar visible only when the mouse cursor is moved over the ...

Display a semantic-ui-react popup in React utilizing Typescript, without the need for a button or anchor tag to trigger it

Is there a way to trigger a popup that displays "No Data Found" if the backend API returns no data? I've been trying to implement this feature without success. Any assistance would be greatly appreciated. I'm currently making a fetch call to retr ...

Generating automatic generic types in Typescript without needing to explicitly declare the type

In the scenario where I have an interface containing two functions - one that returns a value, and another that uses the type of that value in the same interface - generics were initially used. However, every time a new object was created, the type had to ...

Inconsistency with Angular 4 instance variables causes ambiguity within a function

Here is the code snippet: @Component({ selector: 'unb-navbar', templateUrl: './navbar.html' }) export class NavbarComponent implements OnInit { @Input() brand: string; controlador:boolean=false; overlay:string=""; @Input() menu ...