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

The functionality of the Auth0 Logout feature has ceased to work properly following the

Previously, the code worked flawlessly on Angular version 14. However, after updating to version 17 due to persistent dependency issues, a problem arose. logout(): void { sessionStorage.clear(); this.auth.logout({ returnTo: window.location.origin } ...

Leverage the power of react-redux useSelector with the precision of TypeScript

When attempting to utilize the new useSelector hook (shown in the example below) from react-redux in TypeScript, an error is encountered indicating that the function does not exist: Module '"../../../node_modules/@types/react-redux"' has no expo ...

Having trouble with the authorization aspect of Next Auth. The error message reads: "NextAuth.js does not support the action api with HTTP GET method

Recently, I've been encountering a puzzling error with my Next.js app authentication. It seems that I am unable to authenticate with the provided credentials. After reviewing the documentation, everything appears to be correct on my end. Additionall ...

Refreshing Ajax in a different tab causes the selectize element to become unbound from the editing form in Rails 5

In my Rails 5.1 application, I have multiple views with the main view being the calls index view. In this view, I perform an ajax refresh of partials and use a callback to re-initialize the selectize JS element in the calls/index view as shown below: < ...

Tips for transferring the id from the url to a php function seamlessly without causing a page refresh

I have a div that includes a button (Book it). When the button is clicked, I want to append the id of the item I clicked on to the current URL. Then, use that id to display a popup box with the details of the clicked item without refreshing the page, as it ...

When using Angular and Express together, the session is not continuous as each new request generates a fresh session

Unique Question I have encountered an issue with passport.js while trying to implement authentication in my express application. When I use req.flash('message', 'message content') within a passport strategy, the flashed information see ...

This error occurs because the argument type 'AsyncThunkAction<any, void, {}>' cannot be assigned to a parameter of type 'AnyAction'

I encountered an error that I couldn't find a solution for on Stack Overflow The argument of type 'AsyncThunkAction<any, void, {}>' is not compatible with the parameter of type 'AnyAction'. <MenuItem onClick={() =&g ...

Using JSON parsing to extract an integer as the key in JavaScript

I've been searching for almost 2 hours now, but I can't seem to find anyone using an integer as the key in their json object. The structure of my json object is as follows: {"342227492064425": {"added":"2020-10-04T23: ...

unusual behavior observed in addEventListener

I have recently delved into learning about the DOM. I have a project in mind where I want to create a website with buttons that, when clicked, play different drum sounds. As part of my experimentation with JavaScript, I wanted to explore the this keyword. ...

Can Angular facilitate the establishment of an Express.js server directly in the browser?

Can an Express.js server be initialized within the browser using Angular? I am interested in executing Node scripts on the Express server via an Angular component. ...

I require assistance in displaying a dynamic, multi-level nested object in HTML using AngularJS

I have a complex dynamic object and I need to extract all keys in a nested ul li format using AngularJS. The object looks like this: [{"key":"campaign_1","values":[{"key":"Furniture","values":[{"key":"Gene Hale","values":[{}],"rowLevel":2},{"key":"Ruben A ...

What is the best way to manage returning to the original page that was loaded when utilizing the History API?

I'm in a bit of a pickle here. I've been using History.js with the History API and everything was going smoothly until I encountered an issue. Let's start with a simple page setup like this: <div ="header"> Header </div> <d ...

The absence of a 'body' argument in the send() json() method within the Next.js API, coupled with TypeScript, raises an important argument

Currently, I have set up an API route within Next.js. Within the 'api' directory, my 'file.tsx' consists of the following code: import type { NextApiRequest, NextApiResponse } from "next"; const someFunction = (req: NextApiReq ...

I am having trouble with Angular not redirecting to the correct page and I'm not sure how to troubleshoot this issue

After creating a component named detail that is supposed to be associated with a button, I encountered an issue where it doesn't route me to the desired location. Strangely, no error messages are displayed, leaving me puzzled about how to solve this p ...

Error: The function expressValidator is not recognized in the current environment. This issue is occurring in a project utilizing

I am currently working on building a validation form with Express and node. As a beginner in this field, I encountered an error in my console that says: ReferenceError: expressValidator is not defined index.js code var express = require('express& ...

Creating a custom function in JavaScript to interact with the `windows.external` object specifically for use

In my current project, I am facing an issue with JavaScript file compatibility across different browsers. Specifically, I have a JavaScript file that calls an external function (from a separate file) using windows.external, like this: windows.external.se ...

Tips for transferring localstorage values from the view to the controller in order to utilize them as a PHP variable in a separate view

How can I pass the value from local storage as a PHP variable when using location.href in the controller after clicking a button? In the view: echo Html::button('Proceed', [ 'onclick' => " var dataVal = localStorage.g ...

Why does the browser indicate that a GET request has been sent but the promise does not return anything?

Having trouble with a puzzling issue and unable to find any solutions online. I am attempting to make a basic HTTP get request to receive a JSON object from an API I created (using express+CORS). I have experimented with both Axios and VueResource, yet en ...

What is the best method to assign each key in an Object to the corresponding value in another Object?

Suppose I have an object called data: { first: 'Zaaac', last: 'Ezzell', title: 'Mrs', mail: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="83ece6f9f9e6efefb3c3f1e6e7e7eaf7ade ...

What is the best way to incorporate a unique font with special effects on a website?

Is there a way to achieve an Outer Glow effect for a non-standard font like Titillium on a website, even if not everyone has the font installed on their computer? I'm open to using Javascript (including jQuery) and CSS3 to achieve this effect. Any sug ...