37

I have an Angular2 application with one router outlet that displays different components depending on which link is clicked in a side menu.

The markup for the main component containing the <router-outlet> looks like this

<div *ngIf="authenticated == false">
  <app-login></app-login>
</div>
<div *ngIf="authenticated">
  <div class="page home-page">
    <header class="header">
      <app-navbar></app-navbar>
    </header>
    <div class="page-content d-flex align-items-stretch">
      <div class="sidebar-container">
        <app-sidebar-menu></app-sidebar-menu>
      </div>
      <div class="content-inner">
      <app-page-header></app-page-header>
        <div id="sub-content">
          <router-outlet></router-outlet>
        </div>
        <app-footer></app-footer>
      </div>
    </div>
  </div>
</div>

If I click the Demo link, the demo component is rendered, but if I then click the Home link, the home component is rendered above the demo component in DOM. Clicking them a few times will result with a DOM like this

<div _ngcontent-c0="" id="sub-content">
    <router-outlet _ngcontent-c0=""></router-outlet>
  <app-home _nghost-c6="">...</app-home>
  <app-demo _nghost-c7="">...</app-demo>
  <app-home _nghost-c6="">...</app-home> <!-- Why so many here? Should be just either one <app-home> or <app-demo>  -->
  <app-demo _nghost-c7="">...</app-demo>
  <app-home _nghost-c6="">...</app-home>
  <app-demo _nghost-c7="">...</app-demo>
  <app-footer _ngcontent-c0="" _nghost-c5="">...</app-footer>
</div>

The routes are defined as

export const router: Routes = [
    { path: 'demo', component: DemoComponent, canActivate: [AuthGuard] },
    { path: 'home', component: HomeComponent, canActivate: [AuthGuard] }
]

How come that the <router-outlet> doesn't replace the component, but instead adds another "instance" of the component when switching between the routes?

Daniel B
  • 8,770
  • 5
  • 43
  • 76
  • 1
    Wow ... it certainly shouldn't be doing that. It correctly replaces the content in my apps using similar code. Can you do a plunker that reproduces the error? Or provide the html for the home component in your question above? – DeborahK Aug 10 '17 at 19:58
  • Thought I was going crazy! I'm working on a plunker! – Daniel B Aug 10 '17 at 20:01
  • This is also happening to me in a custom tabs component. I've had a few people look over it with me and I can't find a thing. I thought that I might have an *ngFor hanging out somewhere that was somehow creating divs for each click, but that isn't the issue. If I inspect the code in browser it shows the router link directly followed by a list of components that is added to on each click. Hope someone can find an answer soon. – Logan Kitchen Aug 11 '17 at 15:41
  • Interesting, I'm not able to replicate it in a Plunker, it behaves just as it should. I'll look for an *ngFor, that's something I hadn't thought of! Will keep you updated! – Daniel B Aug 11 '17 at 15:44
  • Just a quick thought, are you using firebase? This happened just after I implemented firebase in my app. – Logan Kitchen Aug 11 '17 at 21:24
  • Yep, Firebase and AngularFire2! – Daniel B Aug 11 '17 at 21:25
  • Hmm. Maybe it is making some kind of change to the router outlet. – Logan Kitchen Aug 11 '17 at 21:46
  • Update. This also adds all modals to the bottom of the page (I'm using angular material modals) in the same place as my tab content. – Logan Kitchen Aug 11 '17 at 21:49
  • I just solved mine. I was using the asynchronous pipe on an element contained inside of one of my tabs. I removed the pipe and now it is working again. – Logan Kitchen Aug 11 '17 at 22:40
  • Interesting! I have an `| async` aswell, but removing it does nothing for me except breaking my other functionality! :( – Daniel B Aug 12 '17 at 09:26
  • Hmmm... Well, good luck. I know mine seemed to be caused by an error with | async. Maybe resolve any console errors and see if that helps. I'll make sure to post here if I find a definite answer. – Logan Kitchen Aug 12 '17 at 20:06

6 Answers6

56

By using the method of elimination, I found out that the culprit of the issue was the BrowserAnimations module in my app.module.ts. By removing it from my imports it the problem went away. I'll look into creating a Plunker to demonstrate it.

Update: This is described in this Github issue.

Update 2017-12-13: This has now been fixed with this PR, fix(animations): properly recover and cleanup DOM when CD failures occur.

Daniel B
  • 8,770
  • 5
  • 43
  • 76
  • 2
    Thank you for clarification, this has been the most important message I have read the whole day! You rock. – Tomas Sykora Sep 16 '17 at 21:06
  • Glad it helped you! I've tried to recreate it with a Plunkr without any success to be able to find out if it's a bug or not! – Daniel B Sep 17 '17 at 15:00
  • 2
    This is also not solving the problem, very strange ! – A.Chakroun Sep 26 '17 at 01:53
  • This is a known Angular issue: https://github.com/angular/angular/issues/19093 Until this is fixed you have to identify and isolate the exception as TetraDev suggested. – Francesco Nov 09 '17 at 11:03
  • 1
    [Master branch commit](https://github.com/angular/angular/commit/46aa0a1cf689e98ee37c9be488431fe48b3608a8) for the fix. – user1338062 Feb 10 '18 at 06:50
  • Removing BrowserAnimations had temporarily fixed my problem but it didn't add up. I never had console errors. It turned out to be an issue with my route guard. canActivate returned/resolved a promise in a way Angular didn't like. I improved the logic and suddenly the issue is gone – SeanMC Oct 08 '18 at 02:19
  • I am having same issue, I am using Angular 8.1.3. Please take a look my stackblitz. I am able to reproduce there. I am not using BrowserAnimationsModule. https://stackblitz.com/edit/angular-a8lmjs – GeetT Sep 24 '19 at 03:04
  • @GeetT your stackblitz is correct though. You have the router outlet in your `app.component.html` and it shows everything in that template and components you navigate to. [I forked your StackBlitz](https://stackblitz.com/edit/angular-8kqvyb) with an example that shows it works as expected. – Daniel B Sep 25 '19 at 07:19
26

This happens also when component A is throwing an error, so when navigating to Component B, Component A could not be destroyed due to the error. This is a bug with Angular. Until they fix, find the cause of the error being thrown and fix it. Check your dev tools console.

TetraDev
  • 16,074
  • 6
  • 60
  • 61
  • In my case, it was a parent component that contained an error (that look harmless at first sight) and apparently was causing the APPENDING instead of REPLACING. Thanks for pointing out. – Jeankes Oct 25 '17 at 13:53
  • Thanks. it helped. In my case, it was happening if either of the components had an error! – vishwajeet kumar Jan 18 '18 at 14:01
  • Thanks! I was facing the same issue and there was no error in console when I navigate to component B. This issue may come when you do not see any errors on the console as well. In my case fix was as follows: Original Code: this.obj1 = obj2; Fixed Code: this.obj1 = _.extend(this.obj1, obj2); – viks Feb 28 '18 at 09:33
  • I'm having this issue as well. I don't get the appending issue once I remove BrowserAnimationsModule, but now I get an error (Found the synthetic listener @inOut.done.) because I don't have BrowserAnimationsModule! – Reverend Bubbles Jun 13 '18 at 15:32
  • Yea, I realize now that I need BrowserAnimationsModule for my Toastr stuff. Any thoughts on how to get around this without removing that module? – Reverend Bubbles Jun 13 '18 at 15:44
7

I had a very similar issue, also using Firebase.

See the components being appended to router outlet

However, I found that the issue was coming from an error within one of my components, not related to my routing. One of the components had a reference to a "FormsArray", which was not used and malformed. It threw errors in the devtools console, but I didn't think of checking there, since everything was compiling fine.

Not sure if that will help anybody.

  • It actually helped me! My problem was that a new child component failed to render completely (use date pipe for wrong data). In this scenarion angular was showing partial data for new component and old component - one after another. As soon as I fixed the pipe issue the problem solved. Thanks a lot for this hint! – Julia Passynkova Oct 05 '17 at 19:07
  • 1
    How did you debug.? I'm getting the same issue.! , can you show code changes which you made.! – Mr world wide Dec 27 '17 at 17:01
  • I have the same problem as @Mrworldwide. This is one of the things I don't like about using frameworks, it's impossible to debug sometimes. – krummens Apr 21 '20 at 04:08
3

I was using NgZone inside the component and my routerLink was having the same issue, without any errors inside console.

Changed the routerLink to a (click) inside and called a function like this:

constructor(
    (...)
    private zone: NgZone,
    (...)
  ) { }

goToPage() {
    this.zone.run(() => this.router.navigate(['/page']));
}
1

I had a similar issue , and the reason was that i used a directive that is not declared in component A and there was no error in compile or console. so when navigate to component B the router was appending the content.

To debug I commented out all the html markup in both components leaving only a h1 to see if the content was appending. with some tests i found the directive and by removing it, router back to normal again.

Hassan Juniedi
  • 403
  • 5
  • 14
0

I'm not sure if this matches your situation exactly, however I have had previous Components append to the DOM when trying to load a different route, and finally I figured that the use of Hash was conflicting with addresses of the component,

export const AppRouting = RouterModule.forRoot(routes, { useHash: false });

This fixed all my problems with unwanted appended components

random-parts
  • 2,137
  • 2
  • 13
  • 20