Ensure the variable is valid by using a type guard in the false branch

I am attempting to use a type guard to narrow down a complex type. In my scenario, I want the false branch of the type guard to recognize the complement of the narrowed type.

interface Model { side: 'left' | 'right'; }
interface LeftModel { side: 'left'; }
interface RightModel { side: 'right'; }
type Either = LeftModel | RightModel;

function isLeft(value: Either): value is LeftModel { // else is RightModel
  return value.side === 'left';
}

It seems that achieving this is not feasible with my current approach. While TypeScript can infer that an Either may be a model, it does not accept that a Model can be an Either. This results in an error:

declare const model: Model;
isLeft(model) // ts(2345)

Is there no solution to this issue?

If so, how can I ensure that the false branch narrows down to the complement?

View the complete example in this Typescript Playground

UPDATE

In this basic example, it appears that Model and Either are interchangeable. However, this might not always hold true. I tried merging two type guards to inform the type system that Model is indeed a valid Either (see this new Playground). However, this led to an unwanted branch (refer to line 22), making it less than ideal.

Is there a way to convince the type system that Either and Model are essentially the same?

I do not necessarily need to rely on type guards or union types as my initial attempt raised its own issues. Union types would only work if we could guarantee that the union of a narrowed type and its relative complement aligns with the narrowed type. This assumption relies on the type system recognizing the concept of complement, which may not currently be the case. Refer to this typescript complement search and the handbook on utility types.

Someone recommended utilizing fp-ts and/or monocle-ts to address this issue, but some of these functional programming concepts are still beyond my grasp. If someone knows how to apply them here, it would be greatly appreciated. Either seems like a possible solution...

Answer №1

The union operator | doesn't create a union of property types when used on specified types.

For example:

type Either = LeftModel | RightModel === { side: 'left ' } | { side: 'right' } 
           !== { side: 'left' | 'right' } === Model

Either represents a strict union between LeftModel and RightModel, not a union of the type side.

This error excerpt highlights the issue:

Argument of type 'Model' is not compatible with 'Either'. Type 'Model' cannot be assigned to 'RightModel'. The property types for 'side' are conflicting. Type '"left" | "right"' cannot be assigned to '"right"'. Type '"left"' cannot be assigned to '"right"'.

Answer №2

To achieve this functionality, you can utilize a type union without the need for a type guard:

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function something(model: LeftModel | RightModel) {
  model.side // left or right

  if (model.side === 'left') {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

Playground

The inheritance from Model is not essential here since the type union is doing the main work. However, it can be helpful to restrict any subclasses to 'right' or 'left' only.

Is this the functionality you were looking to implement?

Even though the type guard is not mandatory, it can still be implemented. It may not automatically determine the complementary RightModel type, but the caller can discern by already having value constrained to a union of LeftModel and RightModel.

interface Model { side: 'left' | 'right'; }
interface LeftModel extends Model { side: 'left'; }
interface RightModel extends Model { side: 'right'; }

function isLeft(value: Model): value is LeftModel {
  return value.side === 'left';
}

function something(model: LeftModel | RightModel) { 
  model.side // left or right

  if (isLeft(model)) {
    model.side; // left - model is also LeftModel at this point
  } else {
    model.side; // right - model is a RightModel
  }
}

With type guard

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

Struggling to successfully pass a function as an argument to the setTimeout method within an array in node.js using TypeScript

Here is an example that successfully demonstrates a function being called using setTimeout: function displayMessage(msg: string){ console.log(msg); } setTimeout(displayMessage, 1000, ["Hi!"]; After one second, it will print out "Hi!" to the console. ...

In Angular 4, you can easily preselect multiple options in a mat-select dropdown by passing an

Seeking assistance with setting the options of a mat-select in Angular 4. The issue at hand is as follows: 1. Initially, there are two variables: options and checkedOptions options: string[]; checkedOptions: string[] //Retrieved from the database; 2. T ...

An error message occurs in TypeScript when trying to access a property that does not exist in an array

I'm having trouble figuring out this issue... I am receiving data from an API and storing it as an array. I'm attempting to access the data in this format... this.data.results[i].datas[j].dataType However, I keep getting the error "property res ...

The functionality of CSS transitions may be affected when dynamically adding a class

Creating a custom CSS for my main div: .main { height: 0%; position: absolute; width: 100%; z-index: 100; background: whitesmoke; bottom: 0; border-radius: 15px; padding: 20px; color: gray; left: 0; right: 0; transition: height 1s e ...

What are some ways to control providers in targeted tests using ng-mocks?

I recently started utilizing ng-mocks to streamline my testing process. However, I am struggling to figure out how to modify the value of mock providers in nested describes/tests after MockBuilder/MockRender have already been defined. Specifically, my que ...

Obtain the data from onTouchTap action

Currently, I have a class that is returning an event to the parent. My goal is to extract the number of the touched button (the label on RaisedButton). Although I am successfully returning the event, I am unable to locate the desired information when I l ...

How can one properly extend the Object class in JavaScript?

I have a scenario where I want to enhance a record (plain Javascript object) of arrays with additional properties/methods, ideally by instantiating a new class: class Dataframe extends Object { _nrow: number; _ncol: number; _identity: number[]; co ...

Adding Components Dynamically to Angular Parent Dashboard: A Step-by-Step Guide

I have a dynamic dashboard of cards that I created using the ng generate @angular/material:material-dashboard command. The cards in the dashboard are structured like this: <div class="grid-container"> <h1 class="mat-h1">Dashboard</h1> ...

Neither Output nor EventEmitter are transmitting data

I am struggling to pass data from my child component to my parent component using the Output() and EventEmitter methods. Despite the fact that the emitter function in the child component is being called, it seems like no data is actually being sent through ...

Implementing unique union type in React: Effective ways to declare typescript type for prop value

I am currently facing an issue where I need to set a specific type for a prop value. However, the challenge lies in the fact that the types are string unions which can vary depending on the usage of the React Component. Let me provide you with the TypeScr ...

Route Handler 13 is encountering difficulties in retrieving data from the body in the (app/api/auth) endpoint

Whenever I attempt to retrieve the body from the new export async function POST( req: Request), it seems to come through as a stream instead of the expected content type. The route handler can be found in api/auth/signup See folder layout image export asyn ...

how can one exhibit the value of an object in TypeScript

Does anyone know how to properly display object values in forms using Angular? I can see the object and its values fine in the browser developer tools, but I'm having trouble populating the form with these values. In my *.ts file: console.log(this.pr ...

What are the best ways to maximize a web worker's ability to handle multiple tasks at once

I'm currently working on implementing a Web-Worker to handle its state while also managing multiple asynchronous requests. worker.ts file let a =0; //state of the worker let worker=self as unknown as Worker; worker.onmessage =(e)=>{ console ...

TSLint throws an error, expecting either an assignment or function call

While running tslint on my angular project, I encountered an error that I am having trouble understanding. The error message is: expected an assignment or function call getInfoPrinting() { this.imprimirService.getInfoPrinting().subscribe( response => ...

ngOnChanges will not be triggered if a property is set directly

I utilized the modal feature from ng-bootstrap library Within my parent component, I utilized modalService to trigger the modal, and data was passed to the modal using componentInstance. In the modal component, I attempted to retrieve the sent data using ...

Is there a way to restrict an array to only accept distinct string literals?

export interface IGUser { biography: string; id: string; ig_id: string; followers_count: number; follows_count: number; media_count: number; name: string; profile_picture_url: string; shopping_product_tag_eligibility: boolean; username: ...

Can anyone guide me on implementing getServerSideProps in a TypeScript NextPage component?

I've come across a page that I'd like to replicate, with the code sourced from https://github.com/dabit3/nextjs-lit-token-gating/blob/main/pages/protected.js: import Cookies from 'cookies' import LitJsSdk from 'lit-js-sdk' ex ...

The Sanity npm package encounters a type error during the build process

Recently, I encountered an issue with my Next.js blog using next-sanity. After updating all npm packages, I found that running npm run build resulted in a type error within one of the dependencies: ./node_modules/@sanity/types/lib/dts/src/index.d.ts:756:3 ...

Is it possible for FormArray to return null?

Hello there. I've attempted various methods, but none of them seem to be effective. Currently, I am working on this task where I need to start a formArray for emails. email: [testestest] However, what I have is: email: [testestest] I'm encoun ...

What is the method for extracting user input from a text box on a webpage?

Having trouble with retrieving the value from a text box in my search function. SearchBar Component import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-search', templateUrl: './search.compon ...