Skip to content

Commit ac3e624

Browse files
committed
feat(View): add support for styleUrls and styles
fixes #2382
1 parent f065a2e commit ac3e624

File tree

12 files changed

+220
-102
lines changed

12 files changed

+220
-102
lines changed

modules/angular2/src/core/annotations_impl/view.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ export class View {
4949
*/
5050
template: string;
5151

52+
/**
53+
* Specifies stylesheet URLs for an angular component.
54+
*/
55+
styleUrls: List<string>;
56+
57+
/**
58+
* Specifies an inline stylesheet for an angular component.
59+
*/
60+
styles: List<string>;
61+
5262
/**
5363
* Specifies a list of directives that can be used within a template.
5464
*
@@ -78,13 +88,16 @@ export class View {
7888

7989
/**
8090
* Specify a custom renderer for this View.
81-
* If this is set, neither `template`, `templateURL` nor `directives` are used.
91+
* If this is set, neither `template`, `templateUrl`, `styles`, `styleUrls` nor `directives` are
92+
* used.
8293
*/
8394
renderer: string;
8495

85-
constructor({templateUrl, template, directives, renderer}: ViewArgs = {}) {
96+
constructor({templateUrl, template, directives, renderer, styles, styleUrls}: ViewArgs = {}) {
8697
this.templateUrl = templateUrl;
8798
this.template = template;
99+
this.styleUrls = styleUrls;
100+
this.styles = styles;
88101
this.directives = directives;
89102
this.renderer = renderer;
90103
}
@@ -94,4 +107,6 @@ export interface ViewArgs {
94107
template?: string;
95108
directives?: List<Type | any | List<any>>;
96109
renderer?: string;
110+
styles?: List<string>;
111+
styleUrls?: List<string>;
97112
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export class Compiler {
205205
var componentUrl =
206206
this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component));
207207
var templateAbsUrl = null;
208+
var styleAbsUrls = null;
208209
if (isPresent(view.templateUrl)) {
209210
templateAbsUrl = this._urlResolver.resolve(componentUrl, view.templateUrl);
210211
} else if (isPresent(view.template)) {
@@ -213,9 +214,15 @@ export class Compiler {
213214
// is able to resolve urls in stylesheets.
214215
templateAbsUrl = componentUrl;
215216
}
217+
if (isPresent(view.styleUrls)) {
218+
styleAbsUrls =
219+
ListWrapper.map(view.styleUrls, url => this._urlResolver.resolve(componentUrl, url));
220+
}
216221
return new renderApi.ViewDefinition({
217222
componentId: stringify(component),
218-
absUrl: templateAbsUrl, template: view.template,
223+
templateAbsUrl: templateAbsUrl, template: view.template,
224+
styleAbsUrls: styleAbsUrls,
225+
styles: view.styles,
219226
directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata)
220227
});
221228
}

modules/angular2/src/render/api.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,19 +256,25 @@ export class RenderViewRef {}
256256

257257
export class ViewDefinition {
258258
componentId: string;
259-
absUrl: string;
259+
templateAbsUrl: string;
260260
template: string;
261261
directives: List<DirectiveMetadata>;
262+
styleAbsUrls: List<string>;
263+
styles: List<string>;
262264

263-
constructor({componentId, absUrl, template, directives}: {
265+
constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives}: {
264266
componentId?: string,
265-
absUrl?: string,
267+
templateAbsUrl?: string,
266268
template?: string,
269+
styleAbsUrls?: List<string>,
270+
styles?: List<string>,
267271
directives?: List<DirectiveMetadata>
268272
}) {
269273
this.componentId = componentId;
270-
this.absUrl = absUrl;
274+
this.templateAbsUrl = templateAbsUrl;
271275
this.template = template;
276+
this.styleAbsUrls = styleAbsUrls;
277+
this.styles = styles;
272278
this.directives = directives;
273279
}
274280
}

modules/angular2/src/render/dom/compiler/compiler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ export class DomCompiler extends RenderCompiler {
4343
compileHost(directiveMetadata: DirectiveMetadata): Promise<ProtoViewDto> {
4444
var hostViewDef = new ViewDefinition({
4545
componentId: directiveMetadata.id,
46-
absUrl: null, template: null,
46+
templateAbsUrl: null, template: null,
47+
styles: null,
48+
styleAbsUrls: null,
4749
directives: [directiveMetadata]
4850
});
4951
var element = DOM.createElement(directiveMetadata.selector);
Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Injectable} from 'angular2/di';
22
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
3-
import {Map, MapWrapper, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
3+
import {Map, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
44
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
55
import {DOM} from 'angular2/src/dom/dom_adapter';
66

@@ -15,35 +15,62 @@ import {UrlResolver} from 'angular2/src/services/url_resolver';
1515
*/
1616
@Injectable()
1717
export class TemplateLoader {
18-
_htmlCache: StringMap<string, /*element*/ any> = StringMapWrapper.create();
18+
_cache: Map<string, Promise<string>> = MapWrapper.create();
1919

20-
constructor(public _xhr: XHR, urlResolver: UrlResolver) {}
20+
constructor(private _xhr: XHR, urlResolver: UrlResolver) {}
2121

22-
load(template: ViewDefinition): Promise</*element*/ any> {
23-
if (isPresent(template.template)) {
24-
return PromiseWrapper.resolve(DOM.createTemplate(template.template));
22+
load(view: ViewDefinition): Promise</*element*/ any> {
23+
let html;
24+
let fetchedStyles;
25+
26+
// Load the HTML
27+
if (isPresent(view.template)) {
28+
html = PromiseWrapper.resolve(view.template);
29+
} else if (isPresent(view.templateAbsUrl)) {
30+
html = this._loadText(view.templateAbsUrl);
31+
} else {
32+
throw new BaseException('View should have either the templateUrl or template property set');
33+
}
34+
35+
// Load the styles
36+
if (isPresent(view.styleAbsUrls) && view.styleAbsUrls.length > 0) {
37+
fetchedStyles = ListWrapper.map(view.styleAbsUrls, url => this._loadText(url));
38+
} else {
39+
fetchedStyles = [];
2540
}
26-
var url = template.absUrl;
27-
if (isPresent(url)) {
28-
var promise = StringMapWrapper.get(this._htmlCache, url);
29-
30-
if (isBlank(promise)) {
31-
// TODO(vicb): change error when TS gets fixed
32-
// https://github.com/angular/angular/issues/2280
33-
// throw new BaseException(`Failed to fetch url "${url}"`);
34-
promise = PromiseWrapper.then(this._xhr.get(url), html => {
35-
var template = DOM.createTemplate(html);
36-
return template;
37-
}, _ => PromiseWrapper.reject(new BaseException(`Failed to fetch url "${url}"`), null));
38-
39-
StringMapWrapper.set(this._htmlCache, url, promise);
40-
}
41-
42-
// We need to clone the result as others might change it
43-
// (e.g. the compiler).
44-
return promise.then((tplElement) => DOM.clone(tplElement));
41+
42+
// Inline the styles and return a template element
43+
return PromiseWrapper.all(ListWrapper.concat([html], fetchedStyles))
44+
.then((res: List<string>) => {
45+
let html = res[0];
46+
let fetchedStyles = ListWrapper.slice(res, 1);
47+
48+
html = _createStyleTags(view.styles) + _createStyleTags(fetchedStyles) + html;
49+
50+
return DOM.createTemplate(html);
51+
});
52+
}
53+
54+
private _loadText(url: string): Promise<string> {
55+
var response = MapWrapper.get(this._cache, url);
56+
57+
if (isBlank(response)) {
58+
// TODO(vicb): change error when TS gets fixed
59+
// https://github.com/angular/angular/issues/2280
60+
// throw new BaseException(`Failed to fetch url "${url}"`);
61+
response = PromiseWrapper.catchError(
62+
this._xhr.get(url),
63+
_ => PromiseWrapper.reject(new BaseException(`Failed to fetch url "${url}"`), null));
64+
65+
MapWrapper.set(this._cache, url, response);
4566
}
4667

47-
throw new BaseException('View should have either the url or template property set');
68+
return response;
4869
}
4970
}
71+
72+
function _createStyleTags(styles?: List<string>): string {
73+
return isBlank(styles) ?
74+
'' :
75+
ListWrapper.map(styles, css => `<style type='text/css'>${css}</style>`).join('');
76+
}

modules/angular2/src/render/dom/shadow_dom/shadow_dom_compile_step.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class ShadowDomCompileStep implements CompileStep {
2929

3030
_processStyleElement(current: CompileElement, control: CompileControl) {
3131
var stylePromise = this._shadowDomStrategy.processStyleElement(
32-
this._template.componentId, this._template.absUrl, current.element);
32+
this._template.componentId, this._template.templateAbsUrl, current.element);
3333
if (isPresent(stylePromise) && PromiseWrapper.isPromise(stylePromise)) {
3434
ListWrapper.push(this._subTaskPromises, stylePromise);
3535
}

modules/angular2/src/render/dom/view/proto_view.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {NG_BINDING_CLASS} from '../util';
88

99
import {RenderProtoViewRef} from '../../api';
1010

11-
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef) {
11+
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
1212
return (<DomProtoViewRef>protoViewRef)._protoView;
1313
}
1414

modules/angular2/src/transform/template_compiler/generator.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class _TemplateExtractor {
8888

8989
Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async {
9090
// Check for "imperative views".
91-
if (viewDef.template == null && viewDef.absUrl == null) return null;
91+
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null;
9292

9393
var templateEl = await _loader.load(viewDef);
9494

modules/angular2/src/transform/template_compiler/view_definition_creator.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,11 @@ class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
210210
return null;
211211
}
212212
if (keyString == 'templateUrl') {
213-
if (viewDef.absUrl != null) {
213+
if (viewDef.templateAbsUrl != null) {
214214
logger.error(
215215
'Found multiple values for "templateUrl". Source: ${node}');
216216
}
217-
viewDef.absUrl = valueString;
217+
viewDef.templateAbsUrl = valueString;
218218
} else {
219219
// keyString == 'template'
220220
if (viewDef.template != null) {

modules/angular2/test/core/compiler/compiler_spec.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,30 +100,45 @@ export function main() {
100100
});
101101
}));
102102

103-
it('should fill absUrl given inline templates', inject([AsyncTestCompleter], (async) => {
103+
it('should fill templateAbsUrl given inline templates',
104+
inject([AsyncTestCompleter], (async) => {
104105
cmpUrlMapper.setComponentUrl(MainComponent, '/mainComponent');
105106
captureTemplate(new viewAnn.View({template: '<div></div>'}))
106107
.then((renderTpl) => {
107-
expect(renderTpl.absUrl).toEqual('http://www.app.com/mainComponent');
108+
expect(renderTpl.templateAbsUrl).toEqual('http://www.app.com/mainComponent');
108109
async.done();
109110
});
110111
}));
111112

112-
it('should not fill absUrl given no inline template or template url',
113+
it('should not fill templateAbsUrl given no inline template or template url',
113114
inject([AsyncTestCompleter], (async) => {
114115
cmpUrlMapper.setComponentUrl(MainComponent, '/mainComponent');
115116
captureTemplate(new viewAnn.View({template: null, templateUrl: null}))
116117
.then((renderTpl) => {
117-
expect(renderTpl.absUrl).toBe(null);
118+
expect(renderTpl.templateAbsUrl).toBe(null);
118119
async.done();
119120
});
120121
}));
121122

122-
it('should fill absUrl given url template', inject([AsyncTestCompleter], (async) => {
123+
it('should fill templateAbsUrl given url template', inject([AsyncTestCompleter], (async) => {
123124
cmpUrlMapper.setComponentUrl(MainComponent, '/mainComponent');
124125
captureTemplate(new viewAnn.View({templateUrl: '/someTemplate'}))
125126
.then((renderTpl) => {
126-
expect(renderTpl.absUrl).toEqual('http://www.app.com/mainComponent/someTemplate');
127+
expect(renderTpl.templateAbsUrl)
128+
.toEqual('http://www.app.com/mainComponent/someTemplate');
129+
async.done();
130+
});
131+
}));
132+
133+
it('should fill styleAbsUrls given styleUrls', inject([AsyncTestCompleter], (async) => {
134+
cmpUrlMapper.setComponentUrl(MainComponent, '/mainComponent');
135+
captureTemplate(new viewAnn.View({styleUrls: ['/1.css', '/2.css']}))
136+
.then((renderTpl) => {
137+
expect(renderTpl.styleAbsUrls)
138+
.toEqual([
139+
'http://www.app.com/mainComponent/1.css',
140+
'http://www.app.com/mainComponent/2.css'
141+
]);
127142
async.done();
128143
});
129144
}));

0 commit comments

Comments
 (0)