Checking conditions sequentially in Angular

I have a unique use case that requires me to verify certain conditions. If one condition fails, I should not proceed to the next one. Instead, based on the failed condition, I need to display a dialog with a title and description explaining what went wrong. Assume I have a service (pseudo code) // verify-condition.service.ts

public validateConditions():Observable<ValidationData> {
  public validateConditions(): Observable<ValidationModal> {
    const hasContract$: Observable<ContractStatus> = this.getContractSignStatus();
    const hasTUTSigned$: Observable<boolean> = this.hasTUTSigned();
    const doData$: Observable<DoModel> = this.getDoData();
    return combineLatest([hasContract$, hasTUTSigned$, doData$]).pipe(
      map(([hasContract, hasTUTSigned, doData,]) => {
        const validationConditions: ValidationModal = {
          conditions: {
            hasContract: hasContract === ContractStatus.SIGNED,
            hasTUTSigned,
            wasSigned: doData.wasSigned,
            valid: doData.valid,
            signDate: doData.signDate,
          }
        };
        return validationConditions;
      })
    );
  }
}

and in the component where it is being used

 public verifyProcess(): void {
  verifyConditionService.validateConditions().pipe(take(1)).subscribe((validationData:ValidationData=> {
      if (validationData) {
        this.modalValidationService.openModal(validationData);
      } else {
        this.beginProcess();
      }
    })
  }

The challenge here is that in the service, all conditions are checked at once using combineLatest. However, I want to check them one by one. If the first condition fails, I should not proceed to the next one but instead throw an error or indicate that it fails and provide the data necessary to display the dialog.

  public validateConditionsInOrder():Observable<ValidationData|boolean> {
    return this.getContractSignStatus().pipe(
        map((signStatus:ContractStatus)=>{
            if(signStatus !== ContractStatus.SIGNED){
                return {
                    hasContract:signStatus
                }
            } else {
                return true;
            }
        }),
        switchMap((previousCondition)=>{ 
            if(!previousCondition){ // if previous condition failed, stop verification and return data from the failed condition

            } else {
                this.hasTUTSigned(); // if previous condition passes, move on to the next one. Need to know what went wrong for displaying dialog.
            }
        })
    )
  }

Answer №1

To create a sequence of observables/calls, consider utilizing the iif operator. While it may not be the most elegant solution, it can serve as a starting point for developing a custom pipe, as suggested by @Will Alexander.

public validateConditions(): Observable<ValidationModal> {
  // Prepared validateConditions object to provide a basis for partial Observables
  const result = {
    hasContract: false,
    hasTUTSigned: false,
    wasSigned: false,
    valid: null,
    signDate: null,
  };

  // Directly map the hasContract state and store for later use
  const hasContract$: Observable<ContractStatus> =
    this.getContractSignStatus().pipe(
      map((hasContract) => ({
        ...result,
        hasContract: hasContract === ContractStatus.SIGNED,
      }))
    );

  const hasTUTSigned$: Observable<boolean> = this.hasTUTSigned();
  const doData$: Observable<DoModel> = this.getDoData();

  // Begin by piping hasContract$ instead of using combineLatest
  return hasContract$.pipe(
    switchMap((validateConditions) =>
      iif(
        // Chain hasTUTSigned$ if hasContract is true, or return previous state
        () => validateConditions.hasContract,
        hasTUTSigned$.pipe(
          map((hasTUTSigned) => ({ ...validateConditions, hasTUTSigned }))
        ),
        of(validateConditions) // Return unchanged data
      )
    ),
    switchMap((validateConditions) =>
      iif(
        // Map different data based on condition (hasTUTSigned)
        () => validateConditions.hasTUTSigned,
        doData$.pipe(map((data) => ({ ...validateConditions, ...data }))),
        of(validateConditions) // Return unchanged data
      )
    )
  );
}

By leveraging iif and of, you can either chain real observables or maintain the original validateConditions object if conditions are not met.

Answer №2

Below is a practical example:

const checkValidity = (validity: boolean) => of(validity).pipe(delay(1000));

const validityCheck1$ = checkValidity(true);
const validityCheck2$ = checkValidity(true);
const validityCheck3$ = checkValidity(true);

const throwErrorOnFalse =
  (error: string) =>
  <T>(obs$: Observable<T>): Observable<T> =>
    obs$.pipe(
      tap((x) => {
        if (!x) {
          throw new Error(error);
        }
      })
    );

concat(
  validityCheck1$.pipe(throwErrorOnFalse(`Condition 1 was violated`)),
  validityCheck2$.pipe(throwErrorOnFalse(`Condition 2 was violated`)),
  validityCheck3$.pipe(throwErrorOnFalse(`Condition 3 was violated`))
)
  .pipe(
    tap(() => console.log(`[DEBUG] A condition has passed`)),
    ignoreElements(),
    tap({ complete: () => console.log(`All conditions met`) })
  )
  .subscribe();

Also, explore this live demonstration on Stackblitz, where you can switch the conditions to false to observe different outcomes.

The conditions are executed in sequence. If one fails, it will terminate the process and display the specified error message. This makes it easy to handle errors downstream, such as displaying them in a modal.

Experiment with toggling the conditions - when all are true, you'll see "All conditions met" after 3 seconds. However, setting one to false will trigger an error and halt the process.

Answer №3

function checkConditionsValidity () {
    const hasContract$: Observable<ContractStatus> = fetchContractSignStatus();
    const hasTUTSigned$: Observable<boolean> = checkTUTSigning();
    const doData$: Observable<DoModel> = retrieveDoData();
    return new Observable(subscriber => {
        const conditions = {
            hasContract : null,
            hasTUTSigned: null,
            wasSigned   : null,
            valid       : null,
            signDate    : null,
        }

        concat(
            hasContract$.pipe(tap(hasContract => conditions.hasContract = hasContract === ContractStatus.SIGNED)), 
            hasTUTSigned$.pipe(tap(hasTUTSigned => conditions.hasTUTSigned = hasTUTSigned)),
            doData$.pipe(tap(doData => Object.assign(conditions, {
                wasSigned : doData.wasSigned,
                valid     : doData.valid,
                signDate  : doData.signDate,
            }))), 
        ).subscribe({
            complete    : ()    => subscriber.next(conditions),
            error       : error => {
                console.error(error);       
                subscriber.next(conditions);     
            }
        })
    }).pipe(take(1), map(conditions => ({conditions  : conditions,}) as ValidationModal))
}

The essence of my approach involves using rxjs.concat to organize and control the sequence of multiple observables before creating a new Observable instance to handle the data translation.

Key Points:

  • Each subsequent observable waits for the previous one to finish before executing
  • Upon receiving data from an observable, update the corresponding validation property with the tap operator
  • Once all three observables have completed, return the final validation status
  • If an error occurs in any observable, the chain halts execution
  • In case of errors, trigger the error handler and return the partially updated conditions
  • Structure the finalized conditions into a format suitable for ValidationModal

Note: My method expects observables to be completed and does not function with ongoing streams.

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

Skrollr immediate pop-up notification

Can anyone help me figure out how to make text appear suddenly and then disappear using skrollr? I've been able to get it to fade in and out, but I want it to just show up without any transition. Here's the code I have so far: <div id="style" ...

Leveraging environmental variables in a Vue.js web application

The article I recently came across discussed how to effectively utilize environment variables in vuejs. Following the instructions, I set up my local .env.local file and also installed dotenv. VUE_APP_AUTH_AUTHORITY = 'http://localhost/auth' I ...

Is it feasible for a React-based shell to host or load an Angular component using Module Federation in Webpack 5?

I am currently developing a web application using Angular that will be embedded or loaded from another web application built with React. I am unsure if this integration can be achieved using webpack 5's module federation. Module federation involves l ...

Using backslashes to escape a JavaScript array elements

How can I modify the code below to properly escape HTML and strings in JavaScript arrays? I've been attempting to use a function called escapeHtml to add the necessary slashes, but it's not functioning as expected. Any help would be appreciated. ...

The Fixed Navbar is causing sections to be slightly off from their intended positions

Utilizing a bootstrap navigation menu on my website with a fixed position. When clicking a menu item, it takes me to the designated section but slightly above the desired position. How can I ensure that it goes to the exact position upon clicking the men ...

Executing React Fetch API Twice upon loading the page

Double-fetching Issue with React Fetch API on Initial Page Load import React, { useState, useEffect } from 'react' import axios from 'axios'; import { Grid, Paper, TextField } from '@mui/material' import PersonOut ...

UPDATE: Choosing several classes and then toggling the classes independently for each one

I have managed to make this work, but I am considering if there is a more efficient solution. My objective is to modify the divs using classes exclusively. I aim to toggle 4 classes with just one click. First, I obtain the class for the button and then a ...

Maintain hover effect of main menu on sub-menu in CSS3 flip dropdown menu

Check out the fiddle I created for my query https://jsfiddle.net/e7te8hf1/ <section id="action-bar"> <div id="logo"> <a href="#"><img src="img/logo.png"></a> </div><!-- end logo --> <nav class="navbar navigat ...

Running tests on functions that are asynchronous is ineffective

Recently, I made the switch from Java to TypeScript and encountered a challenging problem that has been occupying my time for hours. Here is the schema that I am working with: const userSchema = new Schema({ username : { type: String, required: true }, pa ...

Generating new elements on the fly using jQuery and passing a string parameter through the onclick event

While dynamically creating HTML, I encountered a syntax error. If I modify href="javascript:startChat(' + user_id + ','video')" to href="javascript:startChat(' + user_id + ','"video"')", I receive an error stating &a ...

Node.js refusing to acknowledge the get request handler for the homepage of the website

My Node.js server setup is quite simple: const express = require('express'); const app = express(); const http = require("http"); const path = require('path'); const favicon = require('serve-favicon'); // Public fil ...

When using Material UI TextField with input type "date", the event.target.value does not seem to be accessible

I am currently working with Material UI and React Grid from DevExtreme to build a table that includes an input field of type date. However, when I attempt to enter the date, it does not register the change in value until I reach the year field, at which po ...

Incorporating responsive design with React and Typescript

Trying to utilize React with TypeScript, I aim to dynamically generate components based on a field name // Storing all available components const components = { ComponentA, ComponentB, }; // Dynamically render the component based on fieldName const di ...

Dealing with React and Firebase Authentication Errors: How to Handle Errors for Existing Accounts with Different Credentials

According to the documentation at https://firebase.google.com/docs/auth/web/google-signin#expandable-1, when error.code === 'auth/account-exists-with-different-credential', signInWithPopup() should return an error.email. However, in my specific c ...

After successfully binding data in Angular, the Select component is failing to display any content

I encountered an issue where the select option disappeared completely after trying to bind countries data inside a sign-up form. Below is the relevant code snippet: Data Model export class Countries { public name: string; public code: string; ...

A guide on styling JSON key values within HTML

I need help organizing my JSON Document on an HTML page. Here is the JavaScript code I am using: xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { // Optionally, here you can update ...

The $http function in AngularJS consistently returns undefined instead of the expected value

var result = dataService.makeHttpRequest("GET", "/documents/checkfilename/", null, function (response, status, headers, config) { // I can see `true` if I alert(response); here // I want to return the contents of ...

Tips for addressing the error message "Cannot PUT /":

I have been working on updating a local database using MongoDB for my project. Here is the code snippet I am using to update the data. The second part involves editing that redirects the updated data. I am not encountering any errors, so I am unable to i ...

Running a CSS keyframes animation can be achieved by removing the class associated with it

Is there a way to reverse the CSS animation when a class is removed? I'm trying to achieve this on my simple example here: https://codepen.io/MichaelRydl/pen/MWPvxex - How can I make the animation play in reverse when clicking the button that removes ...

Show the meta-field mp3 and place it in a lightbox container on the portfolio page

My Current Portfolio Gallery Setup: Currently, my portfolio gallery is displayed in a masonry grid format using the JIG plugin. This grid showcases images from my custom post_type. When clicking on a thumbnail, a lightbox pops up showing the full photo. T ...