Skip to content

Commit

Permalink
fix(views): remove dynamic component views, free host views, free emb…
Browse files Browse the repository at this point in the history
…edded views

Closes angular#2472
Closes angular#2339

BREAKING CHANGE
- `DynamicComponentLoader.loadInto{Existing,New}Location` is removed
  * use `DynamicComponentLoader.loadNextToLocation` instead
- `DynamicComponentLoader.loadNextToExistingLocation` was renamed into
  `DynamicComponentLoader.loadNextToLocation`
- `AppViewManager.{create,destroy}Free{Host,Embedded}View` was removed
  * use `AppViewManager.createViewInContainer` and then move the view nodes
    manually around via `DomRenderer.getRootNodes()`
  • Loading branch information
tbosch committed Jun 16, 2015
1 parent 598a75e commit 613ef87
Show file tree
Hide file tree
Showing 21 changed files with 240 additions and 1,015 deletions.
48 changes: 0 additions & 48 deletions modules/angular2/src/core/annotations_impl/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,54 +848,6 @@ export interface ComponentArgs {
* ```
*
*
* Dynamically loading a component at runtime:
*
* Regular Angular components are statically resolved. Dynamic components allows to resolve a
* component at runtime
* instead by providing a placeholder into which a regular Angular component can be dynamically
* loaded. Once loaded,
* the dynamically-loaded component becomes permanent and cannot be changed.
* Dynamic components are declared just like components, but without a `@View` annotation.
*
*
* ## Example
*
* Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic
* component
* `DynamicComp` requests loading of the `HelloCmp` component.
*
* There is nothing special about `HelloCmp`, which is a regular Angular component. It can also be
* used in other static
* locations.
*
* ```
* @Component({
* selector: 'dynamic-comp'
* })
* class DynamicComp {
* helloCmp:HelloCmp;
* constructor(loader:DynamicComponentLoader, location:ElementRef) {
* loader.load(HelloCmp, location).then((helloCmp) => {
* this.helloCmp = helloCmp;
* });
* }
* }
*
* @Component({
* selector: 'hello-cmp'
* })
* @View({
* template: "{{greeting}}"
* })
* class HelloCmp {
* greeting:string;
* constructor() {
* this.greeting = "hello";
* }
* }
* ```
*
*
* @exportedAs angular2/annotations
*/
@CONST()
Expand Down
64 changes: 37 additions & 27 deletions modules/angular2/src/core/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as renderApi from 'angular2/src/render/api';
@Injectable()
export class CompilerCache {
_cache: Map<Type, AppProtoView> = MapWrapper.create();
_hostCache: Map<Type, AppProtoView> = MapWrapper.create();

set(component: Type, protoView: AppProtoView): void {
MapWrapper.set(this._cache, component, protoView);
Expand All @@ -43,7 +44,19 @@ export class CompilerCache {
return normalizeBlank(result);
}

clear(): void { MapWrapper.clear(this._cache); }
setHost(component: Type, protoView: AppProtoView): void {
MapWrapper.set(this._hostCache, component, protoView);
}

getHost(component: Type): AppProtoView {
var result = MapWrapper.get(this._hostCache, component);
return normalizeBlank(result);
}

clear(): void {
MapWrapper.clear(this._cache);
MapWrapper.clear(this._hostCache);
}
}

/**
Expand Down Expand Up @@ -94,20 +107,19 @@ export class Compiler {
Compiler._assertTypeIsComponent(componentBinding);

var directiveMetadata = componentBinding.metadata;
return this._render.compileHost(directiveMetadata)
.then((hostRenderPv) => {
return this._compileNestedProtoViews(componentBinding, hostRenderPv, [componentBinding]);
})
.then((appProtoView) => { return new ProtoViewRef(appProtoView); });
}

compile(component: Type): Promise<ProtoViewRef> {
var componentBinding = this._bindDirective(component);
Compiler._assertTypeIsComponent(componentBinding);
var pvOrPromise = this._compile(componentBinding);
var pvPromise = isPromise(pvOrPromise) ? <Promise<AppProtoView>>pvOrPromise :
PromiseWrapper.resolve(pvOrPromise);
return pvPromise.then((appProtoView) => { return new ProtoViewRef(appProtoView); });
var hostPvPromise;
var component = <Type>componentBinding.key.token;
var hostAppProtoView = this._compilerCache.getHost(component);
if (isPresent(hostAppProtoView)) {
hostPvPromise = PromiseWrapper.resolve(hostAppProtoView);
} else {
hostPvPromise = this._render.compileHost(directiveMetadata)
.then((hostRenderPv) => {
return this._compileNestedProtoViews(componentBinding, hostRenderPv,
[componentBinding]);
});
}
return hostPvPromise.then((hostAppProtoView) => { return new ProtoViewRef(hostAppProtoView); });
}

private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
Expand All @@ -128,9 +140,6 @@ export class Compiler {
return pvPromise;
}
var template = this._templateResolver.resolve(component);
if (isBlank(template)) {
return null;
}

var directives = this._flattenDirectives(template);

Expand Down Expand Up @@ -160,16 +169,17 @@ export class Compiler {
var protoViews =
this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives);
var protoView = protoViews[0];
// TODO(tbosch): we should be caching host protoViews as well!
// -> need a separate cache for this...
if (renderPv.type === renderApi.ViewType.COMPONENT && isPresent(componentBinding)) {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
if (isPresent(componentBinding)) {
var component = componentBinding.key.token;
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
if (renderPv.type === renderApi.ViewType.COMPONENT) {
// Populate the cache before compiling the nested components,
// so that components can reference themselves in their template.
this._compilerCache.set(component, protoView);
MapWrapper.delete(this._compiling, component);
} else {
this._compilerCache.setHost(component, protoView);
}
}

var nestedPVPromises = [];
ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => {
var nestedComponent = elementBinder.componentDirective;
Expand All @@ -179,7 +189,7 @@ export class Compiler {
if (isPromise(nestedCall)) {
ListWrapper.push(nestedPVPromises,
(<Promise<AppProtoView>>nestedCall).then(elementBinderDone));
} else if (isPresent(nestedCall)) {
} else {
elementBinderDone(<AppProtoView>nestedCall);
}
});
Expand Down
72 changes: 12 additions & 60 deletions modules/angular2/src/core/compiler/dynamic_component_loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,14 @@ export class ComponentRef {
export class DynamicComponentLoader {
constructor(private _compiler: Compiler, private _viewManager: AppViewManager) {}

/**
* Loads a component into the location given by the provided ElementRef. The loaded component
* receives injection as if it in the place of the provided ElementRef.
*/
loadIntoExistingLocation(typeOrBinding, location: ElementRef,
injector: Injector = null): Promise<ComponentRef> {
var binding = this._getBinding(typeOrBinding);
return this._compiler.compile(binding.token)
.then(componentProtoViewRef => {
this._viewManager.createDynamicComponentView(location, componentProtoViewRef, binding,
injector);
var component = this._viewManager.getComponent(location);
var dispose = () => { this._viewManager.destroyDynamicComponent(location); };
return new ComponentRef(location, component, dispose);
});
}

/**
* Loads a root component that is placed at the first element that matches the
* component's selector.
* The loaded component receives injection normally as a hosted view.
*/
loadAsRoot(typeOrBinding, overrideSelector: string = null,
loadAsRoot(typeOrBinding: Type | Binding, overrideSelector: string = null,
injector: Injector = null): Promise<ComponentRef> {
return this._compiler.compileInHost(this._getBinding(typeOrBinding))
return this._compiler.compileInHost(typeOrBinding)
.then(hostProtoViewRef => {
var hostViewRef =
this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector);
Expand All @@ -62,55 +45,24 @@ export class DynamicComponentLoader {
}

/**
* Loads a component into a free host view that is not yet attached to
* a parent on the render side, although it is attached to a parent in the injector hierarchy.
* The loaded component receives injection normally as a hosted view.
* Loads a component next to the provided ElementRef. The loaded component receives
* injection normally as a hosted view.
*/
loadIntoNewLocation(typeOrBinding, parentComponentLocation: ElementRef,
injector: Injector = null): Promise<ComponentRef> {
return this._compiler.compileInHost(this._getBinding(typeOrBinding))
loadNextToLocation(typeOrBinding: Type | Binding, location: ElementRef,
injector: Injector = null): Promise<ComponentRef> {
return this._compiler.compileInHost(typeOrBinding)
.then(hostProtoViewRef => {
var hostViewRef = this._viewManager.createFreeHostView(parentComponentLocation,
hostProtoViewRef, injector);
var viewContainer = this._viewManager.getViewContainer(location);
var hostViewRef =
viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector);
var newLocation = new ElementRef(hostViewRef, 0);
var component = this._viewManager.getComponent(newLocation);

var dispose = () => {
this._viewManager.destroyFreeHostView(parentComponentLocation, hostViewRef);
var index = viewContainer.indexOf(hostViewRef);
viewContainer.remove(index);
};
return new ComponentRef(newLocation, component, dispose);
});
}

/**
* Loads a component next to the provided ElementRef. The loaded component receives
* injection normally as a hosted view.
*/
loadNextToExistingLocation(typeOrBinding, location: ElementRef,
injector: Injector = null): Promise<ComponentRef> {
var binding = this._getBinding(typeOrBinding);
return this._compiler.compileInHost(binding).then(hostProtoViewRef => {
var viewContainer = this._viewManager.getViewContainer(location);
var hostViewRef =
viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector);
var newLocation = new ElementRef(hostViewRef, 0);
var component = this._viewManager.getComponent(newLocation);

var dispose = () => {
var index = viewContainer.indexOf(hostViewRef);
viewContainer.remove(index);
};
return new ComponentRef(newLocation, component, dispose);
});
}

private _getBinding(typeOrBinding): Binding {
var binding;
if (typeOrBinding instanceof Binding) {
binding = typeOrBinding;
} else {
binding = bind(typeOrBinding).toClass(typeOrBinding);
}
return binding;
}
}
4 changes: 0 additions & 4 deletions modules/angular2/src/core/compiler/element_binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ export class ElementBinder {
return isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
}

hasDynamicComponent() {
return isPresent(this.componentDirective) && isBlank(this.nestedProtoView);
}

hasEmbeddedProtoView() {
return !isPresent(this.componentDirective) && isPresent(this.nestedProtoView);
}
Expand Down
46 changes: 2 additions & 44 deletions modules/angular2/src/core/compiler/element_injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,9 +662,6 @@ export class ElementInjector extends TreeNode<ElementInjector> {
private _preBuiltObjects = null;
private _constructionCounter: number = 0;

private _dynamicallyCreatedComponent: any;
private _dynamicallyCreatedComponentBinding: DirectiveBinding;

// Queries are added during construction or linking with a new parent.
// They are never removed.
private _query0: QueryRef;
Expand Down Expand Up @@ -693,20 +690,10 @@ export class ElementInjector extends TreeNode<ElementInjector> {
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._strategy.callOnDestroy();
this.destroyDynamicComponent();
this._strategy.clearInstances();
this._constructionCounter = 0;
}

destroyDynamicComponent(): void {
if (isPresent(this._dynamicallyCreatedComponentBinding) &&
this._dynamicallyCreatedComponentBinding.callOnDestroy) {
this._dynamicallyCreatedComponent.onDestroy();
this._dynamicallyCreatedComponentBinding = null;
this._dynamicallyCreatedComponent = null;
}
}

onAllChangesDone(): void {
if (isPresent(this._query0) && this._query0.originator === this)
this._query0.list.fireCallbacks();
Expand Down Expand Up @@ -743,14 +730,6 @@ export class ElementInjector extends TreeNode<ElementInjector> {
}
}

dynamicallyCreateComponent(componentDirective: DirectiveBinding, parentInjector: Injector): any {
this._shadowDomAppInjector =
this._createShadowDomAppInjector(componentDirective, parentInjector);
this._dynamicallyCreatedComponentBinding = componentDirective;
this._dynamicallyCreatedComponent = this._new(this._dynamicallyCreatedComponentBinding);
return this._dynamicallyCreatedComponent;
}

private _checkShadowDomAppInjector(shadowDomAppInjector: Injector): void {
if (this._proto._firstBindingIsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException(
Expand All @@ -761,18 +740,7 @@ export class ElementInjector extends TreeNode<ElementInjector> {
}
}

get(token): any {
if (this._isDynamicallyLoadedComponent(token)) {
return this._dynamicallyCreatedComponent;
}

return this._getByKey(Key.get(token), self, false, null);
}

private _isDynamicallyLoadedComponent(token): boolean {
return isPresent(this._dynamicallyCreatedComponentBinding) &&
Key.get(token) === this._dynamicallyCreatedComponentBinding.key;
}
get(token): any { return this._getByKey(Key.get(token), self, false, null); }

hasDirective(type: Type): boolean {
return this._strategy.getObjByKeyId(Key.get(type).id, LIGHT_DOM_AND_SHADOW_DOM) !== _undefined;
Expand All @@ -796,17 +764,10 @@ export class ElementInjector extends TreeNode<ElementInjector> {
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
}

getDynamicallyLoadedComponent(): any { return this._dynamicallyCreatedComponent; }

directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; }

private _isComponentKey(key: Key): boolean { return this._strategy.isComponentKey(key); }

private _isDynamicallyLoadedComponentKey(key: Key): boolean {
return isPresent(this._dynamicallyCreatedComponentBinding) &&
key.id === this._dynamicallyCreatedComponentBinding.key.id;
}

_new(binding: ResolvedBinding): any {
if (this._constructionCounter++ > this._strategy.getMaxDirectives()) {
throw new CyclicDependencyError(binding.key);
Expand Down Expand Up @@ -1113,8 +1074,6 @@ export class ElementInjector extends TreeNode<ElementInjector> {

if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else if (isPresent(this._host) && this._host._isDynamicallyLoadedComponentKey(key)) {
return this._host.getDynamicallyLoadedComponent();
} else if (optional) {
return this._appInjector(requestor).getOptional(key);
} else {
Expand All @@ -1123,8 +1082,7 @@ export class ElementInjector extends TreeNode<ElementInjector> {
}

private _appInjector(requestor: Key): Injector {
if (isPresent(requestor) &&
(this._isComponentKey(requestor) || this._isDynamicallyLoadedComponentKey(requestor))) {
if (isPresent(requestor) && this._isComponentKey(requestor)) {
return this._shadowDomAppInjector;
} else {
return this._lightDomAppInjector;
Expand Down
3 changes: 1 addition & 2 deletions modules/angular2/src/core/compiler/template_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export class TemplateResolver {
return annotation;
}
}
// No annotation = dynamic component!
return null;
throw new BaseException(`No View annotation found on component ${stringify(component)}`);
}
}
Loading

0 comments on commit 613ef87

Please sign in to comment.