Skip to content

Commit b1df545

Browse files
committed
feat(compiler): attach components and project light dom during compilation.
Closes angular#2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
1 parent d449ea5 commit b1df545

82 files changed

Lines changed: 3413 additions & 2801 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

modules/angular2/angular2.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ export {
5858

5959
export * from './http';
6060
export {
61-
EventDispatcher,
61+
RenderEventDispatcher,
6262
Renderer,
6363
RenderElementRef,
6464
RenderViewRef,
65-
RenderProtoViewRef
65+
RenderProtoViewRef,
66+
RenderFragmentRef,
67+
RenderViewWithFragments
6668
} from 'angular2/src/render/api';
6769
export {
6870
DomRenderer,

modules/angular2/docs/core/02_directives.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class Pane { | Component controller class
137137
<div class="outer">
138138
<h1>{{title}}</h1>
139139
<div class="inner" [hidden]="!open">
140-
<content></content>
140+
<ng-content></ng-content>
141141
</div>
142142
</div>
143143
```

modules/angular2/src/core/compiler/compiler.ts

Lines changed: 108 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection
1515

1616
import {DirectiveResolver} from './directive_resolver';
1717

18-
import {AppProtoView} from './view';
19-
import {ElementBinder} from './element_binder';
18+
import {AppProtoView, AppProtoViewMergeMapping} from './view';
2019
import {ProtoViewRef} from './view_ref';
2120
import {DirectiveBinding} from './element_injector';
2221
import {ViewResolver} from './view_resolver';
@@ -91,6 +90,7 @@ export class Compiler {
9190
private _appUrl: string;
9291
private _render: renderApi.RenderCompiler;
9392
private _protoViewFactory: ProtoViewFactory;
93+
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = [];
9494

9595
/**
9696
* @private
@@ -137,13 +137,17 @@ export class Compiler {
137137
Compiler._assertTypeIsComponent(componentBinding);
138138

139139
var directiveMetadata = componentBinding.metadata;
140-
hostPvPromise = this._render.compileHost(directiveMetadata)
141-
.then((hostRenderPv) => {
142-
return this._compileNestedProtoViews(componentBinding, hostRenderPv,
143-
[componentBinding]);
144-
});
140+
hostPvPromise =
141+
this._render.compileHost(directiveMetadata)
142+
.then((hostRenderPv) => {
143+
var protoView = this._protoViewFactory.createAppProtoViews(
144+
componentBinding, hostRenderPv, [componentBinding]);
145+
this._compilerCache.setHost(componentType, protoView);
146+
return this._compileNestedProtoViews(hostRenderPv, protoView, componentType);
147+
});
145148
}
146-
return hostPvPromise.then((hostAppProtoView) => { return new ProtoViewRef(hostAppProtoView); });
149+
return hostPvPromise.then(hostAppProtoView => this._mergeCyclicEmbeddedProtoViews().then(
150+
_ => new ProtoViewRef(hostAppProtoView)));
147151
}
148152

149153
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
@@ -156,12 +160,12 @@ export class Compiler {
156160
return protoView;
157161
}
158162

159-
var pvPromise = this._compiling.get(component);
160-
if (isPresent(pvPromise)) {
163+
var resultPromise = this._compiling.get(component);
164+
if (isPresent(resultPromise)) {
161165
// The component is already being compiled, attach to the existing Promise
162166
// instead of re-compiling the component.
163167
// It happens when a template references a component multiple times.
164-
return pvPromise;
168+
return resultPromise;
165169
}
166170
var view = this._viewResolver.resolve(component);
167171

@@ -178,14 +182,19 @@ export class Compiler {
178182
ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
179183

180184
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
181-
pvPromise =
182-
this._render.compile(renderTemplate)
183-
.then((renderPv) => {
184-
return this._compileNestedProtoViews(componentBinding, renderPv, boundDirectives);
185-
});
186-
187-
this._compiling.set(component, pvPromise);
188-
return pvPromise;
185+
resultPromise = this._render.compile(renderTemplate)
186+
.then((renderPv) => {
187+
var protoView = this._protoViewFactory.createAppProtoViews(
188+
componentBinding, renderPv, boundDirectives);
189+
// Populate the cache before compiling the nested components,
190+
// so that components can reference themselves in their template.
191+
this._compilerCache.set(component, protoView);
192+
MapWrapper.delete(this._compiling, component);
193+
194+
return this._compileNestedProtoViews(renderPv, protoView, component);
195+
});
196+
this._compiling.set(component, resultPromise);
197+
return resultPromise;
189198
}
190199

191200
private _removeDuplicatedDirectives(directives: List<DirectiveBinding>): List<DirectiveBinding> {
@@ -194,24 +203,11 @@ export class Compiler {
194203
return MapWrapper.values(directivesMap);
195204
}
196205

197-
private _compileNestedProtoViews(componentBinding, renderPv, directives): Promise<AppProtoView>|
198-
AppProtoView {
199-
var protoViews =
200-
this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives);
201-
var protoView = protoViews[0];
202-
if (isPresent(componentBinding)) {
203-
var component = componentBinding.key.token;
204-
if (renderPv.type === renderApi.ViewType.COMPONENT) {
205-
// Populate the cache before compiling the nested components,
206-
// so that components can reference themselves in their template.
207-
this._compilerCache.set(component, protoView);
208-
MapWrapper.delete(this._compiling, component);
209-
} else {
210-
this._compilerCache.setHost(component, protoView);
211-
}
212-
}
206+
private _compileNestedProtoViews(renderProtoView: renderApi.ProtoViewDto,
207+
appProtoView: AppProtoView,
208+
componentType: Type): Promise<AppProtoView> {
213209
var nestedPVPromises = [];
214-
ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => {
210+
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => {
215211
var nestedComponent = elementBinder.componentDirective;
216212
var elementBinderDone =
217213
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
@@ -222,24 +218,85 @@ export class Compiler {
222218
elementBinderDone(<AppProtoView>nestedCall);
223219
}
224220
});
221+
return PromiseWrapper.all(nestedPVPromises)
222+
.then((_) => {
223+
var appProtoViewsToMergeInto = [];
224+
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse(
225+
renderProtoView, appProtoView, appProtoViewsToMergeInto);
226+
if (isBlank(mergeRenderProtoViews)) {
227+
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
228+
}
229+
return this._mergeProtoViews(appProtoViewsToMergeInto, mergeRenderProtoViews);
230+
});
231+
}
225232

226-
if (nestedPVPromises.length > 0) {
227-
return PromiseWrapper.all(nestedPVPromises).then((_) => protoView);
228-
} else {
229-
return protoView;
233+
private _mergeProtoViews(
234+
appProtoViewsToMergeInto: AppProtoView[],
235+
mergeRenderProtoViews:
236+
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> {
237+
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews)
238+
.then((mergeResults: List<renderApi.RenderProtoViewMergeMapping>) => {
239+
// Note: We don't need to check for nulls here as we filtered them out before!
240+
// (in RenderCompiler.mergeProtoViewsRecursively and
241+
// _collectMergeRenderProtoViewsRecurse).
242+
for (var i = 0; i < mergeResults.length; i++) {
243+
appProtoViewsToMergeInto[i].mergeMapping =
244+
new AppProtoViewMergeMapping(mergeResults[i]);
245+
}
246+
return appProtoViewsToMergeInto[0];
247+
});
248+
}
249+
250+
private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) {
251+
appProtoView.elementBinders.forEach((elementBinder) => {
252+
if (isPresent(elementBinder.componentDirective)) {
253+
callback(appProtoView, elementBinder);
254+
} else if (isPresent(elementBinder.nestedProtoView)) {
255+
this._loopComponentElementBinders(elementBinder.nestedProtoView, callback);
256+
}
257+
});
258+
}
259+
260+
private _collectMergeRenderProtoViewsRecurse(
261+
renderProtoView: renderApi.ProtoViewDto, appProtoView: AppProtoView,
262+
targetAppProtoViews: AppProtoView[]): List<renderApi.RenderProtoViewRef | List<any>> {
263+
targetAppProtoViews.push(appProtoView);
264+
var result = [renderProtoView.render];
265+
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
266+
var binder = appProtoView.elementBinders[i];
267+
if (binder.hasStaticComponent()) {
268+
if (isBlank(binder.nestedProtoView.mergeMapping)) {
269+
// cycle via an embedded ProtoView. store the AppProtoView and ProtoViewDto for later.
270+
this._unmergedCyclicEmbeddedProtoViews.push(
271+
new RecursiveEmbeddedProtoView(appProtoView, renderProtoView));
272+
return null;
273+
}
274+
result.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
275+
} else if (binder.hasEmbeddedProtoView()) {
276+
result.push(this._collectMergeRenderProtoViewsRecurse(
277+
renderProtoView.elementBinders[i].nestedProtoView, binder.nestedProtoView,
278+
targetAppProtoViews));
279+
}
230280
}
281+
return result;
231282
}
232283

233-
private _collectComponentElementBinders(protoViews: List<AppProtoView>): List<ElementBinder> {
234-
var componentElementBinders = [];
235-
ListWrapper.forEach(protoViews, (protoView) => {
236-
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
237-
if (isPresent(elementBinder.componentDirective)) {
238-
componentElementBinders.push(elementBinder);
284+
private _mergeCyclicEmbeddedProtoViews() {
285+
var pvs = this._unmergedCyclicEmbeddedProtoViews;
286+
this._unmergedCyclicEmbeddedProtoViews = [];
287+
var promises = pvs.map(entry => {
288+
var appProtoView = entry.appProtoView;
289+
var mergeRenderProtoViews = [entry.renderProtoView.render];
290+
appProtoView.elementBinders.forEach((binder) => {
291+
if (binder.hasStaticComponent()) {
292+
mergeRenderProtoViews.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
293+
} else if (binder.hasEmbeddedProtoView()) {
294+
mergeRenderProtoViews.push(null);
239295
}
240296
});
297+
return this._mergeProtoViews([appProtoView], mergeRenderProtoViews);
241298
});
242-
return componentElementBinders;
299+
return PromiseWrapper.all(promises);
243300
}
244301

245302
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
@@ -299,3 +356,7 @@ export class Compiler {
299356
}
300357
}
301358
}
359+
360+
class RecursiveEmbeddedProtoView {
361+
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
362+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ export class DirectiveBinding extends ResolvedBinding {
278278
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
279279
export class PreBuiltObjects {
280280
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
281-
public protoView: viewModule.AppProtoView) {}
281+
public elementRef: ElementRef, public protoView: viewModule.AppProtoView) {}
282282
}
283283

284284
export class EventEmitterAccessor {
@@ -575,7 +575,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
575575

576576
getComponent(): any { return this._strategy.getComponent(); }
577577

578-
getElementRef(): ElementRef { return this._preBuiltObjects.view.elementRefs[this._proto.index]; }
578+
getElementRef(): ElementRef { return this._preBuiltObjects.elementRef; }
579579

580580
getViewContainerRef(): ViewContainerRef {
581581
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
@@ -606,7 +606,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
606606
// We provide the component's view change detector to components and
607607
// the surrounding component's change detector to directives.
608608
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
609-
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
609+
var componentView = this._preBuiltObjects.view.getNestedView(
610+
this._preBuiltObjects.elementRef.boundElementIndex);
610611
return componentView.changeDetector.ref;
611612
} else {
612613
return this._preBuiltObjects.view.changeDetector.ref;

modules/angular2/src/core/compiler/element_ref.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {BaseException} from 'angular2/src/facade/lang';
1+
import {BaseException, isPresent} from 'angular2/src/facade/lang';
22
import {ViewRef} from './view_ref';
33
import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/render/api';
44

@@ -24,9 +24,18 @@ export class ElementRef implements RenderElementRef {
2424
*/
2525
boundElementIndex: number;
2626

27-
constructor(parentView: ViewRef, boundElementIndex: number, private _renderer: Renderer) {
27+
/**
28+
* Index of the element inside the {@link RenderViewRef}.
29+
*
30+
* This is used internally by the Angular framework to locate elements.
31+
*/
32+
renderBoundElementIndex: number;
33+
34+
constructor(parentView: ViewRef, boundElementIndex: number, renderBoundElementIndex: number,
35+
private _renderer: Renderer) {
2836
this.parentView = parentView;
2937
this.boundElementIndex = boundElementIndex;
38+
this.renderBoundElementIndex = renderBoundElementIndex;
3039
}
3140

3241
/**

0 commit comments

Comments
 (0)