簡體   English   中英

角異步管道與Router結合使用時,在開發構建和生產構建中表現出不同的行為

[英]Angular async pipe in combination with Router shows different behavior in build for development and in build for production

我有一個非常簡單的由2個組件組成的測試應用

  • AppComponent
  • ListComponent

這兩個組件具有相同的行為:在加載時,它們使用異步管道顯示來自Observable的項目列表。 AppComponent另外還有一個按鈕和一個RouterOutlet,單擊該按鈕后,它將在其中加載ListComponent

當編譯此代碼以進行開發時,即使用ng build ,一切都會按預期進行。 如果為prod編譯了相同的代碼,即ng build --prod ,則行為是不同的。 在第二種情況下,如果我單擊按鈕轉到ListComponent ,則在頁面加載時不再發出 Observable的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']);
  }
}

非常感謝任何關於出問題的想法。

任何想法

奇怪/怪異的行為,但是很好,您發布了此問題/問題。

我認為生產模式中的問題正在發生,因為更改檢測在生產模式與開發模式下的工作方式[ https://blog.angularindepth.com/a-gentle-introduction-into-change-detection-in-angular-33f9ffff6f10]

在開發模式下,更改檢測運行兩次以確保[因為您可能已經看到Expression changed.....異常[ https://blog.angularindepth.com/everything-you-need-to-know-關於表達式的更改,之后將檢查錯誤error-e3fd9ce7dbb4] 另請注意,您正在ngAfterViewInit中設置可觀察ngAfterViewInit 由於有兩個變更檢測周期,因此在開發人員模式下,您可以看到ListComponent正確呈現。 在第一個更改檢測周期之后,您已在ngAfterViewInit分配了可觀察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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM