fix(core): don’t stop change detection because of errors
- prevents unsubscribing from the zone on error - prevents unsubscribing from directive `EventEmitter`s on error - prevents detaching views in dev mode if there on error - ensures that `ngOnInit` is only called 1x (also in prod mode) Fixes #9531 Fixes #2413 Fixes #15925
This commit is contained in:

committed by
Matias Niemelä

parent
ac220fc2bb
commit
e263e19a2a
@ -560,6 +560,9 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
if (this._enforceNoNewChanges) {
|
||||
this._views.forEach((view) => view.checkNoChanges());
|
||||
}
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
|
||||
this._exceptionHandler.handleError(e);
|
||||
} finally {
|
||||
this._runningTick = false;
|
||||
wtfLeave(scope);
|
||||
|
@ -189,7 +189,14 @@ export function listenToElementOutputs(view: ViewData, compView: ViewData, def:
|
||||
}
|
||||
|
||||
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => dispatchEvent(view, index, eventName, event);
|
||||
return (event: any) => {
|
||||
try {
|
||||
return dispatchEvent(view, index, eventName, event);
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow, to keep in sync with directive events.
|
||||
view.root.errorHandler.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,7 +148,14 @@ export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
||||
}
|
||||
|
||||
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
||||
return (event: any) => dispatchEvent(view, index, eventName, event);
|
||||
return (event: any) => {
|
||||
try {
|
||||
return dispatchEvent(view, index, eventName, event);
|
||||
} catch (e) {
|
||||
// Attention: Don't rethrow, as it would cancel Observable subscriptions!
|
||||
view.root.errorHandler.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkAndUpdateDirectiveInline(
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
|
||||
import {Sanitizer} from '../security';
|
||||
@ -104,11 +105,12 @@ function createRootData(
|
||||
elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2,
|
||||
projectableNodes: any[][], rootSelectorOrNode: any): RootData {
|
||||
const sanitizer = ngModule.injector.get(Sanitizer);
|
||||
const errorHandler = ngModule.injector.get(ErrorHandler);
|
||||
const renderer = rendererFactory.createRenderer(null, null);
|
||||
return {
|
||||
ngModule,
|
||||
injector: elInjector, projectableNodes,
|
||||
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
|
||||
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler
|
||||
};
|
||||
}
|
||||
|
||||
@ -439,7 +441,6 @@ function callWithDebugContext(action: DebugAction, fn: any, self: any, args: any
|
||||
if (isViewDebugError(e) || !_currentView) {
|
||||
throw e;
|
||||
}
|
||||
_currentView.state |= ViewState.Errored;
|
||||
throw viewWrappedDebugError(e, getCurrentDebugContext() !);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
@ -323,13 +324,14 @@ export interface ViewData {
|
||||
* Bitmask of states
|
||||
*/
|
||||
export const enum ViewState {
|
||||
FirstCheck = 1 << 0,
|
||||
Attached = 1 << 1,
|
||||
ChecksEnabled = 1 << 2,
|
||||
Errored = 1 << 3,
|
||||
BeforeFirstCheck = 1 << 0,
|
||||
FirstCheck = 1 << 1,
|
||||
Attached = 1 << 2,
|
||||
ChecksEnabled = 1 << 3,
|
||||
Destroyed = 1 << 4,
|
||||
|
||||
CatDetectChanges = Attached | ChecksEnabled,
|
||||
CatInit = BeforeFirstCheck | CatDetectChanges
|
||||
}
|
||||
|
||||
export interface DisposableFn { (): void; }
|
||||
@ -432,6 +434,7 @@ export interface RootData {
|
||||
selectorOrNode: any;
|
||||
renderer: Renderer2;
|
||||
rendererFactory: RendererFactory2;
|
||||
errorHandler: ErrorHandler;
|
||||
sanitizer: Sanitizer;
|
||||
}
|
||||
|
||||
|
@ -100,10 +100,10 @@ export function checkAndUpdateBinding(
|
||||
export function checkBindingNoChanges(
|
||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||
if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
|
||||
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
Services.createDebugContext(view, def.index), oldValue, value,
|
||||
(view.state & ViewState.FirstCheck) !== 0);
|
||||
(view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ function createView(
|
||||
viewContainerParent: null, parentNodeDef,
|
||||
context: null,
|
||||
component: null, nodes,
|
||||
state: ViewState.FirstCheck | ViewState.CatDetectChanges, root, renderer,
|
||||
state: ViewState.CatInit, root, renderer,
|
||||
oldValues: new Array(def.bindingCount), disposables
|
||||
};
|
||||
return view;
|
||||
@ -323,6 +323,12 @@ export function checkNoChangesView(view: ViewData) {
|
||||
}
|
||||
|
||||
export function checkAndUpdateView(view: ViewData) {
|
||||
if (view.state & ViewState.BeforeFirstCheck) {
|
||||
view.state &= ~ViewState.BeforeFirstCheck;
|
||||
view.state |= ViewState.FirstCheck;
|
||||
} else {
|
||||
view.state &= ~ViewState.FirstCheck;
|
||||
}
|
||||
Services.updateDirectives(view, CheckType.CheckAndUpdate);
|
||||
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
|
||||
execQueriesAction(
|
||||
@ -345,7 +351,6 @@ export function checkAndUpdateView(view: ViewData) {
|
||||
if (view.def.flags & ViewFlags.OnPush) {
|
||||
view.state &= ~ViewState.ChecksEnabled;
|
||||
}
|
||||
view.state &= ~ViewState.FirstCheck;
|
||||
}
|
||||
|
||||
export function checkAndUpdateNode(
|
||||
@ -453,7 +458,7 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
|
||||
if (queryList.dirty) {
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
Services.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query!.id} not dirty`,
|
||||
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.FirstCheck) !== 0);
|
||||
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -543,13 +548,13 @@ function callViewAction(view: ViewData, action: ViewAction) {
|
||||
switch (action) {
|
||||
case ViewAction.CheckNoChanges:
|
||||
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
|
||||
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
|
||||
(viewState & ViewState.Destroyed) === 0) {
|
||||
checkNoChangesView(view);
|
||||
}
|
||||
break;
|
||||
case ViewAction.CheckAndUpdate:
|
||||
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges &&
|
||||
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
|
||||
(viewState & ViewState.Destroyed) === 0) {
|
||||
checkAndUpdateView(view);
|
||||
}
|
||||
break;
|
||||
|
Reference in New Issue
Block a user