Compare commits

..

116 Commits

Author SHA1 Message Date
f114e40212 docs(changelog): add changelog for 4.0.0-beta.1 2016-12-21 16:48:14 -08:00
952471e25d chore(release): cut the 4.0.0-beta.1 release 2016-12-21 16:44:56 -08:00
c65e428778 docs(changelog): add changelog for 2.4.1 2016-12-21 16:43:13 -08:00
842f52e841 fix(animations): always recover from a failed animation step (#13604) 2016-12-21 14:14:45 -08:00
eb2ceff4ba fix(router): should reset location if a navigation by location is successful (#13545)
Closes #13491
2016-12-21 12:47:58 -08:00
f49ab56160 fix(animations): always quote string map key values in AOT code (#13602) 2016-12-20 18:17:58 -08:00
c0f750af4e fix(compiler): ignore @import in comments (#13368)
* refactor(compiler): clean up style url resolver
* fix(compiler): ignore @import in css comments

Closes #12196
2016-12-20 17:51:02 -08:00
bcd37f52fb Include bower instructions in DEVELOPER.md (#13591) 2016-12-20 17:50:04 -08:00
e69c1fb36c refactor(platform-browser): resolver merge conflict for tslint (#13601) 2016-12-20 17:49:25 -08:00
9da4c259a5 feat(upgrade): support the $doCheck() lifecycle hook in UpgradeComponent (#13015) 2016-12-20 16:18:43 -08:00
fcd116fdc0 fix(common): throw an error if trackBy is not a function (#13420)
* fix(common): throw an error if trackBy is not a function

Closes #13388

* refactor(platform-browser): disable no-console rule in DomAdapter
2016-12-20 16:18:24 -08:00
383adc9ad9 fix(core): improve error message when component factory cannot be found (#13541)
Closes #12678
2016-12-20 16:17:22 -08:00
9b8488f007 build: fix publish-build-artifacts branch detection (#13599) 2016-12-20 15:59:15 -08:00
1817ddb57b build: publish build artifacts to branches (#13529)
Fix #13126
2016-12-20 14:52:50 -08:00
1ee574c51e docs(changelog): add changelog for 2.4.0 2016-12-20 11:14:28 -08:00
171a9bdc85 feat: update to rxjs@5.0.1 and unpin the rxjs peerDeps via ^5.0.1 (#13572)
Now that rxjs is stable and the rxjs team follows semver, we can update and unpin the dependency safely.

From now on the Angular application/library developers are in charge of controlling the rxjs version as long as it's newer than 5.0.1.

closes #13561
closes #13478
closes #13572
2016-12-19 16:24:53 -08:00
896916af29 build(npm): update angular version in shrinkwrap files 2016-12-19 16:23:54 -08:00
e49c7fae22 refactor(compiler-cli): support extracting the mesage bundle without writing a file (#13580) 2016-12-19 15:28:55 -08:00
6b65fc1286 feat(compiler-cli): private i18n API for the CLI (#13536)
Also change the Extractor API to align with the Codegen API (internal APIs)
2016-12-19 11:56:10 -08:00
0e3981afc1 fix(compiler-cli): produce metadata for .d.ts files without metadata (#13526)
Fixes #13307
Fixes #13473
Fixes #13521
2016-12-16 15:33:47 -08:00
e78508507d fix(compiler): do not lex }} when interpolation is disabled (#13531)
* doc(compiler): fix the ICU expander API docs

* test(compiler): add lexer and parser specs

* fix(compiler): do not lex `}}` when interpolation is disabled

fix #13525
2016-12-16 15:33:16 -08:00
a23fa94ca8 fix(common): capitalize first letter of all words in TitleCasePipe (#13511) 2016-12-16 15:24:26 -08:00
4568d5ddac refactor(core): fix typo (#13515)
Closes #13512
2016-12-16 15:21:58 -08:00
c6e893953f fix(upgrade): fix registerForNg1Tests (#13522)
Fix an issue in `registerForNg1Tests`, where it passes a `null` as
`ng1Injector` to `_bootstrapDone`. This causes a "TypeError: Cannot
read property 'get' of null" to be thrown from `_bootstrapDone`.
2016-12-16 15:14:16 -08:00
55dfa1b69d test(forms): refactor integration tests to improve speed (#13500) 2016-12-15 17:07:26 -08:00
0fe3cd9a4c fix(i18n): add a default example to xmb placeholders (#13507)
Otherwise the TC would not be able to load the message
2016-12-15 15:33:42 -08:00
0c19898694 fix(animations): allow players to be destroyed before initialized (#13346)
Closes #13293
Closes #13346
2016-12-15 14:18:57 -08:00
5b6e8ea3ec refactor(compiler): format update (#13506) 2016-12-15 13:54:38 -08:00
732f446ad2 docs(common): fix ngIf example (#13496) 2016-12-15 13:07:36 -08:00
f0e092515c refactor(compiler): don't print stack trace on template parse errors (#13390) 2016-12-15 13:07:12 -08:00
14e785f5b7 fix(build): use bash string comparison operator (#13502) 2016-12-15 12:05:29 -08:00
01d1624884 feature(DEVELOPER.md): add easy way to publish personal snapshot builds (#13469) 2016-12-15 11:19:21 -08:00
33910ddfc9 refactor(compiler): store metadata of top level symbols also in summaries (#13289)
This allows a build using summaries to not need .metadata.json files at all
any more.

Part of #12787
2016-12-15 09:12:40 -08:00
01ca2db6ae docs(changelog): add changelog for 4.0.0-beta.0 2016-12-14 21:54:31 -08:00
6cefccb314 build: bump angular to 4.0.0-beta.0 & tsc-wrapped to 0.5.0 2016-12-14 16:42:44 -08:00
fa9e21e83c fix(compiler): fix merge error in compiler_host 2016-12-14 15:36:49 -08:00
b6078f5887 fix(compiler): update to metadata version 3 (#13464)
This change retracts support for metadata version 2.

The collector used to produce version 2 metadata was incomplete
and can cause the AOT compiler to fail to resolve symbols or
produce other spurious errors.

All libraries compiled and published with 2.3.0 ngc will need
to be recompiled and updated with this change.
2016-12-14 15:28:51 -08:00
c65b4fa9dc refactor: format & lint 2016-12-14 15:10:43 -08:00
169ed82900 feat(testing): add overrideTemplate method (#13372)
Closes #10685
2016-12-14 15:05:17 -08:00
fd8e15b15d chore(animations/aot): always export NoOpAnimationDriver (#13480) 2016-12-14 14:51:29 -08:00
aa40366a92 fix(compiler): fix simplify a reference without a name
closes #13470
2016-12-14 14:33:10 -08:00
40d8d9c3e3 fix(tsc-wrapped): generate metadata for exports without module specifier
fixes #13327
2016-12-14 14:33:04 -08:00
ee2ac025ef fix(compiler): propagate exports when upgrading metadata to v2 2016-12-14 14:33:04 -08:00
aa3769ba69 fix(compiler): resolver should merge host bindings and listeners (#13474)
fixes #13327
2016-12-14 14:31:57 -08:00
d4ddb6004e refactor: format & lint 2016-12-14 13:05:04 -08:00
84400bcc86 docs(upgrade): fix UpgradeAdapter examples
closes #12675
2016-12-14 13:02:31 -08:00
42d9998cbb docs(upgrade/upgrade_adapter): fix up references to AngularJS and Angular 2 2016-12-14 13:02:27 -08:00
c18d2fe5e3 feat(upgrade): enable Angular 1 unit testing of upgrade module
- New method `UpgradeAdapter.registerForNg1Tests(modules)` declares the
  Angular 1 upgrade module and provides it to the `angular.mock.module()`
  helper.
  This prevents the need to bootstrap the entire hybrid for every test.

Closes #5462, #12675
2016-12-14 13:02:27 -08:00
d91a86aac6 fix(upgrade): fix downgrade content projection and injector inheritance
- Full support for content projection in downgraded Angular 2
  components. In particular, this enables multi-slot projection and
  other features on <ng-content>.
- Correctly wire up hierarchical injectors for downgraded Angular 2
  components: downgraded components inherit the injector of the first
  other downgraded Angular 2 component they find up the DOM tree.

Closes #6629, #7727, #8729, #9643, #9649, #12675
2016-12-14 13:02:27 -08:00
d6e5e9283c refactor(upgrade/upgrade_adapter): use Deferred helper
Making Angular 1's `$compile` asynchronous by chaining injector promises
in linking functions can cause flickering views in applications.
2016-12-14 13:02:27 -08:00
eab7e490c9 refactor(upgrade/util): remove unused stringify() method 2016-12-14 13:02:27 -08:00
3e90605db9 refactor(compiler/template_parser): export createElementCssSelector
This is needed in `ngUpgrade`.
2016-12-14 13:02:27 -08:00
79671a6f12 refactor(upgrade): add missing Angular 1 type info 2016-12-14 13:02:27 -08:00
a659259962 fix(core): detectChanges() doesn't work on detached instance
Closes #13426
Closes #13472
2016-12-14 13:01:06 -08:00
b56474d067 fix(animations): throw errors and normalize offset beyond the range of [0,1]
Closes #13348
Closes #13440
2016-12-14 12:59:47 -08:00
8395f0e138 perf(animations): always run the animation queue outside of zones
Related #12732
Closes #13440
2016-12-14 12:59:36 -08:00
dd0519abad fix(compiler): emit quoted object literal keys if the source is quoted
feat(tsc-wrapped): recored when to quote a object literal key

Collecting quoted literals is off by default as it introduces
a breaking change in the .metadata.json file. A follow-up commit
will address this.

Fixes #13249
Closes #13356
2016-12-14 12:58:41 -08:00
f238c8ac7a Revert "fix(compiler): xmb <ph> tags should not self close (#13413)"
This reverts commit 4b3d135193.
closes #13463
2016-12-14 12:54:58 -08:00
8c27c62fab Revert "test(i18n): fix a typo in the reference xmb (#13441)"
This reverts commit a8d237581d.
2016-12-14 12:54:50 -08:00
5031adc7a3 refactor(facade): don't expect super() to return a new Error object in BaseError (#12600)
Related to #12575
2016-12-14 11:54:57 -08:00
821b8f09d6 fix(forms): ensure select[multiple] retains selections
If you bound an array to select[multiple] via ngModel and subsequently
changed the options to select from, the UI would drop any selections
made since by the user. This was due to
SelectMultipleControlValueAccessor not keeping a reference to the new
model arrays it generated when users interacted with the select control.
Update code to keep the reference.

Closes #12527
Closes #12654
2016-12-14 08:52:07 -08:00
2bf1bbc071 fix(forms): introduce checkbox required validator
Closes #11459
Closes #13364
2016-12-14 08:44:24 -08:00
7b0a86718c fix (forms): clear selected options when model is not an array (#12519)
When an invalid model value (eg empty string) was preset ngModel on
select[multiple] would throw an error, which is inconsistent with how it
works on other user input elements. Setting the model value to null or
undefined would also have no effect on what was already selected in the
UI. Fix this by clearing selected options when model set to null,
undefined or a type other than Array.

Closes #11926
2016-12-14 08:34:19 -08:00
3edca4d37e fix(core): properly destroy embedded Views attatched to ApplicationRef (#13459)
Fixes #13062
2016-12-14 08:33:29 -08:00
a0a05041ac refactor: format & lint 2016-12-13 17:44:52 -08:00
7256d0ede5 chore(internal API): introduce an internal API for ngtools. (#13415) 2016-12-13 17:35:06 -08:00
d62d89319e fix(compiler): generated CSS files suffixed with ngstyle. (#13353)
Mirrors factories which ends in `ngfactory`.

Closes #13141.
2016-12-13 17:34:46 -08:00
f5f1d5f65c fix(compiler): make sure provider values with name property don’t break.
Fixes #13394
Closes #13445
2016-12-13 17:25:59 -08:00
a8d237581d test(i18n): fix a typo in the reference xmb (#13441) 2016-12-13 12:35:09 -08:00
d036165a19 refactor: remove intl from facades (#13404)
The existing intl.ts file is not a facade but
rather a set of utils used by i18n-related pipes only.
As such moving it back to common module so those utils
are not used accidently from other places.
2016-12-13 12:34:50 -08:00
d17e690eb4 test(upgrade): fix failing test in browsers which do not support RAF
closes #13399
2016-12-13 12:28:44 -08:00
714f2af0dd ci(browser providers): update browsers in SL and BS (#13431) 2016-12-13 11:32:31 -08:00
2b90cd532f fix(compiler): narrow the span reported for invalid pipes
fixes #13326
closes #13411
2016-12-13 11:23:47 -08:00
3a64ad895a fix(language-service): correctly type undefined
fixes #13412
closes #13414
2016-12-13 11:23:08 -08:00
9ec0a4e105 feat(language-service): warn when a method isn't called in an event (#13437)
Closes 13435
2016-12-13 11:20:45 -08:00
4b3d135193 fix(compiler): xmb <ph> tags should not self close (#13413) 2016-12-12 19:10:20 -08:00
1d0ed6f75f docs(core): update OnDestroy description (#13369)
Closes #11228
2016-12-12 16:45:56 -08:00
6f330a5fc9 fix(language-service): treat string unions as strings (#13406)
Fixes #13403
2016-12-12 16:42:20 -08:00
e23076f767 build: update the package list of the symlinks scripts for Windows (#13408) 2016-12-12 16:41:35 -08:00
7295a5e7f2 refactor: format and lint code 2016-12-12 11:30:25 -08:00
20bed46737 docs(Location): updating Location docs and adding example
closes #11500
2016-12-12 11:19:21 -08:00
2a5012d515 chore: Add @types/systemjs 2016-12-12 11:19:05 -08:00
fb38fba8f9 chore: convert hash_location_strategy example to a tested spec 2016-12-12 11:19:05 -08:00
4c35be3e07 feat(forms): add novalidate by default (#13092) 2016-12-12 11:17:42 -08:00
e9f307f948 fix(forms): fix Validators.min/maxLength with FormArray (#13095)
Fixes #13089
2016-12-12 11:17:12 -08:00
2e500cc85b fix(http): create a copy of headers when merge options (#13365)
Closes #11980
2016-12-12 11:16:34 -08:00
56dce0e26d feat(common): export NgLocaleLocalization (#13367)
Closes #11921
2016-12-12 11:16:12 -08:00
8a8c53250e fix(dom_adapter): remove logError from logGroup (#12925) 2016-12-09 15:40:26 -08:00
08ff2e5249 fix(http): check response body text against undefined (#13017) 2016-12-09 15:39:39 -08:00
a006c1418a feat(router): routerLink add tabindex attribute (#13094)
Fixes #10895
2016-12-09 15:38:50 -08:00
90c223591f feat(http): simplify URLSearchParams creation (#13338)
Closes #8858
2016-12-09 15:38:29 -08:00
aaf6e05f56 refactor(commonn): fix lint issues
closes #13352
2016-12-09 15:37:46 -08:00
3bee521aa4 fix(compiler): support dotted property binding
fixes angular/flex-layout#34
2016-12-09 15:37:41 -08:00
95f48292b1 test(Selector): add a test for dotted attribute names 2016-12-09 15:37:41 -08:00
04cfa1ebdf refactor(Compiler): cleanup 2016-12-09 15:37:41 -08:00
4022173d1e fix(compiler): fix PR 13322 (#13331) 2016-12-09 11:22:44 -08:00
c8baf51f4f style: clang-format the code 2016-12-09 11:19:55 -08:00
b4db73d0bf feat: ngIf now supports else; saves condition to local var
NgIf syntax has been extended to support else clause to display template
when the condition is false. In addition the condition value can now
be stored in local variable, for later reuse. This is especially useful
when used with the `async` pipe.

Example:

```
<div *ngIf="userObservable | async; else loading; let user">
  Hello {{user.last}}, {{user.first}}!
</div>
<template #loading>Waiting...</template>
```

closes #13061
closes #13297
2016-12-09 11:19:08 -08:00
e15a3f273f fix: Better instructions on running examples and their tests 2016-12-09 11:16:49 -08:00
213c713409 fix: Better error when directive not listed in NgModule.declarations 2016-12-09 11:16:28 -08:00
9a8423da36 fix(selector): SelectorMatcher match elements with :not selector (#12977) 2016-12-09 10:45:48 -08:00
f0b0762f4a fix(animations): always cleanup players after they have finished internally (#13334)
Closes #13333
Closes #13334
2016-12-09 10:45:10 -08:00
b5c4bf1c59 refactor(router): misc refactoring (#13330) 2016-12-09 10:44:46 -08:00
56c361ff6a test(compiler): test i18n explicit id
closes #13272
2016-12-09 10:43:57 -08:00
562f7a2f8b feat(compiler): digest methods return i18nMessage id if sets 2016-12-09 10:43:47 -08:00
6dd5201765 feat(compiler): add id property to i18nMessage 2016-12-09 10:43:47 -08:00
72361fb68f feat(platform browser): introduce Meta service (#12322) 2016-12-08 18:44:28 -08:00
5c6ec20c7e refactor(router): simplify regexp
closes #11373
closes #13329
2016-12-08 18:43:17 -08:00
440ef02f29 fix(router): add support for query params with mulitple values
closes #11373
2016-12-08 18:42:58 -08:00
4e3d58a792 Revert "fix(compiler): fix transpiled ES5 code (#13322)"
This reverts commit 4398056146.
2016-12-08 17:53:58 -08:00
61d7c1e0b3 feat(common): add a titlecase pipe (#13324)
closes #11436
2016-12-08 16:33:24 -08:00
bf93389615 doc: update triage owners for language service and router (#13325) 2016-12-08 15:42:34 -08:00
4398056146 fix(compiler): fix transpiled ES5 code (#13322)
fixes #13301

The inner class would transpile to a nested function declaration which is not
allowed in ES5.

See http://eslint.org/docs/rules/no-inner-declarations
2016-12-08 15:02:59 -08:00
1b547886d0 build(tslint): enable no-inner-declarations (#13316) 2016-12-08 13:46:08 -08:00
9591a08dfb fix(router): Use T type in Resolve interface (#13242) 2016-12-08 11:24:38 -08:00
65965c27a8 docs(changelog): fix a typo (#13298) 2016-12-08 11:23:57 -08:00
311 changed files with 4776 additions and 6517 deletions

2
.nvmrc
View File

@ -1 +1 @@
6.9.5
6.6.0

View File

@ -1,248 +0,0 @@
# Configuration for pullapprove.com
#
# Approval access and primary role is determined by info in the project ownership spreadsheet:
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
#
# === GitHub username to Full name map ===
#
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# chuckjaz - Chuck Jazdzewski
# gkalpak - George Kalpakas
# IgorMinar - Igor Minar
# kara - Kara Erickson
# matsko - Matias Niemelä
# mhevery - Misko Hevery
# petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# tbosch - Tobias Bosch
# vicb - Victor Berchet
# vikerman - Vikram Subramanian
version: 2
group_defaults:
required: 1
reset_on_reopened:
enabled: true
approve_by_comment:
enabled: false
groups:
root:
conditions:
files:
include:
- "*"
exclude:
- "angular.io/*"
- "integration/*"
- "modules/*"
- "tools/*"
users:
- IgorMinar
- mhevery
public-api:
conditions:
files:
include:
- "tools/public_api_guard/*"
users:
- IgorMinar
- mhevery
build-and-ci:
conditions:
files:
include:
- "*.yml"
- "*.json"
- "*.lock"
- "tools/*"
exclude:
- "tools/@angular/tsc-wrapped/*"
- "tools/public_api_guard/*"
users:
- IgorMinar #primary
- mhevery
integration:
conditions:
files:
- "integration/*"
users:
- alexeagle
- mhevery
- tbosch
- vicb
- IgorMinar #fallback
core:
conditions:
files:
- "modules/@angular/core/*"
users:
- tbosch #primary
- mhevery
- vicb
- IgorMinar #fallback
compiler/animations:
conditions:
files:
- "modules/@angular/compiler/src/animation/*"
users:
- matsko #primary
- tbosch
- IgorMinar #fallback
- mhevery #fallback
compiler/i18n:
conditions:
files:
- "modules/@angular/compiler/src/i18n/*"
users:
- vicb #primary
- tbosch
- IgorMinar #fallback
- mhevery #fallback
compiler:
conditions:
files:
- "modules/@angular/compiler/*"
users:
- tbosch #primary
- vicb
- chuckjaz
- mhevery
- IgorMinar #fallback
compiler-cli:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "modules/@angular/compiler-cli/*"
users:
- alexeagle
- chuckjaz
- tbosch
- IgorMinar #fallback
- mhevery #fallback
common:
conditions:
files:
- "modules/@angular/common/*"
users:
- pkozlowski-opensource #primary
- vicb
- IgorMinar #fallback
- mhevery #fallback
forms:
conditions:
files:
- "modules/@angular/forms/*"
users:
- kara #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
http:
conditions:
files:
- "modules/@angular/http/*"
users:
- vikerman #primary
- alxhub
- IgorMinar #fallback
- mhevery #fallback
language-service:
conditions:
files:
- "modules/@angular/language-service/*"
users:
- chuckjaz #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
router:
conditions:
files:
- "modules/@angular/router/*"
users:
- vicb #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
upgrade:
conditions:
files:
- "modules/@angular/upgrade/*"
users:
- petebacondarwin #primary
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
platform-browser:
conditions:
files:
- "modules/@angular/platform-browser/*"
users:
- tbosch #primary
- vicb #secondary
- IgorMinar #fallback
- mhevery #fallback
platform-server:
conditions:
files:
- "modules/@angular/platform-server/*"
users:
- vikerman #primary
- alxhub
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
platform-webworker:
conditions:
files:
- "modules/@angular/platform-webworker/*"
users:
- vicb #primary
- tbosch #secondary
- IgorMinar #fallback
- mhevery #fallback
benchpress:
conditions:
files:
- "modules/@angular/benchpress/*"
users:
- tbosch #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
angular.io:
conditions:
files:
- "angular.io/*"
users:
- IgorMinar
- robwormald
- petebacondarwin
- mhevery #fallback

View File

@ -1,7 +1,7 @@
language: node_js
sudo: false
node_js:
- '6.9.5'
- '6.6.0'
addons:
# firefox: "38.0"
@ -23,9 +23,9 @@ cache:
env:
global:
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# GITHUB_TOKEN_ANGULAR
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=js

View File

@ -1,152 +1,11 @@
<a name="2.4.10"></a>
## [2.4.10](https://github.com/angular/angular/compare/2.4.9...2.4.10) (2017-03-17)
<a name="4.0.0-beta.1"></a>
# [4.0.0-beta.1](https://github.com/angular/angular/compare/2.4.0-marker...4.0.0-beta.1) (2016-12-22)
### Features
### Bug Fixes
* **compiler:** fix decoding surrogate pairs ([#15154](https://github.com/angular/angular/issues/15154)) ([e5c9bbc](https://github.com/angular/angular/commit/e5c9bbc))
* **router:** do not finish bootstrap until all the routes are resolved ([#15121](https://github.com/angular/angular/issues/15121)) ([34403cd](https://github.com/angular/angular/commit/34403cd))
<a name="2.4.9"></a>
## [2.4.9](https://github.com/angular/angular/compare/2.4.8...2.4.9) (2017-03-02)
### Bug Fixes
* **http:** Make ResponseOptionsArgs an interface ([b658fa9](https://github.com/angular/angular/commit/b658fa9)), closes [#13708](https://github.com/angular/angular/issues/13708)
* **router:** improve robustness ([#14602](https://github.com/angular/angular/issues/14602)) ([2a12346](https://github.com/angular/angular/commit/2a12346))
### Reverts
* fix(router): do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([de36f8a](https://github.com/angular/angular/commit/de36f8a)), closes [#14681](https://github.com/angular/angular/issues/14681) [#14588](https://github.com/angular/angular/issues/14588)
<a name="2.4.8"></a>
## [2.4.8](https://github.com/angular/angular/compare/2.4.7...2.4.8) (2017-02-18)
### Bug Fixes
* **forms:** getRawValue should correctly work with nested FormGroups/Arrays ([#12964](https://github.com/angular/angular/issues/12964)) ([ea7737e](https://github.com/angular/angular/commit/ea7737e)), closes [#12963](https://github.com/angular/angular/issues/12963)
* **http:** REVERT: remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([9ceb5d1](https://github.com/angular/angular/commit/9ceb5d1))
* **platform-browser:** should only add styles with native encapsulation in shadow DOM ([#14313](https://github.com/angular/angular/issues/14313)) ([fadaf1e](https://github.com/angular/angular/commit/fadaf1e)), closes [#7887](https://github.com/angular/angular/issues/7887)
* **router:** do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([541de26](https://github.com/angular/angular/commit/541de26)), closes [#12162](https://github.com/angular/angular/issues/12162)
* **upgrade:** correctly project content on downgraded components with structural directives ([#14274](https://github.com/angular/angular/issues/14274)) ([74cb575](https://github.com/angular/angular/commit/74cb575)), closes [#14260](https://github.com/angular/angular/issues/14260)
* **upgrade:** pass correct values to `ngOnChanges` for interpolation bindings ([#14400](https://github.com/angular/angular/issues/14400)) ([7c87c52](https://github.com/angular/angular/commit/7c87c52))
<a name="2.4.7"></a>
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
### Bug Fixes
* **upgrade:** allow non-element selectors for downgraded components ([#14291](https://github.com/angular/angular/issues/14291)) ([5bb47db](https://github.com/angular/angular/commit/5bb47db))
<a name="2.4.6"></a>
## [2.4.6](https://github.com/angular/angular/compare/2.4.5...2.4.6) (2017-02-03)
### Bug Fixes
* **common:** add PopStateEvent interface ([#13400](https://github.com/angular/angular/issues/13400)) ([71567d1](https://github.com/angular/angular/commit/71567d1)), closes [#13378](https://github.com/angular/angular/issues/13378)
* **common:** DatePipe does't throw for NaN ([#14117](https://github.com/angular/angular/issues/14117)) ([32cc675](https://github.com/angular/angular/commit/32cc675)), closes [#14103](https://github.com/angular/angular/issues/14103)
* **common:** DatePipe parses input string if it's not a valid date in browser ([#13895](https://github.com/angular/angular/issues/13895)) ([e641636](https://github.com/angular/angular/commit/e641636)), closes [#12334](https://github.com/angular/angular/issues/12334) [#13874](https://github.com/angular/angular/issues/13874)
* **common:** introduce isObservable method ([#14067](https://github.com/angular/angular/issues/14067)) ([109f0d1](https://github.com/angular/angular/commit/109f0d1)), closes [#8848](https://github.com/angular/angular/issues/8848)
* **compiler:** allow empty translations for attributes ([#14085](https://github.com/angular/angular/issues/14085)) ([f3d5506](https://github.com/angular/angular/commit/f3d5506)), closes [#13897](https://github.com/angular/angular/issues/13897)
* **core:** add bootstrapped modules into platform modules list ([#13740](https://github.com/angular/angular/issues/13740)) ([250dbc4](https://github.com/angular/angular/commit/250dbc4)), closes [#12015](https://github.com/angular/angular/issues/12015)
* **core:** ViewContainerRef.indexOf should not throw error when empty ([#13220](https://github.com/angular/angular/issues/13220)) ([41b8d95](https://github.com/angular/angular/commit/41b8d95))
* **forms:** show a blank line when nothing is selected in IE or Edge ([#13903](https://github.com/angular/angular/issues/13903)) ([09e2d20](https://github.com/angular/angular/commit/09e2d20)), closes [#10010](https://github.com/angular/angular/issues/10010)
* **forms:** verify functions passed into async validators returns Observable or Promise ([#14053](https://github.com/angular/angular/issues/14053)) ([774e1db](https://github.com/angular/angular/commit/774e1db))
* ngModel should use rxjs/symbol/observable to detect observable ([#14236](https://github.com/angular/angular/issues/14236)) ([7e639aa](https://github.com/angular/angular/commit/7e639aa))
* **http:** remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([1eece50](https://github.com/angular/angular/commit/1eece50))
* **i18n:** parse ICU messages while normalizing templates ([#14153](https://github.com/angular/angular/issues/14153)) ([8d4aa82](https://github.com/angular/angular/commit/8d4aa82))
* **language-service:** do not crash when Angular cannot be located ([#14123](https://github.com/angular/angular/issues/14123)) ([a5b4af0](https://github.com/angular/angular/commit/a5b4af0)), closes [#14122](https://github.com/angular/angular/issues/14122)
* **platform-browser:** remove style nodes on destroy ([#13744](https://github.com/angular/angular/issues/13744)) ([0614289](https://github.com/angular/angular/commit/0614289)), closes [#11746](https://github.com/angular/angular/issues/11746)
* **router:** fix CanActivate redirect to the root on initial load ([#13929](https://github.com/angular/angular/issues/13929)) ([a047124](https://github.com/angular/angular/commit/a047124)), closes [#13530](https://github.com/angular/angular/issues/13530)
* **router:** should find guard provided in a lazy loaded module ([#13989](https://github.com/angular/angular/issues/13989)) ([0965636](https://github.com/angular/angular/commit/0965636)), closes [#12275](https://github.com/angular/angular/issues/12275)
* **router:** should allow navigation from root component in ngOnInit hook ([#13932](https://github.com/angular/angular/issues/13932)) ([4d2901d](https://github.com/angular/angular/commit/4d2901d)), closes [#13795](https://github.com/angular/angular/issues/13795)
* **testing:** async/fakeAsync/inject/withModule helpers should pass through context to callback functions ([#13718](https://github.com/angular/angular/issues/13718)) ([70bbdf5](https://github.com/angular/angular/commit/70bbdf5))
* **upgrade:** detect async downgrade component changes ([#14039](https://github.com/angular/angular/issues/14039)) ([117fa79](https://github.com/angular/angular/commit/117fa79)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385)
<a name="2.4.5"></a>
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
### Bug Fixes
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
<a name="2.4.4"></a>
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
### Bug Fixes
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
<a name="2.4.3"></a>
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
### Bug Fixes
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
<a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
### Bug Fixes
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
* **upgrade:** support the `$doCheck()` lifecycle hook in `UpgradeComponent` ([#13015](https://github.com/angular/angular/issues/13015)) ([9da4c25](https://github.com/angular/angular/commit/9da4c25))
Note: 4.0.0-beta.1 release also contains all the changes present in the 2.4.0 and the 2.4.1 releases.
<a name="2.4.1"></a>
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
@ -180,6 +39,25 @@
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
<a name="4.0.0-beta.0"></a>
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
### Features
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
<a name="2.3.1"></a>
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
@ -226,15 +104,14 @@
### Note ###
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
<a name="2.3.0"></a>
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)

View File

@ -6,16 +6,29 @@ for details about how we maintain a linear commit history, and the rules for com
As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request.
Someone with committer access will do the rest.
# Change approvals
## The `PR: merge` label and `presubmit-*` branches
Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for
Angular committers and also prevent breakages on master.
When a PR has `pr_state: LGTM` and is ready to merge, you should add the `pr_action: merge` label.
Currently (late 2015), we need to ensure that each PR will cleanly merge into the Google-internal version control,
so the caretaker reviews the changes manually.
# Merging
After this review, the caretaker adds `zomg_admin: do_merge` which is restricted to admins only.
A robot running as [mary-poppins](https://github.com/mary-poppins)
is notified that the label was added by an authorized person,
and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`.
Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with "PR: merge" label.
This signals to the caretaker that the PR should be merged.
(Note: if the automation fails, committers can instead push the commits to a branch following this naming scheme.)
# Who is the Caretaker?
When a Travis build succeeds for a presubmit branch named following the convention,
Travis will re-base the commits, merge to master, and close the PR automatically.
See [this explanation](https://twitter.com/IgorMinar/status/799365744806854656).
Finally, after merge `mary-poppins` removes the presubmit branch.
## Administration
The list of users who can trigger a merge by adding the `zomg_admin: do_merge` label is stored in our appengine app datastore.
Edit the contents of the [CoreTeamMember Table](
https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember)

View File

@ -71,13 +71,7 @@ particular `gulp` and `protractor` commands. If you prefer, you can drop this pa
Since global installs can become stale, and required versions can vary by project, we avoid their
use in these instructions.
*Option 2*: globally installing the package `npm-run` by running `npm install -g npm-run`
(you might need to prefix this command with `sudo`). You will then be able to run locally installed
package scripts by invoking: e.g., `npm-run gulp build`
(see [npm-run project page](https://github.com/timoxley/npm-run) for more details).
*Option 3*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Installing Bower Modules
@ -139,8 +133,7 @@ If you happen to modify the public API of Angular, API golden files must be upda
$ gulp public-api:update
```
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
the project before trying to verify after an API change.
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
## <a name="clang-format"></a> Formatting your source code
@ -153,14 +146,6 @@ You can automatically format your code by running:
$ gulp format
```
## Linting/verifying your source code
You can check that your code is properly formatted and adheres to coding style by running:
``` shell
$ gulp lint
```
## Publishing your own personal snapshot build
You may find that your un-merged change needs some validation from external participants.

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2014-2017 Google, Inc. http://angular.io
Copyright (c) 2014-2016 Google, Inc. http://angular.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,10 +1,10 @@
machine:
node:
version: 6.9.5
version: 5.4.1
dependencies:
pre:
- npm install -g npm@3.10.7
- npm install -g npm@3.6.0
test:
override:

View File

@ -19,7 +19,7 @@ One intentional omission from this list is `@angular/compiler`, which is current
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
Other projects developed by the Angular team like angular-cli, Angular Material, will be covered by these or similar guarantees in the future as they mature.
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature.
Within the supported packages, we provide guarantees for:
@ -37,4 +37,4 @@ We explicitly don't consider the following to be our public API surface:
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
Our peer dependencies (e.g. TypeScript, Zone.js, or RxJS) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.

View File

@ -11,8 +11,8 @@
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
// This is to ensure that we catch env issues before we error while requiring other dependencies.
require('./tools/check-environment')({
requiredNpmVersion: '>=3.10.7 <4.0.0',
requiredNodeVersion: '>=6.9.5 <7.0.0',
requiredNpmVersion: '>=3.5.3 <4.0.0',
requiredNodeVersion: '>=5.4.1 <7.0.0',
});
const gulp = require('gulp');

View File

@ -12,9 +12,9 @@
* Entry point for all public APIs of the common package.
*/
export * from './location/index';
export {NgLocalization} from './localization';
export {NgLocaleLocalization, NgLocalization} from './localization';
export {CommonModule} from './common_module';
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
export {VERSION} from './version';
export {Version} from '@angular/core';

View File

@ -9,7 +9,7 @@
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection';
import {stringify} from '../facade/lang';
import {isPresent, stringify} from '../facade/lang';
/**
* @ngModule CommonModule
@ -25,8 +25,6 @@ import {stringify} from '../facade/lang';
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
*
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
*
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
* ```
*
* @description
@ -134,7 +132,7 @@ export class NgClass implements DoCheck {
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else {
Object.keys(rawClassVal).forEach(klass => {
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
});
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang';
@ -91,13 +91,8 @@ export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any;
@Input()
set ngForTrackBy(fn: TrackByFn) {
if (isDevMode() && fn != null && typeof fn !== 'function') {
// TODO(vicb): use a log service once there is a public one available
if (<any>console && <any>console.warn) {
console.warn(
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
}
if (typeof fn !== 'function') {
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
}
this._trackByFn = fn;
}

View File

@ -6,46 +6,152 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* Removes or recreates a portion of the DOM tree based on an {expression}.
* Conditionally includes a template based on the value of an `expression`.
*
* If the expression assigned to `ngIf` evaluates to a falsy value then the element
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
* when expression is thruthy or falsy respectively. Typically the:
* - `then` template is the inline template of `ngIf` unless bound to a different value.
* - `else` template is blank unless its bound.
*
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
* # Most common usage
*
* The most common usage of the `ngIf` is to conditionally show the inline template as seen in this
* example:
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
*
* # Showing an alternative template using `else`
*
* If it is necessary to display a template when the `expression` is falsy use the `else` template
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
* The template can be defined anywhere in the component view but is typically placed right after
* `ngIf` for readability.
*
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
*
* # Using non-inlined `then` template
*
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
* change at runtime as shown in thise example.
*
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
*
* # Storing conditional result in a variable
*
* A common patter is that we need to show a set of properties from the same object. if the
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
* dereferencing a `null` value. This is especially the case when waiting on async data such as
* when using the `async` pipe as shown in folowing example:
*
* ```
* <div *ngIf="errorCount > 0" class="error">
* <!-- Error message displayed when the errorCount property in the current context is greater
* than 0. -->
* {{errorCount}} errors detected
* </div>
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
* ```
*
* There are several inefficiencies in the above example.
* - We create multiple subscriptions on the `userStream`. One for each `async` pipe, or two
* as shown in the example above.
* - We can not display an alternative screen while waiting for the data to arrive asynchronously.
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
* - We have to place the `async` pipe in parenthesis.
*
* A better way to do this is to use `ngIf` and store the result of the condition in a local
* variable as shown in the the example below:
*
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
*
* Notice that:
* - We use only one `async` pipe and hence only one subscription gets created.
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
* - The local `user` can than be bound repeatedly in a more efficient way.
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
* display the data if `userStream` returns a value.
* - We can display an alternative template while waiting for the data.
*
* ### Syntax
*
* Simple form:
* - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>`
* - `<template [ngIf]="condition"><div>...</div></template>`
*
* Form with an else block:
* ```
* <div *ngIf="condition; else elseBlock">...</div>
* <template #elseBlock>...</template>
* ```
*
* Form with a `then` and `else` block:
* ```
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
* <template #thenBlock>...</template>
* <template #elseBlock>...</template>
* ```
*
* Form with storing the value locally:
* ```
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
* <template #elseBlock>...</template>
* ```
*
* @stable
*/
@Directive({selector: '[ngIf]'})
export class NgIf {
private _hasView = false;
private _context: NgIfContext = new NgIfContext();
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
}
@Input()
set ngIf(condition: any) {
if (condition && !this._hasView) {
this._hasView = true;
this._viewContainer.createEmbeddedView(this._template);
} else if (!condition && this._hasView) {
this._hasView = false;
this._viewContainer.clear();
this._context.$implicit = condition;
this._updateView();
}
@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any.
this._updateView();
}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any.
this._updateView();
}
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
this._elseViewRef = null;
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainer.clear();
this._thenViewRef = null;
if (this._elseTemplateRef) {
this._elseViewRef =
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
}
}
}
}
}
export class NgIfContext { public $implicit: any = null; }

View File

@ -21,9 +21,10 @@ import {SwitchView} from './ng_switch';
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <template ngPluralCase="=0">there is nothing</template>
* <template ngPluralCase="=1">there is one</template>
* <template ngPluralCase="few">there are a few</template>
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
* </some-element>
* ```
*
@ -89,8 +90,8 @@ export class NgPlural {
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <template ngPluralCase="=0">...</template>
* <template ngPluralCase="other">...</template>
* <ng-container *ngPluralCase="'=0'">...</ng-container>
* <ng-container *ngPluralCase="'other'">...</ng-container>
* </some-element>
*```
*
@ -103,7 +104,6 @@ export class NgPluralCase {
constructor(
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
const isANumber: boolean = !isNaN(Number(value));
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
ngPlural.addCase(value, new SwitchView(viewContainer, template));
}
}

View File

@ -49,10 +49,10 @@ export function getPluralCategory(
*/
@Injectable()
export class NgLocaleLocalization extends NgLocalization {
constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
getPluralCategory(value: any): string {
const plural = getPluralCase(this._locale, value);
const plural = getPluralCase(this.locale, value);
switch (plural) {
case Plural.Zero:
@ -431,4 +431,4 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
default:
return Plural.Other;
}
}
}

View File

@ -10,12 +10,6 @@ import {EventEmitter, Injectable} from '@angular/core';
import {LocationStrategy} from './location_strategy';
/** @experimental */
export interface PopStateEvent {
pop?: boolean;
type?: string;
url?: string;
}
/**
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
@ -128,7 +122,7 @@ export class Location {
* Subscribe to the platform's `popState` events.
*/
subscribe(
onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
/**
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
@ -51,12 +50,6 @@ export abstract class PlatformLocation {
abstract back(): void;
}
/**
* @whatItDoes indicates when a location is initialized
* @experimental
*/
export const LOCATION_INITIALIZED = new OpaqueToken('Location Initialized');
/**
* A serializable version of the event from onPopState or onHashChange
*

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, WrappedValue} from '@angular/core';
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
import {EventEmitter, Observable} from '../facade/async';
import {isObservable, isPromise} from '../private_import_core';
import {isPromise} from '../private_import_core';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
interface SubscriptionStrategy {
@ -66,7 +66,7 @@ const _observableStrategy = new ObservableStrategy();
* @stable
*/
@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
export class AsyncPipe implements OnDestroy {
private _latestValue: Object = null;
private _latestReturnedValue: Object = null;
@ -116,7 +116,7 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
return _promiseStrategy;
}
if (isObservable(obj)) {
if ((<any>obj).subscribe) {
return _observableStrategy;
}
@ -131,7 +131,7 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
this._obj = null;
}
private _updateLatestValue(async: any, value: Object): void {
private _updateLatestValue(async: any, value: Object) {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();

View File

@ -0,0 +1,72 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* Transforms text to lowercase.
*
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe' }
*
* @stable
*/
@Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}
/**
* Helper method to transform a single word to titlecase.
*
* @stable
*/
function titleCaseWord(word: string) {
if (!word) return word;
return word[0].toUpperCase() + word.substr(1).toLowerCase();
}
/**
* Transforms text to titlecase.
*
* @stable
*/
@Pipe({name: 'titlecase'})
export class TitleCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(TitleCasePipe, value);
}
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
}
}
/**
* Transforms text to uppercase.
*
* @stable
*/
@Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -7,13 +7,13 @@
*/
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {NumberWrapper} from '../facade/lang';
import {NumberWrapper, isDate} from '../facade/lang';
import {DateFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
const ISO8601_DATE_REGEX =
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
// 1 2 3 4 5 6 7 8 9 10 11
/**
* @ngModule CommonModule
@ -24,7 +24,7 @@ const ISO8601_DATE_REGEX =
* Where:
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime).
* - `format` indicates which date/time components to include. The format can be predefined as
* - `format` indicates which date/time components to include. The format can be predifined as
* shown below or custom as shown in the table.
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
@ -103,7 +103,7 @@ export class DatePipe implements PipeTransform {
transform(value: any, pattern: string = 'mediumDate'): string {
let date: Date;
if (isBlank(value) || value !== value) return null;
if (isBlank(value)) return null;
if (typeof value === 'string') {
value = value.trim();
@ -130,12 +130,7 @@ export class DatePipe implements PipeTransform {
}
if (!isDate(date)) {
let match: RegExpMatchArray;
if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
date = isoStringToDate(match);
} else {
throw new InvalidPipeArgumentError(DatePipe, value);
}
throw new InvalidPipeArgumentError(DatePipe, value);
}
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
@ -145,31 +140,3 @@ export class DatePipe implements PipeTransform {
function isBlank(obj: any): boolean {
return obj == null || obj === '';
}
function isDate(obj: any): obj is Date {
return obj instanceof Date && !isNaN(obj.valueOf());
}
function isoStringToDate(match: RegExpMatchArray): Date {
const date = new Date(0);
let tzHour = 0;
let tzMin = 0;
const dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear;
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
tzHour = toInt(match[9] + match[10]);
tzMin = toInt(match[9] + match[11]);
}
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
const h = toInt(match[4] || '0') - tzHour;
const m = toInt(match[5] || '0') - tzMin;
const s = toInt(match[6] || '0');
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
function toInt(str: string): number {
return parseInt(str, 10);
}

View File

@ -7,6 +7,7 @@
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -34,7 +35,7 @@ export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {}
transform(value: number, pluralMap: {[count: string]: string}): string {
if (value == null) return '';
if (isBlank(value)) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) {
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);

View File

@ -12,14 +12,13 @@
* This module provides a set of common Pipes.
*/
import {AsyncPipe} from './async_pipe';
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
import {DatePipe} from './date_pipe';
import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe';
import {JsonPipe} from './json_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
import {SlicePipe} from './slice_pipe';
import {UpperCasePipe} from './uppercase_pipe';
export {
AsyncPipe,
@ -32,9 +31,11 @@ export {
LowerCasePipe,
PercentPipe,
SlicePipe,
TitleCasePipe,
UpperCasePipe
};
/**
* A collection of Angular pipes that are likely to be used in each and every application.
*/
@ -46,6 +47,7 @@ export const COMMON_PIPES = [
SlicePipe,
DecimalPipe,
PercentPipe,
TitleCasePipe,
CurrencyPipe,
DatePipe,
I18nPluralPipe,

View File

@ -1,37 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Transforms string to lowercase.
* @howToUse `expression | lowercase`
* @description
*
* Converts value into a lowercase string using `String.prototype.toLowerCase()`.
*
* ### Example
*
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
*
* @stable
*/
@Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}

View File

@ -8,7 +8,7 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberWrapper} from '../facade/lang';
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
import {NumberFormatStyle, NumberFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
function formatNumber(
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
if (value == null) return null;
if (isBlank(value)) return null;
// Convert strings to numbers
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
@ -41,13 +41,13 @@ function formatNumber(
if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`);
}
if (parts[1] != null) { // min integer digits
if (isPresent(parts[1])) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
}
if (parts[3] != null) { // min fraction digits
if (isPresent(parts[3])) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
}
if (parts[5] != null) { // max fraction digits
if (isPresent(parts[5])) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
}
}

View File

@ -7,6 +7,7 @@
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
@ -57,7 +58,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any {
if (value == null) return value;
if (isBlank(value)) return value;
if (!this.supports(value)) {
throw new InvalidPipeArgumentError(SlicePipe, value);

View File

@ -1,36 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Transforms string to uppercase.
* @howToUse `expression | uppercase`
* @description
*
* Converts value into an uppercase string using `String.prototype.toUpperCase()`.
*
* ### Example
*
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
*
* @stable
*/
@Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -9,4 +9,3 @@
import {__core_private__ as r} from '@angular/core';
export const isPromise: typeof r.isPromise = r.isPromise;
export const isObservable: typeof r.isObservable = r.isObservable;

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {Component, ContentChild, TemplateRef} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers';
@ -29,7 +29,10 @@ export function main() {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
declarations: [
TestComponent,
ComponentUsingTestComponent,
],
imports: [CommonModule],
});
});
@ -74,7 +77,7 @@ export function main() {
}));
it('should iterate over an array of objects', async(() => {
const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
fixture = createTestComponent(template);
// INIT
@ -92,7 +95,7 @@ export function main() {
}));
it('should gracefully handle nulls', async(() => {
const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
fixture = createTestComponent(template);
detectChangesAndExpectText('');
@ -137,8 +140,12 @@ export function main() {
}));
it('should repeat over nested arrays', async(() => {
const template = '<div *ngFor="let item of items">' +
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
const template = '<div>' +
'<div template="ngFor let item of items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>';
fixture = createTestComponent(template);
@ -150,9 +157,10 @@ export function main() {
}));
it('should repeat over nested arrays with no intermediate element', async(() => {
const template = '<div *ngFor="let item of items">' +
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
'</div>';
const template = '<div><template ngFor let-item [ngForOf]="items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div></template></div>';
fixture = createTestComponent(template);
getComponent().items = [['a', 'b'], ['c']];
@ -162,11 +170,10 @@ export function main() {
detectChangesAndExpectText('e-1;f-2;g-2;');
}));
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
const template = `<div *ngFor="let item of items; let i=index">` +
`<div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div>` +
`</div>`;
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
const template =
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
fixture = createTestComponent(template);
@ -182,7 +189,8 @@ export function main() {
}));
it('should display indices correctly', async(() => {
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
const template =
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -194,7 +202,7 @@ export function main() {
it('should display first item correctly', async(() => {
const template =
'<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>';
fixture = createTestComponent(template);
getComponent().items = [0, 1, 2];
@ -206,7 +214,7 @@ export function main() {
it('should display last item correctly', async(() => {
const template =
'<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>';
fixture = createTestComponent(template);
getComponent().items = [0, 1, 2];
@ -218,7 +226,7 @@ export function main() {
it('should display even items correctly', async(() => {
const template =
'<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>';
fixture = createTestComponent(template);
getComponent().items = [0, 1, 2];
@ -230,7 +238,7 @@ export function main() {
it('should display odd items correctly', async(() => {
const template =
'<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>';
fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3];
@ -241,57 +249,64 @@ export function main() {
}));
it('should allow to use a custom template', async(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c'];
const tcTemplate =
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>';
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}});
const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
detectChangesAndExpectText('0: a;1: b;2: c;');
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
}));
it('should use a default template if a custom one is null', async(() => {
const template =
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c'];
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`;
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
detectChangesAndExpectText('0: a;1: b;2: c;');
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
}));
it('should use a custom template when both default and a custom one are present', async(() => {
const template =
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c'];
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`;
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
const cutTemplate =
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
detectChangesAndExpectText('0: a;1: b;2: c;');
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
}));
describe('track by', () => {
it('should console.warn if trackBy is not a function', async(() => {
// TODO(vicb): expect a warning message when we have a proper log service
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
it('should throw if trackBy is not a function', async(() => {
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
fixture = createTestComponent(template);
fixture.componentInstance.value = 0;
fixture.detectChanges();
}));
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
// TODO(vicb): expect no warning message when we have a proper log service
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
fixture = createTestComponent(template);
fixture.componentInstance.items = ['a', 'b', 'c'];
fixture.componentInstance.value = null;
detectChangesAndExpectText('abc');
fixture.componentInstance.value = undefined;
detectChangesAndExpectText('abc');
getComponent().items = [{id: 1}, {id: 2}];
expect(() => fixture.detectChanges())
.toThrowError(/trackBy must be a function, but received null/);
}));
it('should set the context to the component instance', async(() => {
const template =
`<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
fixture = createTestComponent(template);
thisArg = null;
@ -301,7 +316,9 @@ export function main() {
it('should not replace tracked items', async(() => {
const template =
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
<p>{{items[i]}}</p>
</template>`;
fixture = createTestComponent(template);
const buildItemList = () => {
@ -317,7 +334,7 @@ export function main() {
it('should update implicit local variable on view', async(() => {
const template =
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}];
@ -326,10 +343,9 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red');
}));
it('should move items around and keep them updated ', async(() => {
const template =
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
@ -340,7 +356,8 @@ export function main() {
}));
it('should handle added and removed items properly when tracking by index', async(() => {
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c', 'd'];
@ -360,16 +377,21 @@ class Foo {
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
value: any;
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
trackByContext(): void { thisArg = this; }
}
const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
@Component({selector: 'outer-cmp', template: ''})
class ComponentUsingTestComponent {
items: any = [1, 2];
}
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}
}

View File

@ -9,7 +9,6 @@
import {CommonModule} from '@angular/common';
import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
@ -29,114 +28,203 @@ export function main() {
});
it('should work in a template attribute', async(() => {
const template = '<span *ngIf="booleanCondition">hello</span>';
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
}));
it('should work on a template element', async(() => {
const template = '<template [ngIf]="booleanCondition">hello2</template>';
it('should work in a template element', async(() => {
const template =
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello2');
}));
it('should toggle node when condition changes', async(() => {
const template = '<span *ngIf="booleanCondition">hello</span>';
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
fixture = createTestComponent(template);
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
}));
it('should handle nested if correctly', async(() => {
const template =
'<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>';
fixture = createTestComponent(template);
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
getComponent().nestedBooleanCondition = false;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
getComponent().nestedBooleanCondition = true;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
expect(fixture.nativeElement).toHaveText('');
}));
it('should update several nodes with if', async(() => {
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
const template = '<div>' +
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' +
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' +
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3);
expect(getDOM().getText(fixture.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction');
getComponent().numberCondition = 0;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloString');
getComponent().numberCondition = 1;
getComponent().stringCondition = 'bar';
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloNumber');
}));
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
const template = '<span *ngIf="numberCondition">hello</span>';
it('should not add the element twice if the condition goes from true to true (JS)',
async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
let els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
getDOM().addClass(els[0].nativeElement, 'marker');
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
getComponent().numberCondition = 2;
fixture.detectChanges();
els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
expect(getDOM().hasClass(els[0].nativeElement, 'marker')).toBe(true);
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello');
}));
it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
getComponent().numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
.toBe(true);
}));
describe('else', () => {
it('should support else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
'<template #elseBlock>FALSE</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('TRUE');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE');
}));
it('should support then and else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
'<template #thenBlock>THEN</template>' +
'<template #elseBlock>ELSE</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('THEN');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('ELSE');
}));
it('should support dynamic else', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
'<template #b1>FALSE1</template>' +
'<template #b2>FALSE2</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('TRUE');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE1');
getComponent().nestedBooleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('FALSE2');
}));
it('should support binding to variable', async(() => {
const template = '<div>' +
'<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
'<template #elseBlock let-v>{{v}}</template>' +
'</div>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('true');
getComponent().booleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('false');
}));
});
});
}

View File

@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('ngPlural', () => {
describe('switch', () => {
let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; }
@ -33,25 +33,10 @@ export function main() {
});
it('should display the template according to the exact value', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul>';
fixture = createTestComponent(template);
getComponent().switchValue = 0;
detectChangesAndExpectText('you have no messages.');
getComponent().switchValue = 1;
detectChangesAndExpectText('you have one message.');
}));
it('should display the template according to the exact numeric value', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
@ -66,9 +51,10 @@ export function main() {
// https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul>';
'</ul></div>';
fixture = createTestComponent(template);
@ -78,10 +64,11 @@ export function main() {
it('should be applicable to <ng-container> elements', async(() => {
const template = '<ng-container [ngPlural]="switchValue">' +
const template = '<div>' +
'<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container>';
'</ng-container></div>';
fixture = createTestComponent(template);
@ -93,10 +80,11 @@ export function main() {
}));
it('should display the template according to the category', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul>';
'</ul></div>';
fixture = createTestComponent(template);
@ -108,10 +96,11 @@ export function main() {
}));
it('should default to other when no matches are found', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul>';
'</ul></div>';
fixture = createTestComponent(template);
@ -120,10 +109,11 @@ export function main() {
}));
it('should prioritize value matches over category matches', async(() => {
const template = '<ul [ngPlural]="switchValue">' +
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'</ul>';
'</ul></div>';
fixture = createTestComponent(template);

View File

@ -29,19 +29,22 @@ export function main() {
it('should add styles specified in an object literal', async(() => {
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
}));
it('should add and change styles specified in an object expression', async(() => {
const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template);
let expr: {[k: string]: string};
getComponent().expr = {'max-width': '40px'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
let expr = getComponent().expr;
expr = getComponent().expr;
expr['max-width'] = '30%';
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});

View File

@ -33,10 +33,11 @@ export function main() {
describe('switch value changes', () => {
it('should switch amongst when values', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchCase="\'a\'">when a</li>' +
'<li *ngSwitchCase="\'b\'">when b</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a</li></template>' +
'<template ngSwitchCase="b"><li>when b</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
@ -50,10 +51,11 @@ export function main() {
});
it('should switch amongst when values with fallback to default', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchCase="\'a\'">when a</li>' +
'<li *ngSwitchDefault>when default</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchCase \'a\'">when a</li>' +
'<li template="ngSwitchDefault">when default</li>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default');
@ -69,14 +71,15 @@ export function main() {
});
it('should support multiple whens with the same value', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
'<li *ngSwitchDefault>when default1;</li>' +
'<li *ngSwitchDefault>when default2;</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
@ -91,11 +94,12 @@ export function main() {
describe('when values changes', () => {
it('should switch amongst when values', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchCase="when1">when 1;</li>' +
'<li *ngSwitchCase="when2">when 2;</li>' +
'<li *ngSwitchDefault>when default;</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
'<template ngSwitchDefault><li>when default;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
getComponent().when1 = 'a';
@ -144,10 +148,11 @@ export function main() {
});
it('should create the default case if there is no other case', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchDefault>when default1;</li>' +
'<li *ngSwitchDefault>when default2;</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
@ -155,14 +160,15 @@ export function main() {
});
it('should allow defaults before cases', () => {
const template = '<ul [ngSwitch]="switchValue">' +
'<li *ngSwitchDefault>when default1;</li>' +
'<li *ngSwitchDefault>when default2;</li>' +
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
'</ul>';
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');

View File

@ -34,22 +34,29 @@ export function main() {
});
});
it('should do nothing if templateRef is `null`', async(() => {
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
it('should do nothing if templateRef is null', async(() => {
const template = `<template [ngTemplateOutlet]="null"></template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
}));
it('should insert content specified by TemplateRef', async(() => {
const template = `<template #tpl>foo</template>` +
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
}));
it('should clear content if TemplateRef becomes `null`', async(() => {
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
it('should clear content if TemplateRef becomes null', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
@ -63,8 +70,7 @@ export function main() {
it('should swap content if TemplateRef changes', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
fixture = createTestComponent(template);
fixture.detectChanges();
@ -77,47 +83,70 @@ export function main() {
detectChangesAndExpectText('bar');
}));
it('should display template if context is `null`', async(() => {
const template = `<template #tpl>foo</template>` +
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
it('should display template if context is null', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo');
}));
it('should reflect initial context and changes', async(() => {
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
const template =
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar';
detectChangesAndExpectText('alter-bar');
}));
it('should reflect user defined `$implicit` property in the context', async(() => {
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
it('should reflect user defined $implicit property in the context', async(() => {
const template =
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
detectChangesAndExpectText('bra');
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
detectChangesAndExpectText('bar');
}));
it('should reflect context re-binding', async(() => {
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
const template =
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {shawshank: 'brooks'};
detectChangesAndExpectText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here');
}));
});
}
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
@ -133,4 +162,4 @@ function createTestComponent(template: string): ComponentFixture<TestComponent>
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
.createComponent(TestComponent);
}
}

View File

@ -0,0 +1,67 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
export function main() {
describe('LowerCasePipe', () => {
let pipe: LowerCasePipe;
beforeEach(() => { pipe = new LowerCasePipe(); });
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
it('should lowercase when there is a new value', () => {
expect(pipe.transform('FOO')).toEqual('foo');
expect(pipe.transform('BAr')).toEqual('bar');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
describe('TitleCasePipe', () => {
let pipe: TitleCasePipe;
beforeEach(() => { pipe = new TitleCasePipe(); });
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
it('should return titlecase for subsequent words',
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
it('should persist whitespace',
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
it('should titlecase when there is a new value', () => {
expect(pipe.transform('bar')).toEqual('Bar');
expect(pipe.transform('foo')).toEqual('Foo');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
describe('UpperCasePipe', () => {
let pipe: UpperCasePipe;
beforeEach(() => { pipe = new UpperCasePipe(); });
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
it('should uppercase when there is a new value', () => {
expect(pipe.transform('foo')).toEqual('FOO');
expect(pipe.transform('bar')).toEqual('BAR');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
}

View File

@ -53,13 +53,11 @@ export function main() {
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null));
it('should return null for NaN', () => expect(pipe.transform(Number.NaN)).toEqual(null));
it('should support ISO string without time',
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
it('should not support other objects',
() => expect(() => pipe.transform({})).toThrowError(/Invalid argument/));
() => { expect(() => pipe.transform({})).toThrowError(); });
});
describe('transform', () => {
@ -190,14 +188,8 @@ export function main() {
});
it('should format invalid in IE ISO date',
() => expect(pipe.transform('2017-01-11T09:25:14.014-0500')).toEqual('Jan 11, 2017'));
it('should format invalid in Safari ISO date',
() => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
it('should remove bidi control characters',
() => expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10));
() => { expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10); });
});
});
}

View File

@ -1,42 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {LowerCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() {
describe('LowerCasePipe', () => {
let upper: string;
let lower: string;
let pipe: LowerCasePipe;
beforeEach(() => {
lower = 'something';
upper = 'SOMETHING';
pipe = new LowerCasePipe();
});
describe('transform', () => {
it('should return lowercase', () => {
const val = pipe.transform(upper);
expect(val).toEqual(lower);
});
it('should lowercase when there is a new value', () => {
const val = pipe.transform(upper);
expect(val).toEqual(lower);
const val2 = pipe.transform('WAT');
expect(val2).toEqual('wat');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
});
}

View File

@ -1,43 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {UpperCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
export function main() {
describe('UpperCasePipe', () => {
let upper: string;
let lower: string;
let pipe: UpperCasePipe;
beforeEach(() => {
lower = 'something';
upper = 'SOMETHING';
pipe = new UpperCasePipe();
});
describe('transform', () => {
it('should return uppercase', () => {
const val = pipe.transform(lower);
expect(val).toEqual(upper);
});
it('should uppercase when there is a new value', () => {
const val = pipe.transform(lower);
expect(val).toEqual(upper);
const val2 = pipe.transform('wat');
expect(val2).toEqual('WAT');
});
it('should not support other objects',
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
});
});
}

View File

@ -1,23 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'lazy-feature-comp', template: 'lazy feature, nested!'})
export class LazyFeatureNestedComponent {
}
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyFeatureNestedComponent, pathMatch: 'full'},
])],
declarations: [LazyFeatureNestedComponent]
})
export class LazyFeatureNestedModule {
}

View File

@ -16,10 +16,7 @@ export class LazyFeatureComponent {
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
path: 'nested-feature',
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
}
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
])],
declarations: [LazyFeatureComponent]
})

View File

@ -191,8 +191,6 @@ function lazyRoutesTest() {
'./lazy.module#LazyModule': 'lazy.module.ts',
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
'./feature.module#FeatureModule': 'feature/feature.module.ts',
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
'./default.module': 'feature2/default.module.ts',
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'

View File

@ -15,8 +15,6 @@
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
"outDir": "../node_modules/third_party",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
}
"outDir": "../node_modules/third_party"
}
}

View File

@ -14,9 +14,7 @@
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
"baseUrl": "."
},
"files": [

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "0.5.2",
"@angular/tsc-wrapped": "0.5.0",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},

View File

@ -11,6 +11,7 @@
* Intended to be used in a build step.
*/
import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs';
import * as path from 'path';
@ -18,6 +19,7 @@ import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core';
const GENERATED_META_FILES = /\.json$/;
@ -36,6 +38,29 @@ export class CodeGenerator {
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private ngCompilerHost: CompilerHost) {}
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
codegen(): Promise<any> {
return this.compiler
.compileAll(this.program.getSourceFiles().map(
@ -43,7 +68,7 @@ export class CodeGenerator {
.then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
PREAMBLE + generatedModule.source;
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);

View File

@ -261,29 +261,6 @@ export class CompilerHost implements AotCompilerHost {
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
return !excludeRegex.test(filePath);
}
calculateEmitPath(filePath: string): string {
// Write codegen in a directory structure matching the sources.
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
}
export class CompilerHostContextAdapter {

View File

@ -15,6 +15,8 @@
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
import {NgModule} from '@angular/core';
// We cannot depend directly to @angular/router.
type Route = any;
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
@ -61,37 +63,29 @@ export function listLazyRoutesOfModule(
const className = entryRouteDef.className;
// List loadChildren of this single module.
const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
const routes: LazyRouteMap = {};
const allLazyRoutes = lazyRoutes.reduce(
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
LazyRouteMap {
const route: string = lazyRoute.routeDef.toString();
_assertRoute(allRoutes, lazyRoute);
allRoutes[route] = lazyRoute;
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
const route: string = lazyRoute.routeDef.toString();
_assertRoute(routes, lazyRoute);
routes[route] = lazyRoute;
// StaticReflector does not support discovering annotations like `NgModule` on default
// exports
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
// we cannot parse its routes to see if they have loadChildren or not.
if (!lazyRoute.routeDef.className) {
return allRoutes;
}
const lazyModuleSymbol = reflector.findDeclaration(
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
const lazyModuleSymbol = reflector.findDeclaration(
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
// Populate the map using the routes we just found.
subRoutes.forEach(subRoute => {
_assertRoute(routes, subRoute);
routes[subRoute.routeDef.toString()] = subRoute;
});
});
const subRoutes =
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
},
{});
return allLazyRoutes;
return routes;
}
@ -198,7 +192,7 @@ function _collectRoutes(
*/
function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => {
if (r.loadChildren && typeof r.loadChildren === 'string') {
if (r.loadChildren) {
return m.concat(r.loadChildren);
} else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r));

View File

@ -9,10 +9,10 @@
import {__core_private__ as r} from '@angular/core';
export type ReflectorReader = typeof r._ReflectorReader;
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export type Console = typeof r._Console;
export const Console: typeof r.Console = r.Console;
export var Console: typeof r.Console = r.Console;

View File

@ -122,9 +122,11 @@ export class MockCompilerHost implements ts.CompilerHost {
return ts.getDefaultLibFileName(options);
}
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); }
getCurrentDirectory(): string { return this.context.currentDirectory; }
getCurrentDirectory(): string {
return this.context.currentDirectory;
}
getCanonicalFileName(fileName: string): string { return fileName; }

View File

@ -9,5 +9,5 @@
import {__core_private__ as r} from '@angular/core';
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';

View File

@ -9,10 +9,9 @@
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core';
import {SyntaxError} from '../util';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
@ -311,7 +310,6 @@ export class StaticReflector implements ReflectorReader {
if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
@ -501,15 +499,15 @@ export class StaticReflector implements ReflectorReader {
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, argExpressions);
return simplifyCall(staticSymbol, targetFunction, args);
}
}
break;
@ -539,7 +537,7 @@ export class StaticReflector implements ReflectorReader {
if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column);
}
throw new SyntaxError(message);
throw new Error(message);
}
}
@ -653,6 +651,10 @@ class PopulatedScope extends BindingScope {
}
}
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b;
}
function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
}

View File

@ -273,12 +273,7 @@ export class StaticSymbolResolver {
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
const filePath = this.resolveModule(module, containingFile);
if (!filePath) {
this.reportError(
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
containingFile
} `: ''}`),
null);
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
}
return this.getStaticSymbol(filePath, symbolName);
}

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';

View File

@ -5,16 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {GeneratedFile} from './generated_file';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummarySerializerHost {
/**
* Returns the output file path of a source file.
@ -180,4 +180,4 @@ class Deserializer extends ValueTransformer {
return super.visitStringMap(map, context);
}
}
}
}

View File

@ -15,6 +15,10 @@ import {LifecycleHooks, reflector} from './private_import_core';
import {CssSelector} from './selector';
import {splitAtColon} from './util';
function unimplemented(): any {
throw new Error('unimplemented');
}
// group 0: "[prop] or (event) or @trigger"
// group 1: "prop" from "[prop]"
// group 2: "event" from "(event)"

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';

View File

@ -8,7 +8,7 @@
import * as cdAst from '../expression_parser/ast';
import {isBlank} from '../facade/lang';
import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name);
if (varExpr) {
if (isPresent(varExpr)) {
result = varExpr.callFn(args);
}
}
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name);
if (varExpr) {
if (isPresent(varExpr)) {
throw new Error('Cannot assign to a reference or variable!');
}
}

View File

@ -11,6 +11,10 @@ import {ViewEncapsulation, isDevMode} from '@angular/core';
import {CompileIdentifierMetadata} from './compile_metadata';
import {Identifiers, createIdentifier} from './identifiers';
function unimplemented(): any {
throw new Error('unimplemented');
}
export class CompilerConfig {
public renderTypes: RenderTypes;
public defaultEncapsulation: ViewEncapsulation;
@ -48,12 +52,12 @@ export class CompilerConfig {
* to help tree shaking.
*/
export abstract class RenderTypes {
abstract get renderer(): CompileIdentifierMetadata;
abstract get renderText(): CompileIdentifierMetadata;
abstract get renderElement(): CompileIdentifierMetadata;
abstract get renderComment(): CompileIdentifierMetadata;
abstract get renderNode(): CompileIdentifierMetadata;
abstract get renderEvent(): CompileIdentifierMetadata;
get renderer(): CompileIdentifierMetadata { return unimplemented(); }
get renderText(): CompileIdentifierMetadata { return unimplemented(); }
get renderElement(): CompileIdentifierMetadata { return unimplemented(); }
get renderComment(): CompileIdentifierMetadata { return unimplemented(); }
get renderNode(): CompileIdentifierMetadata { return unimplemented(); }
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); }
}
export class DefaultRenderTypes implements RenderTypes {

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ViewEncapsulation} from '@angular/core';
import {Component, ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
@ -102,12 +102,11 @@ export class DirectiveNormalizer {
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), true, interpolationConfig);
template, stringify(prenomData.componentType), false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw new SyntaxError(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
@ -229,13 +228,9 @@ class TemplatePreparseVisitor implements html.Visitor {
return null;
}
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
html.visitAll(this, ast.expression);
}
visitComment(ast: html.Comment, context: any): any { return null; }
visitAttribute(ast: html.Attribute, context: any): any { return null; }
visitText(ast: html.Text, context: any): any { return null; }
visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
}

View File

@ -14,6 +14,8 @@ import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util';
/*
* Resolve a `Type` for {@link Directive}.
*

View File

@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {Console, LifecycleHooks} from './private_import_core';
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {BindingParser} from './template_parser/binding_parser';
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
@ -128,6 +128,7 @@ class DirectiveWrapperBuilder implements ClassBuilder {
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
];
const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),

View File

@ -7,7 +7,7 @@
*/
import * as chars from '../chars';
import {NumberWrapper} from '../facade/lang';
import {NumberWrapper, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
export enum TokenType {
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
return new Token(index, TokenType.Error, 0, message);
}
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
class _Scanner {
length: number;
@ -241,7 +241,7 @@ class _Scanner {
this.advance();
str += two;
}
if (threeCode != null && this.peek == threeCode) {
if (isPresent(threeCode) && this.peek == threeCode) {
this.advance();
str += three;
}

View File

@ -9,13 +9,13 @@
import * as i18n from './i18n_ast';
export function digest(message: i18n.Message): string {
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
return message.id || sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
}
export function decimalDigest(message: i18n.Message): string {
const visitor = new _SerializerIgnoreIcuExpVisitor();
const parts = message.nodes.map(a => a.visit(visitor, null));
return computeMsgId(parts.join(''), message.meaning);
return message.id || computeMsgId(parts.join(''), message.meaning);
}
/**
@ -206,38 +206,47 @@ enum Endian {
Big,
}
export function utf8Encode(str: string): string {
let encoded = '';
for (let index = 0; index < str.length; index++) {
let codePoint = str.charCodeAt(index);
function utf8Encode(str: string): string {
let encoded: string = '';
// decode surrogate
// see https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
if (codePoint >= 0xd800 && codePoint <= 0xdbff && str.length > (index + 1)) {
const low = str.charCodeAt(index + 1);
if (low >= 0xdc00 && low <= 0xdfff) {
index++;
codePoint = ((codePoint - 0xd800) << 10) + low - 0xdc00 + 0x10000;
}
}
for (let index = 0; index < str.length; index++) {
const codePoint = decodeSurrogatePairs(str, index);
if (codePoint <= 0x7f) {
encoded += String.fromCharCode(codePoint);
} else if (codePoint <= 0x7ff) {
encoded += String.fromCharCode(((codePoint >> 6) & 0x1F) | 0xc0, (codePoint & 0x3f) | 0x80);
encoded += String.fromCharCode(0xc0 | codePoint >>> 6, 0x80 | codePoint & 0x3f);
} else if (codePoint <= 0xffff) {
encoded += String.fromCharCode(
(codePoint >> 12) | 0xe0, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
0xe0 | codePoint >>> 12, 0x80 | codePoint >>> 6 & 0x3f, 0x80 | codePoint & 0x3f);
} else if (codePoint <= 0x1fffff) {
encoded += String.fromCharCode(
((codePoint >> 18) & 0x07) | 0xf0, ((codePoint >> 12) & 0x3f) | 0x80,
((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
0xf0 | codePoint >>> 18, 0x80 | codePoint >>> 12 & 0x3f, 0x80 | codePoint >>> 6 & 0x3f,
0x80 | codePoint & 0x3f);
}
}
return encoded;
}
// see https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
function decodeSurrogatePairs(str: string, index: number): number {
if (index < 0 || index >= str.length) {
throw new Error(`index=${index} is out of range in "${str}"`);
}
const high = str.charCodeAt(index);
if (high >= 0xd800 && high <= 0xdfff && str.length > index + 1) {
const low = byteAt(str, index + 1);
if (low >= 0xdc00 && low <= 0xdfff) {
return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
}
}
return high;
}
function add32(a: number, b: number): number {
return add32to64(a, b)[1];
}

View File

@ -52,7 +52,7 @@ export class Extractor {
extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
const {files, ngModules} =
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
return Promise
.all(ngModules.map(

View File

@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
const _I18N_ATTR = 'i18n';
const _I18N_ATTR_PREFIX = 'i18n-';
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
const MEANING_SEPARATOR = '|';
const ID_SEPARATOR = '@@';
/**
* Extract translatable messages from an html AST
@ -53,22 +55,20 @@ enum _VisitorMode {
* @internal
*/
class _Visitor implements html.Visitor {
private _depth: number;
// <el i18n>...</el>
private _inI18nNode: boolean;
private _depth: number;
private _inImplicitNode: boolean;
// <!--i18n-->...<!--/i18n-->
private _inI18nBlock: boolean;
private _blockMeaningAndDesc: string;
private _blockChildren: html.Node[];
private _blockStartDepth: number;
private _inI18nBlock: boolean;
// {<icu message>}
private _inIcu: boolean;
// set to void 0 when not in a section
private _msgCountAtSectionStart: number;
private _errors: I18nError[];
private _mode: _VisitorMode;
@ -79,7 +79,7 @@ class _Visitor implements html.Visitor {
// _VisitorMode.Merge only
private _translations: TranslationBundle;
private _createI18nMessage:
(msg: html.Node[], meaning: string, description: string) => i18n.Message;
(msg: html.Node[], meaning: string, description: string, id: string) => i18n.Message;
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
@ -210,31 +210,50 @@ class _Visitor implements html.Visitor {
this._depth++;
const wasInI18nNode = this._inI18nNode;
const wasInImplicitNode = this._inImplicitNode;
let childNodes: html.Node[] = [];
let translatedChildNodes: html.Node[];
let childNodes: html.Node[];
// Extract:
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
// - ICU messages
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
// message
const i18nAttr = _getI18nAttr(el);
const i18nMeta = i18nAttr ? i18nAttr.value : '';
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
!this._isInTranslatableSection;
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = wasInImplicitNode || isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit;
if (!this._isInTranslatableSection && !this._inIcu) {
if (i18nAttr || isTopLevelImplicit) {
if (i18nAttr) {
// explicit translation
this._inI18nNode = true;
const message = this._addMessage(el.children, i18nMeta);
translatedChildNodes = this._translateMessage(el, message);
const message = this._addMessage(el.children, i18nAttr.value);
childNodes = this._translateMessage(el, message);
} else if (isTopLevelImplicit) {
// implicit translation
this._inI18nNode = true;
const message = this._addMessage(el.children);
childNodes = this._translateMessage(el, message);
}
if (this._mode == _VisitorMode.Extract) {
const isTranslatable = i18nAttr || isTopLevelImplicit;
if (isTranslatable) this._openTranslatableSection(el);
if (isTranslatable) {
this._openTranslatableSection(el);
}
html.visitAll(this, el.children);
if (isTranslatable) this._closeTranslatableSection(el, el.children);
if (isTranslatable) {
this._closeTranslatableSection(el, el.children);
}
}
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
}
} else {
if (i18nAttr || isTopLevelImplicit) {
@ -246,18 +265,19 @@ class _Visitor implements html.Visitor {
// Descend into child nodes for extraction
html.visitAll(this, el.children);
}
}
if (this._mode === _VisitorMode.Merge) {
const visitNodes = translatedChildNodes || el.children;
visitNodes.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
if (this._mode == _VisitorMode.Merge) {
// Translate attributes in ICU messages
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
}
}
this._visitAttributesOf(el);
@ -267,6 +287,7 @@ class _Visitor implements html.Visitor {
this._inImplicitNode = wasInImplicitNode;
if (this._mode === _VisitorMode.Merge) {
// There are no childNodes in translatable sections - those nodes will be replace anyway
const translatedAttrs = this._translateAttributes(el);
return new html.Element(
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
@ -311,21 +332,20 @@ class _Visitor implements html.Visitor {
}
// add a translatable message
private _addMessage(ast: html.Node[], meaningAndDesc?: string): i18n.Message {
private _addMessage(ast: html.Node[], msgMeta?: string): i18n.Message {
if (ast.length == 0 ||
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
// Do not create empty messages
return;
}
const [meaning, description] = _splitMeaningAndDesc(meaningAndDesc);
const message = this._createI18nMessage(ast, meaning, description);
const {meaning, description, id} = _parseMessageMeta(msgMeta);
const message = this._createI18nMessage(ast, meaning, description, id);
this._messages.push(message);
return message;
}
// Translates the given message given the `TranslationBundle`
// This is used for translating elements / blocks - see `_translateAttributes` for attributes
// no-op when called in extraction mode (returns [])
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
if (message && this._mode === _VisitorMode.Merge) {
@ -350,7 +370,7 @@ class _Visitor implements html.Visitor {
attributes.forEach(attr => {
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
_splitMeaningAndDesc(attr.value)[0];
_parseMessageMeta(attr.value).meaning;
}
});
@ -364,12 +384,10 @@ class _Visitor implements html.Visitor {
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
const meaning = i18nAttributeMeanings[attr.name];
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
const message: i18n.Message = this._createI18nMessage([attr], meaning, '', '');
const nodes = this._translations.get(message);
if (nodes) {
if (nodes.length == 0) {
translatedAttributes.push(new html.Attribute(attr.name, '', attr.sourceSpan));
} else if (nodes[0] instanceof html.Text) {
if (nodes[0] instanceof html.Text) {
const value = (nodes[0] as html.Text).value;
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
} else {
@ -404,7 +422,7 @@ class _Visitor implements html.Visitor {
}
/**
* Marks the start of a section, see `_closeTranslatableSection`
* Marks the start of a section, see `_endSection`
*/
private _openTranslatableSection(node: html.Node): void {
if (this._isInTranslatableSection) {
@ -416,7 +434,7 @@ class _Visitor implements html.Visitor {
/**
* A translatable section could be:
* - the content of translatable element,
* - a translatable element,
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
*/
private get _isInTranslatableSection(): boolean {
@ -480,8 +498,16 @@ function _getI18nAttr(p: html.Element): html.Attribute {
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
}
function _splitMeaningAndDesc(i18n: string): [string, string] {
if (!i18n) return ['', ''];
const pipeIndex = i18n.indexOf('|');
return pipeIndex == -1 ? ['', i18n] : [i18n.slice(0, pipeIndex), i18n.slice(pipeIndex + 1)];
function _parseMessageMeta(i18n: string): {meaning: string, description: string, id: string} {
if (!i18n) return {meaning: '', description: '', id: ''};
const idIndex = i18n.indexOf(ID_SEPARATOR);
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
const [meaningAndDesc, id] =
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
const [meaning, description] = (descIndex > -1) ?
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
['', meaningAndDesc];
return {meaning, description, id};
}

View File

@ -15,11 +15,12 @@ export class Message {
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
* @param meaning
* @param description
* @param id
*/
constructor(
public nodes: Node[], public placeholders: {[phName: string]: string},
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
public description: string) {}
public description: string, public id: string) {}
}
export interface Node {

View File

@ -22,11 +22,11 @@ const _expParser = new ExpressionParser(new ExpressionLexer());
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
*/
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
nodes: html.Node[], meaning: string, description: string) => i18n.Message {
nodes: html.Node[], meaning: string, description: string, id: string) => i18n.Message {
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
return (nodes: html.Node[], meaning: string, description: string) =>
visitor.toI18nMessage(nodes, meaning, description);
return (nodes: html.Node[], meaning: string, description: string, id: string) =>
visitor.toI18nMessage(nodes, meaning, description, id);
}
class _I18nVisitor implements html.Visitor {
@ -40,7 +40,8 @@ class _I18nVisitor implements html.Visitor {
private _expressionParser: ExpressionParser,
private _interpolationConfig: InterpolationConfig) {}
public toI18nMessage(nodes: html.Node[], meaning: string, description: string): i18n.Message {
public toI18nMessage(nodes: html.Node[], meaning: string, description: string, id: string):
i18n.Message {
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
this._icuDepth = 0;
this._placeholderRegistry = new PlaceholderRegistry();
@ -50,7 +51,7 @@ class _I18nVisitor implements html.Visitor {
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
return new i18n.Message(
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description, id);
}
visitElement(el: html.Element, context: any): i18n.Node {
@ -115,7 +116,7 @@ class _I18nVisitor implements html.Visitor {
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '', '');
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
}

View File

@ -111,14 +111,8 @@ export class PlaceholderRegistry {
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
private _generateUniqueName(base: string): string {
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
if (!seen) {
this._placeHolderNameCounts[base] = 1;
return base;
}
const id = this._placeHolderNameCounts[base];
this._placeHolderNameCounts[base] = id + 1;
return `${base}_${id}`;
const next = this._placeHolderNameCounts[base];
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
return next ? `${base}_${next}` : base;
}
}

View File

@ -8,26 +8,10 @@
import * as i18n from '../i18n_ast';
export abstract class Serializer {
abstract write(messages: i18n.Message[]): string;
export interface Serializer {
write(messages: i18n.Message[]): string;
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
abstract digest(message: i18n.Message): string;
// Creates a name mapper, see `PlaceholderMapper`
// Returning `null` means that no name mapping is used.
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
}
/**
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
* back.
*
* It should be used for serialization format that put constraints on the placeholder names.
*/
export interface PlaceholderMapper {
toPublicName(internalName: string): string;
toInternalName(publicName: string): string;
digest(message: i18n.Message): string;
}

View File

@ -27,7 +27,7 @@ const _UNIT_TAG = 'trans-unit';
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
export class Xliff extends Serializer {
export class Xliff implements Serializer {
write(messages: i18n.Message[]): string {
const visitor = new _WriteVisitor();
const visited: {[id: string]: boolean} = {};

View File

@ -9,7 +9,7 @@
import {decimalDigest} from '../digest';
import * as i18n from '../i18n_ast';
import {PlaceholderMapper, Serializer} from './serializer';
import {Serializer} from './serializer';
import * as xml from './xml_helper';
const _MESSAGES_TAG = 'messagebundle';
@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
<!ELEMENT ex (#PCDATA)>`;
export class Xmb extends Serializer {
export class Xmb implements Serializer {
write(messages: i18n.Message[]): string {
const exampleVisitor = new ExampleVisitor();
const visitor = new _Visitor();
@ -51,8 +51,6 @@ export class Xmb extends Serializer {
if (visited[id]) return;
visited[id] = true;
const mapper = this.createNameMapper(message);
const attrs: {[k: string]: string} = {id};
if (message.description) {
@ -64,8 +62,7 @@ export class Xmb extends Serializer {
}
rootNode.children.push(
new xml.CR(2),
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
});
rootNode.children.push(new xml.CR());
@ -85,29 +82,22 @@ export class Xmb extends Serializer {
}
digest(message: i18n.Message): string { return digest(message); }
createNameMapper(message: i18n.Message): PlaceholderMapper {
return new XmbPlaceholderMapper(message);
}
}
class _Visitor implements i18n.Visitor {
visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [new xml.Text(text.value)];
}
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
const nodes: xml.Node[] = [];
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
return nodes;
}
visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
Object.keys(icu.cases).forEach((c: string) => {
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
});
nodes.push(new xml.Text(`}`));
@ -115,34 +105,30 @@ class _Visitor implements i18n.Visitor {
return nodes;
}
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
let name = ctx.mapper.toPublicName(ph.startName);
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
if (ph.isVoid) {
// void tags have no children nor closing tags
return [startTagPh];
}
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
name = ctx.mapper.toPublicName(ph.closeName);
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
}
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
const name = ctx.mapper.toPublicName(ph.name);
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
}
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
const name = ctx.mapper.toPublicName(ph.name);
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
}
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [].concat(...nodes.map(node => node.visit(this, ctx)));
serialize(nodes: i18n.Node[]): xml.Node[] {
return [].concat(...nodes.map(node => node.visit(this)));
}
}
@ -172,69 +158,3 @@ class ExampleVisitor implements xml.IVisitor {
visitDeclaration(decl: xml.Declaration): void {}
visitDoctype(doctype: xml.Doctype): void {}
}
/**
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
*
* Because such restrictions do not exist on placeholder names generated locally, the
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
* serialized and back from XTB to internal names when an XTB is loaded.
*/
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
private internalToXmb: {[k: string]: string} = {};
private xmbToNextId: {[k: string]: number} = {};
private xmbToInternal: {[k: string]: string} = {};
// create a mapping from the message
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
toPublicName(internalName: string): string {
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
null;
}
toInternalName(publicName: string): string {
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
}
visitText(text: i18n.Text, ctx?: any): any { return null; }
visitContainer(container: i18n.Container, ctx?: any): any {
container.children.forEach(child => child.visit(this));
}
visitIcu(icu: i18n.Icu, ctx?: any): any {
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
}
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
this.addPlaceholder(ph.startName);
ph.children.forEach(child => child.visit(this));
this.addPlaceholder(ph.closeName);
}
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
// XMB placeholders could only contains A-Z, 0-9 and _
private addPlaceholder(internalName: string): void {
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
return;
}
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
// Create a new XMB when it has already been used
const nextId = this.xmbToNextId[xmbName];
this.xmbToNextId[xmbName] = nextId + 1;
xmbName = `${xmbName}_${nextId}`;
} else {
this.xmbToNextId[xmbName] = 1;
}
this.internalToXmb[internalName] = xmbName;
this.xmbToInternal[xmbName] = internalName;
}
}

View File

@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
import * as i18n from '../i18n_ast';
import {I18nError} from '../parse_util';
import {PlaceholderMapper, Serializer} from './serializer';
import {XmbPlaceholderMapper, digest} from './xmb';
import {Serializer} from './serializer';
import {digest} from './xmb';
const _TRANSLATIONS_TAG = 'translationbundle';
const _TRANSLATION_TAG = 'translation';
const _PLACEHOLDER_TAG = 'ph';
export class Xtb extends Serializer {
export class Xtb implements Serializer {
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
@ -43,10 +43,6 @@ export class Xtb extends Serializer {
}
digest(message: i18n.Message): string { return digest(message); }
createNameMapper(message: i18n.Message): PlaceholderMapper {
return new XmbPlaceholderMapper(message);
}
}
// Extract messages as xml nodes from the xtb file

View File

@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
import * as i18n from './i18n_ast';
import {I18nError} from './parse_util';
import {PlaceholderMapper, Serializer} from './serializers/serializer';
import {Serializer} from './serializers/serializer';
/**
* A container for translated messages
@ -21,20 +21,16 @@ export class TranslationBundle {
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
public digest: (m: i18n.Message) => string,
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
public digest: (m: i18n.Message) => string) {
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
}
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
const i18nNodesByMsgId = serializer.load(content, url);
const digestFn = (m: i18n.Message) => serializer.digest(m);
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
return new TranslationBundle(i18nNodesByMsgId, digestFn);
}
// Returns the translation as HTML nodes from the given source message.
get(srcMsg: i18n.Message): html.Node[] {
const html = this._i18nToHtml.convert(srcMsg);
@ -50,17 +46,15 @@ export class TranslationBundle {
class I18nToHtmlVisitor implements i18n.Visitor {
private _srcMsg: i18n.Message;
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
private _srcMsgStack: i18n.Message[] = [];
private _errors: I18nError[] = [];
private _mapper: (name: string) => string;
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
private _digest: (m: i18n.Message) => string,
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
private _digest: (m: i18n.Message) => string) {}
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
this._contextStack.length = 0;
this._srcMsgStack.length = 0;
this._errors.length = 0;
// i18n to text
const text = this._convertToText(srcMsg);
@ -94,7 +88,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
}
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
const phName = this._mapper(ph.name);
const phName = ph.name;
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
return this._srcMsg.placeholders[phName];
}
@ -111,26 +105,14 @@ class I18nToHtmlVisitor implements i18n.Visitor {
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
/**
* Convert a source message to a translated text string:
* - text nodes are replaced with their translation,
* - placeholders are replaced with their content,
* - ICU nodes are converted to ICU expressions.
*/
private _convertToText(srcMsg: i18n.Message): string {
const digest = this._digest(srcMsg);
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
this._srcMsgStack.push(this._srcMsg);
this._srcMsg = srcMsg;
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
const nodes = this._i18nNodesByMsgId[digest];
const text = nodes.map(node => node.visit(this)).join('');
const context = this._contextStack.pop();
this._srcMsg = context.msg;
this._mapper = context.mapper;
this._srcMsg = this._srcMsgStack.pop();
return text;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';

View File

@ -114,7 +114,7 @@ export class JitCompiler implements Compiler {
// Note: the loadingPromise for a module only includes the loading of the exported directives
// of imported modules.
// However, for runtime compilation, we want to transitively compile all modules,
// so we also need to call loadNgModuleDirectiveAndPipeMetadata for all nested modules.
// so we also need to call loadNgModuleMetadata for all nested modules.
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
localModuleMeta.reference, isSync));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
@ -40,8 +40,6 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
};
const baseHtmlParser = new OpaqueToken('HtmlParser');
/**
* A set of providers that provide `JitCompiler` and its dependencies to use for
* template compilation.
@ -54,24 +52,17 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
Console,
Lexer,
Parser,
{
provide: baseHtmlParser,
useClass: HtmlParser,
},
HtmlParser,
{
provide: i18n.I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string, format: string) =>
new i18n.I18NHtmlParser(parser, translations, format),
deps: [
baseHtmlParser,
HtmlParser,
[new Optional(), new Inject(TRANSLATIONS)],
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
]
},
{
provide: HtmlParser,
useExisting: i18n.I18NHtmlParser,
},
TemplateParser,
DirectiveNormalizer,
CompileMetadataResolver,

View File

@ -294,14 +294,14 @@ export class CompileMetadataResolver {
/**
* Gets the metadata for the given directive.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) {
this._reportError(
new SyntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType);
}
return dirMeta;
@ -648,14 +648,14 @@ export class CompileMetadataResolver {
/**
* Gets the metadata for the given pipe.
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
this._reportError(
new SyntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeMeta;
@ -987,4 +987,4 @@ function stringifyType(type: any): string {
} else {
return stringify(type);
}
}
}

View File

@ -283,7 +283,7 @@ class _TreeBuilder {
const tagDef = this.getTagDefinition(el.name);
const {parent, container} = this._getParentElementSkippingContainers();
if (parent && tagDef.requireExtraParent(parent.name)) {
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) {
const newParent = new html.Element(
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
this._insertBeforeContainer(parent, container, newParent);

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';

View File

@ -9,7 +9,7 @@
import {NgModule, Type} from '@angular/core';
import {ListWrapper} from './facade/collection';
import {stringify} from './facade/lang';
import {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
@ -30,7 +30,7 @@ export class NgModuleResolver {
const ngModuleMeta: NgModule =
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
if (ngModuleMeta) {
if (isPresent(ngModuleMeta)) {
return ngModuleMeta;
} else {
if (throwIfNotFound) {

View File

@ -68,13 +68,13 @@ export class MapType extends Type {
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
}
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
export interface TypeVisitor {
visitBuiltintType(type: BuiltinType, context: any): any;
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
}
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export const NULL_EXPR = new LiteralExpr(null, null);
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export var NULL_EXPR = new LiteralExpr(null, null);
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
//// Statements
export enum StmtModifier {

View File

@ -7,6 +7,7 @@
*/
import {CompileIdentifierMetadata} from '../compile_metadata';
import {ValueTransformer, visitValue} from '../util';
import * as o from './output_ast';

View File

@ -48,51 +48,6 @@ export class ParseLocation {
}
return new ParseLocation(this.file, offset, line, col);
}
// Return the source around the location
// Up to `maxChars` or `maxLines` on each side of the location
getContext(maxChars: number, maxLines: number): {before: string, after: string} {
const content = this.file.content;
let startOffset = this.offset;
if (isPresent(startOffset)) {
if (startOffset > content.length - 1) {
startOffset = content.length - 1;
}
let endOffset = startOffset;
let ctxChars = 0;
let ctxLines = 0;
while (ctxChars < maxChars && startOffset > 0) {
startOffset--;
ctxChars++;
if (content[startOffset] == '\n') {
if (++ctxLines == maxLines) {
break;
}
}
}
ctxChars = 0;
ctxLines = 0;
while (ctxChars < maxChars && endOffset < content.length - 1) {
endOffset++;
ctxChars++;
if (content[endOffset] == '\n') {
if (++ctxLines == maxLines) {
break;
}
}
}
return {
before: content.substring(startOffset, this.offset),
after: content.substring(this.offset, endOffset + 1),
};
}
return null;
}
}
export class ParseSourceFile {
@ -119,9 +74,47 @@ export class ParseError {
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
toString(): string {
const ctx = this.span.start.getContext(100, 3);
const contextStr = ctx ? ` ("${ctx.before}[ERROR ->]${ctx.after}")` : '';
const details = this.span.details ? `, ${this.span.details}` : '';
const source = this.span.start.file.content;
let ctxStart = this.span.start.offset;
let contextStr = '';
let details = '';
if (isPresent(ctxStart)) {
if (ctxStart > source.length - 1) {
ctxStart = source.length - 1;
}
let ctxEnd = ctxStart;
let ctxLen = 0;
let ctxLines = 0;
while (ctxLen < 100 && ctxStart > 0) {
ctxStart--;
ctxLen++;
if (source[ctxStart] == '\n') {
if (++ctxLines == 3) {
break;
}
}
}
ctxLen = 0;
ctxLines = 0;
while (ctxLen < 100 && ctxEnd < source.length - 1) {
ctxEnd++;
ctxLen++;
if (source[ctxEnd] == '\n') {
if (++ctxLines == 3) {
break;
}
}
}
const context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
source.substring(this.span.start.offset, ctxEnd + 1);
contextStr = ` ("${context}")`;
}
if (this.span.details) {
details = `, ${this.span.details}`;
}
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
}
}

View File

@ -9,7 +9,7 @@
import {Pipe, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper} from './facade/collection';
import {stringify} from './facade/lang';
import {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
@ -38,9 +38,9 @@ export class PipeResolver {
*/
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
const metas = this._reflector.annotations(resolveForwardRef(type));
if (metas) {
if (isPresent(metas)) {
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
if (annotation) {
if (isPresent(annotation)) {
return annotation;
}
}

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
import {isBlank, isPresent} from './facade/lang';
import {Identifiers, resolveIdentifier} from './identifiers';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
@ -114,7 +114,7 @@ export class ProviderElementContext {
let queries: CompileQueryMetadata[];
while (currentEl !== null) {
queries = currentEl._contentQueries.get(tokenReference(token));
if (queries) {
if (isPresent(queries)) {
result.push(...queries.filter((query) => query.descendants || distance <= 1));
}
if (currentEl._directiveAsts.length > 0) {
@ -123,7 +123,7 @@ export class ProviderElementContext {
currentEl = currentEl._parent;
}
queries = this.viewContext.viewQueries.get(tokenReference(token));
if (queries) {
if (isPresent(queries)) {
result.push(...queries);
}
return result;
@ -143,7 +143,7 @@ export class ProviderElementContext {
return null;
}
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (transformedProviderAst) {
if (isPresent(transformedProviderAst)) {
return transformedProviderAst;
}
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -165,11 +165,11 @@ export class ProviderElementContext {
transformedUseExisting = null;
transformedUseValue = existingDiDep.value;
}
} else if (provider.useFactory) {
} else if (isPresent(provider.useFactory)) {
const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
} else if (provider.useClass) {
} else if (isPresent(provider.useClass)) {
const deps = provider.deps || provider.useClass.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
@ -235,7 +235,7 @@ export class ProviderElementContext {
}
} else {
// check parent elements
while (!result && currElement._parent) {
while (!result && isPresent(currElement._parent)) {
const prevElement = currElement;
currElement = currElement._parent;
if (prevElement._isViewRoot) {
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
return null;
}
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (transformedProviderAst) {
if (isPresent(transformedProviderAst)) {
return transformedProviderAst;
}
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
transformedUseExisting = null;
transformedUseValue = existingDiDep.value;
}
} else if (provider.useFactory) {
} else if (isPresent(provider.useFactory)) {
const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
} else if (provider.useClass) {
} else if (isPresent(provider.useClass)) {
const deps = provider.deps || provider.useClass.diDeps;
transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
@ -454,7 +454,7 @@ function _resolveProviders(
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
const viewQueries = new Map<any, CompileQueryMetadata[]>();
if (component.viewQueries) {
if (isPresent(component.viewQueries)) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
}
return viewQueries;
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
Map<any, CompileQueryMetadata[]> {
const contentQueries = new Map<any, CompileQueryMetadata[]>();
directives.forEach(directive => {
if (directive.queries) {
if (isPresent(directive.queries)) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
}
});

View File

@ -9,13 +9,12 @@
import {getHtmlTagDefinition} from './ml_parser/html_tags';
const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g');
/**

View File

@ -11,6 +11,7 @@ import {SecurityContext} from '@angular/core';
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {mergeNsAndName} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
@ -110,14 +111,14 @@ export class BindingParser {
}
parseInlineTemplateBinding(
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
} else if (binding.expression) {
} else if (isPresent(binding.expression)) {
this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
} else {
@ -135,7 +136,7 @@ export class BindingParser {
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (binding.expression) {
if (isPresent(binding.expression)) {
this._checkPipes(binding.expression, sourceSpan);
}
});
@ -192,7 +193,7 @@ export class BindingParser {
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]): boolean {
const expr = this.parseInterpolation(value, sourceSpan);
if (expr) {
if (isPresent(expr)) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true;
}
@ -373,7 +374,7 @@ export class BindingParser {
}
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (ast) {
if (isPresent(ast)) {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => {

View File

@ -149,7 +149,7 @@ export class TemplateParser {
return new TemplateParseResult(result, errors);
}
if (this.transforms) {
if (isPresent(this.transforms)) {
this.transforms.forEach(
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
}
@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
if (expr) {
if (isPresent(expr)) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else {
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
return null;
}
const matchableAttrs: [string, string][] = [];
const matchableAttrs: string[][] = [];
const elementOrDirectiveProps: BoundProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = [];
const events: BoundEventAst[] = [];
const templateElementOrDirectiveProps: BoundProperty[] = [];
const templateMatchableAttrs: [string, string][] = [];
const templateMatchableAttrs: string[][] = [];
const templateElementVars: VariableAst[] = [];
let hasInlineTemplates = false;
@ -268,17 +268,14 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars);
let templateBindingsSource: string|undefined;
let prefixToken: string|undefined;
let normalizedName = this._normalizeAttributeName(attr.name);
if (normalizedName == TEMPLATE_ATTR) {
let templateBindingsSource: string|undefined = undefined;
let prefixToken: string|undefined = undefined;
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value;
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
}
const hasTemplateBinding = isPresent(templateBindingsSource);
if (hasTemplateBinding) {
if (hasInlineTemplates) {
@ -288,7 +285,7 @@ class TemplateParseVisitor implements html.Visitor {
}
hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding(
prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars);
}
@ -309,11 +306,9 @@ class TemplateParseVisitor implements html.Visitor {
const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
references, element.sourceSpan);
const children = html.visitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create(
@ -546,12 +541,10 @@ class TemplateParseVisitor implements html.Visitor {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;
const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end,
`Directive ${identifierName(directive.type)}`);
if (directive.isComponent) {
component = directive;
}
@ -574,7 +567,6 @@ class TemplateParseVisitor implements html.Visitor {
return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
});
elementOrDirectiveRefs.forEach((elOrDirRef) => {
if (elOrDirRef.value.length > 0) {
if (!matchedReferences.has(elOrDirRef.name)) {
@ -589,7 +581,7 @@ class TemplateParseVisitor implements html.Visitor {
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
}
});
}); // fix syntax highlighting issue: `
return directiveAsts;
}
@ -750,7 +742,7 @@ class NonBindableVisitor implements html.Visitor {
return null;
}
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector);
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
@ -819,16 +811,16 @@ class ElementContext {
}
export function createElementCssSelector(
elementName: string, attributes: [string, string][]): CssSelector {
elementName: string, matchableAttrs: string[][]): CssSelector {
const cssSelector = new CssSelector();
const elNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elNameNoNs);
for (let i = 0; i < attributes.length; i++) {
const attrName = attributes[i][0];
for (let i = 0; i < matchableAttrs.length; i++) {
const attrName = matchableAttrs[i][0];
const attrNameNoNs = splitNsName(attrName)[1];
const attrValue = attributes[i][1];
const attrValue = matchableAttrs[i][1];
cssSelector.addAttribute(attrNameNoNs, attrValue);
if (attrName.toLowerCase() == CLASS_ATTR) {

View File

@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
/**
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
*/
export const DEFAULT_PACKAGE_URL_PROVIDER = {
export var DEFAULT_PACKAGE_URL_PROVIDER = {
provide: PACKAGE_ROOT_URL,
useValue: '/'
};

View File

@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
import {CompileMethod} from './compile_method';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {CompileView, CompileViewRootNode} from './compile_view';
import {InjectMethodVars} from './constants';
import {InjectMethodVars, ViewProperties} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
import {getPropertyInView, injectFromViewParentInjector} from './util';
@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
const propName =
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
const instance = createProviderProperty(
propName, providerValueExpressions, resolvedProvider.multiProvider,
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager, this);
if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
@ -211,7 +211,12 @@ export class CompileElement extends CompileNode {
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
}
const queriesWithReads: _QueryWithRead[] = [];
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
queriesWithReads.push(
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
});
Object.keys(this.referenceTokens).forEach(varName => {
const token = this.referenceTokens[varName];
let varValue: o.Expression;
@ -221,6 +226,27 @@ export class CompileElement extends CompileNode {
varValue = this.renderNode;
}
this.view.locals.set(varName, varValue);
const varToken = {value: varName};
queriesWithReads.push(
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (isPresent(queryWithRead.read.identifier)) {
// query for an identifier
value = this.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = this.referenceTokens[queryWithRead.read.value];
if (isPresent(token)) {
value = this.instances.get(tokenReference(token));
} else {
value = this.elementRef;
}
}
if (isPresent(value)) {
queryWithRead.query.addValue(value, this.view);
}
});
}
@ -239,14 +265,12 @@ export class CompileElement extends CompileNode {
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
});
}
finish() {
Array.from(this._queries.values())
.forEach(
queries => queries.forEach(
q => q.generateStatements(
this.view.createMethod, this.view.updateContentQueriesMethod)));
q =>
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
}
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
@ -259,11 +283,12 @@ export class CompileElement extends CompileNode {
null;
}
getProviderTokens(): CompileTokenMetadata[] {
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
getProviderTokens(): o.Expression[] {
return Array.from(this._resolvedProviders.values())
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
}
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
const result: CompileQuery[] = [];
let currentEl: CompileElement = this;
let distance = 0;
@ -289,7 +314,7 @@ export class CompileElement extends CompileNode {
CompileQuery {
const propName =
`_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`;
const queryList = createQueryList(propName, this.view);
const queryList = createQueryList(queryMeta, directiveInstance, propName, this.view);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
addQueryToTokenMap(this._queries, query);
return query;
@ -369,8 +394,8 @@ function createInjectInternalCondition(
}
function createProviderProperty(
propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
compileElement: CompileElement): o.Expression {
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[],
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression {
const view = compileElement.view;
let resolvedProviderValueExpr: o.Expression;
let type: o.Type;
@ -401,3 +426,10 @@ function createProviderProperty(
}
return o.THIS_EXPR.prop(propName);
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast';
import {TemplateAst} from '../template_parser/template_ast';
@ -33,7 +34,7 @@ export class CompileMethod {
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
this._newState.sourceAst !== this._currState.sourceAst) {
const expr = this._updateDebugContext(this._newState);
if (expr) {
if (isPresent(expr)) {
this._bodyStatements.push(expr.toStmt());
}
}
@ -42,12 +43,13 @@ export class CompileMethod {
private _updateDebugContext(newState: _DebugState): o.Expression {
this._currState = this._newState = newState;
if (this._debugEnabled) {
const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
const sourceLocation =
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
return o.THIS_EXPR.callMethod('debug', [
o.literal(newState.nodeIndex),
sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR,
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR
]);
} else {
return null;

View File

@ -8,6 +8,7 @@
import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
@ -32,7 +33,7 @@ export class CompileQuery {
addValue(value: o.Expression, view: CompileView) {
let currentView = view;
const elPath: CompileElement[] = [];
while (currentView && currentView !== this.view) {
while (isPresent(currentView) && currentView !== this.view) {
const parentEl = currentView.declarationElement;
elPath.unshift(parentEl);
currentView = parentEl.view;
@ -63,10 +64,10 @@ export class CompileQuery {
return !this._values.values.some(value => value instanceof ViewQueryValues);
}
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
const values = createQueryValues(this._values);
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (this.ownerDirectiveExpression) {
if (isPresent(this.ownerDirectiveExpression)) {
const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
updateStmts.push(
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
@ -109,7 +110,9 @@ function mapNestedViews(
]);
}
export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
export function createQueryList(
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
compileView: CompileView): o.Expression {
compileView.fields.push(new o.ClassField(
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
const expr = o.THIS_EXPR.prop(propertyName);

View File

@ -12,6 +12,7 @@ import {EventHandlerVars, NameResolver} from '../compiler_util/expression_conver
import {createPureProxy} from '../compiler_util/identifier_util';
import {CompilerConfig} from '../config';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core';
@ -118,7 +119,7 @@ export class CompileView implements NameResolver {
const directiveInstance = o.THIS_EXPR.prop('context');
this.component.viewQueries.forEach((queryMeta, queryIndex) => {
const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`;
const queryList = createQueryList(propName, this);
const queryList = createQueryList(queryMeta, directiveInstance, propName, this);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query);
});
@ -153,11 +154,11 @@ export class CompileView implements NameResolver {
}
}
finish() {
afterNodes() {
Array.from(this.viewQueries.values())
.forEach(
queries => queries.forEach(
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
}
}

View File

@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {createEnumExpression} from '../compiler_util/identifier_util';
import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core';
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression {
@ -25,6 +25,12 @@ export class ViewEncapsulationEnum {
}
}
export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
}
}
export class ChangeDetectorStatusEnum {
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);

View File

@ -15,6 +15,7 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method';
import {ViewProperties} from './constants';
import {getHandleEventMethodName} from './util';
export function bindOutputs(
@ -132,4 +133,4 @@ type EventSummary = {
name: string,
target: string,
phase: string
};
}

View File

@ -1,57 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileTokenMetadata, tokenReference} from '../compile_metadata';
import * as o from '../output/output_ast';
import {CompileElement} from './compile_element';
import {CompileQuery} from './compile_query';
// Note: We can't do this when we create the CompileElements already,
// as we create embedded views before the <template> elements themselves.
export function bindQueryValues(ce: CompileElement) {
const queriesWithReads: _QueryWithRead[] = [];
ce.getProviderTokens().forEach((token) => {
const queriesForProvider = ce.getQueriesFor(token);
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
});
Object.keys(ce.referenceTokens).forEach(varName => {
const varToken = {value: varName};
queriesWithReads.push(
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (queryWithRead.read.identifier) {
// query for an identifier
value = ce.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = ce.referenceTokens[queryWithRead.read.value];
if (token) {
value = ce.instances.get(tokenReference(token));
} else {
value = ce.elementRef;
}
}
if (value) {
queryWithRead.query.addValue(value, ce.view);
}
});
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}

View File

@ -9,6 +9,8 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core';
@ -21,7 +23,7 @@ export function getPropertyInView(
} else {
let viewProp: o.Expression = o.THIS_EXPR;
let currView: CompileView = callingView;
while (currView !== definedView && currView.declarationElement.view) {
while (currView !== definedView && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view;
viewProp = viewProp.prop('parentView');
}

View File

@ -15,7 +15,6 @@ import {CompileView} from './compile_view';
import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
import {bindQueryValues} from './query_binder';
export function bindView(
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
@ -44,7 +43,6 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
ast.directives.forEach((directiveAst, dirIndex) => {
@ -77,7 +75,6 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst, dirIndex) => {
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);

View File

@ -15,6 +15,7 @@ import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
import {createClassStmt} from '../output/class_builder';
import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
@ -47,16 +48,13 @@ export function buildView(
}
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
view.nodes.forEach((node) => {
if (node instanceof CompileElement) {
node.finish();
if (node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
if (node instanceof CompileElement && node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
});
view.finish();
createViewTopLevelStmts(view, targetStatements);
}
class ViewBuilderVisitor implements TemplateAstVisitor {
@ -404,9 +402,8 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
o.literal(view.component.template.ngContentSelectors.length),
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
view.styles,
o.literalMap(
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
null, true),
o.literalMap(view.animations.map(
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
]))
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
}
@ -421,9 +418,7 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
let componentToken: o.Expression = o.NULL_EXPR;
const varTokenEntries: any[] = [];
if (isPresent(compileElement)) {
providerTokens =
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
providerTokens = compileElement.getProviderTokens();
if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
}
@ -695,6 +690,7 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
view.nodes.forEach((node) => {
if (node instanceof CompileElement) {
if (node.embeddedView) {
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
stmts.push(new o.IfStmt(
nodeIndexVar.equals(o.literal(node.nodeIndex)),
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([

Some files were not shown because too many files have changed in this diff Show More