Угловая асинхронная труба в сочетании с Router демонстрирует различное поведение в сборке для разработки и в сборке для производства - PullRequest
2 голосов
/ 21 июня 2019

У меня есть очень простое тестовое приложение, состоящее из 2 компонентов

  • AppComponent
  • ListComponent

Оба компонента имеют одинаковое поведение: во время загрузки они показывают список элементов, поступающих из Observable с использованием асинхронного канала. AppComponent дополнительно имеет кнопку и RouterOutlet, куда он загружает ListComponent , как только кнопка нажата.

Когда этот код компилируется для разработки, то есть с ng build, все работает как положено.Когда один и тот же код скомпилирован для prod, то есть ng build --prod, поведение будет другим.Во втором случае, если я нажму кнопку, чтобы перейти к ListComponent , наблюдаемая ListComponent больше не будет выдаваться при загрузке страницы.

Кодявляется следующим (у меня также пример Stackblitz , где проблема, хотя не возникает)

ListComponent

@Component({
  selector: 'app-list',
  template: `
    <input #searchField type="text">
    <div *ngFor="let item of itemsToShow$ | async">{{item}}</div>
  `,
})
export class ListComponent implements OnInit, AfterViewInit  {
  @ViewChild('searchField', {static: true}) searchField: ElementRef;
  search$: Observable<string>;
  itemsToShow$: Observable<string[]>;

  ngOnInit() {}

  ngAfterViewInit() {
    this.search$ = merge(concat(of(''), fromEvent(this.searchField.nativeElement, 'keyup'))).pipe(
      map(() => this.searchField.nativeElement.value)
    );

    this.itemsToShow$ = this.itemsToShow();
  }

  itemsToShow() {
    let itemAsLocalVar: string[];
    return of(ITEMS).pipe(
      delay(10),
      tap(items => itemAsLocalVar = items),
      switchMap(() => combineLatest([this.search$])),
      map(([searchFilter]) => itemAsLocalVar.filter(i => i.includes(searchFilter))),
    );
  }
}

AppComponent

@Component({
  selector: 'app-root',
  template: `
    <input #searchField type="text">
    <div *ngFor="let item of itemsToShow$ | async">{{item}}</div>
    <router-outlet></router-outlet>
    <button (click)="goToList()">List</button>
  `
})
export class AppComponent implements OnInit, AfterViewInit  {
  @ViewChild('searchField', {static: true}) searchField: ElementRef;
  search$: Observable<string>;
  itemsToShow$: Observable<string[]>;

  constructor(
    private router: Router,
  ) {}

  ngOnInit() {}

  ngAfterViewInit() {
    this.search$ = merge(concat(
       of(''), 
       fromEvent(this.searchField.nativeElement, 'keyup')
     )).pipe(
       map(() => this.searchField.nativeElement.value)
     );
    this.itemsToShow$ = this.itemsToShow();
  }
  itemsToShow() {
    let itemAsLocalVar: string[];
    return of(ITEMS).pipe(
      delay(10),
      tap(items => itemAsLocalVar = items),
      switchMap(() => {
        return combineLatest([this.search$]);
      }),
      map(([searchFilter]) => itemAsLocalVar.filter(i => i.includes(searchFilter))),
      tap(i => console.log(i))
    );
  }
  goToList() {
    this.router.navigate(['list']);
  }
}

Любая идея о том, что идет не так, очень ценится.

Любая идея

1 Ответ

0 голосов
/ 22 июня 2019

Странное / странное поведение, но хорошо, что вы опубликовали эту проблему / вопрос.

Я думаю, что проблема в производственном режиме происходит, потому что способ обнаружения изменений работает в производственном режиме в сравнении с режимом разработки [https://blog.angularindepth.com/a-gentle-introduction-into-change-detection-in-angular-33f9ffff6f10].

В режиме разработки обнаружение изменений запускается два раза, просто чтобы быть уверенным [из-за этого вы могли видеть Expression changed..... исключение [https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4]. * Также обратите внимание, что вы настраиваете свои наблюдаемые в ngAfterViewInit,Из-за 2 циклов обнаружения изменений в режиме разработки вы видите, что ListComponent отображается правильно.После первого цикла обнаружения изменений вы присваиваете наблюдаемое в ngAfterViewInit, которое обнаруживается во 2-м цикле обнаружения изменений, и компонент отображается должным образом.

В производственном режиме обнаружение изменений не запускается два раза.Он запускается только один раз из-за повышения производительности.Ваш ListComponent отобразит список, если вы нажмете кнопку «Список» еще раз [после самого первого щелчка], потому что нажатие кнопки запускает обнаружение изменения угла.

Чтобы исправить это, у вас есть следующие опции -

1. Принудительно обнаружите изменение, введя:

constructor(private _cdref: ChangeDetectorRef) {

  }

и измените ngAfterViewInit() вот так

ngAfterViewInit() {
    this.search$ = merge(concat(of(''), fromEvent(this.searchField.nativeElement, 'keyup'))).pipe(
      map(() => this.searchField.nativeElement.value)
    );
    this.itemsToShow$ = this.itemsToShow();
    this._cdref.detectChanges();
  }

2. Переместите свой код ngAfterViewInit() на ngOnInit() вот так:

ngOnInit() {

    this.search$ = merge(concat(of(''), fromEvent(this.searchField.nativeElement, 'keyup'))).pipe(
      map(() => this.searchField.nativeElement.value)
    );
    this.itemsToShow$ = this.itemsToShow();

  }

Я бы порекомендовал перейти к варианту 2.

...