Skip to content

Commit f45281a

Browse files
committed
feat(view): generalized loading of dynamic components
1 parent e9f7029 commit f45281a

File tree

19 files changed

+424
-245
lines changed

19 files changed

+424
-245
lines changed

modules/angular2/core.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ export * from './src/core/compiler/compiler';
99

1010
// TODO(tbosch): remove this once render migration is complete
1111
export * from 'angular2/src/render/dom/compiler/template_loader';
12-
export * from './src/core/compiler/private_component_loader';
13-
export * from './src/core/compiler/private_component_location';
12+
export * from './src/core/compiler/dynamic_component_loader';
13+
export {ElementRef, DirectiveRef, ComponetRef} from './src/core/compiler/element_injector';
1414
export * from './src/core/compiler/view';
1515
export * from './src/core/compiler/view_container';
1616

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ export class Component extends Directive {
590590
* })
591591
* class DynamicComp {
592592
* helloCmp:HelloCmp;
593-
* constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
593+
* constructor(loader:DynamicComponentLoader, location:PrivateComponentLocation) {
594594
* loader.load(HelloCmp, location).then((helloCmp) => {
595595
* this.helloCmp = helloCmp;
596596
* });

modules/angular2/src/core/application.js

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mappe
2424
import {UrlResolver} from 'angular2/src/services/url_resolver';
2525
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
2626
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
27-
import {Component} from 'angular2/src/core/annotations/annotations';
28-
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
27+
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
2928
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
3029
import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory';
3130
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
@@ -35,7 +34,7 @@ import * as rc from 'angular2/src/render/dom/compiler/compiler';
3534
import * as rvf from 'angular2/src/render/dom/view/view_factory';
3635

3736
import {
38-
appViewToken,
37+
appComponentRefToken,
3938
appChangeDetectorToken,
4039
appElementToken,
4140
appComponentAnnotatedTypeToken,
@@ -66,37 +65,20 @@ function _injectorBindings(appComponentType): List<Binding> {
6665
}
6766
return element;
6867
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
69-
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
70-
appComponentAnnotatedType, testability, registry, viewFactory) => {
68+
bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector, appElement,
69+
appComponentAnnotatedType, testability, registry) => {
7170

7271
// We need to do this here to ensure that we create Testability and
7372
// it's ready on the window for users.
7473
registry.registerApplication(appElement, testability);
75-
var annotation = appComponentAnnotatedType.annotation;
76-
if(!isBlank(annotation) && !(annotation instanceof Component)) {
77-
var type = appComponentAnnotatedType.type;
78-
throw new BaseException(`Only Components can be bootstrapped; ` +
79-
`Directive of ${stringify(type)} is not a Component`);
80-
}
81-
return compiler.compileRoot(
82-
appElement,
83-
appComponentAnnotatedType.type
84-
).then(
85-
(appProtoView) => {
86-
// The light Dom of the app element is not considered part of
87-
// the angular application. Thus the context and lightDomInjector are
88-
// empty.
89-
var view = viewFactory.getView(appProtoView);
90-
view.hydrate(injector, null, new Object(), null);
91-
return view;
92-
});
93-
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,
94-
Testability, TestabilityRegistry, ViewFactory]),
74+
return dynamicComponentLoader.loadIntoNewLocation(appElement, appComponentAnnotatedType.type, null, injector);
75+
}, [DynamicComponentLoader, Injector, appElementToken, appComponentAnnotatedTypeToken,
76+
Testability, TestabilityRegistry]),
9577

96-
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
97-
[appViewToken]),
98-
bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(),
99-
[appViewToken]),
78+
bind(appChangeDetectorToken).toFactory((ref) => ref.hostView.changeDetector,
79+
[appComponentRefToken]),
80+
bind(appComponentType).toFactory((ref) => ref.instance,
81+
[appComponentRefToken]),
10082
bind(LifeCycle).toFactory((exceptionHandler) => new LifeCycle(exceptionHandler, null, assertionsEnabled()),[ExceptionHandler]),
10183
bind(EventManager).toFactory((zone) => {
10284
var plugins = [new HammerGesturesPlugin(), new DomEventsPlugin()];
@@ -136,8 +118,8 @@ function _injectorBindings(appComponentType): List<Binding> {
136118
UrlResolver,
137119
StyleUrlResolver,
138120
StyleInliner,
139-
PrivateComponentLoader,
140-
Testability,
121+
DynamicComponentLoader,
122+
Testability
141123
];
142124
}
143125

@@ -260,8 +242,8 @@ function _createVmZone(givenReporter:Function): VmTurnZone {
260242
* @publicModule angular2/angular2
261243
*/
262244
export function bootstrap(appComponentType: Type,
263-
componentServiceBindings: List<Binding>=null,
264-
errorReporter: Function=null): Promise<Injector> {
245+
componentServiceBindings: List<Binding> = null,
246+
errorReporter: Function = null): Promise<Injector> {
265247
BrowserDomAdapter.makeCurrent();
266248
var bootstrapProcess = PromiseWrapper.completer();
267249

@@ -272,11 +254,11 @@ export function bootstrap(appComponentType: Type,
272254

273255
var appInjector = _createAppInjector(appComponentType, componentServiceBindings, zone);
274256

275-
PromiseWrapper.then(appInjector.asyncGet(appViewToken),
276-
(rootView) => {
257+
PromiseWrapper.then(appInjector.asyncGet(appChangeDetectorToken),
258+
(appChangeDetector) => {
277259
// retrieve life cycle: may have already been created if injected in root component
278-
var lc=appInjector.get(LifeCycle);
279-
lc.registerWith(zone, rootView.changeDetector);
260+
var lc = appInjector.get(LifeCycle);
261+
lc.registerWith(zone, appChangeDetector);
280262
lc.tick(); //the first tick that will bootstrap the app
281263

282264
bootstrapProcess.resolve(appInjector);

modules/angular2/src/core/application_tokens.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {OpaqueToken} from 'angular2/di';
22

3-
export var appViewToken = new OpaqueToken('AppView');
3+
export var appComponentRefToken = new OpaqueToken('ComponentRef');
44
export var appChangeDetectorToken = new OpaqueToken('AppChangeDetector');
55
export var appElementToken = new OpaqueToken('AppElement');
66
export var appComponentAnnotatedTypeToken = new OpaqueToken('AppComponentAnnotatedType');
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {Key, Injector, Injectable} from 'angular2/di'
2+
import {Compiler} from './compiler';
3+
import {DirectiveMetadataReader} from './directive_metadata_reader';
4+
import {Type, BaseException, stringify, isPresent} from 'angular2/src/facade/lang';
5+
import {Promise} from 'angular2/src/facade/async';
6+
import {Component} from 'angular2/src/core/annotations/annotations';
7+
import {ViewFactory} from 'angular2/src/core/compiler/view_factory';
8+
import {Renderer} from 'angular2/src/render/api';
9+
import {ElementRef, DirectiveRef, ComponentRef} from './element_injector';
10+
11+
/**
12+
* Service for dynamically loading a Component into an arbitrary position in the internal Angular
13+
* application tree.
14+
*/
15+
@Injectable()
16+
export class DynamicComponentLoader {
17+
_compiler:Compiler;
18+
_viewFactory:ViewFactory;
19+
_renderer:Renderer;
20+
_directiveMetadataReader:DirectiveMetadataReader;
21+
22+
constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader,
23+
renderer:Renderer, viewFactory:ViewFactory) {
24+
this._compiler = compiler;
25+
this._directiveMetadataReader = directiveMetadataReader;
26+
this._renderer = renderer;
27+
this._viewFactory = viewFactory
28+
}
29+
30+
/**
31+
* Loads a component into the location given by the provided ElementRef. The loaded component
32+
* receives injection as if it in the place of the provided ElementRef.
33+
*/
34+
loadIntoExistingLocation(type:Type, location:ElementRef, injector:Injector = null):Promise<ComponentRef> {
35+
this._assertTypeIsComponent(type);
36+
37+
var annotation = this._directiveMetadataReader.read(type).annotation;
38+
39+
var inj = this._componentAppInjector(location, injector, annotation.services);
40+
41+
var hostEi = location.elementInjector;
42+
var hostView = location.hostView;
43+
44+
return this._compiler.compile(type).then(componentProtoView => {
45+
var context = hostEi.dynamicallyCreateComponent(type, annotation, inj);
46+
var componentView = this._instantiateAndHydrateView(componentProtoView, injector, hostEi, context);
47+
48+
//TODO(vsavkin): do not use component child views as we need to clear the dynamically created views
49+
//same problem exists on the render side
50+
hostView.addComponentChildView(componentView);
51+
52+
this._renderer.setDynamicComponentView(hostView.render, location.boundElementIndex, componentView.render);
53+
54+
// TODO(vsavkin): return a component ref that dehydrates the component view and removes it
55+
// from the component child views
56+
return new ComponentRef(Key.get(type), hostEi, componentView);
57+
});
58+
}
59+
60+
/**
61+
* Loads a component as a child of the View given by the provided ElementRef. The loaded
62+
* component receives injection normally as a hosted view.
63+
*
64+
* TODO(vsavkin, jelbourn): remove protoViewFactory after render layer exists.
65+
*/
66+
loadIntoNewLocation(elementOrSelector:any, type:Type, location:ElementRef,
67+
injector:Injector = null):Promise<ComponentRef> {
68+
this._assertTypeIsComponent(type);
69+
70+
var inj = this._componentAppInjector(location, injector, null);
71+
72+
//TODO(tbosch) this should always be a selector
73+
return this._compiler.compileRoot(elementOrSelector, type).then(pv => {
74+
var hostView = this._instantiateAndHydrateView(pv, inj, null, new Object());
75+
76+
// TODO(vsavkin): return a component ref that dehydrates the host view
77+
return new ComponentRef(Key.get(type), hostView.elementInjectors[0], hostView.componentChildViews[0]);
78+
});
79+
}
80+
81+
_componentAppInjector(location, injector, services) {
82+
var inj = isPresent(injector) ? injector : location.elementInjector.getLightDomAppInjector();
83+
return isPresent(services) ? inj.createChild(services) : inj;
84+
}
85+
86+
_instantiateAndHydrateView(protoView, injector, hostElementInjector, context) {
87+
var componentView = this._viewFactory.getView(protoView);
88+
componentView.hydrate(injector, hostElementInjector, context, null);
89+
return componentView;
90+
}
91+
92+
/** Asserts that the type being dynamically instantiated is a Component. */
93+
_assertTypeIsComponent(type:Type) {
94+
var annotation = this._directiveMetadataReader.read(type).annotation;
95+
if (!(annotation instanceof Component)) {
96+
throw new BaseException(`Could not load '${stringify(type)}' because it is not a component.`);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)