角异步管道与Router结合使用时,在开发构建和生产构建中表现出不同的行为

时间:2019-06-21 17:00:04

标签: angular rxjs angular-routing

我有一个非常简单的由2个组件组成的测试应用程序

  • AppComponent
  • ListComponent

这两个组件具有相同的行为:在加载时,它们显示使用异步管道来自Observable的项目列表。 AppComponent 另外还有一个按钮和一个RouterOutlet,单击该按钮后,它将在其中加载 ListComponent

在编译该代码以进行开发时,即使用ng build时,一切都会按预期进行。当为产品编译相同的代码(即ng build --prod)时,行为就不同了。在第二种情况下,如果我单击按钮转到 ListComponent ,则在页面加载时不再发出 ListComponent 的Observable。

下面是代码(我也有a Stackblitz example,但没有出现问题)

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 :(得分:0)

一种奇怪/怪异的行为,但是很高兴您发布了此问题/问题。

我认为生产模式中的问题正在发生,因为更改检测在生产与开发模式[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中设置观测值。 由于有两个变更检测周期,因此在开发人员模式下,您看到ListComponent正确呈现。在第一个变更检测周期之后,您已在ngAfterViewInit中分配了可观察值,该观测值在第二个变更检测周期中被检测到,并按预期方式呈现了分量。

在生产模式下,更改检测不会运行两次。由于提高了性能,它只能运行一次。如果再次单击“列表”按钮(在第一次单击后),您的ListComponent将呈现列表,因为单击按钮会运行Angular变化检测。

要解决此问题,您可以使用以下选项-

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。