为什么 <app-root> 在其中渲染另一个 <app-root>,其中包含我的路由组件?

msa*_*ord 5 typescript router-outlet angular

我有一个延迟加载的多模块 Angular 7.2.2 应用程序,它似乎AppComponent在自身内部创建 main 模板的副本,然后在其中渲染路由组件。

这是我对完整但简洁的序言的尝试(我无法在 plunkr 中重现这一点)。请询问我遗漏的任何细节。

序言

我有一个index.html包含普通 HTML 的文件,以及<app-root><body>. 这是项目中唯一app-root出现标签的位置。angular.json该文件在的属性中引用projects.app.architect.build.options.index

主要AppComponent声明为

@Component({
  selector: 'app-root',
  template: `
    <router-outlet></router-outlet>
    <message-banner [show]="showMessage" [message]="message"></message-banner>
  `,
  providers: [AuthenticationService, AuthenticationGuard, MessagingService]
})
Run Code Online (Sandbox Code Playgroud)

其余逻辑AppComponent只是调解应用程序范围通知的内容showmessage我没有路由器出口挂钩,也没有直接的 DOM 操作。

main.ts像往常一样引导应用程序

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';
import { environment } from './environments/environment';

import { hmrBootstrap } from './hmr';

if (environment.production) {
  enableProdMode();
}

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.hmr && module['hot']) {
  hmrBootstrap(module, bootstrap);
}
else {
  bootstrap().catch(err => console.log(err));
}
Run Code Online (Sandbox Code Playgroud)

从以下AppModule

@NgModule({
  imports: [
    appRoutes,
    BrowserAnimationsModule,
    BrowserModule,
    CommonModule,
    FormsModule,
    HttpClientModule,
    HttpClientXsrfModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
    SharedGenericModule
  ],
  declarations: [
    AppComponent,
    MessageBannerComponent
  ],
  providers: [
    { provide: APP_BASE_HREF, useValue: '/' },
    appRoutingProviders,
    ChoosePasswordGuard,
    LoadingBarService,
    Title,
    UserService
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  bootstrap: [AppComponent]
})

export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

使用这些app.routes(小摘录)

const routes: Routes = [
  {
    path: '',
    redirectTo: 'shop',
    pathMatch: 'full'
  },
  {
    path: '',
    component: AppComponent,
    children: [
      {
        path: 'shop',
        data: { key: 'shop' },
        loadChildren: "./modules/shop.module#ShopModule",
        pathMatch: 'full'
      },
      {
        path: 'car/:id',
        data: { key: 'car' },
        loadChildren: "./modules/car.module#CarModule",
        pathMatch: 'full'
      },
      {
        // This one has child routes, which I will add if needed
        path: 'workOrder',
        data: { key: 'workOrder' },
        loadChildren: "./modules/workOrder.module#WorkOrderModule",
      }
      // And many more
    ]
  }
]

export const appRoutes: ModuleWithProviders = RouterModule.forRoot(routes, {
  enableTracing: false,
  onSameUrlNavigation: 'reload'
});
Run Code Online (Sandbox Code Playgroud)

问题

一旦应用程序稳定下来,生成的 DOM 如下所示:

<body>
  <app-root ng-version="7.2.2">

    <router-outlet></router-outlet>

    <app-root>
      <router-outlet></router-outlet>
      <default-routed-component _nghost-c0>...</default-routed-component>
      <message-banner ng-reflect-show="true" ng-reflect-message="[object Object]">...</message-banner>
    </app-root>

    <message-banner ng-reflect-show="true" ng-reflect-message="[object Object]">...</message-banner>

  </app-root>
</body>
Run Code Online (Sandbox Code Playgroud)

请注意,内部<app-root>没有用 装饰ng-version

另外,我只能以编程方式与内部进行通信<message-banner>,外部似乎已断开连接。但是在加载第一个路由之前(在应用程序稳定之前)生成的消息将发送到消息横幅的两个实例。

问题

为什么我的应用程序用两个实例化两个<app-root>s <router-outlets>,但只使用其中之一。我怎样才能阻止它这样做?

我需要在应用程序范围内保留一个<message-banner>,但我显然想要一个。如果不是该组件的重复,这可能只是一种神秘的不便 - 该应用程序在其他方面都运行得很好。

msa*_*ord 4

回想起来,答案是极其明显的。

AppComponent(包含<router-outlet>我想在整个应用程序中使用的应用程序的唯一通用代码)是从主 AppModule 引导的,并由Router 作为默认路由的组件第二次实例化,然后将功能模块加载为子路由。

解决方法只是删除 AppComponent 和子路由;所以app.routes.ts要从

const routes: Routes = [
  {
    path: '',
    redirectTo: 'shop',
    pathMatch: 'full'
  },
  {
    path: '',
    component: AppComponent,
    children: [
      {
        path: 'shop',
        data: { key: 'shop' },
        loadChildren: "./modules/shop.module#ShopModule",
        pathMatch: 'full'
      },
  ...
  }
]
Run Code Online (Sandbox Code Playgroud)

const routes: Routes = [
  {
    path: '',
    redirectTo: 'shop',
    pathMatch: 'full'
  },
  {
    path: 'shop',
    data: { key: 'shop' },
    loadChildren: "./modules/shop.module#ShopModule",
    pathMatch: 'full'
  },
  ...
]
Run Code Online (Sandbox Code Playgroud)