Skip to content

Commit 4d20cab

Browse files
committed
45f09ba docs(changelog): update changelog to beta.15
1 parent d982de6 commit 4d20cab

13 files changed

Lines changed: 313 additions & 66 deletions

File tree

BUILD_INFO

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Wed Apr 13 21:53:29 UTC 2016
2-
1cebd318e5b31df5491ef1017a6abf3444f901f4
1+
Wed Apr 13 21:58:42 UTC 2016
2+
45f09ba68603f338f434371cb4816cf5983a01af

lib/src/compiler/static_reflector.dart

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class StaticReflector {
9292
.toList()
9393
.where((decorator) => isPresent(decorator))
9494
.toList();
95+
} else {
96+
annotations = [];
9597
}
9698
this.annotationCache[type] = annotations;
9799
}
@@ -104,6 +106,9 @@ class StaticReflector {
104106
var classMetadata = this.getTypeMetadata(type);
105107
propMetadata =
106108
this.getPropertyMetadata(type.moduleId, classMetadata["members"]);
109+
if (!isPresent(propMetadata)) {
110+
propMetadata = {};
111+
}
107112
this.propertyCache[type] = propMetadata;
108113
}
109114
return propMetadata;
@@ -113,14 +118,22 @@ class StaticReflector {
113118
var parameters = this.parameterCache[type];
114119
if (!isPresent(parameters)) {
115120
var classMetadata = this.getTypeMetadata(type);
116-
var ctorData = classMetadata["members"]["___ctor__"];
117-
if (isPresent(ctorData)) {
118-
var ctor = ((ctorData as List<dynamic>)).firstWhere(
119-
(a) => identical(a["___symbolic"], "constructor"),
120-
orElse: () => null);
121-
parameters = this.simplify(type.moduleId, ctor["parameters"]);
122-
this.parameterCache[type] = parameters;
121+
if (isPresent(classMetadata)) {
122+
var members = classMetadata["members"];
123+
if (isPresent(members)) {
124+
var ctorData = members["___ctor__"];
125+
if (isPresent(ctorData)) {
126+
var ctor = ((ctorData as List<dynamic>)).firstWhere(
127+
(a) => identical(a["___symbolic"], "constructor"),
128+
orElse: () => null);
129+
parameters = this.simplify(type.moduleId, ctor["parameters"]);
130+
}
131+
}
123132
}
133+
if (!isPresent(parameters)) {
134+
parameters = [];
135+
}
136+
this.parameterCache[type] = parameters;
124137
}
125138
return parameters;
126139
}
@@ -304,7 +317,7 @@ class StaticReflector {
304317
});
305318
return result;
306319
}
307-
return null;
320+
return {};
308321
}
309322

310323
// clang-format off

lib/src/core/change_detection/parser/parser.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import "ast.dart"
5454
var _implicitReceiver = new ImplicitReceiver();
5555
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
5656
var INTERPOLATION_REGEXP = new RegExp(r'\{\{([\s\S]*?)\}\}');
57+
var COMMENT_REGEX = new RegExp(r'\/\/');
5758

5859
class ParseException extends BaseException {
5960
ParseException(String message, String input, String errLocation,
@@ -81,7 +82,7 @@ class Parser {
8182
}
8283
ASTWithSource parseAction(String input, dynamic location) {
8384
this._checkNoInterpolation(input, location);
84-
var tokens = this._lexer.tokenize(input);
85+
var tokens = this._lexer.tokenize(this._stripComments(input));
8586
var ast = new _ParseAST(input, location, tokens, this._reflector, true)
8687
.parseChain();
8788
return new ASTWithSource(ast, input, location);
@@ -112,7 +113,7 @@ class Parser {
112113
return quote;
113114
}
114115
this._checkNoInterpolation(input, location);
115-
var tokens = this._lexer.tokenize(input);
116+
var tokens = this._lexer.tokenize(this._stripComments(input));
116117
return new _ParseAST(input, location, tokens, this._reflector, false)
117118
.parseChain();
118119
}
@@ -138,7 +139,8 @@ class Parser {
138139
if (split == null) return null;
139140
var expressions = [];
140141
for (var i = 0; i < split.expressions.length; ++i) {
141-
var tokens = this._lexer.tokenize(split.expressions[i]);
142+
var tokens =
143+
this._lexer.tokenize(this._stripComments(split.expressions[i]));
142144
var ast = new _ParseAST(input, location, tokens, this._reflector, false)
143145
.parseChain();
144146
expressions.add(ast);
@@ -176,6 +178,10 @@ class Parser {
176178
return new ASTWithSource(new LiteralPrimitive(input), input, location);
177179
}
178180

181+
String _stripComments(String input) {
182+
return StringWrapper.split(input, COMMENT_REGEX)[0].trim();
183+
}
184+
179185
void _checkNoInterpolation(String input, dynamic location) {
180186
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
181187
if (parts.length > 1) {

lib/src/core/metadata/di.dart

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -301,23 +301,77 @@ class ViewQueryMetadata extends QueryMetadata {
301301
}
302302

303303
/**
304-
* Configures a view query.
304+
* Declares a list of child element references.
305305
*
306-
* View queries are set before the `ngAfterViewInit` callback is called.
306+
* Angular automatically updates the list when the DOM was updated.
307+
*
308+
* `ViewChildren` takes an argument to select elements.
309+
*
310+
* - If the argument is a type, directives or components with the type will be bound.
311+
*
312+
* - If the argument is a string, the string behaviors as comma-separated selectors. For each
313+
* selector, an element matched template variables (e.g. `#child`) will be bound.
314+
*
315+
* View children are set before the `ngAfterViewInit` callback is called.
307316
*
308317
* ### Example
309318
*
319+
* With type selector:
320+
*
310321
* ```
311322
* @Component({
312-
* selector: 'someDir',
313-
* templateUrl: 'someTemplate',
314-
* directives: [ItemDirective]
323+
* selector: 'child-cmp',
324+
* template: '<p>child</p>'
315325
* })
316-
* class SomeDir {
317-
* @ViewChildren(ItemDirective) viewChildren: QueryList<ItemDirective>;
326+
* class ChildCmp {
327+
* doSomething() {}
328+
* }
329+
*
330+
* @Component({
331+
* selector: 'some-cmp',
332+
* template: `
333+
* <child-cmp></child-cmp>
334+
* <child-cmp></child-cmp>
335+
* <child-cmp></child-cmp>
336+
* `,
337+
* directives: [ChildCmp]
338+
* })
339+
* class SomeCmp {
340+
* @ViewChildren(ChildCmp) children:QueryList<ChildCmp>;
341+
*
342+
* ngAfterViewInit() {
343+
* // children are set
344+
* this.children.toArray().forEach((child)=>child.doSomething());
345+
* }
346+
* }
347+
* ```
348+
*
349+
* With string selector:
350+
*
351+
* ```
352+
* @Component({
353+
* selector: 'child-cmp',
354+
* template: '<p>child</p>'
355+
* })
356+
* class ChildCmp {
357+
* doSomething() {}
358+
* }
359+
*
360+
* @Component({
361+
* selector: 'some-cmp',
362+
* template: `
363+
* <child-cmp #child1></child-cmp>
364+
* <child-cmp #child2></child-cmp>
365+
* <child-cmp #child3></child-cmp>
366+
* `,
367+
* directives: [ChildCmp]
368+
* })
369+
* class SomeCmp {
370+
* @ViewChildren('child1,child2,child3') children:QueryList<ChildCmp>;
318371
*
319372
* ngAfterViewInit() {
320-
* // viewChildren is set
373+
* // children are set
374+
* this.children.toArray().forEach((child)=>child.doSomething());
321375
* }
322376
* }
323377
* ```
@@ -328,23 +382,71 @@ class ViewChildrenMetadata extends ViewQueryMetadata {
328382
}
329383

330384
/**
331-
* Configures a view query.
332385
*
333-
* View queries are set before the `ngAfterViewInit` callback is called.
386+
* Declares a reference of child element.
387+
*
388+
* `ViewChildren` takes an argument to select elements.
389+
*
390+
* - If the argument is a type, a directive or a component with the type will be bound.
391+
*
392+
* - If the argument is a string, the string behaviors as a selectors. An element matched template
393+
* variables (e.g. `#child`) will be bound.
394+
*
395+
* In either case, `@ViewChild()` assigns the first (looking from above) element if the result is
396+
* multiple.
397+
*
398+
* View child is set before the `ngAfterViewInit` callback is called.
334399
*
335400
* ### Example
336401
*
402+
* With type selector:
403+
*
337404
* ```
338405
* @Component({
339-
* selector: 'someDir',
340-
* templateUrl: 'someTemplate',
341-
* directives: [ItemDirective]
406+
* selector: 'child-cmp',
407+
* template: '<p>child</p>'
342408
* })
343-
* class SomeDir {
344-
* @ViewChild(ItemDirective) viewChild:ItemDirective;
409+
* class ChildCmp {
410+
* doSomething() {}
411+
* }
412+
*
413+
* @Component({
414+
* selector: 'some-cmp',
415+
* template: '<child-cmp></child-cmp>',
416+
* directives: [ChildCmp]
417+
* })
418+
* class SomeCmp {
419+
* @ViewChild(ChildCmp) child:ChildCmp;
420+
*
421+
* ngAfterViewInit() {
422+
* // child is set
423+
* this.child.doSomething();
424+
* }
425+
* }
426+
* ```
427+
*
428+
* With string selector:
429+
*
430+
* ```
431+
* @Component({
432+
* selector: 'child-cmp',
433+
* template: '<p>child</p>'
434+
* })
435+
* class ChildCmp {
436+
* doSomething() {}
437+
* }
438+
*
439+
* @Component({
440+
* selector: 'some-cmp',
441+
* template: '<child-cmp #child></child-cmp>',
442+
* directives: [ChildCmp]
443+
* })
444+
* class SomeCmp {
445+
* @ViewChild('child') child:ChildCmp;
345446
*
346447
* ngAfterViewInit() {
347-
* // viewChild is set
448+
* // child is set
449+
* this.child.doSomething();
348450
* }
349451
* }
350452
* ```

lib/src/i18n/i18n_html_parser.dart

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ import "shared.dart"
3030
partition,
3131
Part,
3232
stringifyNodes,
33-
meaning;
33+
meaning,
34+
getPhNameFromBinding,
35+
dedupePhName;
3436

3537
const _I18N_ATTR = "i18n";
3638
const _PLACEHOLDER_ELEMENT = "ph";
3739
const _NAME_ATTR = "name";
3840
const _I18N_ATTR_PREFIX = "i18n-";
3941
var _PLACEHOLDER_EXPANDED_REGEXP =
40-
RegExpWrapper.create('''\\<ph(\\s)+name=("(\\d)+")\\>\\<\\/ph\\>''');
42+
RegExpWrapper.create('''\\<ph(\\s)+name=("(\\w)+")\\>\\<\\/ph\\>''');
4143

4244
/**
4345
* Creates an i18n-ed version of the parsed template.
@@ -323,22 +325,32 @@ class I18nHtmlParser implements HtmlParser {
323325

324326
String _replacePlaceholdersWithExpressions(
325327
String message, List<String> exps, ParseSourceSpan sourceSpan) {
328+
var expMap = this._buildExprMap(exps);
326329
return RegExpWrapper.replaceAll(_PLACEHOLDER_EXPANDED_REGEXP, message,
327330
(match) {
328331
var nameWithQuotes = match[2];
329332
var name = nameWithQuotes.substring(1, nameWithQuotes.length - 1);
330-
var index = NumberWrapper.parseInt(name, 10);
331-
return this._convertIntoExpression(index, exps, sourceSpan);
333+
return this._convertIntoExpression(name, expMap, sourceSpan);
332334
});
333335
}
334336

337+
Map<String, String> _buildExprMap(List<String> exps) {
338+
var expMap = new Map<String, String>();
339+
var usedNames = new Map<String, num>();
340+
for (var i = 0; i < exps.length; i++) {
341+
var phName = getPhNameFromBinding(exps[i], i);
342+
expMap[dedupePhName(usedNames, phName)] = exps[i];
343+
}
344+
return expMap;
345+
}
346+
335347
_convertIntoExpression(
336-
num index, List<String> exps, ParseSourceSpan sourceSpan) {
337-
if (index >= 0 && index < exps.length) {
338-
return '''{{${ exps [ index ]}}}''';
348+
String name, Map<String, String> expMap, ParseSourceSpan sourceSpan) {
349+
if (expMap.containsKey(name)) {
350+
return '''{{${ expMap [ name ]}}}''';
339351
} else {
340352
throw new I18nError(
341-
sourceSpan, '''Invalid interpolation index \'${ index}\'''');
353+
sourceSpan, '''Invalid interpolation name \'${ name}\'''');
342354
}
343355
}
344356
}

lib/src/i18n/shared.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import "package:angular2/src/compiler/html_ast.dart"
1111
HtmlTextAst,
1212
HtmlCommentAst,
1313
htmlVisitAll;
14-
import "package:angular2/src/facade/lang.dart" show isPresent, isBlank;
14+
import "package:angular2/src/facade/lang.dart"
15+
show isPresent, isBlank, StringWrapper;
1516
import "message.dart" show Message;
1617
import "package:angular2/src/core/change_detection/parser/parser.dart"
1718
show Parser;
1819

1920
const I18N_ATTR = "i18n";
2021
const I18N_ATTR_PREFIX = "i18n-";
22+
var CUSTOM_PH_EXP = new RegExp(
23+
r'\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*"([\s\S]*?)"[\s\S]*\)');
2124

2225
/**
2326
* An i18n error.
@@ -124,12 +127,15 @@ String removeInterpolation(
124127
String value, ParseSourceSpan source, Parser parser) {
125128
try {
126129
var parsed = parser.splitInterpolation(value, source.toString());
130+
var usedNames = new Map<String, num>();
127131
if (isPresent(parsed)) {
128132
var res = "";
129133
for (var i = 0; i < parsed.strings.length; ++i) {
130134
res += parsed.strings[i];
131135
if (i != parsed.strings.length - 1) {
132-
res += '''<ph name="${ i}"/>''';
136+
var customPhName = getPhNameFromBinding(parsed.expressions[i], i);
137+
customPhName = dedupePhName(usedNames, customPhName);
138+
res += '''<ph name="${ customPhName}"/>''';
133139
}
134140
}
135141
return res;
@@ -141,6 +147,22 @@ String removeInterpolation(
141147
}
142148
}
143149

150+
String getPhNameFromBinding(String input, num index) {
151+
var customPhMatch = StringWrapper.split(input, CUSTOM_PH_EXP);
152+
return customPhMatch.length > 1 ? customPhMatch[1] : '''${ index}''';
153+
}
154+
155+
String dedupePhName(Map<String, num> usedNames, String name) {
156+
var duplicateNameCount = usedNames[name];
157+
if (isPresent(duplicateNameCount)) {
158+
usedNames[name] = duplicateNameCount + 1;
159+
return '''${ name}_${ duplicateNameCount}''';
160+
} else {
161+
usedNames[name] = 1;
162+
return name;
163+
}
164+
}
165+
144166
String stringifyNodes(List<HtmlAst> nodes, Parser parser) {
145167
var visitor = new _StringifyVisitor(parser);
146168
return htmlVisitAll(visitor, nodes).join("");

0 commit comments

Comments
 (0)