[英]Angular async pipe in combination with Router shows different behavior in build for development and in build for production
我有一個非常簡單的由2個組件組成的測試應用
這兩個組件具有相同的行為:在加載時,它們使用異步管道顯示來自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.