feat(view): add `AppViewListener` interface

Basic functionality how element probe is hooked into
the system.
This commit is contained in:
Tobias Bosch 2015-05-28 14:54:26 -07:00
parent ffb219fb91
commit 75578f41e7
6 changed files with 61 additions and 10 deletions

View File

@ -50,6 +50,7 @@ import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/te
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool'; import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import {Renderer, RenderCompiler} from 'angular2/src/render/api'; import {Renderer, RenderCompiler} from 'angular2/src/render/api';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
@ -112,6 +113,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
bind(APP_VIEW_POOL_CAPACITY).toValue(10000), bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
AppViewManager, AppViewManager,
AppViewManagerUtils, AppViewManagerUtils,
AppViewListener,
Compiler, Compiler,
CompilerCache, CompilerCache,
TemplateResolver, TemplateResolver,

View File

@ -0,0 +1,11 @@
import {Injectable} from 'angular2/di';
import * as viewModule from './view';
/**
* Listener for view creation / destruction.
*/
@Injectable()
export class AppViewListener {
viewCreated(view: viewModule.AppView) {}
viewDestroyed(view: viewModule.AppView) {}
}

View File

@ -7,6 +7,7 @@ import {ViewContainerRef} from './view_container_ref';
import {Renderer, RenderViewRef} from 'angular2/src/render/api'; import {Renderer, RenderViewRef} from 'angular2/src/render/api';
import {AppViewManagerUtils} from './view_manager_utils'; import {AppViewManagerUtils} from './view_manager_utils';
import {AppViewPool} from './view_pool'; import {AppViewPool} from './view_pool';
import {AppViewListener} from './view_listener';
/** /**
* Entry point for creating, moving views in the view hierarchy and destroying views. * Entry point for creating, moving views in the view hierarchy and destroying views.
@ -16,13 +17,16 @@ import {AppViewPool} from './view_pool';
@Injectable() @Injectable()
export class AppViewManager { export class AppViewManager {
_viewPool: AppViewPool; _viewPool: AppViewPool;
_viewListener: AppViewListener;
_utils: AppViewManagerUtils; _utils: AppViewManagerUtils;
_renderer: Renderer; _renderer: Renderer;
constructor(viewPool: AppViewPool, utils: AppViewManagerUtils, renderer: Renderer) { constructor(viewPool: AppViewPool, viewListener: AppViewListener, utils: AppViewManagerUtils,
this._renderer = renderer; renderer: Renderer) {
this._viewPool = viewPool; this._viewPool = viewPool;
this._viewListener = viewListener;
this._utils = utils; this._utils = utils;
this._renderer = renderer;
} }
getComponentView(hostLocation: ElementRef): ViewRef { getComponentView(hostLocation: ElementRef): ViewRef {
@ -74,6 +78,7 @@ export class AppViewManager {
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer); var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
this._renderer.setEventDispatcher(hostView.render, hostView); this._renderer.setEventDispatcher(hostView.render, hostView);
this._createViewRecurse(hostView); this._createViewRecurse(hostView);
this._viewListener.viewCreated(hostView);
this._utils.hydrateRootHostView(hostView, injector); this._utils.hydrateRootHostView(hostView, injector);
this._viewHydrateRecurse(hostView); this._viewHydrateRecurse(hostView);
@ -88,6 +93,7 @@ export class AppViewManager {
// We do want to destroy the component view though. // We do want to destroy the component view though.
this._viewDehydrateRecurse(hostView, true); this._viewDehydrateRecurse(hostView, true);
this._renderer.destroyView(hostView.render); this._renderer.destroyView(hostView.render);
this._viewListener.viewDestroyed(hostView);
} }
createFreeHostView(parentComponentLocation: ElementRef, hostProtoViewRef: ProtoViewRef, createFreeHostView(parentComponentLocation: ElementRef, hostProtoViewRef: ProtoViewRef,
@ -175,6 +181,7 @@ export class AppViewManager {
this._renderer); this._renderer);
this._renderer.setEventDispatcher(view.render, view); this._renderer.setEventDispatcher(view.render, view);
this._createViewRecurse(view); this._createViewRecurse(view);
this._viewListener.viewCreated(view);
} }
return view; return view;
} }
@ -192,8 +199,11 @@ export class AppViewManager {
} }
_destroyPooledView(view: viewModule.AppView) { _destroyPooledView(view: viewModule.AppView) {
// TODO: if the pool is full, call renderer.destroyView as well! var wasReturned = this._viewPool.returnView(view);
this._viewPool.returnView(view); if (!wasReturned) {
this._renderer.destroyView(view.render);
this._viewListener.viewDestroyed(view);
}
} }
_destroyViewInContainer(parentView, boundElementIndex, atIndex: number) { _destroyViewInContainer(parentView, boundElementIndex, atIndex: number) {

View File

@ -27,15 +27,17 @@ export class AppViewPool {
return null; return null;
} }
returnView(view: viewModule.AppView) { returnView(view: viewModule.AppView): boolean {
var protoView = view.proto; var protoView = view.proto;
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView); var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isBlank(pooledViews)) { if (isBlank(pooledViews)) {
pooledViews = []; pooledViews = [];
MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews); MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews);
} }
if (pooledViews.length < this._poolCapacityPerProtoView) { var haveRemainingCapacity = pooledViews.length < this._poolCapacityPerProtoView;
if (haveRemainingCapacity) {
ListWrapper.push(pooledViews, view); ListWrapper.push(pooledViews, view);
} }
return haveRemainingCapacity;
} }
} }

View File

@ -29,6 +29,7 @@ import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {Component} from 'angular2/annotations'; import {Component} from 'angular2/annotations';
import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {AppViewPool} from 'angular2/src/core/compiler/view_pool'; import {AppViewPool} from 'angular2/src/core/compiler/view_pool';
export function main() { export function main() {
@ -37,6 +38,7 @@ export function main() {
describe('AppViewManager', () => { describe('AppViewManager', () => {
var renderer; var renderer;
var utils; var utils;
var viewListener;
var viewPool; var viewPool;
var manager; var manager;
var directiveResolver; var directiveResolver;
@ -113,8 +115,9 @@ export function main() {
directiveResolver = new DirectiveResolver(); directiveResolver = new DirectiveResolver();
renderer = new SpyRenderer(); renderer = new SpyRenderer();
utils = new SpyAppViewManagerUtils(); utils = new SpyAppViewManagerUtils();
viewListener = new SpyAppViewListener();
viewPool = new SpyAppViewPool(); viewPool = new SpyAppViewPool();
manager = new AppViewManager(viewPool, utils, renderer); manager = new AppViewManager(viewPool, viewListener, utils, renderer);
createdViews = []; createdViews = [];
createdRenderViews = []; createdRenderViews = [];
@ -149,6 +152,7 @@ export function main() {
ListWrapper.push(createdRenderViews, rv); ListWrapper.push(createdRenderViews, rv);
return rv; return rv;
}); });
viewPool.spy('returnView').andReturn(true);
}); });
describe('createDynamicComponentView', () => { describe('createDynamicComponentView', () => {
@ -165,6 +169,7 @@ export function main() {
elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null))) elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null)))
.toBe(createdViews[0]); .toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(componentProtoView); expect(createdViews[0].proto).toBe(componentProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
}); });
it('should get the view from the pool', () => { it('should get the view from the pool', () => {
@ -178,6 +183,7 @@ export function main() {
.toBe(createdView); .toBe(createdView);
expect(utils.spy('createView')).not.toHaveBeenCalled(); expect(utils.spy('createView')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled(); expect(renderer.spy('createView')).not.toHaveBeenCalled();
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
}); });
it('should attach the view', () => { it('should attach the view', () => {
@ -315,6 +321,7 @@ export function main() {
wrapPv(hostProtoView), null))) wrapPv(hostProtoView), null)))
.toBe(createdViews[0]); .toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView); expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
}); });
it('should attachAndHydrate the view', () => { it('should attachAndHydrate the view', () => {
@ -377,7 +384,16 @@ export function main() {
it('should return the view to the pool', () => { it('should return the view to the pool', () => {
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView)); manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView); expect(viewPool.spy('returnView')).toHaveBeenCalledWith(hostView);
expect(renderer.spy('destroyView')).not.toHaveBeenCalled();
}); });
it('should destroy the view if the pool is full', () => {
viewPool.spy('returnView').andReturn(false);
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(hostView));
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostView.render);
expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(hostView);
});
}); });
describe('recursively destroy inPlaceHostViews', () => { describe('recursively destroy inPlaceHostViews', () => {
@ -395,6 +411,7 @@ export function main() {
expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null))) expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null)))
.toBe(createdViews[0]); .toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView); expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
}); });
it('should hydrate the view', () => { it('should hydrate the view', () => {
@ -444,6 +461,7 @@ export function main() {
it('should destroy the render view', () => { it('should destroy the render view', () => {
manager.destroyRootHostView(wrapView(hostView)); manager.destroyRootHostView(wrapView(hostView));
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef); expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef);
expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(hostView);
}); });
it('should not return the view to the pool', () => { it('should not return the view to the pool', () => {
@ -473,6 +491,7 @@ export function main() {
wrapPv(childProtoView), null))) wrapPv(childProtoView), null)))
.toBe(createdViews[0]); .toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(childProtoView); expect(createdViews[0].proto).toBe(childProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
}); });
it('should attach the view', () => { it('should attach the view', () => {
@ -622,6 +641,13 @@ class SpyAppViewManagerUtils extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m) } noSuchMethod(m) { return super.noSuchMethod(m) }
} }
@proxy
@IMPLEMENTS(AppViewListener)
class SpyAppViewListener extends SpyObject {
constructor() { super(AppViewListener); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy @proxy
@IMPLEMENTS(ElementInjector) @IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject { class SpyElementInjector extends SpyObject {

View File

@ -58,9 +58,9 @@ export function main() {
var view1 = createView(pv); var view1 = createView(pv);
var view2 = createView(pv); var view2 = createView(pv);
var view3 = createView(pv); var view3 = createView(pv);
vf.returnView(view1); expect(vf.returnView(view1)).toBe(true);
vf.returnView(view2); expect(vf.returnView(view2)).toBe(true);
vf.returnView(view3); expect(vf.returnView(view3)).toBe(false);
expect(vf.getView(pv)).toBe(view2); expect(vf.getView(pv)).toBe(view2);
expect(vf.getView(pv)).toBe(view1); expect(vf.getView(pv)).toBe(view1);