Проблема маршрутизации 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

notification-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;
  }
}

notification-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, она дважды попадает в эту точку останова.

notification-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", значение не отображается на экране, хотя я также вижу с расширением Chrome Augury как запись объекта на консоль, что значение заполняется в компоненте.

notification-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, и все равно сталкивался с той же проблемой всякий раз, когда я использовал реальный сервис, а не просто фиктивные данные.

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

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

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

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

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

Журнал маршрута


person Andrew Mairose    schedule 09.03.2017    source источник
comment
Вы используете самую последнюю версию Angular2? Некоторое время назад возникла проблема, которая вызвала такое поведение, но была исправлена ​​некоторое время назад AFAIR.   -  person Günter Zöchbauer    schedule 09.03.2017
comment
Попробуйте удалить подстановочный маршрут и посмотрите, исчезнет ли проблема. Я помню, что читал что-то о маршрутах с подстановочными знаками, которые должны быть последними в списке, и я не уверен, что это так в вашей конфигурации.   -  person wolfhoundjesse    schedule 09.03.2017
comment
Я использую 2.4.8 и @angular/router версию 3.4.8. И проблема все еще возникает, даже без подстановочного маршрута.   -  person Andrew Mairose    schedule 09.03.2017
comment
Даже если я также удалю пустой маршрут, проблема все еще сохраняется.   -  person Andrew Mairose    schedule 09.03.2017
comment
Не могли бы вы предоставить код уведомления-list.component.html?   -  person user3249448    schedule 14.03.2017
comment
@user3249448 user3249448 Добавил его в уведомление-list.component.ts   -  person Andrew Mairose    schedule 14.03.2017
comment
Хорошо HTML выглядит хорошо. Давайте узнаем, как работает навигация. нам нужно зарегистрировать различные события изменения / начала / окончания маршрута, которые дадут нам представление и могут быть причиной того, какой был URL-адрес до навигации. попробуйте этот stackoverflow.com/questions/33520043/   -  person user3249448    schedule 14.03.2017


Ответы (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">

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

person Andrew Mairose    schedule 15.03.2017
comment
Бесплатный совет, вы также можете использовать оператор Элвиса stackoverflow.com/questions/34833358/ - person Fabio Antunes; 15.03.2017
comment
Официальные документы здесь: angular.io/ docs/ts/latest/guide/ - person Fabio Antunes; 15.03.2017

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

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

person snorkpete    schedule 13.03.2017
comment
Нет, переход на div не сработал. И это не фактическая страница, которая мигает. Страница фактически правильно отображает шаблон редактирования (кроме того, что модель не привязывается к входным данным правильно), это только адресная строка, которая мигает для правильного маршрута редактирования, а затем возвращается к маршруту списка, в то время как шаблон редактирования остается отображаться на фактической странице . - person Andrew Mairose; 13.03.2017