Combining two observables into one and returning it may cause Angular guards to malfunction

There are two important services in my Angular 11 project. One is the admin service, which checks if a user is an admin, and the other is a service responsible for fetching CVs to determine if a user has already created one. The main goal is to restrict access to the /new page if a user has already created a CV, but this restriction should be lifted for admins or managers at all times :) Interestingly, my guard implementation fails to work when the user is both an admin and has a CV created; however, it performs well under other circumstances. I am utilizing rxjs version 6.6.3 for this task.

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { Observable, combineLatest } from 'rxjs';
import { map, finalize } from 'rxjs/operators';
import { PersonsService } from '../services/persons.service';
import { AdministrationService } from '../services/administration.service';
import { CustomSnackbarService } from '../services/custom-snackbar.service';

@Injectable({
  providedIn: 'root',
})
export class CanCreateNewCv implements CanActivate {
  constructor(
    private usersService: PersonsService,
    private router: Router,
    private administrationService: AdministrationService,
    private snackbarService: CustomSnackbarService
  ) {}

  canActivate(): Observable<boolean> | boolean | Promise<boolean> {
    let isAllowed = false;
    const cv$ = this.usersService.getPersonsByPageAndFilter(10, 0).pipe(
      map((data) => {
        if (data.allDataCount > 0) {
          this.router.navigateByUrl('/list');
          return true;
        }
        return false;
      })
    );

    const admin$ = this.administrationService.getCurrentUser().pipe(
      map((currentUser) => {
        if (currentUser.isAdmin || currentUser.isManager) {
          return true;
        }
        return false;
      })
    );

    return combineLatest([cv$, admin$], (isCvUploaded, isAdminOrManager) => {
      isAllowed = isAdminOrManager ? true : isCvUploaded ? false : true;
      return isAllowed;
    }).pipe(
      finalize(() => {
        if (!isAllowed)
          this.snackbarService.open(
            'This profile has CV already created!',
            'Info'
          );
      })
    );
  }
}

I have also experimented with the zip operator, but unfortunately, it did not yield the desired outcome.

Answer №1

Your current code is causing an issue because it redirects the user regardless of whether they are a superuser or not.

A better approach would be as follows:

export class CanCreateNewCv implements CanActivate {
  constructor(
    private usersService: PersonsService,
    private router: Router,
    private administrationService: AdministrationService,
    private snackbarService: CustomSnackbarService
  ) {}

  canActivate(): Observable<boolean> | boolean | Promise<boolean> {
    let isAllowed = false;
    const cv$ = this.usersService
       .getPersonsByPageAndFilter(10, 0)
       .pipe(
          map((data) => data.allDataCount > 0)
       );

    const admin$ = this.administrationService
       .getCurrentUser()
       .pipe(
          map((currentUser) => currentUser.isAdmin || currentUser.isManager)
       );

    return combineLatest([cv$, admin$], (isCvUploaded, isAdminOrManager) => {
      isAllowed = isAdminOrManager ? true : isCvUploaded ? false : true;
      if (!isAllowed) {
        this.router.navigateByUrl('/list');
      }
      return isAllowed;
    }).pipe(
      finalize(() => {
        if (!isAllowed)
          this.snackbarService.open(
            'This profile has CV already created!',
            'Info'
          );
      })
    );
  }
}

Answer №2

Everything appears to be in order. The combineLatest function will only complete once all source observables have completed. If you're experiencing issues, it could be due to one of your observables, cv$ or admin$, not completing in a specific case. Without knowledge of how they are constructed, it's difficult to pinpoint the exact issue.

const cv$ = this.usersService.getPersonsByPageAndFilter(10, 0).pipe(
  take(1),
  map((data) => {
    if (data.allDataCount > 0) {
      this.router.navigateByUrl('/list');
      return true;
    }
    return false;
  })
);

const admin$ = this.administrationService.getCurrentUser().pipe(
  take(1),
  map((currentUser) => {
    if (currentUser.isAdmin || currentUser.isManager) {
      return true;
    }
    return false;
  })
);

If adding take(1) doesn't solve the issue, try using console.log statements within the combineLatest block to verify if the observables are emitting values and completing.

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

What steps are involved in implementing Local fonts in theme UI for Next JS?

I am currently developing an application using next JS with [theme-UI][1]. However, I need to implement local or custom fonts in my project and I'm unsure of how to do this. Below is the current theming setup: const theme = { fonts: { ...

Revamping the login interface for enhanced user

Whenever I attempt to login by clicking the login button, there seems to be an issue as it does not redirect me to any other page. Instead, I am left on the same page where I initially clicked the button. The intended behavior is for users to be redirected ...

Managing spinners in Protractor when they are concealed by a wrapper element

While writing a test for an Angular app using Protractor, I encountered several issues with handling spinners. I managed to solve some of them, but I'm unsure how to test spinners that are hidden by a wrapper. For instance, when the parent tag has ng- ...

Is there a way to identify legitimate contacts and phone numbers within an Android application using Javascript or Typescript?

I am developing an Android app where I need to show a list of contacts and specify if they are part of the app's network. However, my goal is to only display valid contacts while excluding unwanted ones such as toll-free numbers or data balance check ...

How to retrieve the total count of dynamically inserted list items within an unordered list in Angular

Is there a way to calculate the number of dynamically added list items within a ul element? I need this information to adjust the width of a queue element based on the number of list items with a formula like [style.width.px]="numberOfLi * 50". Any sugge ...

Top solution for efficiently capturing and storing user input in a React JS application: Event Handler

I've recently designed an input field for inputting details of items. In order to effectively capture and save the entered information, which of the following event handlers would be most suitable? onClick onChange onLoad onKeyPress ...

Axios is experiencing challenges in transmitting the cookie

Even after attempting to include {withCredentials: true}, I am still facing issues with sending the cookie to the backend server. return await axios.post(`${API_URL}/posts/category/${id}`, { withCredentials: true }) https://i.stack.imgur.com/Cr8xv.png ...

Pass an object along with the rendering of the page using ExpressJS and Mongoose

After reading through this post: mongoose find all not sending callback I am currently working on sending an Object along with a page in my nodejs/expressjs application, instead of just responding with JSON data. This is the route for my page: //Get lat ...

I'm having trouble with my scroll bar in a React project using JavaScript. Can anyone spot what might be causing the issue?

Attempting to create a React site for the first time, so please forgive any novice mistakes or oversights on my part. Currently, my navigation bar is fixed at the top of the page with basic hover animations. I am aiming for it to disappear when scrolling d ...

Exploring the integration of Bootstrap Confirmation into an ASP.NET web page

I'm currently working on implementing Bootstrap Confirmation (http://ethaizone.github.io/Bootstrap-Confirmation/#top) on an ASP.NET web page using C#, but I've encountered some issues. Master Page References <asp:ScriptReference Path="Script ...

How to eliminate a div using jQuery

Within a repeater, there is a button that should remove the respective div followed by a database query using AJAX when clicked. The issue arises when attempting to remove the div in the success part of the AJAX call. Here is the code snippet: <asp:Up ...

Is it possible to assign a type to an anonymous object in TypeScript?

Check out the code snippet below: hello({ name: "Michael" } as x) // <-- Except missing id here, but it doesn't type x = { id: string name: string } function hello(x: any) { console.log(x) } TS Playground No error is thrown de ...

Troubleshooting: Issues with the functionality of ng-include in AngularJS

Hi there, I am new to angular js and I'm attempting to integrate a simple html file into my page. Below is the code I am using: <!DOCTYPE html> <html ng-app=""> <head> </head> <body ng-controller="userController"> < ...

I find it impossible to avoid using the withRouter and withAlert functionalities in Reactjs

When using withRouter, the alert.success property is not accessible. A TypeError is thrown with the message "Cannot read property 'success' of undefined". This issue prevents the successful display of alerts in my application. The error occurred ...

Change to the parent frame using webdriver

I am facing an issue while trying to switch to the parent frame or iFrame using webdriver.js. Although I have implemented a function that works, it only goes up to the second level of frames. When attempting to switch to the parent frame from a deeper fr ...

Switch out two for loops with the find or filter method in JavaScript

In my unique approach, I am showcasing a variety of product details lists based on availability in various shops. To achieve this, I have implemented the following method. for (let i = 0; i < this.prodList.length; i++) { let setContent = false; for ...

Restrict the duplication of div elements with JQuery

Here is the structure I'm working with: <div class="checkmark-outer"> <div class="checkmark-33"> <div class="fa-stack fa-1x checkmark-icon"> <i class="fa fa-circle fa-stack-2x icon-background"></i> ...

Utilizing the power of JavaScript/JQuery to showcase a compilation of all input values within an HTML form

I'm struggling to showcase all the input values from my HTML form on the final page before hitting the "submit" button. Unfortunately, I am facing a challenge as the values are not appearing on the page as expected. Despite several attempts, the valu ...

Iterate over JSON dates in JavaScript

Trying to utilize JavaScript in looping through a JSON file containing time periods (start date/time and end date/time) to determine if the current date/time falls within any of these periods. The provided code doesn't seem to be working as expected. ...

Guide on using javascript to alter a json structure

Recently, I discovered a method to manipulate a JSON file using JavaScript. Although I don't have any formal training in JavaScript, I understand that it is primarily utilized on web browsers. Can someone guide me on how to execute this script? I cons ...