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

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

У меня есть NotificationListComponent и a 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

уведомление-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;
  }
}

уведомительный 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, она попадает в эту точку останова в два раза.

уведомление-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, а также регистрацию объекта на консоли, чтобы значение было заполнено компонентом.

уведомительный 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>

Кто-нибудь знает, почему это происходит?

Update:

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

notification.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 с расширением Chrome. Но данные не отображаются в шаблоне, а маршрут в URL-адресе возвращается к /maintenance/list, хотя шаблон для маршрута /maintenance/edit/:id все еще отображается.

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

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

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

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

Журнал маршрутизации

Ответ 1

Наконец-то удалось решить проблему после получения справки по отладке из @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">

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

Ответ 2

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

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