Angular 2 application where most of the State of the application lives in @Injectable services that expose the state via an Observable. At some point the application is dropping out of the Angular Zone, which causes change detection to not work. In my subscribe I need to call ChangeDetectorRef.detectChanges()
when this occurs. How do I make sure the application doesn't drop out of the Angular Zone? Or determine what is causing it?
Here is my Service:
@Injectable()
export class CalendarFilterStore {
private currentState: CalendarFilter;
private state$ = new ReplaySubject<CalendarFilter>(1);
constructor() {
this.currentState = {
startDate: new Date(2016, 10, 14, 2)
};
this.state$.next(this.currentState);
}
get state(): Observable<CalendarFilter> {
console.log('calendar filter state, zone: ' + Zone.current.name);
return this.state$.asObservable();
}
Here is the ngOnInit function for the component that is using this service:
ngOnInit() {
console.log('ng init zone: ' + Zone.current.name);
this.calendarFilterStore.state
.filter(state => {
console.log('got filter state, zone: ' + Zone.current.name);
return (state.areaIDs !== undefined && state.worksiteID !== undefined && state.startDate !== undefined && state.numberOfShifts !== undefined);
})
.switchMap(state => this.getCalendar(state))
.subscribe(res => {
console.log('got calendar, dashboard-table, zone: ' + Zone.current.name);
this.loadCalendar(res);
}, error => {
console.log(error);
});
Here is the console output that shows when we drop out of the Angular Zone. It appears to happen when the Calendar filter emits it's second value.
calendar filter state, zone: angular
calendar-filter - store.ts:21 calendar filter state, zone: angular
calendar-store.ts:22 get calendar state, zone: angular
dashboard-table.component.ts:60 ng init zone: angular
calendar-filter - store.ts:21 calendar filter state, zone: angular
dashboard-table.component.ts:63 got filter state, zone: angular
dashboard-table.component.ts:63 got filter state, zone: <root>
dashboard-table.component.ts:63 got filter state, zone: <root>
calendar.service.ts:25 get calendar, zone: <root>
calendar.service.ts:31 got calendar, zone: <root>
dashboard-table.component.ts:68 got calendar, dashboard-table, zone: <root>
calendar-store.ts:27 segments returned, zone: <root>
I've narrowed it down to components that are several levels deep into my component tree. When I check Zone.current.name in their constructors they are in the root
zone and not the angular
zone. Any events they emit up remain in the root
zone, even as they propagate to the parent->service->subscriber.
Here is a component template that works and runs in the angular zone, but its child, sb-worksite-selector, runs in the root zone, as seen by the output.
<div class="dashboard-navigation" *ngIf="optionsLoaded">
<button type="button" class="btn btn-secondary" (click)="previousDay()"><i class="fa fa-backward" aria-hidden="true"></i> Previous Day</button>
<div class="inline-block">
<sb-worksite-selector [worksiteOptions]="worksiteOptions"
(onWorksiteChanged)="worksiteChanged($event)">
</sb-worksite-selector>
</div>
sb-worksite-selector
@Component({
selector: 'sb-worksite-selector',
templateUrl: 'worksite-selector.component.html',
styleUrls: ['worksite-selector.component.scss']
})
export class WorksiteSelectorComponent implements OnInit {
@Input() worksiteOptions: Array<any>;
@Output() onWorksiteChanged = new EventEmitter<number>();
constructor() {
console.log('create worksite selector, zone: ' + Zone.current.name);
}
ngOnInit() {
console.log('init worksite selector, zone: ' + Zone.current.name);
}
}
Logging for Worksite selector:
calendar filter state, zone: angular
dashboard - table.component.ts:63 got filter state, zone: angular
calendar.service.ts:25 get calendar, zone: angular
dashboard - navigation.component.ts:63 dashboard navigation got areas and worksites, zone: angular
worksite - selector.component.ts:14 create worksite selector, zone: <root>
worksite - selector.component.ts:18 init worksite selector, zone: <root>