Skip to content

Commit ada1e64

Browse files
committed
feat(view): add imperative views
1 parent 817c79c commit ada1e64

28 files changed

+458
-120
lines changed

modules/angular2/angular2.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export * from './annotations';
44
export * from './directives';
55
export * from './forms';
66
export {Observable, EventEmitter} from 'angular2/src/facade/async';
7+
export * from 'angular2/src/render/api';
8+
export {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';

modules/angular2/src/core/annotations/view.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,28 @@ export class View {
7171
*/
7272
directives:any; //List<Type>;
7373

74+
/**
75+
* Specify a custom renderer for this View.
76+
* If this is set, neither `template`, `templateURL` nor `directives` are used.
77+
*/
78+
renderer:any; // string;
79+
7480
@CONST()
7581
constructor({
7682
templateUrl,
7783
template,
78-
directives
84+
directives,
85+
renderer
7986
}: {
8087
templateUrl: string,
8188
template: string,
82-
directives: List<Type>
89+
directives: List<Type>,
90+
renderer: string
8391
})
8492
{
8593
this.templateUrl = templateUrl;
8694
this.template = template;
8795
this.directives = directives;
96+
this.renderer = renderer;
8897
}
8998
}

modules/angular2/src/core/application.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function _injectorBindings(appComponentType): List<Binding> {
7575
// We need to do this here to ensure that we create Testability and
7676
// it's ready on the window for users.
7777
registry.registerApplication(appElement, testability);
78-
return dynamicComponentLoader.loadIntoNewLocation(appElement, appComponentAnnotatedType.type, injector);
78+
return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, appElement, injector);
7979
}, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken,
8080
Testability, TestabilityRegistry]),
8181

@@ -91,6 +91,7 @@ function _injectorBindings(appComponentType): List<Binding> {
9191
bind(ShadowDomStrategy).toFactory(
9292
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
9393
[StyleUrlResolver, appDocumentToken]),
94+
DirectDomRenderer,
9495
bind(Renderer).toClass(DirectDomRenderer),
9596
bind(rc.Compiler).toClass(rc.DefaultCompiler),
9697
// TODO(tbosch): We need an explicit factory here, as
@@ -105,8 +106,8 @@ function _injectorBindings(appComponentType): List<Binding> {
105106
// TODO(tbosch): We need an explicit factory here, as
106107
// we are getting errors in dart2js with mirrors...
107108
bind(ViewFactory).toFactory(
108-
(capacity, renderer, appViewHydrator) => new ViewFactory(capacity, renderer, appViewHydrator),
109-
[VIEW_POOL_CAPACITY, Renderer, AppViewHydrator]
109+
(capacity, renderer) => new ViewFactory(capacity, renderer),
110+
[VIEW_POOL_CAPACITY, Renderer]
110111
),
111112
bind(VIEW_POOL_CAPACITY).toValue(10000),
112113
AppViewHydrator,

modules/angular2/src/core/compiler/compiler.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,21 @@ export class Compiler {
114114
}
115115

116116
var template = this._templateResolver.resolve(component);
117-
var directives = ListWrapper.map(
118-
this._flattenDirectives(template),
119-
(directive) => this._bindDirective(directive)
120-
);
121-
var renderTemplate = this._buildRenderTemplate(component, template, directives);
122-
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
123-
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
124-
});
117+
if (isPresent(template.renderer)) {
118+
var directives = [];
119+
pvPromise = this._renderer.createImperativeComponentProtoView(template.renderer).then( (renderPv) => {
120+
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
121+
});
122+
} else {
123+
var directives = ListWrapper.map(
124+
this._flattenDirectives(template),
125+
(directive) => this._bindDirective(directive)
126+
);
127+
var renderTemplate = this._buildRenderTemplate(component, template, directives);
128+
pvPromise = this._renderer.compile(renderTemplate).then( (renderPv) => {
129+
return this._compileNestedProtoViews(componentBinding, renderPv, directives, true);
130+
});
131+
}
125132

126133
MapWrapper.set(this._compiling, component, pvPromise);
127134
return pvPromise;

modules/angular2/src/core/compiler/dynamic_component_loader.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,10 @@ export class DynamicComponentLoader {
7070

7171
return this._compiler.compile(type).then(componentProtoView => {
7272
var componentView = this._viewFactory.getView(componentProtoView);
73-
var hostView = location.hostView;
7473
this._viewHydrator.hydrateDynamicComponentView(
75-
hostView, location.boundElementIndex, componentView, componentBinding, injector);
74+
location, componentView, componentBinding, injector);
7675

77-
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
78-
// from the component child views
79-
// See ViewFactory.returnView
80-
// See AppViewHydrator.dehydrateDynamicComponentView
81-
var dispose = () => {throw "Not implemented";};
76+
var dispose = () => {throw new BaseException("Not implemented");};
8277
return new ComponentRef(location, location.elementInjector.getDynamicallyLoadedComponent(), componentView, dispose);
8378
});
8479
}
@@ -87,19 +82,22 @@ export class DynamicComponentLoader {
8782
* Loads a component in the element specified by elementOrSelector. The loaded component receives
8883
* injection normally as a hosted view.
8984
*/
90-
loadIntoNewLocation(elementOrSelector:any, type:Type, injector:Injector = null):Promise<ComponentRef> {
85+
loadIntoNewLocation(type:Type, parentComponentLocation:ElementRef, elementOrSelector:any,
86+
injector:Injector = null):Promise<ComponentRef> {
9187
this._assertTypeIsComponent(type);
9288

9389
return this._compiler.compileInHost(type).then(hostProtoView => {
9490
var hostView = this._viewFactory.getView(hostProtoView);
95-
this._viewHydrator.hydrateInPlaceHostView(null, elementOrSelector, hostView, injector);
91+
this._viewHydrator.hydrateInPlaceHostView(
92+
parentComponentLocation, elementOrSelector, hostView, injector
93+
);
9694

97-
// TODO(vsavkin): return a component ref that dehydrates the host view
98-
// See ViewFactory.returnView
99-
// See AppViewHydrator.dehydrateInPlaceHostView
10095
var newLocation = hostView.elementInjectors[0].getElementRef();
10196
var component = hostView.elementInjectors[0].getComponent();
102-
var dispose = () => {throw "Not implemented";};
97+
var dispose = () => {
98+
this._viewHydrator.dehydrateInPlaceHostView(parentComponentLocation, hostView);
99+
this._viewFactory.returnView(hostView);
100+
};
103101
return new ComponentRef(newLocation, component, hostView.componentChildViews[0], dispose);
104102
});
105103
}

modules/angular2/src/core/compiler/element_injector.js

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,21 @@ var _staticKeys;
2626
* @exportedAs angular2/view
2727
*/
2828
export class ElementRef {
29+
hostView:viewModule.AppView;
30+
boundElementIndex:number;
31+
injector:Injector;
2932
elementInjector:ElementInjector;
3033

31-
constructor(elementInjector:ElementInjector){
34+
constructor(elementInjector, hostView, boundElementIndex, injector){
3235
this.elementInjector = elementInjector;
33-
}
34-
35-
get hostView() {
36-
return this.elementInjector._preBuiltObjects.view;
36+
this.hostView = hostView;
37+
this.boundElementIndex = boundElementIndex;
38+
this.injector = injector;
3739
}
3840

3941
get viewContainer() {
4042
return this.hostView.getOrCreateViewContainer(this.boundElementIndex);
4143
}
42-
43-
get injector() {
44-
return this.elementInjector._lightDomAppInjector;
45-
}
46-
47-
get boundElementIndex() {
48-
return this.elementInjector._proto.index;
49-
}
5044
}
5145

5246
class StaticKeys {
@@ -673,7 +667,7 @@ export class ElementInjector extends TreeNode {
673667
}
674668

675669
getElementRef() {
676-
return new ElementRef(this);
670+
return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector);
677671
}
678672

679673
getDynamicallyLoadedComponent() {

modules/angular2/src/core/compiler/view.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export class AppView {
2525
elementInjectors:List<ElementInjector>;
2626
changeDetector:ChangeDetector;
2727
componentChildViews: List<AppView>;
28+
/// Host views that were added by an imperative view.
29+
/// This is a dynamically growing / shrinking array.
30+
imperativeHostViews: List<AppView>;
2831
viewContainers: List<ViewContainer>;
2932
preBuiltObjects: List<PreBuiltObjects>;
3033
proto: AppProtoView;
@@ -46,7 +49,7 @@ export class AppView {
4649
*/
4750
locals:Locals;
4851

49-
constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, viewHydrator:vhModule.AppViewHydrator, proto:AppProtoView, protoLocals:Map) {
52+
constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, proto:AppProtoView, protoLocals:Map) {
5053
this.render = null;
5154
this.proto = proto;
5255
this.changeDetector = null;
@@ -59,7 +62,8 @@ export class AppView {
5962
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
6063
this.renderer = renderer;
6164
this.viewFactory = viewFactory;
62-
this.viewHydrator = viewHydrator;
65+
this.viewHydrator = null;
66+
this.imperativeHostViews = [];
6367
}
6468

6569
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,
@@ -151,7 +155,7 @@ export class AppView {
151155
}
152156
var result = expr.eval(context, new Locals(this.locals, locals));
153157
if (isPresent(result)) {
154-
allowDefaultBehavior = allowDefaultBehavior && result;
158+
allowDefaultBehavior = allowDefaultBehavior && result;
155159
}
156160
});
157161
}

modules/angular2/src/core/compiler/view_factory.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
55
import {NgElement} from 'angular2/src/core/compiler/ng_element';
66
import * as viewModule from './view';
77
import {Renderer} from 'angular2/src/render/api';
8-
import {AppViewHydrator} from './view_hydrator';
98

109
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
1110
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
@@ -15,13 +14,11 @@ export class ViewFactory {
1514
_poolCapacityPerProtoView:number;
1615
_pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>;
1716
_renderer:Renderer;
18-
_viewHydrator:AppViewHydrator;
1917

20-
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer, viewHydrator:AppViewHydrator) {
18+
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer) {
2119
this._poolCapacityPerProtoView = poolCapacityPerProtoView;
2220
this._pooledViewsPerProtoView = MapWrapper.create();
2321
this._renderer = renderer;
24-
this._viewHydrator = viewHydrator;
2522
}
2623

2724
getView(protoView:viewModule.AppProtoView):viewModule.AppView {
@@ -48,7 +45,7 @@ export class ViewFactory {
4845
}
4946

5047
_createView(protoView:viewModule.AppProtoView): viewModule.AppView {
51-
var view = new viewModule.AppView(this._renderer, this, this._viewHydrator, protoView, protoView.protoLocals);
48+
var view = new viewModule.AppView(this._renderer, this, protoView, protoView.protoLocals);
5249
var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings,
5350
protoView.getVariableBindings(), protoView.getdirectiveRecords());
5451

modules/angular2/src/core/compiler/view_hydrator.js

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as viewModule from './view';
77
import {BindingPropagationConfig, Locals} from 'angular2/change_detection';
88

99
import * as renderApi from 'angular2/src/render/api';
10+
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
1011

1112
/**
1213
* A dehydrated view is a state of the view that allows it to be moved around
@@ -27,13 +28,17 @@ import * as renderApi from 'angular2/src/render/api';
2728
@Injectable()
2829
export class AppViewHydrator {
2930
_renderer:renderApi.Renderer;
31+
_viewFactory:ViewFactory;
3032

31-
constructor(renderer:renderApi.Renderer) {
33+
constructor(renderer:renderApi.Renderer, viewFactory:ViewFactory) {
3234
this._renderer = renderer;
35+
this._viewFactory = viewFactory;
3336
}
3437

35-
hydrateDynamicComponentView(hostView:viewModule.AppView, boundElementIndex:number,
38+
hydrateDynamicComponentView(location:eli.ElementRef,
3639
componentView:viewModule.AppView, componentDirective:eli.DirectiveBinding, injector:Injector) {
40+
var hostView = location.hostView;
41+
var boundElementIndex = location.boundElementIndex;
3742
var binder = hostView.proto.elementBinders[boundElementIndex];
3843
if (!binder.hasDynamicComponent()) {
3944
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
@@ -84,28 +89,37 @@ export class AppViewHydrator {
8489
// parentView.componentChildViews[boundElementIndex] = null;
8590
}
8691

87-
hydrateInPlaceHostView(parentView:viewModule.AppView, hostElementSelector, hostView:viewModule.AppView, injector:Injector) {
92+
hydrateInPlaceHostView(parentComponentLocation:eli.ElementRef,
93+
hostElementSelector, hostView:viewModule.AppView, injector:Injector) {
8894
var parentRenderViewRef = null;
89-
if (isPresent(parentView)) {
90-
// Needed for user views
91-
throw new BaseException('Not yet supported');
95+
if (isPresent(parentComponentLocation)) {
96+
var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex];
97+
parentRenderViewRef = parentView.render;
98+
parentView.changeDetector.addChild(hostView.changeDetector);
99+
ListWrapper.push(parentView.imperativeHostViews, hostView);
100+
101+
if (isBlank(injector)) {
102+
injector = parentComponentLocation.injector;
103+
}
92104
}
105+
93106
var binder = hostView.proto.elementBinders[0];
94107
var shadowDomAppInjector = this._createShadowDomAppInjector(binder.componentDirective, injector);
95108

96-
// render views
97109
var renderViewRefs = this._renderer.createInPlaceHostView(parentRenderViewRef, hostElementSelector, hostView.proto.render);
98110

99111
this._viewHydrateRecurse(
100112
hostView, renderViewRefs, 0, shadowDomAppInjector, null, new Object(), null
101113
);
102114
}
103115

104-
dehydrateInPlaceHostView(parentView:viewModule.AppView, hostView:viewModule.AppView) {
116+
dehydrateInPlaceHostView(parentComponentLocation:eli.ElementRef, hostView:viewModule.AppView) {
105117
var parentRenderViewRef = null;
106-
if (isPresent(parentView)) {
107-
// Needed for user views
108-
throw new BaseException('Not yet supported');
118+
if (isPresent(parentComponentLocation)) {
119+
var parentView = parentComponentLocation.hostView.componentChildViews[parentComponentLocation.boundElementIndex];
120+
parentRenderViewRef = parentView.render;
121+
ListWrapper.remove(parentView.imperativeHostViews, hostView);
122+
parentView.changeDetector.removeChild(hostView.changeDetector);
109123
}
110124
var render = hostView.render;
111125
this._viewDehydrateRecurse(hostView);
@@ -137,7 +151,7 @@ export class AppViewHydrator {
137151
appInjector: Injector, hostElementInjector: eli.ElementInjector,
138152
context: Object, locals:Locals):number {
139153
if (view.hydrated()) throw new BaseException('The view is already hydrated.');
140-
154+
view.viewHydrator = this;
141155
view.render = renderComponentViewRefs[renderComponentIndex++];
142156

143157
view.context = context;
@@ -215,12 +229,22 @@ export class AppViewHydrator {
215229
this._viewDehydrateRecurse(componentView);
216230
var binder = view.proto.elementBinders[i];
217231
if (binder.hasDynamicComponent()) {
218-
view.componentChildViews[i] = null;
219232
view.changeDetector.removeShadowDomChild(componentView.changeDetector);
233+
view.componentChildViews[i] = null;
234+
this._viewFactory.returnView(componentView);
220235
}
221236
}
222237
}
223238

239+
// imperativeHostViews
240+
for (var i = 0; i < view.imperativeHostViews.length; i++) {
241+
var hostView = view.imperativeHostViews[i];
242+
this._viewDehydrateRecurse(hostView);
243+
view.changeDetector.removeChild(hostView.changeDetector);
244+
this._viewFactory.returnView(hostView);
245+
}
246+
view.imperativeHostViews = [];
247+
224248
// elementInjectors
225249
for (var i = 0; i < view.elementInjectors.length; i++) {
226250
if (isPresent(view.elementInjectors[i])) {

modules/angular2/src/dom/browser_adapter.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {
151151
void appendChild(Node el, Node node) {
152152
el.append(node);
153153
}
154-
void removeChild(Element el, Node node) {
154+
void removeChild(el, Node node) {
155155
node.remove();
156156
}
157157
void replaceChild(Node el, Node newNode, Node oldNode) {

0 commit comments

Comments
 (0)