fix(dynamic_component_loader): leave the view tree in a consistent state when hydration fails
Closes #5718
This commit is contained in:
@ -1,5 +1,12 @@
|
|||||||
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||||
import {Type, isBlank, isPresent, assertionsEnabled, print, IS_DART} from 'angular2/src/facade/lang';
|
import {
|
||||||
|
Type,
|
||||||
|
isBlank,
|
||||||
|
isPresent,
|
||||||
|
assertionsEnabled,
|
||||||
|
print,
|
||||||
|
IS_DART
|
||||||
|
} from 'angular2/src/facade/lang';
|
||||||
import {provide, Provider, Injector, OpaqueToken} from 'angular2/src/core/di';
|
import {provide, Provider, Injector, OpaqueToken} from 'angular2/src/core/di';
|
||||||
import {
|
import {
|
||||||
APP_COMPONENT_REF_PROMISE,
|
APP_COMPONENT_REF_PROMISE,
|
||||||
|
@ -169,7 +169,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
|
|||||||
// any work done in `hydrateDirectives`.
|
// any work done in `hydrateDirectives`.
|
||||||
dehydrateDirectives(destroyPipes: boolean): void {}
|
dehydrateDirectives(destroyPipes: boolean): void {}
|
||||||
|
|
||||||
hydrated(): boolean { return this.context !== null; }
|
hydrated(): boolean { return isPresent(this.context); }
|
||||||
|
|
||||||
afterContentLifecycleCallbacks(): void {
|
afterContentLifecycleCallbacks(): void {
|
||||||
this.dispatcher.notifyAfterContentChecked();
|
this.dispatcher.notifyAfterContentChecked();
|
||||||
|
@ -319,9 +319,15 @@ export class AppViewManager_ extends AppViewManager {
|
|||||||
}
|
}
|
||||||
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView,
|
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView,
|
||||||
contextBoundElementIndex, index, view);
|
contextBoundElementIndex, index, view);
|
||||||
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
|
|
||||||
contextBoundElementIndex, index,
|
try {
|
||||||
imperativelyCreatedInjector);
|
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
|
||||||
|
contextBoundElementIndex, index,
|
||||||
|
imperativelyCreatedInjector);
|
||||||
|
} catch (e) {
|
||||||
|
this._utils.detachViewInContainer(parentView, boundElementIndex, index);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
return view.ref;
|
return view.ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ import {ElementRef} from 'angular2/src/core/linker/element_ref';
|
|||||||
import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
|
import {DOCUMENT} from 'angular2/src/platform/dom/dom_tokens';
|
||||||
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
|
||||||
import {ComponentFixture_} from "angular2/src/testing/test_component_builder";
|
import {ComponentFixture_} from "angular2/src/testing/test_component_builder";
|
||||||
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
|
import {PromiseWrapper} from 'angular2/src/facade/promise';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('DynamicComponentLoader', function() {
|
describe('DynamicComponentLoader', function() {
|
||||||
@ -124,6 +126,28 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should leave the view tree in a consistent state if hydration fails',
|
||||||
|
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(loader, tcb: TestComponentBuilder, async) => {
|
||||||
|
tcb.overrideView(MyComp, new ViewMetadata({
|
||||||
|
template: '<div><location #loc></location></div>',
|
||||||
|
directives: [Location]
|
||||||
|
}))
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((tc: ComponentFixture) => {
|
||||||
|
tc.debugElement
|
||||||
|
|
||||||
|
PromiseWrapper.catchError(
|
||||||
|
loader.loadIntoLocation(DynamicallyLoadedThrows,
|
||||||
|
tc.debugElement.elementRef, 'loc'),
|
||||||
|
error => {
|
||||||
|
expect(error.message).toContain("ThrownInConstructor");
|
||||||
|
expect(() => tc.detectChanges()).not.toThrow();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should throw if the variable does not exist',
|
it('should throw if the variable does not exist',
|
||||||
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
|
inject([DynamicComponentLoader, TestComponentBuilder, AsyncTestCompleter],
|
||||||
(loader, tcb: TestComponentBuilder, async) => {
|
(loader, tcb: TestComponentBuilder, async) => {
|
||||||
@ -223,6 +247,7 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadAsRoot', () => {
|
describe('loadAsRoot', () => {
|
||||||
@ -291,6 +316,12 @@ class DynamicallyCreatedCmp implements OnDestroy {
|
|||||||
class DynamicallyLoaded {
|
class DynamicallyLoaded {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'dummy'})
|
||||||
|
@View({template: "DynamicallyLoaded;"})
|
||||||
|
class DynamicallyLoadedThrows {
|
||||||
|
constructor() { throw new BaseException("ThrownInConstructor"); }
|
||||||
|
}
|
||||||
|
|
||||||
@Component({selector: 'dummy'})
|
@Component({selector: 'dummy'})
|
||||||
@View({template: "DynamicallyLoaded2;"})
|
@View({template: "DynamicallyLoaded2;"})
|
||||||
class DynamicallyLoaded2 {
|
class DynamicallyLoaded2 {
|
||||||
|
Reference in New Issue
Block a user