fix(view): remove dynamic components when the parent view is dehydrated
Also adds a bunch of unit tests for affected parts. Fixes #1201
This commit is contained in:
@ -49,6 +49,7 @@ export class ChangeDetector {
|
||||
addChild(cd:ChangeDetector) {}
|
||||
addShadowDomChild(cd:ChangeDetector) {}
|
||||
removeChild(cd:ChangeDetector) {}
|
||||
removeShadowDomChild(cd:ChangeDetector) {}
|
||||
remove() {}
|
||||
hydrate(context:any, locals:Locals, directives:any) {}
|
||||
dehydrate() {}
|
||||
|
@ -43,14 +43,12 @@ export class ComponentRef {
|
||||
export class DynamicComponentLoader {
|
||||
_compiler:Compiler;
|
||||
_viewFactory:ViewFactory;
|
||||
_renderer:Renderer;
|
||||
_directiveMetadataReader:DirectiveMetadataReader;
|
||||
|
||||
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
|
||||
renderer:Renderer, viewFactory:ViewFactory) {
|
||||
this._compiler = compiler;
|
||||
this._directiveMetadataReader = directiveMetadataReader;
|
||||
this._renderer = renderer;
|
||||
this._viewFactory = viewFactory
|
||||
}
|
||||
|
||||
@ -67,16 +65,13 @@ export class DynamicComponentLoader {
|
||||
|
||||
var hostEi = location.elementInjector;
|
||||
var hostView = location.hostView;
|
||||
|
||||
return this._compiler.compile(type).then(componentProtoView => {
|
||||
var component = hostEi.dynamicallyCreateComponent(type, directiveMetadata.annotation, inj);
|
||||
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, component);
|
||||
|
||||
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
|
||||
//same problem exists on the render side
|
||||
hostView.addComponentChildView(componentView);
|
||||
|
||||
this._renderer.setDynamicComponentView(hostView.render, location.boundElementIndex, componentView.render);
|
||||
hostView.setDynamicComponentChildView(location.boundElementIndex, componentView);
|
||||
|
||||
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
|
||||
// from the component child views
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {int, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {int, isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as eiModule from './element_injector';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {List, StringMap} from 'angular2/src/facade/collection';
|
||||
@ -32,4 +32,12 @@ export class ElementBinder {
|
||||
// updated later, so we are able to resolve cycles
|
||||
this.nestedProtoView = null;
|
||||
}
|
||||
|
||||
hasStaticComponent() {
|
||||
return isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
|
||||
}
|
||||
|
||||
hasDynamicComponent() {
|
||||
return isPresent(this.componentDirective) && isBlank(this.nestedProtoView);
|
||||
}
|
||||
}
|
||||
|
27
modules/angular2/src/core/compiler/view.js
vendored
27
modules/angular2/src/core/compiler/view.js
vendored
@ -143,7 +143,6 @@ export class AppView {
|
||||
}
|
||||
|
||||
var binders = this.proto.elementBinders;
|
||||
var componentChildViewIndex = 0;
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var componentDirective = binders[i].componentDirective;
|
||||
var shadowDomAppInjector = null;
|
||||
@ -176,8 +175,8 @@ export class AppView {
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) {
|
||||
renderComponentIndex = this.componentChildViews[componentChildViewIndex].internalHydrateRecurse(
|
||||
if (binders[i].hasStaticComponent()) {
|
||||
renderComponentIndex = this.componentChildViews[i].internalHydrateRecurse(
|
||||
renderComponentViewRefs,
|
||||
renderComponentIndex,
|
||||
shadowDomAppInjector,
|
||||
@ -185,7 +184,6 @@ export class AppView {
|
||||
elementInjector.getComponent(),
|
||||
null
|
||||
);
|
||||
componentChildViewIndex++;
|
||||
}
|
||||
}
|
||||
this._hydrateChangeDetector();
|
||||
@ -198,7 +196,15 @@ export class AppView {
|
||||
|
||||
// componentChildViews
|
||||
for (var i = 0; i < this.componentChildViews.length; i++) {
|
||||
this.componentChildViews[i].internalDehydrateRecurse();
|
||||
var componentView = this.componentChildViews[i];
|
||||
if (isPresent(componentView)) {
|
||||
componentView.internalDehydrateRecurse();
|
||||
var binder = this.proto.elementBinders[i];
|
||||
if (binder.hasDynamicComponent()) {
|
||||
this.componentChildViews[i] = null;
|
||||
this.changeDetector.removeShadowDomChild(componentView.changeDetector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elementInjectors
|
||||
@ -255,9 +261,16 @@ export class AppView {
|
||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||
}
|
||||
|
||||
addComponentChildView(view:AppView) {
|
||||
ListWrapper.push(this.componentChildViews, view);
|
||||
setDynamicComponentChildView(boundElementIndex, view:AppView) {
|
||||
if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) {
|
||||
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
|
||||
}
|
||||
if (isPresent(this.componentChildViews[boundElementIndex])) {
|
||||
throw new BaseException(`There already is a bound component at element ${boundElementIndex}`);
|
||||
}
|
||||
this.componentChildViews[boundElementIndex] = view;
|
||||
this.changeDetector.addShadowDomChild(view.changeDetector);
|
||||
this.proto.renderer.setDynamicComponentView(this.render, boundElementIndex, view.render);
|
||||
}
|
||||
|
||||
// implementation of EventDispatcher#dispatchEvent
|
||||
|
@ -57,7 +57,7 @@ export class ViewFactory {
|
||||
var rootElementInjectors = [];
|
||||
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
||||
var viewContainers = ListWrapper.createFixedSize(binders.length);
|
||||
var componentChildViews = [];
|
||||
var componentChildViews = ListWrapper.createFixedSize(binders.length);
|
||||
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
@ -78,13 +78,13 @@ export class ViewFactory {
|
||||
|
||||
// componentChildViews
|
||||
var bindingPropagationConfig = null;
|
||||
if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) {
|
||||
if (binder.hasStaticComponent()) {
|
||||
var childView = this._createView(binder.nestedProtoView);
|
||||
changeDetector.addChild(childView.changeDetector);
|
||||
changeDetector.addShadowDomChild(childView.changeDetector);
|
||||
|
||||
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
|
||||
|
||||
ListWrapper.push(componentChildViews, childView);
|
||||
componentChildViews[binderIdx] = childView;
|
||||
}
|
||||
|
||||
// viewContainers
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {AST} from 'angular2/change_detection';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
@ -38,6 +39,14 @@ export class ElementBinder {
|
||||
this.distanceToParent = distanceToParent;
|
||||
this.propertySetters = propertySetters;
|
||||
}
|
||||
|
||||
hasStaticComponent() {
|
||||
return isPresent(this.componentId) && isPresent(this.nestedProtoView);
|
||||
}
|
||||
|
||||
hasDynamicComponent() {
|
||||
return isPresent(this.componentId) && isBlank(this.nestedProtoView);
|
||||
}
|
||||
}
|
||||
|
||||
export class Event {
|
||||
|
5
modules/angular2/src/render/dom/view/view.js
vendored
5
modules/angular2/src/render/dom/view/view.js
vendored
@ -165,6 +165,11 @@ export class RenderView {
|
||||
var cv = this.componentChildViews[i];
|
||||
if (isPresent(cv)) {
|
||||
cv.dehydrate();
|
||||
if (this.proto.elementBinders[i].hasDynamicComponent()) {
|
||||
ViewContainer.removeViewNodes(cv);
|
||||
this.lightDoms[i] = null;
|
||||
this.componentChildViews[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,6 @@ export class ViewFactory {
|
||||
} else {
|
||||
viewRootNodes = [rootElementClone];
|
||||
}
|
||||
|
||||
var binders = protoView.elementBinders;
|
||||
var boundTextNodes = [];
|
||||
var boundElements = ListWrapper.createFixedSize(binders.length);
|
||||
@ -133,7 +132,7 @@ export class ViewFactory {
|
||||
var element = boundElements[binderIdx];
|
||||
|
||||
// static child components
|
||||
if (isPresent(binder.componentId) && isPresent(binder.nestedProtoView)) {
|
||||
if (binder.hasStaticComponent()) {
|
||||
var childView = this._createView(binder.nestedProtoView);
|
||||
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
library test_lib.test_lib;
|
||||
|
||||
import 'package:guinness/guinness.dart' as gns;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit, SpyObject;
|
||||
import 'package:unittest/unittest.dart' hide expect;
|
||||
|
||||
import 'dart:async';
|
||||
@ -149,6 +149,13 @@ xit(name, fn) {
|
||||
_it(gns.xit, name, fn);
|
||||
}
|
||||
|
||||
class SpyObject extends gns.SpyObject {
|
||||
// Need to take an optional type as this is required by
|
||||
// the JS SpyObject.
|
||||
SpyObject([type = null]) {
|
||||
}
|
||||
}
|
||||
|
||||
String elementText(n) {
|
||||
hasNodes(n) {
|
||||
var children = DOM.childNodes(n);
|
||||
|
Reference in New Issue
Block a user