X Tutup
Skip to content

Commit bd31b01

Browse files
committed
feat(core): add syntax sugar to make @view optional
1 parent f7b7533 commit bd31b01

File tree

7 files changed

+264
-12
lines changed

7 files changed

+264
-12
lines changed

modules/angular2/src/core/linker/view_resolver.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {Injectable} from 'angular2/src/core/di';
22
import {ViewMetadata} from '../metadata/view';
3+
import {ComponentMetadata} from '../metadata/directives';
34

4-
import {Type, stringify, isBlank} from 'angular2/src/core/facade/lang';
5+
import {Type, stringify, isBlank, isPresent} from 'angular2/src/core/facade/lang';
56
import {BaseException} from 'angular2/src/core/facade/exceptions';
67
import {Map, MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
78

@@ -24,13 +25,70 @@ export class ViewResolver {
2425
}
2526

2627
_resolve(component: Type): ViewMetadata {
27-
var annotations = reflector.annotations(component);
28-
for (var i = 0; i < annotations.length; i++) {
29-
var annotation = annotations[i];
30-
if (annotation instanceof ViewMetadata) {
31-
return annotation;
28+
var compMeta: ComponentMetadata;
29+
var viewMeta: ViewMetadata;
30+
31+
reflector.annotations(component).forEach(m => {
32+
if (m instanceof ViewMetadata) {
33+
viewMeta = m;
34+
}
35+
if (m instanceof ComponentMetadata) {
36+
compMeta = m;
37+
}
38+
});
39+
40+
if (isPresent(compMeta)) {
41+
if (isBlank(compMeta.template) && isBlank(compMeta.templateUrl) && isBlank(viewMeta)) {
42+
throw new BaseException(
43+
`Component '${stringify(component)}' must have either 'template', 'templateUrl', or '@View' set.`);
44+
45+
} else if (isPresent(compMeta.template) && isPresent(viewMeta)) {
46+
this._throwMixingViewAndComponent("template", component);
47+
48+
} else if (isPresent(compMeta.templateUrl) && isPresent(viewMeta)) {
49+
this._throwMixingViewAndComponent("templateUrl", component);
50+
51+
} else if (isPresent(compMeta.directives) && isPresent(viewMeta)) {
52+
this._throwMixingViewAndComponent("directives", component);
53+
54+
} else if (isPresent(compMeta.pipes) && isPresent(viewMeta)) {
55+
this._throwMixingViewAndComponent("pipes", component);
56+
57+
} else if (isPresent(compMeta.encapsulation) && isPresent(viewMeta)) {
58+
this._throwMixingViewAndComponent("encapsulation", component);
59+
60+
} else if (isPresent(compMeta.styles) && isPresent(viewMeta)) {
61+
this._throwMixingViewAndComponent("styles", component);
62+
63+
} else if (isPresent(compMeta.styleUrls) && isPresent(viewMeta)) {
64+
this._throwMixingViewAndComponent("styleUrls", component);
65+
66+
} else if (isPresent(viewMeta)) {
67+
return viewMeta;
68+
69+
} else {
70+
return new ViewMetadata({
71+
templateUrl: compMeta.templateUrl,
72+
template: compMeta.template,
73+
directives: compMeta.directives,
74+
pipes: compMeta.pipes,
75+
encapsulation: compMeta.encapsulation,
76+
styles: compMeta.styles,
77+
styleUrls: compMeta.styleUrls
78+
});
79+
}
80+
} else {
81+
if (isBlank(viewMeta)) {
82+
throw new BaseException(`No View decorator found on component '${stringify(component)}'`);
83+
} else {
84+
return viewMeta;
3285
}
3386
}
34-
throw new BaseException(`No View annotation found on component ${stringify(component)}`);
87+
return null;
88+
}
89+
90+
_throwMixingViewAndComponent(propertyName: string, component: Type): void {
91+
throw new BaseException(
92+
`Component '${stringify(component)}' cannot have both '${propertyName}' and '@View' set at the same time"`);
3593
}
3694
}

modules/angular2/src/core/metadata.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ class Component extends ComponentMetadata {
4545
Map<String, String> host,
4646
List bindings, String exportAs, String moduleId,
4747
Map<String, dynamic> queries,
48-
List viewBindings, ChangeDetectionStrategy changeDetection})
48+
List viewBindings, ChangeDetectionStrategy changeDetection,
49+
String templateUrl, String template, dynamic directives,
50+
dynamic pipes, ViewEncapsulation encapsulation, List<String> styles,
51+
List<String> styleUrls
52+
})
4953
: super(
5054
selector: selector,
5155
inputs: inputs,
@@ -58,7 +62,15 @@ class Component extends ComponentMetadata {
5862
moduleId: moduleId,
5963
viewBindings: viewBindings,
6064
queries: queries,
61-
changeDetection: changeDetection);
65+
changeDetection: changeDetection,
66+
templateUrl: templateUrl,
67+
template: template,
68+
directives: directives,
69+
pipes: pipes,
70+
encapsulation: encapsulation,
71+
styles: styles,
72+
styleUrls: styleUrls
73+
);
6274
}
6375

6476
/**

modules/angular2/src/core/metadata.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ export interface ComponentFactory {
228228
queries?: {[key: string]: any},
229229
viewBindings?: any[],
230230
changeDetection?: ChangeDetectionStrategy,
231+
templateUrl?: string,
232+
template?: string,
233+
styleUrls?: string[],
234+
styles?: string[],
235+
directives?: Array<Type | any[]>,
236+
pipes?: Array<Type | any[]>,
237+
encapsulation?: ViewEncapsulation
231238
}): ComponentDecorator;
232239
new (obj: {
233240
selector?: string,
@@ -242,6 +249,13 @@ export interface ComponentFactory {
242249
queries?: {[key: string]: any},
243250
viewBindings?: any[],
244251
changeDetection?: ChangeDetectionStrategy,
252+
templateUrl?: string,
253+
template?: string,
254+
styleUrls?: string[],
255+
styles?: string[],
256+
directives?: Array<Type | any[]>,
257+
pipes?: Array<Type | any[]>,
258+
encapsulation?: ViewEncapsulation
245259
}): ComponentMetadata;
246260
}
247261

modules/angular2/src/core/metadata/directives.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {isPresent, CONST, CONST_EXPR, Type} from 'angular2/src/core/facade/lang';
22
import {InjectableMetadata} from 'angular2/src/core/di/metadata';
33
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
4+
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
45

56
/**
67
* Directives allow you to attach behavior to elements in the DOM.
@@ -876,8 +877,23 @@ export class ComponentMetadata extends DirectiveMetadata {
876877
*/
877878
viewBindings: any[];
878879

880+
templateUrl: string;
881+
882+
template: string;
883+
884+
styleUrls: string[];
885+
886+
styles: string[];
887+
888+
directives: Array<Type | any[]>;
889+
890+
pipes: Array<Type | any[]>;
891+
892+
encapsulation: ViewEncapsulation;
893+
879894
constructor({selector, inputs, outputs, properties, events, host, exportAs, moduleId, bindings,
880-
viewBindings, changeDetection = ChangeDetectionStrategy.Default, queries}: {
895+
viewBindings, changeDetection = ChangeDetectionStrategy.Default, queries,
896+
templateUrl, template, styleUrls, styles, directives, pipes, encapsulation}: {
881897
selector?: string,
882898
inputs?: string[],
883899
outputs?: string[],
@@ -890,6 +906,13 @@ export class ComponentMetadata extends DirectiveMetadata {
890906
viewBindings?: any[],
891907
queries?: {[key: string]: any},
892908
changeDetection?: ChangeDetectionStrategy,
909+
templateUrl?: string,
910+
template?: string,
911+
styleUrls?: string[],
912+
styles?: string[],
913+
directives?: Array<Type | any[]>,
914+
pipes?: Array<Type | any[]>,
915+
encapsulation?: ViewEncapsulation
893916
} = {}) {
894917
super({
895918
selector: selector,
@@ -906,6 +929,14 @@ export class ComponentMetadata extends DirectiveMetadata {
906929

907930
this.changeDetection = changeDetection;
908931
this.viewBindings = viewBindings;
932+
933+
this.templateUrl = templateUrl;
934+
this.template = template;
935+
this.styleUrls = styleUrls;
936+
this.styles = styles;
937+
this.directives = directives;
938+
this.pipes = pipes;
939+
this.encapsulation = encapsulation;
909940
}
910941
}
911942

modules/angular2/test/core/linker/integration_spec.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,8 +1231,8 @@ export function main() {
12311231
try {
12321232
tcb.createAsync(ComponentWithoutView);
12331233
} catch (e) {
1234-
expect(e.message).toEqual(
1235-
`No View annotation found on component ${stringify(ComponentWithoutView)}`);
1234+
expect(e.message)
1235+
.toContain(`must have either 'template', 'templateUrl', or '@View' set.`);
12361236
return null;
12371237
}
12381238
}));
@@ -1696,6 +1696,21 @@ export function main() {
16961696
});
16971697
}));
16981698
}
1699+
1700+
it('should support defining views in the component decorator',
1701+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
1702+
tcb.overrideView(MyComp, new ViewMetadata({
1703+
template: '<component-with-template></component-with-template>',
1704+
directives: [ComponentWithTempalte]
1705+
}))
1706+
.createAsync(MyComp)
1707+
.then((rootTC) => {
1708+
rootTC.detectChanges();
1709+
var native = rootTC.debugElement.componentViewChildren[0].nativeElement;
1710+
expect(native).toHaveText("No View Decorator: 123");
1711+
async.done();
1712+
});
1713+
}));
16991714
});
17001715
});
17011716
}
@@ -2251,6 +2266,14 @@ class DirectiveThrowingAnError {
22512266
constructor() { throw new BaseException("BOOM"); }
22522267
}
22532268

2269+
@Component({
2270+
selector: 'component-with-template',
2271+
directives: [NgFor], template: `No View Decorator: <div *ng-for="#item of items">{{item}}</div>`
2272+
})
2273+
class ComponentWithTempalte {
2274+
items = [1, 2, 3];
2275+
}
2276+
22542277
@Directive({selector: 'with-prop-decorators'})
22552278
class DirectiveWithPropDecorators {
22562279
target;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
2+
import {ViewResolver} from 'angular2/src/core/linker/view_resolver';
3+
import {Component, View, ViewMetadata} from 'angular2/src/core/metadata';
4+
5+
class SomeDir {}
6+
class SomePipe {}
7+
8+
@Component({selector: 'sample'})
9+
@View(
10+
{template: "some template", directives: [SomeDir], pipes: [SomePipe], styles: ["some styles"]})
11+
class ComponentWithView {
12+
}
13+
14+
@Component({
15+
selector: 'sample',
16+
template: "some template",
17+
directives: [SomeDir],
18+
pipes: [SomePipe],
19+
styles: ["some styles"]
20+
})
21+
class ComponentWithTemplate {
22+
}
23+
24+
@Component({selector: 'sample', template: "some template"})
25+
@View({template: "some template"})
26+
class ComponentWithViewTemplate {
27+
}
28+
29+
@Component({selector: 'sample'})
30+
class ComponentWithoutView {
31+
}
32+
33+
@Component({selector: 'sample', templateUrl: "some template url"})
34+
@View({template: "some template"})
35+
class ComponentWithViewTemplateUrl {
36+
}
37+
38+
@View({template: "some template"})
39+
class ClassWithView {
40+
}
41+
42+
class SimpleClass {}
43+
44+
export function main() {
45+
describe("ViewResolver", () => {
46+
var resolver;
47+
48+
beforeEach(() => { resolver = new ViewResolver(); });
49+
50+
it('should read out the View metadata', () => {
51+
var viewMetadata = resolver.resolve(ComponentWithView);
52+
expect(viewMetadata)
53+
.toEqual(new View({
54+
template: "some template",
55+
directives: [SomeDir],
56+
pipes: [SomePipe],
57+
styles: ["some styles"]
58+
}));
59+
});
60+
61+
it('should read out the View metadata from the Component metadata', () => {
62+
var viewMetadata = resolver.resolve(ComponentWithTemplate);
63+
expect(viewMetadata)
64+
.toEqual(new ViewMetadata({
65+
template: "some template",
66+
directives: [SomeDir],
67+
pipes: [SomePipe],
68+
styles: ["some styles"]
69+
}));
70+
});
71+
72+
it('should read out the View metadata from a simple class', () => {
73+
var viewMetadata = resolver.resolve(ClassWithView);
74+
expect(viewMetadata).toEqual(new View({template: "some template"}));
75+
});
76+
77+
it('should throw when Component.template is specified together with the View metadata', () => {
78+
expect(() => resolver.resolve(ComponentWithViewTemplate))
79+
.toThrowErrorWith(
80+
"Component 'ComponentWithViewTemplate' cannot have both 'template' and '@View' set at the same time");
81+
});
82+
83+
it('should throw when Component.template is specified together with the View metadata', () => {
84+
expect(() => resolver.resolve(ComponentWithViewTemplateUrl))
85+
.toThrowErrorWith(
86+
"Component 'ComponentWithViewTemplateUrl' cannot have both 'templateUrl' and '@View' set at the same time");
87+
});
88+
89+
it('should throw when Component has no View decorator and no template is set', () => {
90+
expect(() => resolver.resolve(ComponentWithoutView))
91+
.toThrowErrorWith(
92+
"Component 'ComponentWithoutView' must have either 'template', 'templateUrl', or '@View' set");
93+
});
94+
95+
it('should throw when simple class has no View decorator and no template is set', () => {
96+
expect(() => resolver.resolve(SimpleClass))
97+
.toThrowErrorWith("No View decorator found on component 'SimpleClass'");
98+
});
99+
});
100+
}

modules/angular2/test/public_api_spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ var NG_API = [
166166
'Component.queries',
167167
'Component.selector',
168168
'Component.viewBindings',
169+
'Component.directives',
170+
'Component.encapsulation',
171+
'Component.pipes',
172+
'Component.styleUrls',
173+
'Component.styles',
174+
'Component.template',
175+
'Component.templateUrl',
169176
'ComponentMetadata',
170177
'ComponentMetadata.bindings',
171178
'ComponentMetadata.changeDetection',
@@ -179,6 +186,13 @@ var NG_API = [
179186
'ComponentMetadata.queries',
180187
'ComponentMetadata.selector',
181188
'ComponentMetadata.viewBindings',
189+
'ComponentMetadata.directives',
190+
'ComponentMetadata.encapsulation',
191+
'ComponentMetadata.pipes',
192+
'ComponentMetadata.styleUrls',
193+
'ComponentMetadata.styles',
194+
'ComponentMetadata.template',
195+
'ComponentMetadata.templateUrl',
182196
'ComponentRef',
183197
'ComponentRef.componentType',
184198
'ComponentRef.componentType=',

0 commit comments

Comments
 (0)
X Tutup