feat(Compiler): Multiple template per component
fixes #596 - TemplateConfig becomes Template - introduce a TemplateResolver to pick the cmp template, - @Component and @Template are disociated
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import {ABSTRACT, CONST, normalizeBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {TemplateConfig} from './template_config';
|
||||
|
||||
@ABSTRACT()
|
||||
export class Directive {
|
||||
@ -38,7 +37,6 @@ export class Directive {
|
||||
|
||||
export class Component extends Directive {
|
||||
//TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works
|
||||
template:any; //TemplateConfig;
|
||||
lightDomServices:any; //List;
|
||||
shadowDomServices:any; //List;
|
||||
componentServices:any; //List;
|
||||
@ -48,7 +46,6 @@ export class Component extends Directive {
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
template,
|
||||
lightDomServices,
|
||||
shadowDomServices,
|
||||
componentServices,
|
||||
@ -57,7 +54,6 @@ export class Component extends Directive {
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
template:TemplateConfig,
|
||||
lightDomServices:List,
|
||||
shadowDomServices:List,
|
||||
componentServices:List,
|
||||
@ -73,7 +69,6 @@ export class Component extends Directive {
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
|
||||
this.template = template;
|
||||
this.lightDomServices = lightDomServices;
|
||||
this.shadowDomServices = shadowDomServices;
|
||||
this.componentServices = componentServices;
|
||||
|
@ -1,25 +1,31 @@
|
||||
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
|
||||
export class TemplateConfig {
|
||||
export class Template {
|
||||
url:any; //string;
|
||||
inline:any; //string;
|
||||
directives:any; //List<Type>;
|
||||
formatters:any; //List<Type>;
|
||||
source:any;//List<TemplateConfig>;
|
||||
source:any;//List<Template>;
|
||||
locale:any; //string
|
||||
device:any; //string
|
||||
@CONST()
|
||||
constructor({
|
||||
url,
|
||||
inline,
|
||||
directives,
|
||||
formatters,
|
||||
source
|
||||
source,
|
||||
locale,
|
||||
device
|
||||
}: {
|
||||
url: string,
|
||||
inline: string,
|
||||
directives: List<Type>,
|
||||
formatters: List<Type>,
|
||||
source: List<TemplateConfig>
|
||||
source: List<Template>,
|
||||
locale: string,
|
||||
device: string
|
||||
})
|
||||
{
|
||||
this.url = url;
|
||||
@ -27,5 +33,7 @@ export class TemplateConfig {
|
||||
this.directives = directives;
|
||||
this.formatters = formatters;
|
||||
this.source = source;
|
||||
this.locale = locale;
|
||||
this.device = device;
|
||||
}
|
||||
}
|
4
modules/angular2/src/core/application.js
vendored
4
modules/angular2/src/core/application.js
vendored
@ -6,6 +6,7 @@ import {ProtoView} from './compiler/view';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
|
||||
import {TemplateLoader} from './compiler/template_loader';
|
||||
import {TemplateResolver} from './compiler/template_resolver';
|
||||
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
|
||||
import {DirectiveMetadata} from './compiler/directive_metadata';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
@ -28,6 +29,7 @@ var _rootBindings = [
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
TemplateLoader,
|
||||
TemplateResolver,
|
||||
DirectiveMetadataReader,
|
||||
Parser,
|
||||
Lexer,
|
||||
@ -62,7 +64,7 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
|
||||
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
|
||||
appComponentAnnotatedType, strategy, eventManager) => {
|
||||
return compiler.compile(appComponentAnnotatedType.type, null).then(
|
||||
return compiler.compile(appComponentAnnotatedType.type).then(
|
||||
(protoView) => {
|
||||
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
|
||||
appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'),
|
||||
|
90
modules/angular2/src/core/compiler/compiler.js
vendored
90
modules/angular2/src/core/compiler/compiler.js
vendored
@ -11,8 +11,10 @@ import {CompilePipeline} from './pipeline/compile_pipeline';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {createDefaultSteps} from './pipeline/default_steps';
|
||||
import {TemplateLoader} from './template_loader';
|
||||
import {TemplateResolver} from './template_resolver';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {Component} from '../annotations/annotations';
|
||||
import {Template} from '../annotations/template';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {CompileStep} from './pipeline/compile_step';
|
||||
@ -55,13 +57,15 @@ export class Compiler {
|
||||
_compiling:Map<Type, Promise>;
|
||||
_shadowDomStrategy: ShadowDomStrategy;
|
||||
_shadowDomDirectives: List<DirectiveMetadata>;
|
||||
_templateResolver: TemplateResolver;
|
||||
|
||||
constructor(changeDetection:ChangeDetection,
|
||||
templateLoader:TemplateLoader,
|
||||
reader: DirectiveMetadataReader,
|
||||
parser:Parser,
|
||||
cache:CompilerCache,
|
||||
shadowDomStrategy: ShadowDomStrategy) {
|
||||
shadowDomStrategy: ShadowDomStrategy,
|
||||
templateResolver: TemplateResolver) {
|
||||
this._changeDetection = changeDetection;
|
||||
this._reader = reader;
|
||||
this._parser = parser;
|
||||
@ -74,32 +78,38 @@ export class Compiler {
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
|
||||
}
|
||||
this._templateResolver = templateResolver;
|
||||
}
|
||||
|
||||
createSteps(component:DirectiveMetadata):List<CompileStep> {
|
||||
var directives = []
|
||||
var cmpDirectives = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
|
||||
directives = ListWrapper.concat(directives, cmpDirectives);
|
||||
directives = ListWrapper.concat(directives, this._shadowDomDirectives);
|
||||
return createDefaultSteps(this._changeDetection, this._parser, component, directives,
|
||||
createSteps(component:Type, template: Template):List<CompileStep> {
|
||||
// Merge directive metadata (from the template and from the shadow dom strategy)
|
||||
var dirMetadata = [];
|
||||
var tplMetadata = ListWrapper.map(this._flattenDirectives(template),
|
||||
(d) => this._reader.read(d));
|
||||
dirMetadata = ListWrapper.concat(dirMetadata, tplMetadata);
|
||||
dirMetadata = ListWrapper.concat(dirMetadata, this._shadowDomDirectives);
|
||||
|
||||
var cmpMetadata = this._reader.read(component);
|
||||
|
||||
return createDefaultSteps(this._changeDetection, this._parser, cmpMetadata, dirMetadata,
|
||||
this._shadowDomStrategy);
|
||||
}
|
||||
|
||||
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
||||
var protoView = this._compile(this._reader.read(component), templateRoot);
|
||||
compile(component: Type):Promise<ProtoView> {
|
||||
var protoView = this._compile(component);
|
||||
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
|
||||
}
|
||||
|
||||
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
||||
_compile(cmpMetadata: DirectiveMetadata, templateRoot:Element = null) {
|
||||
var protoView = this._compilerCache.get(cmpMetadata.type);
|
||||
_compile(component: Type) {
|
||||
var protoView = this._compilerCache.get(component);
|
||||
if (isPresent(protoView)) {
|
||||
// The component has already been compiled into a ProtoView,
|
||||
// returns a resolved Promise.
|
||||
return protoView;
|
||||
}
|
||||
|
||||
var pvPromise = MapWrapper.get(this._compiling, cmpMetadata.type);
|
||||
var pvPromise = MapWrapper.get(this._compiling, component);
|
||||
if (isPresent(pvPromise)) {
|
||||
// The component is already being compiled, attach to the existing Promise
|
||||
// instead of re-compiling the component.
|
||||
@ -107,30 +117,32 @@ export class Compiler {
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
var template = isBlank(templateRoot) ? this._templateLoader.load(cmpMetadata) : templateRoot;
|
||||
var template = this._templateResolver.resolve(component);
|
||||
|
||||
if (PromiseWrapper.isPromise(template)) {
|
||||
pvPromise = PromiseWrapper.then(template,
|
||||
(el) => this._compileTemplate(el, cmpMetadata),
|
||||
(_) => { throw new BaseException(`Failed to load the template for ${stringify(cmpMetadata.type)}`); }
|
||||
var tplElement = this._templateLoader.load(template);
|
||||
|
||||
if (PromiseWrapper.isPromise(tplElement)) {
|
||||
pvPromise = PromiseWrapper.then(tplElement,
|
||||
(el) => this._compileTemplate(template, el, component),
|
||||
(_) => { throw new BaseException(`Failed to load the template for ${stringify(component)}`); }
|
||||
);
|
||||
MapWrapper.set(this._compiling, cmpMetadata.type, pvPromise);
|
||||
MapWrapper.set(this._compiling, component, pvPromise);
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
return this._compileTemplate(template, cmpMetadata);
|
||||
return this._compileTemplate(template, tplElement, component);
|
||||
}
|
||||
|
||||
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
||||
_compileTemplate(template: Element, cmpMetadata) {
|
||||
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
|
||||
var compileElements = pipeline.process(template);
|
||||
_compileTemplate(template: Template, tplElement: Element, component: Type) {
|
||||
var pipeline = new CompilePipeline(this.createSteps(component, template));
|
||||
var compileElements = pipeline.process(tplElement);
|
||||
var protoView = compileElements[0].inheritedProtoView;
|
||||
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(cmpMetadata.type, protoView);
|
||||
MapWrapper.delete(this._compiling, cmpMetadata.type);
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
|
||||
// Compile all the components from the template
|
||||
var nestedPVPromises = [];
|
||||
@ -146,7 +158,7 @@ export class Compiler {
|
||||
// The promise will resolved after nested ProtoViews are compiled.
|
||||
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
|
||||
(_) => protoView,
|
||||
(e) => { throw new BaseException(`${e.message} -> Failed to compile ${stringify(cmpMetadata.type)}`); }
|
||||
(e) => { throw new BaseException(`${e.message} -> Failed to compile ${stringify(component)}`); }
|
||||
);
|
||||
}
|
||||
|
||||
@ -154,9 +166,8 @@ export class Compiler {
|
||||
return protoView;
|
||||
}
|
||||
|
||||
_compileNestedProtoView(ce: CompileElement, promises: List<Promise>)
|
||||
{
|
||||
var protoView = this._compile(ce.componentDirective);
|
||||
_compileNestedProtoView(ce: CompileElement, promises: List<Promise>) {
|
||||
var protoView = this._compile(ce.componentDirective.type);
|
||||
|
||||
if (PromiseWrapper.isPromise(protoView)) {
|
||||
ListWrapper.push(promises, protoView);
|
||||
@ -167,4 +178,27 @@ export class Compiler {
|
||||
ce.inheritedElementBinder.nestedProtoView = protoView;
|
||||
}
|
||||
}
|
||||
|
||||
_flattenDirectives(template: Template):List<Type> {
|
||||
if (isBlank(template.directives)) return [];
|
||||
|
||||
var directives = [];
|
||||
this._flattenList(template.directives, directives);
|
||||
|
||||
return directives;
|
||||
}
|
||||
|
||||
_flattenList(tree:List<any>, out:List<Type>) {
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = tree[i];
|
||||
if (ListWrapper.isList(item)) {
|
||||
this._flattenList(item, out);
|
||||
} else {
|
||||
ListWrapper.push(out, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
import {Directive} from 'angular2/src/core/annotations/annotations'
|
||||
import {List} from 'angular2/src/facade/collection'
|
||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Combination of a type with the Directive annotation
|
||||
@ -9,13 +7,9 @@ import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'
|
||||
export class DirectiveMetadata {
|
||||
type:Type;
|
||||
annotation:Directive;
|
||||
componentDirectives:List<Type>;
|
||||
|
||||
constructor(type:Type,
|
||||
annotation:Directive,
|
||||
componentDirectives:List<Type>) {
|
||||
constructor(type:Type, annotation:Directive) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
this.componentDirectives = componentDirectives;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Directive, Component} from '../annotations/annotations';
|
||||
import {Directive} from '../annotations/annotations';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {ShadowDom, ShadowDomStrategy, ShadowDomNative} from './shadow_dom_strategy';
|
||||
|
||||
export class DirectiveMetadataReader {
|
||||
read(type:Type):DirectiveMetadata {
|
||||
@ -12,39 +10,12 @@ export class DirectiveMetadataReader {
|
||||
for (var i=0; i<annotations.length; i++) {
|
||||
var annotation = annotations[i];
|
||||
|
||||
if (annotation instanceof Component) {
|
||||
return new DirectiveMetadata(
|
||||
type,
|
||||
annotation,
|
||||
this.componentDirectivesMetadata(annotation)
|
||||
);
|
||||
}
|
||||
|
||||
if (annotation instanceof Directive) {
|
||||
return new DirectiveMetadata(type, annotation, null);
|
||||
return new DirectiveMetadata(type, annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
|
||||
}
|
||||
|
||||
componentDirectivesMetadata(annotation:Component):List<Type> {
|
||||
var template = annotation.template;
|
||||
var result:List<Type> = ListWrapper.create();
|
||||
if (isPresent(template) && isPresent(template.directives)) {
|
||||
this._buildList(result, template.directives);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_buildList(out:List<Type>, tree:List<any>) {
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = tree[i];
|
||||
if (ListWrapper.isList(item)) {
|
||||
this._buildList(out, item);
|
||||
} else {
|
||||
ListWrapper.push(out, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {TemplateElement, DOM, Element} from 'angular2/src/facade/dom';
|
||||
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
|
||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||
|
||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||
|
||||
import {XHR} from './xhr/xhr';
|
||||
|
||||
import {Template} from 'angular2/src/core/annotations/template';
|
||||
|
||||
/**
|
||||
* Strategy to load component templates.
|
||||
*/
|
||||
@ -23,16 +20,13 @@ export class TemplateLoader {
|
||||
}
|
||||
|
||||
// TODO(vicb): union type: return an Element or a Promise<Element>
|
||||
load(cmpMetadata: DirectiveMetadata) {
|
||||
var annotation:Component = cmpMetadata.annotation;
|
||||
var tplConfig:TemplateConfig = annotation.template;
|
||||
|
||||
if (isPresent(tplConfig.inline)) {
|
||||
return DOM.createTemplate(tplConfig.inline);
|
||||
load(template: Template) {
|
||||
if (isPresent(template.inline)) {
|
||||
return DOM.createTemplate(template.inline);
|
||||
}
|
||||
|
||||
if (isPresent(tplConfig.url)) {
|
||||
var url = tplConfig.url;
|
||||
if (isPresent(template.url)) {
|
||||
var url = template.url;
|
||||
var promise = StringMapWrapper.get(this._cache, url);
|
||||
|
||||
if (isBlank(promise)) {
|
||||
@ -46,6 +40,6 @@ export class TemplateLoader {
|
||||
return promise;
|
||||
}
|
||||
|
||||
throw new BaseException(`No template configured for component ${stringify(cmpMetadata.type)}`);
|
||||
throw new BaseException(`Templates should have either their url or inline property set`);
|
||||
}
|
||||
}
|
||||
|
38
modules/angular2/src/core/compiler/template_resolver.js
vendored
Normal file
38
modules/angular2/src/core/compiler/template_resolver.js
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
import {Template} from 'angular2/src/core/annotations/template';
|
||||
|
||||
import {Type, stringify, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
|
||||
export class TemplateResolver {
|
||||
_cache: Map;
|
||||
|
||||
constructor() {
|
||||
this._cache = MapWrapper.create();
|
||||
}
|
||||
|
||||
resolve(component: Type): Template {
|
||||
var template = MapWrapper.get(this._cache, component);
|
||||
|
||||
if (isBlank(template)) {
|
||||
template = this._resolve(component);
|
||||
MapWrapper.set(this._cache, component, template);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
_resolve(component: Type) {
|
||||
var annotations = reflector.annotations(component);
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var annotation = annotations[i];
|
||||
if (annotation instanceof Template) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BaseException(`No template found for ${stringify(component)}`);
|
||||
}
|
||||
}
|
8
modules/angular2/src/forms/directives.js
vendored
8
modules/angular2/src/forms/directives.js
vendored
@ -1,4 +1,4 @@
|
||||
import {TemplateConfig, Component, Decorator, NgElement, Ancestor, onChange} from 'angular2/core';
|
||||
import {Template, Component, Decorator, NgElement, Ancestor, onChange} from 'angular2/core';
|
||||
import {DOM} from 'angular2/src/facade/dom';
|
||||
import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
@ -164,11 +164,9 @@ export class ControlGroupDirective extends ControlGroupDirectiveBase {
|
||||
selector: '[new-control-group]',
|
||||
bind: {
|
||||
'new-control-group' : 'initData'
|
||||
},
|
||||
template: new TemplateConfig({
|
||||
inline: '<content>'
|
||||
})
|
||||
}
|
||||
})
|
||||
@Template({inline: '<content>'})
|
||||
export class NewControlGroupDirective extends ControlGroupDirectiveBase {
|
||||
_initData:any;
|
||||
_controlGroup:ControlGroup;
|
||||
|
Reference in New Issue
Block a user