19

I have set up my app so that I have a Recipe Book which has a list of Recipies which when I click on a Recipe it then shows the Recipe Details in a nested route. This then also has a button that when clicked loads the ingredients in a nested route inside the Recipes Details.

So far the routing seems to work, except when I try to navigate to another Recipe. If the Ingredients tray(route) is active then it will change Recipe and collapse the Ingredients Route, If I then try and navigate (without opening the Ingredients) I get the following error:

Uncaught (in promise): Error: Outlet is not activated
Error: Outlet is not activated

It looks like I the router needs Ingredients to be active or else it doesn't understand the nested route. Thats my take on it but not sure how to fix it or when I went wrong.

single-recipe-book-page.component.html

<app-tray class="recipe-list">
    <app-recipe-list [recipe]="recipe"></app-recipe-list>
</app-tray>
<router-outlet></router-outlet>

recipe-detail.component.html

<app-tray class="recipe">
    The routing has worked and loaded recipe.
</app-tray>
<router-outlet></router-outlet>

ingredients-list.component.html

<app-tray class="ingredients-list">
    Recipe Ingredients have loaded.
</app-tray>

app.routes.ts (updated)

export const routerConfig : Route[] = [
  {
    path: 'recipe-books',
    children: [
      {
        path: ':id', component: SingleRecipeBookPageComponent,
        children: [
          {
            path: 'recipes',
            children: [
              { path: ':id', component: RecipeDetailComponent,
                children: [
                  { path: '', component: IngredientsListComponent },
                  { path: 'ingredients', component: IngredientsListComponent }
                ]
              },
              { path: 'new', component: IngredientsListComponent },
              { path: '', component: RecipeDetailComponent }
            ]
          },
          { path: '', redirectTo: 'recipes', pathMatch: 'full' }
        ]
      },
      { path: '', component: RecipeBooksPageComponent }
    ]
  },
  { path: 'ingredients', component: IngredientsComponent },
  { path: '', redirectTo: 'recipe-books', pathMatch: 'full' },
  { path: '**', redirectTo: 'recipe-books', pathMatch: 'full' }
];
Daimz
  • 3,243
  • 14
  • 49
  • 76

4 Answers4

25

This route is invalid and you should get an error for it.

{ path: '' },

A route either needs to have a component, a redirectTo, or children.

If an empty path route has no children it also should have pathMatch: 'full'.

I guess what you want is

{ path: '', component: DummyComponent, pathMatch: 'full' },

Where DummyComponent is a component with an empty view. You can reuse the same DummyComponent for all these paths.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Thanks for the clarity, I have searched the heck out of the internet around this issue and so far missed that one important point, infact in tutorials I have followed, they use `{ path: '' }` so it's been very confusing whilst trying to learn. – Daimz Nov 12 '16 at 11:29
  • Could you solve all issues or is there still something not working as expected? – Günter Zöchbauer Nov 12 '16 at 11:31
  • Updated my code above with new routing. I can now navigate between `Recipe-books>Single Recipe>Ingredients` no outlet error. But now if I visit the `Recipe 1`, the Ingredients are not show(This is correct it should only show when I click ingredients button and it goes to `recipe-books/1/recipe/1/notes`) if I click the button the ingredients show. Perfect! Then I click on the `Recipe 2` (ingredients still active) I get `Recipe 2` but with `Recipe 1` Ingredients. If I click the Ingredients button now `Recipe 2s` ingredients load. Any ideas where I could be going wrong? – Daimz Nov 12 '16 at 11:39
  • Can you please try to reproduce in a button. In Plunker editor the `[ New | V ]` button provides a ready-to-use Angular 2 TS template. If you run into issues just add a comment and I try to help. – Günter Zöchbauer Nov 12 '16 at 11:42
  • I'll have a go and see if I can get a plunker working. – Daimz Nov 12 '16 at 11:43
  • 1
    I couldn't get a build working in plunker, but after some tinkeringI was able to figure it out. I had really just missed how empty paths need to be treated and you covered that clearly. Thanks for the help! – Daimz Nov 16 '16 at 00:25
  • 1
    You can block component creation with `runGuardsAndResolvers: () => false`. No DummyComponent needed :-) – Simon_Weaver Feb 25 '19 at 22:00
  • @Simon_Weaver interesting. Never saw that mentioned, but haven't worked with Angular in a while. – Günter Zöchbauer Feb 25 '19 at 22:01
  • 1
    @GünterZöchbauer I added a full answer to this question but wanted to ping you too. Seems like this is working ok now - it’s one of those massively annoying error messages when you almost get rid of it then it reappears! One github issue had discussion of ‘never’ as an option which would make a lot of sense here but that didn’t get added. But forcing false is working for me :) – Simon_Weaver Feb 25 '19 at 22:11
  • 1
    Worked out for me Thanks :) – Techiemanu May 08 '20 at 04:43
4

TLDR; answer :

{
      path: ':question',
      runGuardsAndResolvers: GUARDS_RESOLVERS_DISABLED   // blocks component creation
}

Where GUARDS_RESOLVERS_DISABLED is a global function (to avoid issues with AOT).

 export function GUARDS_RESOLVERS_DISABLED() { return false; }

There's a new feature in Angular router that sounds related to this problem, but isn't - as far as I can tell - a direct solution for it. The new option is runGuardsAndResolvers = 'pathParamsChange' and it affects when Angular checks guards and resolvers - and crucially whether it runs this outlet check.

So before I found the new option:

  • I was stepping through into angular code - and the actual error occurs here (highlighted I n red) at context.outlet.component (where component is a getter that throws).

enter image description here

  • So to fix I'd just need to make shouldRun == false somehow.

  • And shouldRun is simply shouldRunGuardsAndResolvers(...) so if that can be made to return false then it won't try to create a component in the 'wrong' place.

So the solution is quite simple:

 {
        path: 'contactus',
        component: ContactUsComponent,

        children: [
            {
                path: 'topics',
                pathMatch: 'full'
            },
            
            {
                path: 'faq',
                component: FaqPanelComponent
            },
            
            { 
                path: 'test',
                component: ContactusTopicListComponent
            },
            {
                path: ':category',
                
                children: 
                [
                    {
                        path: ':question',
                        runGuardsAndResolvers: () => false   // blocks component creation
                    }
                ]
            }
        ]
    },

Currently runGuardsAndResolvers has quite a few options, but the one I'm using just always returns false which is equivalent to never which really ought to be made into an option. But that's how I found out about the new router features :-)

Also if you have two levels you need to put this at both (or possibly just the highest) level:

        {
            path: ':category',
            runGuardsAndResolvers: () => false,
            
            children: 
            [
                {
                    path: ':question',
                    runGuardsAndResolvers: () => false
                }
            ]
        }

Further reading on resolvers: https://blog.angularindepth.com/new-in-angular-v7-1-updates-to-the-router-fd67d526ad05


Edit: To make this work properly with AOT / Angular 9/10+ you can create a global function to avoid the issue mentioned by Sunil.

export function GUARDS_RESOLVERS_DISABLED() { return false; }

Then use this syntax

runGuardsAndResolvers: GUARDS_RESOLVERS_DISABLED
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • if I am using runGuardsAndResolvers: () => false for development it is working fine, but for production it is throwing error while compiling ERROR in Error during template compile of 'ɵ0' Function expressions are not supported in decorators Consider changing the function expression into an exported function. src/app/app-routing.module.ts(128,31): Error during template compile of 'ɵ1' Function expressions are not supported in decorators Consider changing the function expression into an exported function. – Sunil Soni Jan 08 '20 at 05:48
  • did you try that with --prod – Sunil Soni Jan 08 '20 at 05:50
  • @sunil yes I’m using this in an aot production site – Simon_Weaver Jan 08 '20 at 06:09
  • I am facing issue with that, can you share code snippet for routing module – Sunil Soni Jan 08 '20 at 06:48
3

This might help someone else.

I was getting this error. I have checked all my defined routes in my route file, the path and components are defined correctly.

The reason of getting this error was that I forgot to add reference of my service in app.module.ts which was being used in one of my component to get data from RestApi.

So, always add service reference in app.module.ts in providers section, immediately after creating service.

imports: [
   BrowserModule,
   HttpModule,
   ......
 ],

providers: [
    AuthService, 
    ........ // Here, service reference
]
mmushtaq
  • 3,430
  • 7
  • 30
  • 47
0

For production runGuardsAndResolvers: () => is not working. Angular has new feature for runGuardsAndResolvers.

For working you can try runGuardsAndResolvers: "pathParamsChange" Tested in Angular 8 works fine.

Arzu Suleymanov
  • 671
  • 2
  • 11
  • 33