fix(views): adds dehydration calls to ng-repeat removed views.
Closes #416
This commit is contained in:
parent
9957c1338e
commit
8612af9c50
@ -89,18 +89,29 @@ export class ViewPort {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(atIndex=-1): View {
|
remove(atIndex=-1) {
|
||||||
if (atIndex == -1) atIndex = this._views.length - 1;
|
if (atIndex == -1) atIndex = this._views.length - 1;
|
||||||
var removedView = this.get(atIndex);
|
var view = this.detach(atIndex);
|
||||||
|
view.dehydrate();
|
||||||
|
// view is intentionally not returned to the client.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method can be used together with insert to implement a view move, i.e.
|
||||||
|
* moving the dom nodes while the directives in the view stay intact.
|
||||||
|
*/
|
||||||
|
detach(atIndex=-1): View {
|
||||||
|
if (atIndex == -1) atIndex = this._views.length - 1;
|
||||||
|
var detachedView = this.get(atIndex);
|
||||||
ListWrapper.removeAt(this._views, atIndex);
|
ListWrapper.removeAt(this._views, atIndex);
|
||||||
if (isBlank(this._lightDom)) {
|
if (isBlank(this._lightDom)) {
|
||||||
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, removedView);
|
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
|
||||||
} else {
|
} else {
|
||||||
this._lightDom.redistribute();
|
this._lightDom.redistribute();
|
||||||
}
|
}
|
||||||
removedView.changeDetector.remove();
|
detachedView.changeDetector.remove();
|
||||||
this._unlinkElementInjectors(removedView);
|
this._unlinkElementInjectors(detachedView);
|
||||||
return removedView;
|
return detachedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentTagContainers() {
|
contentTagContainers() {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
|
||||||
import {View, ProtoView} from 'core/compiler/view';
|
import {View, ProtoView} from 'core/compiler/view';
|
||||||
import {ViewPort} from 'core/compiler/viewport';
|
import {ViewPort} from 'core/compiler/viewport';
|
||||||
|
import {proxy, IMPLEMENTS} from 'facade/lang';
|
||||||
import {DOM} from 'facade/dom';
|
import {DOM} from 'facade/dom';
|
||||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||||
import {Injector} from 'di/di';
|
import {Injector} from 'di/di';
|
||||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||||
import {ProtoChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
|
import {ProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
|
||||||
|
|
||||||
function createView(nodes) {
|
function createView(nodes) {
|
||||||
var view = new View(null, nodes, new ProtoChangeDetector(), MapWrapper.create());
|
var view = new View(null, nodes, new ProtoChangeDetector(), MapWrapper.create());
|
||||||
@ -13,6 +14,51 @@ function createView(nodes) {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
@IMPLEMENTS(ChangeDetector)
|
||||||
|
class AttachableChangeDetector {
|
||||||
|
parent;
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
remove() {
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
noSuchMethod(i) {
|
||||||
|
super.noSuchMethod(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@proxy
|
||||||
|
@IMPLEMENTS(View)
|
||||||
|
class HydrateAwareFakeView {
|
||||||
|
isHydrated: boolean;
|
||||||
|
nodes: List<Nodes>;
|
||||||
|
changeDetector: ChangeDetector;
|
||||||
|
rootElementInjectors;
|
||||||
|
constructor(isHydrated) {
|
||||||
|
this.isHydrated = isHydrated;
|
||||||
|
this.nodes = [DOM.createElement('div')];
|
||||||
|
this.rootElementInjectors = [];
|
||||||
|
this.changeDetector = new AttachableChangeDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrated() {
|
||||||
|
return this.isHydrated;
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrate(_, __, ___) {
|
||||||
|
this.isHydrated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dehydrate() {
|
||||||
|
this.isHydrated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
noSuchMethod(i) {
|
||||||
|
super.noSuchMethod(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('viewport', () => {
|
describe('viewport', () => {
|
||||||
var viewPort, parentView, protoView, dom, customViewWithOneNode,
|
var viewPort, parentView, protoView, dom, customViewWithOneNode,
|
||||||
@ -81,10 +127,9 @@ export function main() {
|
|||||||
it('should remove the last view by default', () => {
|
it('should remove the last view by default', () => {
|
||||||
viewPort.insert(customViewWithOneNode);
|
viewPort.insert(customViewWithOneNode);
|
||||||
|
|
||||||
var removedView = viewPort.remove();
|
viewPort.remove();
|
||||||
|
|
||||||
expect(textInViewPort()).toEqual('filler');
|
expect(textInViewPort()).toEqual('filler');
|
||||||
expect(removedView).toBe(customViewWithOneNode);
|
|
||||||
expect(viewPort.length).toBe(1);
|
expect(viewPort.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -92,13 +137,63 @@ export function main() {
|
|||||||
viewPort.insert(customViewWithOneNode);
|
viewPort.insert(customViewWithOneNode);
|
||||||
viewPort.insert(customViewWithTwoNodes);
|
viewPort.insert(customViewWithTwoNodes);
|
||||||
|
|
||||||
var removedView = viewPort.remove(1);
|
viewPort.remove(1);
|
||||||
expect(removedView).toBe(customViewWithOneNode);
|
|
||||||
expect(textInViewPort()).toEqual('filler one two');
|
expect(textInViewPort()).toEqual('filler one two');
|
||||||
expect(viewPort.get(1)).toBe(customViewWithTwoNodes);
|
expect(viewPort.get(1)).toBe(customViewWithTwoNodes);
|
||||||
expect(viewPort.length).toBe(2);
|
expect(viewPort.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should detach the last view by default', () => {
|
||||||
|
viewPort.insert(customViewWithOneNode);
|
||||||
|
expect(viewPort.length).toBe(2);
|
||||||
|
|
||||||
|
var detachedView = viewPort.detach();
|
||||||
|
|
||||||
|
expect(detachedView).toBe(customViewWithOneNode);
|
||||||
|
expect(textInViewPort()).toEqual('filler');
|
||||||
|
expect(viewPort.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detach the view at a given index', () => {
|
||||||
|
viewPort.insert(customViewWithOneNode);
|
||||||
|
viewPort.insert(customViewWithTwoNodes);
|
||||||
|
expect(viewPort.length).toBe(3);
|
||||||
|
|
||||||
|
var detachedView = viewPort.detach(1);
|
||||||
|
expect(detachedView).toBe(customViewWithOneNode);
|
||||||
|
expect(textInViewPort()).toEqual('filler one two');
|
||||||
|
expect(viewPort.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep views hydration state during insert', () => {
|
||||||
|
var hydratedView = new HydrateAwareFakeView(true);
|
||||||
|
var dehydratedView = new HydrateAwareFakeView(false);
|
||||||
|
viewPort.insert(hydratedView);
|
||||||
|
viewPort.insert(dehydratedView);
|
||||||
|
|
||||||
|
expect(hydratedView.hydrated()).toBe(true);
|
||||||
|
expect(dehydratedView.hydrated()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dehydrate on remove', () => {
|
||||||
|
var hydratedView = new HydrateAwareFakeView(true);
|
||||||
|
viewPort.insert(hydratedView);
|
||||||
|
viewPort.remove();
|
||||||
|
|
||||||
|
expect(hydratedView.hydrated()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep views hydration state during detach', () => {
|
||||||
|
var hydratedView = new HydrateAwareFakeView(true);
|
||||||
|
var dehydratedView = new HydrateAwareFakeView(false);
|
||||||
|
viewPort.insert(hydratedView);
|
||||||
|
viewPort.insert(dehydratedView);
|
||||||
|
|
||||||
|
expect(viewPort.detach().hydrated()).toBe(false);
|
||||||
|
expect(viewPort.detach().hydrated()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should support adding/removing views with more than one node', () => {
|
it('should support adding/removing views with more than one node', () => {
|
||||||
viewPort.insert(customViewWithTwoNodes);
|
viewPort.insert(customViewWithTwoNodes);
|
||||||
viewPort.insert(customViewWithOneNode);
|
viewPort.insert(customViewWithOneNode);
|
||||||
|
@ -59,10 +59,12 @@ export class NgRepeat extends OnChange {
|
|||||||
var movedTuples = [];
|
var movedTuples = [];
|
||||||
for (var i = tuples.length - 1; i >= 0; i--) {
|
for (var i = tuples.length - 1; i >= 0; i--) {
|
||||||
var tuple = tuples[i];
|
var tuple = tuples[i];
|
||||||
var view = viewPort.remove(tuple.record.previousIndex);
|
// separate moved views from removed views.
|
||||||
if (isPresent(tuple.record.currentIndex)) {
|
if (isPresent(tuple.record.currentIndex)) {
|
||||||
tuple.view = view;
|
tuple.view = viewPort.detach(tuple.record.previousIndex);
|
||||||
ListWrapper.push(movedTuples, tuple);
|
ListWrapper.push(movedTuples, tuple);
|
||||||
|
} else {
|
||||||
|
viewPort.remove(tuple.record.previousIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return movedTuples;
|
return movedTuples;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user