我有一个非常简单的由2个组件组成的测试应用程序
这两个组件具有相同的行为:在加载时,它们显示使用异步管道来自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']);
}
}
非常感谢出现问题的任何想法。
关于
的任何想法答案 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。