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

View File

@ -27,15 +27,17 @@ export class AppViewPool {
return null;
}
returnView(view: viewModule.AppView) {
returnView(view: viewModule.AppView): boolean {
var protoView = view.proto;
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isBlank(pooledViews)) {
pooledViews = [];
MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews);
}
if (pooledViews.length < this._poolCapacityPerProtoView) {
var haveRemainingCapacity = pooledViews.length < this._poolCapacityPerProtoView;
if (haveRemainingCapacity) {
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 {AppViewManager} from 'angular2/src/core/compiler/view_manager';
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';
export function main() {
@ -37,6 +38,7 @@ export function main() {
describe('AppViewManager', () => {
var renderer;
var utils;
var viewListener;
var viewPool;
var manager;
var directiveResolver;
@ -113,8 +115,9 @@ export function main() {
directiveResolver = new DirectiveResolver();
renderer = new SpyRenderer();
utils = new SpyAppViewManagerUtils();
viewListener = new SpyAppViewListener();
viewPool = new SpyAppViewPool();
manager = new AppViewManager(viewPool, utils, renderer);
manager = new AppViewManager(viewPool, viewListener, utils, renderer);
createdViews = [];
createdRenderViews = [];
@ -149,6 +152,7 @@ export function main() {
ListWrapper.push(createdRenderViews, rv);
return rv;
});
viewPool.spy('returnView').andReturn(true);
});
describe('createDynamicComponentView', () => {
@ -165,6 +169,7 @@ export function main() {
elementRef(wrapView(hostView), 0), wrapPv(componentProtoView), null, null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(componentProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
});
it('should get the view from the pool', () => {
@ -178,6 +183,7 @@ export function main() {
.toBe(createdView);
expect(utils.spy('createView')).not.toHaveBeenCalled();
expect(renderer.spy('createView')).not.toHaveBeenCalled();
expect(viewListener.spy('viewCreated')).not.toHaveBeenCalled();
});
it('should attach the view', () => {
@ -315,6 +321,7 @@ export function main() {
wrapPv(hostProtoView), null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
});
it('should attachAndHydrate the view', () => {
@ -377,7 +384,16 @@ export function main() {
it('should return the view to the pool', () => {
manager.destroyFreeHostView(elementRef(wrapView(parentHostView), 0), wrapView(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', () => {
@ -395,6 +411,7 @@ export function main() {
expect(internalView(manager.createRootHostView(wrapPv(hostProtoView), null, null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(hostProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
});
it('should hydrate the view', () => {
@ -444,6 +461,7 @@ export function main() {
it('should destroy the render view', () => {
manager.destroyRootHostView(wrapView(hostView));
expect(renderer.spy('destroyView')).toHaveBeenCalledWith(hostRenderViewRef);
expect(viewListener.spy('viewDestroyed')).toHaveBeenCalledWith(hostView);
});
it('should not return the view to the pool', () => {
@ -473,6 +491,7 @@ export function main() {
wrapPv(childProtoView), null)))
.toBe(createdViews[0]);
expect(createdViews[0].proto).toBe(childProtoView);
expect(viewListener.spy('viewCreated')).toHaveBeenCalledWith(createdViews[0]);
});
it('should attach the view', () => {
@ -622,6 +641,13 @@ class SpyAppViewManagerUtils extends SpyObject {
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(AppViewListener)
class SpyAppViewListener extends SpyObject {
constructor() { super(AppViewListener); }
noSuchMethod(m) { return super.noSuchMethod(m) }
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {

View File

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