0

I have a UserProfileResolver to provide data to a UserProfileComponent. I noticed i can click the navigation link a bunch of times sending a ton of HTTP requests.

Is there a standard way to prevent subsequent requests from being made until the initial one completes?

enter image description here

nav

<li class="nav-item">
  <a [routerLink]="['/user-profile/'+userId]" class="nav-link" routerLinkActive="active">
    <i class="fa fa-user" aria-hidden="true"></i>
    <ng-container i18n>Edit Profile</ng-container>
  </a>
</li>

routing module

const routes: Routes = [
  {
    path: ':id',
    component: UserProfileComponent,
    resolve: {data: UserProfileResolver},
  },
];

resolver

export class UserProfileResolver {

  constructor(private readonly userService: UserService, private readonly facilityService: FacilityService) {
  }

  public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {

    return new Observable((observer) => {

      forkJoin([
        this.userService.getSingle(route.paramMap.get('id')),
        this.facilityService.getAll(),
      ]).pipe(
        map(([user, facilities]) => {
          return {user, facilities};
        }),

        catchError(() => of(undefined)),

        // dont resolve on error
        tap(data => {
          if (data !== undefined) {
            observer.next(data);
          } else {
            observer.unsubscribe();
          }
          observer.complete();
        }),
      ).subscribe();
    });
  }
}

Ricardo Saracino
  • 1,345
  • 2
  • 16
  • 37
  • 1
    I think the easiest way is to disable the link, you can look [here](https://stackoverflow.com/questions/35431188/how-can-i-conditionally-disable-the-routerlink-attribute) how to implement it – vitaliy kotov Sep 24 '20 at 19:43
  • Disabling the link opens up a whole mess of the navigation having know when the `UserProfileComponent` has finished loading. I wonder if there is a way to check route state? – Ricardo Saracino Sep 24 '20 at 20:09
  • I ended up doing just that .. i didnt need to get to hacky either – Ricardo Saracino Sep 24 '20 at 23:17

2 Answers2

0

You can use some Block UI implementation for Angular such as ng-block-ui which can be configured to automatically initiate on the httpRequests and prevent user to any other interactions before response resolving;

You can install it by this command:

npm install ng-block-ui --save

and import in your Angular app as follows:

// All other imports
import { BlockUIModule } from 'ng-block-ui';
 
@NgModule({
  imports: [
    BlockUIModule.forRoot()
  ],
  ...
})
export class AppModule { }

Here is a simple sample on Stackblitz.

ng-hobby
  • 2,077
  • 2
  • 13
  • 26
0

So i ended up just disabling the button until the navigation resolved

Nav

<li class="nav-item">
  <a (click)="userProfileClick()" [class.disabled]="userProfileNavigating"
     [routerLink]="['/user-profile/'+userId]" class="nav-link" routerLinkActive="active">
    <i aria-hidden="true" class="fa fa-user"></i>
    <ng-container i18n>Edit Profile</ng-container>
  </a>
</li>

Side Nav

export class SideNavComponent {

  public userId: string;

  public userProfileNavigating: boolean;

  constructor(public readonly authService: AuthService,
              private readonly router: Router,
  ) {
    this.userId = authService.userId;

    // noticed i could click a router link a bunch of times before it resolved
    router.events.pipe(filter(e => e instanceof NavigationEnd || e instanceof NavigationCancel)).subscribe(e => {
      this.userProfileNavigating = false;
    });
  }

  userProfileClick() {
    if (this.router.routerState.snapshot.url !== `/user-profile/${this.userId}`) {
      this.userProfileNavigating = true;
    }
  }
}

Resolver

Updated for Angular 10, this cancels Resolver Navigation by using EMPTY

export class UserProfileResolver implements Resolve<any> {

  constructor(private readonly userService: UserService, private readonly facilityService: FacilityService) {
  }

  public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
    Observable<{ user: User; facilities: Array<Facility> }> {
    return forkJoin([
      this.userService.getSingle(route.paramMap.get('id')),
      this.facilityService.getAll(),
    ]).pipe(
      map(([user, facilities]) => {
        return {user, facilities};
      }),
      catchError(() => EMPTY),
    );
  }
}
Ricardo Saracino
  • 1,345
  • 2
  • 16
  • 37