我有一个由2个组件组成的非常简单的测试应用
这两个组件具有相同的行为:在加载时,它们使用异步管道显示来自Observable的项目列表。 AppComponent 另外还有一个按钮和一个RouterOutlet,单击该按钮后,它将在其中加载 ListComponent 。
编译此代码以进行开发时,即使用
ng build
,一切都会按预期进行。当为产品编译相同的代码(即ng build --prod
)时,行为就不同了。在第二种情况下,如果单击按钮转到 ListComponent ,则在页面加载时不再发出 ListComponent的可观察项。代码如下(我也有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']);
}
}
非常感谢任何关于出问题的想法。
任何想法
最佳答案
奇怪/怪异的行为,但是很好,您发布了此问题/问题。
我认为生产模式中的问题正在发生,因为变更检测在生产与开发模式中的工作方式[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. 通过注入(inject)以下方式强制进行更改检测:
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。