Kabb5
Kabb5

Reputation: 3870

Redirect all URLs with hash to Angular Routes without hash

I'm replacing an existing AngularJS 1.6.x SPA with an Angular 5.x SPA and I want to make the change transparent to my users.

I'm concerned about users who have bookmarks to the existing app because it has hashes in the URLs (for example: example.com/#/menu and example.com/#/item/37);

However, the new app does not have hashes in the URLs (for example: example.com/menu and example.com/item/37).

The paths and routing are all the same, with the exception of the #/ in the current app.

Is there a way I can configure the Angular routing to drop the #/ and use the hash-free routing configuration of the new app?

I could duplicate all of my routing to accommodate paths with and without the hash, but there must be a way that doesn't require doubling my code.

// Don't really want to do this:
const routes: Routes = [
  {
    path: 'menu',
    component: MenuComponent
  },
  {
    path: '#/menu',
    component: MenuComponent
  },
  // etc.
];

Similarly, redirecting every #/ path would double the code, too.

// Don't really want to do this:
const routes: Routes = [
  {
    path: 'menu',
    component: MenuComponent
  },
  {
    path: '#/menu',
    redirectTo: 'menu'
  },
  // etc.
];

I'm hoping there is something along these lines:

{
  path: '#/*',
  redirectTo: '*' // Somehow reference the wildcard part of the path here 
}

Thanks in advance for your assistance.

Upvotes: 21

Views: 11563

Answers (4)

fservantdev
fservantdev

Reputation: 755

You can use a canActivate guard to redirect to hash-free URLs when needed:

export const hashRedirectGuard = () =>
  ((_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
    const router = inject(Router);
    const url = state.url;
    const newUrl = '/' + trimStart(url, '/#');
    return url === newUrl || router.parseUrl(newUrl);
  }) satisfies CanActivateFn;

// app-routing.module.ts
const APP_ROUTES: Routes = [
  {
    path: '',
    canActivateChild: [hashRedirectGuard()], // Wrap all routes, see: https://stackoverflow.com/a/43487962/12292636
    children: [...],
  }
];

Upvotes: 0

Brad C
Brad C

Reputation: 2982

Migrated an app from HashLocationStrategy to PathLocationStrategy and ended up doing this in AppComponent > ngOnInit:

this.router.events
  .pipe(filter((event) => event instanceof NavigationStart && /^\/#/.test(event.url)))
  .subscribe((event: NavigationStart) => this.router.navigateByUrl(event.url.replace('/#', '')));

Note that router.navigate will strip any query params so I used router.navigateByUrl instead.

Once this is in place you can safely toggle useHash to useHash: false in your RoutingModule and both new and old URLs will continue working!

Upvotes: 0

Kabb5
Kabb5

Reputation: 3870

The answer posted by @Yanis almost worked, but required a few slight tweaks. His answer definitely deserves an upvote; however, below is the working solution I implemented:

export class AppComponent implements OnInit {
    constructor (private router: Router) { }

    ngOnInit() {
      this.router.events.subscribe(event => {
        if (event instanceof NavigationStart) {
          if (!!event.url && event.url.match(/^\/#/)) {
            this.router.navigate([event.url.replace('/#', '')]);
          }
        }
      });
    }
}

Upvotes: 22

Yanis-git
Yanis-git

Reputation: 7875

i don't know if is right way to do it. But you can do something like this : Goal is to subscribe to NavigationChange, then you check if your current route start by '#!', if yes, you redirect to the right route.

class AppComponent implement OnInit {
    constructor(router: Router) {
        //When url change, we check if actual url have #! on it, then we redirect to the route without it.
        router.events.subscribe((event: NavigationEvent): void => {
            this.url = router.url;
            if (this.url.match('/^#!/')) {          
              this.router.navigate(
                this.url.replace('#!','')
              );
            }
          }
        );

    }
}

Another approch more complicated in my opinion is to use custom "matcher". More information here :

https://github.com/angular/angular/issues/12442

Upvotes: 11

Related Questions