Проблема маршрутизации Angular2 и ngOnInit вызывается дважды

У меня очень странная проблема с маршрутизацией Angular 2 туда, где мой ngOnInit в компоненте, к которому я направляюсь, вызывается дважды, а маршрут в браузере сбрасывается на исходный маршрут.

у меня есть NotificationListComponent и NotificationEditComponent в MaintenanceModule,

В моем корне AppModuleЯ настраиваю RouterModule перенаправить любые не отображенные маршруты в /maintenance/list,

app.module.ts:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpModule,
    RouterModule.forRoot([
      {path: "", redirectTo: "maintenance/list", pathMatch: "full"},
      {path: "**", redirectTo: "maintenance/list", pathMatch: "full"}
    ], {useHash: true}),
    CoreModule.forRoot({notificationUrl: "http://localhost:8080/notification-service/notifications"}),
    MaintenanceModule
  ],
  providers: [NotificationService],
  bootstrap: [AppComponent]
})
export class AppModule { }

И у меня есть /maintenance/list маршрут, определенный в моем MaintenanceModule, который указывает на мой NotificationListComponent, также как и /maintenance/edit/:id маршрут, который указывает на мой NotificationEditComponent,

maintenance.module.ts:

@NgModule({
  imports: [
    CommonModule,
    RouterModule.forChild([
      {path: "maintenance/list", component: NotificationListComponent, pathMatch: 'full'},
      {path: "maintenance/edit/:id", component: NotificationEditComponent, pathMatch: 'full'}
    ]),
    FormsModule
  ],
  declarations: [
    NotificationListComponent,
    NotificationEditComponent
  ]
})
export class MaintenanceModule {}

Когда мое приложение загружается, оно правильно следует /maintenance/list маршрут, и я могу видеть все мои уведомления в списке. Для каждого уведомления в списке есть значок редактирования, который имеет свой click событие связано с edit(id: number) метод в моем NotificationListComponent

tification-list.component.ts:

@Component({
  templateUrl: 'notification-list.component.html'
})
export class NotificationListComponent implements OnInit {

  notifications: Notification[];
  errorMessage: string;

  constructor(private _notificationService: NotificationService,
              private _router: Router) {}

  ngOnInit(): void {
    this._notificationService.getNotifications()
      .subscribe(
        notifications => this.notifications = notifications,
        error => this.errorMessage = <any>error);
  }

  clearError(): void {
    this.errorMessage = null;
  }
}

tification-list.component.html:

<div class="row">
  <h1>Notification Maintenance</h1>

  <div *ngIf="errorMessage" class="alert-box alert">
    <span>{{errorMessage}}</span>
    <a class="right" (click)="clearError()">&times;</a>
  </div>

  <p-dataTable [value]="notifications" [sortField]="'code'" [responsive]="true" [sortOrder]="1" [rows]="10" [paginator]="true" [rowsPerPageOptions]="[10,50,100]">
    <p-header>Available Notifications</p-header>
    <p-column [field]="'code'" [header]="'Code'" [sortable]="true" [style]="{'width':'10%'}"></p-column>
    <p-column [field]="'name'" [header]="'Name'" [sortable]="true" [style]="{'width':'40%'}"></p-column>
    <p-column [field]="'roles'" [header]="'Roles'" [style]="{'width':'40%'}"></p-column>
    <p-column [field]="'notificationId'" [header]="'Edit'" [style]="{'width':'10%'}">
      <template let-row="rowData" pTemplate="body">
        <a [routerLink]="'/maintenance/edit/' + row['notificationId']"><span class="fa fa-pencil fa-2x"></span></a>
      </template>
    </p-column>
  </p-dataTable>
</div>

Как видите, edit(id: number) метод должен перейти к /maintenance/edit/:id маршрут. Когда я нажимаю значок, чтобы перейти к этому маршруту, браузер мигает правильный маршрут в адресной строке (например, localhost:4200/#/maintenance/edit/2), но затем маршрут в адресной строке немедленно изменится на localhost:4200/#/maintenance/list, Хотя маршрут вернулся в /maintenance/list в адресной строке мой NotificationEditComponent все еще виден в реальном приложении. Тем не менее, я вижу, что ngOnInit метод вызывается дважды в моем NotificationEditComponent, поскольку id дважды регистрируется на консоли, и если я поставлю точку останова в ngOnInit функция, она достигает этой точки останова дважды.

tification-edit.component.ts:

@Component({
  templateUrl: "notification-edit.component.html"
})
export class NotificationEditComponent implements OnInit{

  notification: Notification;
  errorMessage: string;

  constructor(private _notificationService: NotificationService,
              private _route: ActivatedRoute,
              private _router: Router) {
  }

  ngOnInit(): void {
    let id = +this._route.snapshot.params['id'];
    console.log(id);
    this._notificationService.getNotification(id)
      .subscribe(
        notification => this.notification = notification,
        error => this.errorMessage = <any>error
      );
  }
}

По-видимому, это также вызывает другие проблемы, потому что при попытке связать input значения к значениям в моем NotificationEditComponent используя, например, [(ngModel)]="notification.notificationId"значение не отображается на экране, хотя я вижу с расширением Augury chrome, а также записываю объект на консоль, что значение заполняется в компоненте.

tification-edit.component.html:

<div class="row">
  <h1>Notification Maintenance</h1>

  <div *ngIf="errorMessage" class="alert-box alert">
    <span>{{errorMessage}}</span>
    <a class="right" (click)="clearError()">&times;</a>
  </div>

  <p-fieldset [legend]="'Edit Notification'">
    <label for="notificationId">ID:
      <input id="notificationId" type="number" disabled [(ngModel)]="notification.notificationId"/>
    </label>
  </p-fieldset>

</div>

У кого-нибудь есть идеи, почему это будет происходить?

Обновить:

Я удалил свои звонки на NotificationService, и заменил их только некоторыми фиктивными данными, и тогда маршрутизация начала работать! Но как только я добавляю звонки в свой сервис, у меня возникает та же проблема, что я описал выше. Я даже удалила CoreModule и просто добавил сервис прямо в мой MaintenanceModuleи по-прежнему возникает та же проблема, когда я использую реальный сервис, а не просто имитировать данные.

tification.service.ts:

@Injectable()
export class NotificationService {
  private _notificationUrl : string = environment.servicePath;

  constructor(private _http: Http) {
  }

  getNotifications(): Observable<Notification[]> {
    return this._http.get(this._notificationUrl)
      .map((response: Response) => <Notification[]>response.json())
      .catch(this.handleGetError);
  }

  getNotification(id: number): Observable<Notification> {
    return this._http.get(this._notificationUrl + "/" + id)
      .map((response: Response) => <Notification>response.json())
      .catch(this.handleGetError);
  }

  postNotification(notification: Notification): Observable<number> {
    let id = notification.notificationId;
    let requestUrl = this._notificationUrl + (id ? "/" + id : "");
    return this._http.post(requestUrl, notification)
      .map((response: Response) => <number>response.json())
      .catch(this.handlePostError);
  }

  private handleGetError(error: Response) {
    console.error(error);
    return Observable.throw('Error retrieving existing notification(s)!');
  }

  private handlePostError(error: Response) {
    console.error(error);
    return Observable.throw('Error while attempting to save notification!');
  }
}

И служба, кажется, работает нормально - я вижу, что конечная точка успешно возвращает данные, и я вижу, что данные выглядят правильно, когда я смотрю на мой NotificationEditComponent с хромовым расширением Augury. Но данные не отображаются в шаблоне, и маршрут в URL возвращается к /maintenance/list хотя шаблон для /maintenance/edit/:id маршрут по-прежнему отображается.

Обновление 2:

В соответствии с предложением @user3249448 я добавил следующее AppComponent для некоторой отладки:

constructor(private _router: Router) {
  this._router.events.pairwise().subscribe((event) => {
    console.log(event);
  });
}

Вот результат этого, когда я нажимаю на одну из ссылок "изменить":

Регистрация маршрута

2 ответа

Решение

Наконец удалось решить проблему после получения справки по отладке от @user3249448.

Оказывается, я получаю это NavigationErrorдаже при отсутствии ошибок в консоли:

Вот полная трассировка стека:

TypeError: Cannot read property 'notificationId' of undefined
    at CompiledTemplate.proxyViewClass.View_NotificationEditComponent0.detectChangesInternal (/MaintenanceModule/NotificationEditComponent/component.ngfactory.js:487:49)
    at CompiledTemplate.proxyViewClass.AppView.detectChanges (http://localhost:4200/vendor.bundle.js:80125:14)
    at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (http://localhost:4200/vendor.bundle.js:80320:44)
    at CompiledTemplate.proxyViewClass.AppView.internalDetectChanges (http://localhost:4200/vendor.bundle.js:80110:18)
    at CompiledTemplate.proxyViewClass.View_NotificationEditComponent_Host0.detectChangesInternal (/MaintenanceModule/NotificationEditComponent/host.ngfactory.js:29:19)
    at CompiledTemplate.proxyViewClass.AppView.detectChanges (http://localhost:4200/vendor.bundle.js:80125:14)
    at CompiledTemplate.proxyViewClass.DebugAppView.detectChanges (http://localhost:4200/vendor.bundle.js:80320:44)
    at ViewRef_.detectChanges (http://localhost:4200/vendor.bundle.js:60319:20)
    at RouterOutlet.activate (http://localhost:4200/vendor.bundle.js:65886:42)
    at ActivateRoutes.placeComponentIntoOutlet (http://localhost:4200/vendor.bundle.js:24246:16)
    at ActivateRoutes.activateRoutes (http://localhost:4200/vendor.bundle.js:24213:26)
    at http://localhost:4200/vendor.bundle.js:24149:58
    at Array.forEach (native)
    at ActivateRoutes.activateChildRoutes (http://localhost:4200/vendor.bundle.js:24149:29)
    at ActivateRoutes.activate (http://localhost:4200/vendor.bundle.js:24123:14)

По сути, мой шаблон обрабатывался до того, как был возвращен вызов к моему веб-сервису, и мой шаблон не мог быть правильно отображен, потому что notification было undefinedвот так я и получил NavigationError, что вызвало описанное поведение (не кажется ли, что эта ошибка должна быть зарегистрирована на консоли без необходимости добавления дополнительного кода отладки в ваш AppComponent?).

Чтобы это исправить, все что мне нужно было сделать, это добавить *ngIf к моему fieldset который содержал всю информацию о notification,

<p-fieldset [legend]="'Edit Notification'" *ngIf="notification">

И теперь мой шаблон загружается правильно, как только данные возвращаются из веб-службы.

Я предполагаю, что ваш браузер интерпретирует вашу ссылку редактирования как фактическую, а не делает то, что вы хотите, чтобы она делала. Это связано с тем, что вы видите вспышку - похоже, что браузер пытается перейти по указанной ссылке.

Попробуйте изменить элемент, на который вы щелкаете в своем списке уведомлений, чтобы отредактировать уведомление от тега привязки до простого span или div. Если вы не получаете вспышку, то вы знаете, что это была ваша проблема.

Другие вопросы по тегам