11

I'm trying to load the home page of my app for visitors who are not authenticated.

const routes: Routes = [
    { path: '', loadChildren: './home/home.module#HomeModule' }
...

Authenticated users should get their feed via that module, also on the empty path.

{ path: '', loadChildren: './feed/feed.module#FeedModule', canActivate: [IsAuthenticationGuard] },
{ path: '', loadChildren: './home/home.module#HomeModule', canActivate: [NoAuthenticationGuard] },

I would expect that IsAuthenticationGuard would fail and load the default home component.

Instead it DOES download the feed module package (shown in the network tab) but loads nothing in the router outlet. Very confusing.

How can I do conditional routing (based on guards or otherwise) on the empty path?

Update: Here are the guards by request

@Injectable()
export class IsAuthenticationGuard implements CanActivate {
    constructor(
        private authenticationService: AuthenticationService
    ) { }

    public canActivate(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<boolean> {
        return this.authenticationService.isAuthenticated.pipe(take(1), map((isAuthentication) => {
            return isAuthentication;
        }));
    }
}

I've researched the new urlTree and it's cool that you can now redirect via the route instead of within the guard. However, redirects don't seem applicable if you're trying to use the same route with a different module. Plz correct me if there is a way.

Ben Racicot
  • 5,332
  • 12
  • 66
  • 130
  • 2
    You needd to use `matchers`. check this for the clues: https://medium.com/@lenseg1/loading-different-angular-modules-or-components-on-routes-with-same-path-2bb9ba4b6566 – nircraft Dec 26 '18 at 21:03
  • 1
    please show us your guards – bodorgergely Dec 26 '18 at 21:48
  • you can't put two empty paths, you should put one empty and give the other a url, then in the guards, you can do redirects by router.navigate – Raeef Refai Jan 09 '19 at 10:06
  • provide the code of the **AuthenticationService** so I can provide a solution – Raeef Refai Jan 09 '19 at 10:09
  • Are you thinking about doing redirects from the AuthComponent? – Ben Racicot Jan 09 '19 at 12:47
  • @TR3B you can build routes dynamically and on that case, it would be the same path (empty) for both of your scenarios. – un.spike Feb 07 '19 at 08:01
  • 1
    Although this feature does not exists there is a feature request at: https://github.com/angular/angular/issues/12088 – Top-Master Feb 07 '19 at 11:08
  • 1
    **SOLUTION:** You can do it with creating route `matcher`. **CODE:** I answered that here: https://stackoverflow.com/a/67422797/14389830 **NOTE:** In the linked answer there is a working example and linked relevant `blogs` for in-depth details and walk-through how to do it. – NeNaD May 07 '21 at 05:59

4 Answers4

3

There are a couple things that you are doing wrong in my opinion.

  1. You shouldn't have two routes defined with the same path.
  2. In your guard the map is pointless. As long as your service method returns Observable<boolean> you don't need to map its value.
  3. Also in your guard if you're not returning true, you need to navigate to the other module's path. For that you will need to implement some other logic for telling whether the user is logged in or not.

Take a look at the official angular routing guide here. There is a lot of useful information that will help you. Especially these:

EDIT 2018/12/27

So if the question is how to conditionally load two feature modules on the same path,

the answer is, in my experience, you cannot.

bodorgergely
  • 558
  • 1
  • 3
  • 12
  • Thanks bodogergely for the answer! But I know how to do all of that. Also redirects aren't applicable. The question is how to conditionally load a feature module on an empty path. – Ben Racicot Dec 27 '18 at 00:00
  • Did you check what is happening with debug enabled? It might shed some light on what is happening with your setup. – bodorgergely Dec 27 '18 at 01:39
  • I think you might be correct that it cannot be done without hacking at the router like nircraft mentioned. – Ben Racicot Dec 27 '18 at 13:32
  • Make the path for unauthorized different from empty. On your Guard, you can handle the authentication response before return it. So in case, this is false you can use the router navigate to the unauthorized path. – Sinuee Hernández Jan 09 '19 at 16:05
3

It turns out that this was basically not (safely) possible until Ivy. Now in Angular 9/10 we can lazy load feature-modules without the router very easily.

StackBlitz
V13 StackBlitz Update

@Component({
    selector: 'my-app',
    templateUrl: './app.component.html',
})
export class AppComponent implements OnInit  {
    @ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;

    constructor( 
        private compiler: Compiler,
        private injector: Injector
    ){  }

    public ngOnInit(): void {
        if (loggedin) {
            this.loadModule(await import('./lazy/lazy.module').then(m => m.LazyModule));
        } else {
            this.loadModule(await import('./lazy2/lazy2.module').then(m => m.Lazy2Module));
        }
    }

    async loadModule(module: Type<any>) {
        let ref;
        try {
            this.container.clear();

            // Angular < 13 
            const moduleFactory = await this.compiler.compileModuleAsync(module);
            const moduleRef: any = moduleFactory.create(this.injector);
            const componentFactory = moduleRef.instance.resolveComponent(); // ASSERTION ERROR
            ref = this.container.createComponent(componentFactory, null, moduleRef.injector);

            // Angular 13 update
            const moduleRef = createNgModuleRef(module, this.injector);
            const componentFactory = moduleRef.instance.resolveComponent();
            ref = container.createComponent(
                componentFactory,
                undefined,
                moduleRef.injector
            );

        } catch (e) {
            console.error(e);
        }
            return ref;
        }
    }
Ben Racicot
  • 5,332
  • 12
  • 66
  • 130
  • 1
    For Angular 13/14 etc see the updates here https://stackoverflow.com/questions/70204754/lazy-loading-angular-13-modules-without-the-deprecated-compiler – Ben Racicot Nov 25 '22 at 13:47
0

I believe what you intend to achieve is not possible through guards. Angular searches the routes array from top to bottom and as soon as it gets a path match, the corresponding module/component is loaded. In your case, { path: '', loadChildren: './feed/feed.module#FeedModule', canActivate: [IsAuthenticationGuard] } is a url path match and thus Angular loads the Feed Module, however does not display anything as the guard returns failure. It doesn't even proceed to check the url match for line below.

Instead what you can do is,

  1. change the routes as { path: '', loadChildren:'./home/home.module#HomeModule', canActivate: [IsAuthenticationGuard] },
  2. navigate to feed module if the user is authenticated succesfully. Follow this thread to get more clue on how to do this.

I hope this solves your purpose.

M M
  • 655
  • 1
  • 7
  • 16
  • Thanks M M! Yes you can def redirect from within the guard. Especially now in 7.2 with the new UrlTree it's easier. But this would still be a common redirect. I'm talking about leveraging the same path depending on auth status. – Ben Racicot Jan 09 '19 at 17:22
-2

Try maybe this:

const routes: Routes = [
  { path: '', redirectTo: condition ? 'home' : 'feed', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'feed', component: FeedComponent },
  { path: '*', redirectTo: 'home', pathMatch: 'full' },
];

That's not directly based on Guards, but you should probably be able to get your Guard return as the condition

Alex
  • 1,090
  • 7
  • 21