fix(dynamic_component_loader): leave the view tree in a consistent state when hydration fails

Closes #5718
This commit is contained in:
vsavkin
2015-12-08 14:51:54 -08:00
committed by Jeremy Elbourn
parent 0d9a1de4d9
commit 0df8bc4e52
4 changed files with 49 additions and 5 deletions

View File

@ -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,

View File

@ -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();

View File

@ -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;
} }

View File

@ -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 {