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
161 changed files with 7703 additions and 4678 deletions

View File

@ -1,27 +1,11 @@
<a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
<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
* **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)
* **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)
@ -55,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)
@ -109,7 +112,6 @@ The >=2.3.1 compiler will issue is the following error if it encounters componen
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

@ -133,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
@ -147,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

@ -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

@ -25,8 +25,6 @@ import {isPresent, 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

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

@ -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

@ -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

@ -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

@ -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

@ -294,25 +294,14 @@ export function main() {
}));
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
it('should throw if trackBy is not a function', async(() => {
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></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 =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
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(() => {
@ -354,7 +343,6 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red');
}));
it('should move items around and keep them updated ', async(() => {
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
@ -390,7 +378,6 @@ class Foo {
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
value: any;
items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
@ -407,4 +394,4 @@ const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString(
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}
}

View File

@ -153,6 +153,78 @@ export function main() {
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

@ -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

@ -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

@ -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

@ -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$/;

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

@ -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';

View File

@ -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;

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);
}
/**

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
@ -77,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[]}) {}
@ -330,15 +332,15 @@ 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;
}
@ -368,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;
}
});
@ -382,7 +384,7 @@ 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[0] instanceof html.Text) {
@ -496,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

@ -987,4 +987,4 @@ function stringifyType(type: any): string {
} else {
return stringify(type);
}
}
}

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

@ -9,12 +9,12 @@
import {getHtmlTagDefinition} from './ml_parser/html_tags';
const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g');
/**

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) {
@ -544,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;
}
@ -572,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)) {
@ -587,7 +581,7 @@ class TemplateParseVisitor implements html.Visitor {
}
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
}
});
}); // fix syntax highlighting issue: `
return directiveAsts;
}
@ -748,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);
@ -817,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

@ -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;
@ -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

@ -64,7 +64,7 @@ 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 (isPresent(this.ownerDirectiveExpression)) {

View File

@ -154,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

@ -133,4 +133,4 @@ type EventSummary = {
name: string,
target: string,
phase: string
};
}

View File

@ -1,55 +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 {CompileQueryMetadata, 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 token = ce.referenceTokens[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

@ -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

@ -48,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 {
@ -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));
}

View File

@ -6,10 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
import {computeMsgId, sha1} from '../../src/i18n/digest';
import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
export function main(): void {
describe('digest', () => {
describe('digest', () => {
it('must return the ID if it\'s explicit', () => {
expect(digest({
id: 'i',
nodes: [],
placeholders: {},
placeholderToMessage: {},
meaning: '',
description: '',
})).toEqual('i');
});
});
describe('sha1', () => {
it('should work on empty strings',
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });

View File

@ -20,7 +20,10 @@ export function main() {
describe('elements', () => {
it('should extract from elements', () => {
expect(extract('<div i18n="m|d|e">text<span>nested</span></div>')).toEqual([
[['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e'],
[
['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e',
''
],
]);
});
@ -29,11 +32,45 @@ export function main() {
extract(
'<div i18n="m1|d1"><span i18n-title="m2|d2" title="single child">nested</span></div>'))
.toEqual([
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1'],
[['single child'], 'm2', 'd2'],
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1', ''],
[['single child'], 'm2', 'd2', ''],
]);
});
it('should extract from attributes with id', () => {
expect(
extract(
'<div i18n="m1|d1@@i1"><span i18n-title="m2|d2@@i2" title="single child">nested</span></div>'))
.toEqual([
[
['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1',
'i1'
],
[['single child'], 'm2', 'd2', 'i2'],
]);
});
it('should extract from attributes without meaning and with id', () => {
expect(
extract(
'<div i18n="d1@@i1"><span i18n-title="d2@@i2" title="single child">nested</span></div>'))
.toEqual([
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', 'd1', 'i1'],
[['single child'], '', 'd2', 'i2'],
]);
});
it('should extract from attributes with id only', () => {
expect(
extract(
'<div i18n="@@i1"><span i18n-title="@@i2" title="single child">nested</span></div>'))
.toEqual([
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', '', 'i1'],
[['single child'], '', '', 'i2'],
]);
});
it('should extract from ICU messages', () => {
expect(
extract(
@ -43,10 +80,10 @@ export function main() {
[
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">]}}'
],
'm', 'd'
'm', 'd', ''
],
[['title'], '', ''],
[['desc'], '', ''],
[['title'], '', '', ''],
[['desc'], '', '', ''],
]);
});
@ -55,7 +92,7 @@ export function main() {
it('should ignore implicit elements in translatable elements', () => {
expect(extract('<div i18n="m|d"><p></p></div>', ['p'])).toEqual([
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
]);
});
});
@ -64,17 +101,19 @@ export function main() {
it('should extract from blocks', () => {
expect(extract(`<!-- i18n: meaning1|desc1 -->message1<!-- /i18n -->
<!-- i18n: desc2 -->message2<!-- /i18n -->
<!-- i18n -->message3<!-- /i18n -->`))
<!-- i18n -->message3<!-- /i18n -->
<!-- i18n: meaning4|desc4@@id4 -->message4<!-- /i18n -->
<!-- i18n: @@id5 -->message5<!-- /i18n -->`))
.toEqual([
[['message1'], 'meaning1', 'desc1'],
[['message2'], '', 'desc2'],
[['message3'], '', ''],
[['message1'], 'meaning1', 'desc1', ''], [['message2'], '', 'desc2', ''],
[['message3'], '', '', ''], [['message4'], 'meaning4', 'desc4', 'id4'],
[['message5'], '', '', 'id5']
]);
});
it('should ignore implicit elements in blocks', () => {
expect(extract('<!-- i18n:m|d --><p></p><!-- /i18n -->', ['p'])).toEqual([
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
]);
});
@ -88,7 +127,7 @@ export function main() {
[
'{count, plural, =0 {[<ph tag name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}'
],
'', ''
'', '', ''
],
[
[
@ -98,15 +137,15 @@ export function main() {
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
'[<ph name="INTERPOLATION">interp</ph>]'
],
'', ''
'', '', ''
],
]);
});
it('should ignore other comments', () => {
expect(extract(`<!-- i18n: meaning1|desc1 --><!-- other -->message1<!-- /i18n -->`))
expect(extract(`<!-- i18n: meaning1|desc1@@id1 --><!-- other -->message1<!-- /i18n -->`))
.toEqual([
[['message1'], 'meaning1', 'desc1'],
[['message1'], 'meaning1', 'desc1', 'id1'],
]);
});
@ -118,34 +157,37 @@ export function main() {
it('should extract ICU messages from translatable elements', () => {
// single message when ICU is the only children
expect(extract('<div i18n="m|d">{count, plural, =0 {text}}</div>')).toEqual([
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
]);
// single message when ICU is the only (implicit) children
expect(extract('<div>{count, plural, =0 {text}}</div>', ['div'])).toEqual([
[['{count, plural, =0 {[text]}}'], '', ''],
[['{count, plural, =0 {[text]}}'], '', '', ''],
]);
// one message for the element content and one message for the ICU
expect(extract('<div i18n="m|d">before{count, plural, =0 {text}}after</div>')).toEqual([
[['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd'],
[['{count, plural, =0 {[text]}}'], '', ''],
expect(extract('<div i18n="m|d@@i">before{count, plural, =0 {text}}after</div>')).toEqual([
[
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd',
'i'
],
[['{count, plural, =0 {[text]}}'], '', '', ''],
]);
});
it('should extract ICU messages from translatable block', () => {
// single message when ICU is the only children
expect(extract('<!-- i18n:m|d -->{count, plural, =0 {text}}<!-- /i18n -->')).toEqual([
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
]);
// one message for the block content and one message for the ICU
expect(extract('<!-- i18n:m|d -->before{count, plural, =0 {text}}after<!-- /i18n -->'))
.toEqual([
[['{count, plural, =0 {[text]}}'], '', ''],
[['{count, plural, =0 {[text]}}'], '', '', ''],
[
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm',
'd'
'd', ''
],
]);
});
@ -156,20 +198,20 @@ export function main() {
it('should ignore nested ICU messages', () => {
expect(extract('<div i18n="m|d">{count, plural, =0 { {sex, select, male {m}} }}</div>'))
.toEqual([
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd'],
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd', ''],
]);
});
it('should ignore implicit elements in non translatable ICU messages', () => {
expect(
extract(
'<div i18n="m|d">{count, plural, =0 { {sex, select, male {<p>ignore</p>}} }}</div>',
['p']))
expect(extract(
'<div i18n="m|d@@i">{count, plural, =0 { {sex, select, male {<p>ignore</p>}}' +
' }}</div>',
['p']))
.toEqual([[
[
'{count, plural, =0 {[{sex, select, male {[<ph tag name="START_PARAGRAPH">ignore</ph name="CLOSE_PARAGRAPH">]}}, ]}}'
],
'm', 'd'
'm', 'd', 'i'
]]);
});
@ -181,46 +223,45 @@ export function main() {
describe('attributes', () => {
it('should extract from attributes outside of translatable sections', () => {
expect(extract('<div i18n-title="m|d" title="msg"></div>')).toEqual([
[['msg'], 'm', 'd'],
expect(extract('<div i18n-title="m|d@@i" title="msg"></div>')).toEqual([
[['msg'], 'm', 'd', 'i'],
]);
});
it('should extract from attributes in translatable elements', () => {
expect(extract('<div i18n><p><b i18n-title="m|d" title="msg"></b></p></div>')).toEqual([
expect(extract('<div i18n><p><b i18n-title="m|d@@i" title="msg"></b></p></div>')).toEqual([
[
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
'', ''
'', '', ''
],
[['msg'], 'm', 'd'],
[['msg'], 'm', 'd', 'i'],
]);
});
it('should extract from attributes in translatable blocks', () => {
expect(extract('<!-- i18n --><p><b i18n-title="m|d" title="msg"></b></p><!-- /i18n -->'))
.toEqual([
[['msg'], 'm', 'd'],
[['msg'], 'm', 'd', ''],
[
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
'', ''
'', '', ''
],
]);
});
it('should extract from attributes in translatable ICUs', () => {
expect(
extract(
'<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}<!-- /i18n -->'))
expect(extract(`<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d@@i"
title="msg"></b></p>}}<!-- /i18n -->`))
.toEqual([
[['msg'], 'm', 'd'],
[['msg'], 'm', 'd', 'i'],
[
[
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag' +
' name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
],
'', ''
'', '', ''
],
]);
});
@ -228,7 +269,7 @@ export function main() {
it('should extract from attributes in non translatable ICUs', () => {
expect(extract('{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}'))
.toEqual([
[['msg'], 'm', 'd'],
[['msg'], 'm', 'd', ''],
]);
});
@ -239,7 +280,7 @@ export function main() {
describe('implicit elements', () => {
it('should extract from implicit elements', () => {
expect(extract('<b>bold</b><i>italic</i>', ['b'])).toEqual([
[['bold'], '', ''],
[['bold'], '', '', ''],
]);
});
@ -251,7 +292,7 @@ export function main() {
}).not.toThrow();
expect(result).toEqual([
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', ''],
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', '', ''],
]);
});
@ -261,7 +302,7 @@ export function main() {
it('should extract implicit attributes', () => {
expect(extract('<b title="bb">bold</b><i title="ii">italic</i>', [], {'b': ['title']}))
.toEqual([
[['bb'], '', ''],
[['bb'], '', '', ''],
]);
});
});
@ -433,7 +474,7 @@ function extract(
// clang-format off
// https://github.com/angular/clang-format/issues/35
return result.messages.map(
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][];
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, message.id]) as [string[], string, string][];
// clang-format on
}

View File

@ -59,14 +59,17 @@ export function main() {
tb.detectChanges();
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('un');
cmp.count = 2;
tb.detectChanges();
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('deux');
cmp.count = 3;
tb.detectChanges();
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('beaucoup');
cmp.sex = 'm';
cmp.sexB = 'f';
@ -90,8 +93,8 @@ export function main() {
.toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
expectHtml(el, '#i18n-13')
.toBe('<div id="i18n-13" title="dans une section traductible"></div>');
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
expectHtml(el, '#i18n-16').toMatch(/avec un ID explicite/);
});
});
}
@ -141,6 +144,8 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
<!-- /i18n -->
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
<div id="i18n-16" i18n="@@i18n16">with an explicit ID</div>
<div id="i18n-17" i18n="@@i18n17">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
`
})
class I18nComponent {
@ -182,6 +187,9 @@ const XTB = `
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
</translation>
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
<translation id="i18n16">avec un ID explicite</translation>
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</translation>
</translationbundle>`;
// unused, for reference only
@ -210,5 +218,7 @@ const XMB = `
<ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
<msg id="i18n16">with an explicit ID</msg>
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
</messagebundle>
`;

View File

@ -18,6 +18,8 @@ const HTML = `
<p i18n-title title="translatable attribute">not translatable</p>
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
<p i18n="m|d">foo</p>
<p i18n="m|d@@i">foo</p>
<p i18n="@@bar">foo</p>
<p i18n="ph names"><br><img><div></div></p>
`;
@ -39,6 +41,16 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<note priority="1" from="description">d</note>
<note priority="1" from="meaning">m</note>
</trans-unit>
<trans-unit id="i" datatype="html">
<source>foo</source>
<target/>
<note priority="1" from="description">d</note>
<note priority="1" from="meaning">m</note>
</trans-unit>
<trans-unit id="bar" datatype="html">
<source>foo</source>
<target/>
</trans-unit>
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" datatype="html">
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
<target/>
@ -67,6 +79,16 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<note priority="1" from="description">d</note>
<note priority="1" from="meaning">m</note>
</trans-unit>
<trans-unit id="i" datatype="html">
<source>foo</source>
<target>toto</target>
<note priority="1" from="description">d</note>
<note priority="1" from="meaning">m</note>
</trans-unit>
<trans-unit id="bar" datatype="html">
<source>foo</source>
<target>tata</target>
</trans-unit>
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" datatype="html">
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
<target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
@ -107,6 +129,8 @@ export function main(): void {
'ec1d033f2436133c14ab038286c4f5df4697484a':
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
'i': 'toto',
'bar': 'tata',
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
});

View File

@ -18,6 +18,9 @@ export function main(): void {
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
<!-- i18n -->{ count, plural, =0 {<p>test</p>}}<!-- /i18n -->
<p i18n="m|d">foo</p>
<p i18n="m|d@@i">foo</p>
<p i18n="@@bar">foo</p>
<p i18n="@@baz">{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
<p i18n>{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>`;
const XMB = `<?xml version="1.0" encoding="UTF-8" ?>
@ -46,6 +49,9 @@ export function main(): void {
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} }</msg>
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
<msg id="i" desc="d" meaning="m">foo</msg>
<msg id="bar">foo</msg>
<msg id="baz">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} } } }</msg>
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} } } }</msg>
</messagebundle>
`;

View File

@ -21,7 +21,7 @@ export function main(): void {
it('should translate a plain message', () => {
const msgMap = {foo: [new i18n.Text('bar', null)]};
const tb = new TranslationBundle(msgMap, (_) => 'foo');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
});
@ -36,7 +36,7 @@ export function main(): void {
ph1: '*phContent*',
};
const tb = new TranslationBundle(msgMap, (_) => 'foo');
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']);
});
@ -51,8 +51,8 @@ export function main(): void {
new i18n.Text('*refMsg*', null),
],
};
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
let count = 0;
const digest = (_: any) => count++ ? 'ref' : 'foo';
const tb = new TranslationBundle(msgMap, digest);
@ -69,13 +69,13 @@ export function main(): void {
]
};
const tb = new TranslationBundle(msgMap, (_) => 'foo');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/);
});
it('should report missing translation', () => {
const tb = new TranslationBundle({}, (_) => 'foo');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
expect(() => tb.get(msg)).toThrowError(/Missing translation for message foo/);
});
@ -83,8 +83,8 @@ export function main(): void {
const msgMap = {
foo: [new i18n.Placeholder('', 'ph1', span)],
};
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
let count = 0;
const digest = (_: any) => count++ ? 'ref' : 'foo';
const tb = new TranslationBundle(msgMap, digest);
@ -102,7 +102,7 @@ export function main(): void {
ph1: '</b>',
};
const tb = new TranslationBundle(msgMap, (_) => 'foo');
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/);
});
});

View File

@ -1,51 +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, Directive, Input} 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';
export function main() {
describe('integration tests', () => {
let fixture: ComponentFixture<TestComponent>;
describe('directives', () => {
it('should support dotted selectors', async(() => {
@Directive({selector: '[dot.name]'})
class MyDir {
@Input('dot.name') value: string;
}
TestBed.configureTestingModule({
declarations: [
MyDir,
TestComponent,
],
});
const template = `<div [dot.name]="'foo'"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
expect(myDir.value).toEqual('foo');
}));
});
});
}
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
}
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -44,7 +44,7 @@ const baseErrorIdentifier = {
runtime: BaseError
};
export const codegenExportsVars = [
export var codegenExportsVars = [
'getExpressions',
];
@ -190,7 +190,7 @@ const _getExpressionsStmts: o.Statement[] = [
]))
];
export const codegenStmts: o.Statement[] = [
export var codegenStmts: o.Statement[] = [
new o.CommentStmt('This is a comment'),
new o.ClassStmt(

View File

@ -30,14 +30,14 @@ export function main() {
it('should select by element name case sensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
expect(matcher.match(getSelectorFor({tag: 'SOMEOTHERTAG'}), selectableCollector))
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(getSelectorFor({tag: 'SOMETAG'}), selectableCollector)).toEqual(false);
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(getSelectorFor({tag: 'someTag'}), selectableCollector)).toEqual(true);
expect(matcher.match(CssSelector.parse('someTag')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@ -45,22 +45,21 @@ export function main() {
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
expect(matcher.match(getSelectorFor({classes: 'SOMEOTHERCLASS'}), selectableCollector))
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(getSelectorFor({classes: 'SOMECLASS'}), selectableCollector))
.toEqual(true);
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0], 1]);
reset();
expect(matcher.match(getSelectorFor({classes: 'someClass class2'}), selectableCollector))
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
});
it('should not throw for class name "constructor"', () => {
expect(matcher.match(getSelectorFor({classes: 'constructor'}), selectableCollector))
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
});
@ -69,43 +68,36 @@ export function main() {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
expect(matcher.match(getSelectorFor({attrs: [['SOMEOTHERATTR', '']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(getSelectorFor({attrs: [['SOMEATTR', '']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'someValue']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
getSelectorFor({attrs: [['someAttr', ''], ['someAttr2', '']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
getSelectorFor({attrs: [['someAttr', 'someValue'], ['someAttr2', '']]}),
selectableCollector))
CssSelector.parse('[someAttr=someValue][someAttr2]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
getSelectorFor({attrs: [['someAttr2', ''], ['someAttr', 'someValue']]}),
selectableCollector))
CssSelector.parse('[someAttr2][someAttr=someValue]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset();
expect(matcher.match(
getSelectorFor({attrs: [['someAttr2', 'someValue'], ['someAttr', '']]}),
selectableCollector))
CssSelector.parse('[someAttr2=someValue][someAttr]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
});
@ -113,13 +105,11 @@ export function main() {
it('should support "." in attribute names', () => {
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
expect(matcher.match(getSelectorFor({attrs: [['barfoo', '']]}), selectableCollector))
.toEqual(false);
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false);
expect(matched).toEqual([]);
reset();
expect(matcher.match(getSelectorFor({attrs: [['foo.bar', '']]}), selectableCollector))
.toEqual(true);
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@ -137,18 +127,15 @@ export function main() {
it('should select by attr name case sensitive and value case insensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
expect(matcher.match(
getSelectorFor({attrs: [['SOMEATTR', 'SOMEOTHERATTR']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'SOMEVALUE']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(getSelectorFor({attrs: [['someAttr', 'SOMEVALUE']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('[someAttr=SOMEVALUE]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@ -156,38 +143,31 @@ export function main() {
it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
expect(matcher.match(
CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0],
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
getSelectorFor(
{tag: 'someOtherTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
getSelectorFor(
{tag: 'someTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
getSelectorFor(
{tag: 'someTag', classes: 'someClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
expect(
matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
getSelectorFor({tag: 'someTag', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match(
getSelectorFor(
{tag: 'someTag', classes: 'someClass', attrs: [['someAttr', 'someValue']]}),
selectableCollector))
expect(
matcher.match(
CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@ -237,9 +217,7 @@ export function main() {
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
expect(matcher.match(
getSelectorFor({tag: 'p', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
});
@ -250,38 +228,32 @@ export function main() {
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(
matcher.match(
getSelectorFor({tag: 'p', attrs: [['someOtherAttr', '']], classes: 'someOtherClass'}),
selectableCollector))
expect(matcher.match(
CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
});
it('should match * with :not selector', () => {
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
expect(matcher.match(getSelectorFor({tag: 'div'}), () => {})).toEqual(true);
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true);
});
it('should match with multiple :not selectors', () => {
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['a', '']]}), selectableCollector))
.toBe(false);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['b', '']]}), selectableCollector))
.toBe(false);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['c', '']]}), selectableCollector))
.toBe(true);
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
});
it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
expect(matcher.match(getSelectorFor({tag: 'textbox'}), selectableCollector)).toEqual(true);
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[1], 1]);
reset();
expect(matcher.match(
getSelectorFor({tag: 'input', attrs: [['type', 'text']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]);
});
@ -289,8 +261,7 @@ export function main() {
it('should not select twice with two matches in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
expect(
matcher.match(getSelectorFor({tag: 'input', classes: 'someclass'}), selectableCollector))
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector))
.toEqual(true);
expect(matched.length).toEqual(2);
expect(matched).toEqual([s1[0], 1]);
@ -433,16 +404,3 @@ export function main() {
});
});
}
function getSelectorFor(
{tag = '', attrs = [], classes = ''}: {tag?: string, attrs?: any[], classes?: string} = {}):
CssSelector {
const selector = new CssSelector();
selector.setElement(tag);
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
return selector;
}

View File

@ -623,23 +623,6 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
]);
});
it('should parse directive dotted properties', () => {
const dirA =
CompileDirectiveMetadata
.create({
selector: '[dot.name]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
inputs: ['localName: dot.name'],
})
.toSummary();
expect(humanizeTplAst(parse('<div [dot.name]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'],
[DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'localName', 'expr'],
]);
});
it('should locate directives in property bindings', () => {
const dirA =
CompileDirectiveMetadata
@ -1261,15 +1244,8 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
});
it('should parse variables via let ...', () => {
const targetAst = [
[EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
[ElementAst, 'div'],
];
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', []))).toEqual(targetAst);
expect(humanizeTplAst(parse('<div data-*ngIf="let a=b">', []))).toEqual(targetAst);
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', [
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
});
describe('directives', () => {

View File

@ -10,12 +10,14 @@
import {__core_private__ as r} from '@angular/core';
export type ViewMetadata = typeof r._ViewMetadata;
export const ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
export var ViewMetadata: typeof r.ViewMetadata = r.ViewMetadata;
import {__core_private_testing__ as r2} from '@angular/core/testing';
export type TestingCompiler = typeof r2._TestingCompiler;
export const TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
export var TestingCompiler: typeof r2.TestingCompiler = r2.TestingCompiler;
export type TestingCompilerFactory = typeof r2._TestingCompilerFactory;
export const TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;
export var TestingCompilerFactory: typeof r2.TestingCompilerFactory = r2.TestingCompilerFactory;

View File

@ -18,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
// This provider is put here just so that we can access it from multiple
// internal test packages.
// TODO: get rid of it or move to a separate @angular/internal_testing package
export const TEST_COMPILER_PROVIDERS: Provider[] = [
export var TEST_COMPILER_PROVIDERS: Provider[] = [
{provide: ElementSchemaRegistry, useValue: new MockSchemaRegistry({}, {}, {}, [], [])},
{provide: ResourceLoader, useClass: MockResourceLoader},
{provide: UrlResolver, useFactory: createUrlResolverWithoutPackagePrefix}

View File

@ -11,7 +11,6 @@ import {ApplicationInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler';
import {ViewUtils} from './linker/view_utils';
@ -25,10 +24,6 @@ export function _keyValueDiffersFactory() {
return defaultKeyValueDiffers;
}
export function _localeFactory(locale?: string): string {
return locale || 'en-US';
}
/**
* This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`.
@ -46,11 +41,7 @@ export function _localeFactory(locale?: string): string {
AnimationQueue,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
{provide: LOCALE_ID, useValue: 'en-US'},
]
})
export class ApplicationModule {

View File

@ -469,7 +469,7 @@ export class ApplicationRef_ extends ApplicationRef {
this._loadComponent(compRef);
if (isDevMode()) {
this._console.log(
`Angular is running in the development mode. Call enableProdMode() to enable the production mode.`);
`Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.`);
}
return compRef;
}

View File

@ -43,7 +43,7 @@ import * as api from './render/api';
import * as decorators from './util/decorators';
import {isPromise} from './util/lang';
export const __core_private__: {
export var __core_private__: {
isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy,
ChangeDetectorStatus: typeof constants.ChangeDetectorStatus,
_ChangeDetectorStatus?: constants.ChangeDetectorStatus,

View File

@ -225,33 +225,7 @@ export const ContentChildren: ContentChildrenDecorator =
*/
export interface ContentChildDecorator {
/**
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChild to get the first element or the directive matching the selector from
* the content DOM. If the content DOM changes, and a new child matches the selector,
* the property will be updated.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried element.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
* @docsNotRequired
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ContentChild;
@ -267,7 +241,30 @@ export interface ContentChildDecorator {
export type ContentChild = Query;
/**
* ContentChild decorator and metadata.
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChild to get the first element or the directive matching the selector from the
* content DOM. If the content DOM changes, and a new child matches the selector,
* the property will be updated.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried element.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
@ -293,35 +290,8 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
*/
export interface ViewChildrenDecorator {
/**
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
* and the changes observable of the query list will emit a new value.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
* @docsNotRequired
*/ (selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChildren;
}
@ -333,7 +303,30 @@ export interface ViewChildrenDecorator {
export type ViewChildren = Query;
/**
* ViewChildren decorator and metadata.
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
* and the changes observable of the query list will emit a new value.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation

View File

@ -50,7 +50,7 @@ function noopScope(arg0?: any, arg1?: any): any {
*
* @experimental
*/
export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
export var wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
wtfEnabled ? createScope : (signature: string, flags?: any) => noopScope;
/**
@ -62,7 +62,7 @@ export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
* Returns the `returnValue for easy chaining.
* @experimental
*/
export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
export var wtfLeave: <T>(scope: any, returnValue?: T) => T =
wtfEnabled ? leave : (s: any, r?: any) => r;
/**
@ -78,7 +78,7 @@ export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
* }
* @experimental
*/
export const wtfStartTimeRange: (rangeType: string, action: string) => any =
export var wtfStartTimeRange: (rangeType: string, action: string) => any =
wtfEnabled ? startTimeRange : (rangeType: string, action: string) => null;
/**
@ -87,4 +87,4 @@ export const wtfStartTimeRange: (rangeType: string, action: string) => any =
* enabled.
* @experimental
*/
export const wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;
export var wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;

View File

@ -7,7 +7,7 @@
*/
import {global, isPresent, stringify} from '../facade/lang';
import {Type, isType} from '../type';
import {Type} from '../type';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {GetterFn, MethodFn, SetterFn} from './types';
@ -105,10 +105,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
parameters(type: Type<any>): any[][] {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
if (!isType(type)) {
return [];
}
const parentCtor = getParentCtor(type);
const parentCtor = Object.getPrototypeOf(type.prototype).constructor;
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
@ -138,10 +135,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
annotations(typeOrFunc: Type<any>): any[] {
if (!isType(typeOrFunc)) {
return [];
}
const parentCtor = getParentCtor(typeOrFunc);
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
@ -176,10 +170,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
if (!isType(typeOrFunc)) {
return {};
}
const parentCtor = getParentCtor(typeOrFunc);
const parentCtor = Object.getPrototypeOf(typeOrFunc.prototype).constructor;
const propMetadata: {[key: string]: any[]} = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
@ -242,11 +233,3 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[]
return new annotationCls(...annotationArgs);
});
}
function getParentCtor(ctor: Function): Type<any> {
const parentProto = Object.getPrototypeOf(ctor.prototype);
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.
return parentCtor || Object;
}

View File

@ -18,8 +18,5 @@
*/
export const Type = Function;
export function isType(v: any): v is Type<any> {
return typeof v === 'function';
}
export interface Type<T> extends Function { new (...args: any[]): T; }

View File

@ -25,7 +25,6 @@ import {AnimationPlayer, NoOpAnimationPlayer} from '../../src/animation/animatio
import {AnimationStyles} from '../../src/animation/animation_styles';
import {AnimationTransitionEvent} from '../../src/animation/animation_transition_event';
import {AUTO_STYLE, animate, group, keyframes, sequence, state, style, transition, trigger} from '../../src/animation/metadata';
import {Input} from '../../src/core';
import {isPresent} from '../../src/facade/lang';
import {TestBed, fakeAsync, flushMicrotasks} from '../../testing';
import {MockAnimationPlayer} from '../../testing/mock_animation_player';
@ -2244,52 +2243,38 @@ function declareTests({useJit}: {useJit: boolean}) {
});
describe('error handling', () => {
if (!getDOM().supportsWebAnimation()) return;
it('should not throw an error when an animation exists within projected content that is not bound to the DOM',
it('should recover if an animation driver or player throws an error during an animation',
fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [DummyIfCmp, DummyLoadingCmp],
providers: [{provide: AnimationDriver, useClass: WebAnimationsDriver}],
declarations: [DummyIfCmp],
providers: [{provide: AnimationDriver, useClass: ErroneousAnimationDriver}],
imports: [CommonModule]
});
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<dummy-loading-cmp [exp2]="exp">
<div [@myAnimation]="exp ? 'true' : 'false'" (@myAnimation.done)="callback()">world</div>
</dummy-loading-cmp>
<div [@myAnimation]="exp" (@myAnimation.start)="callback1($event)" (@myAnimation.done)="callback2($event)"></div>
`,
animations: [trigger('myAnimation', [transition(
'* => *',
[
style({opacity: 0}),
animate(1000, style({opacity: 1})),
animate(1000, style({transform: 'noooooo'})),
])])]
}
});
TestBed.overrideComponent(
DummyLoadingCmp, {set: {template: `hello <ng-content *ngIf="exp2"></ng-content>`}});
const fixture = TestBed.createComponent(DummyIfCmp);
const cmp = fixture.componentInstance;
const container = fixture.nativeElement;
let animationCalls = 0;
cmp.callback = () => animationCalls++;
cmp.exp = false;
fixture.detectChanges();
flushMicrotasks();
expect(animationCalls).toBe(1);
expect(getDOM().getText(container).trim()).toEqual('hello');
let started = false;
let done = false;
cmp.callback1 = (event: AnimationTransitionEvent) => started = true;
cmp.callback2 = (event: AnimationTransitionEvent) => done = true;
cmp.exp = true;
fixture.detectChanges();
flushMicrotasks();
expect(animationCalls).toBe(2);
expect(getDOM().getText(container).trim()).toMatch(/hello[\s\r\n]+world/m);
expect(started).toBe(true);
expect(done).toBe(true);
}));
});
@ -2475,9 +2460,6 @@ class DummyIfCmp {
class DummyLoadingCmp {
exp: any = false;
callback = () => {};
@Input('exp2')
exp2: any = false;
}
@Component({
@ -2589,3 +2571,11 @@ class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
return player;
}
}
class ErroneousAnimationDriver extends MockAnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): WebAnimationsPlayer {
throw new Error();
}
}

View File

@ -32,7 +32,7 @@ export function main() {
{providers: [{provide: APP_INITIALIZER, multi: true, useValue: () => promise}]});
});
it('should update the status once all async initializers are done',
it('should updat the status once all async initializers are done',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
let completerResolver = false;
setTimeout(() => {

View File

@ -14,4 +14,4 @@ export function main() {
it('should set the default locale to "en-US"',
inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); }));
});
}
}

View File

@ -43,8 +43,6 @@ export function main() {
NeedsContentChildWithRead,
NeedsViewChildrenWithRead,
NeedsViewChildWithRead,
NeedsContentChildTemplateRef,
NeedsContentChildTemplateRefApp,
NeedsViewContainerWithRead,
ManualProjecting
]
@ -264,15 +262,6 @@ export function main() {
expect(comp.textDirChild.text).toEqual('ca');
});
it('should contain the first descendant content child templateRef', () => {
const template = '<needs-content-child-template-ref-app>' +
'</needs-content-child-template-ref-app>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
view.detectChanges();
expect(view.nativeElement).toHaveText('OUTER');
});
it('should contain the first view child', () => {
const template = '<needs-view-child-read></needs-view-child-read>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -741,23 +730,6 @@ class NeedsContentChildWithRead {
@ContentChild('nonExisting', {read: TextDirective}) nonExistingVar: TextDirective;
}
@Component({
selector: 'needs-content-child-template-ref',
template: '<div [ngTemplateOutlet]="templateRef"></div>'
})
class NeedsContentChildTemplateRef {
@ContentChild(TemplateRef) templateRef: TemplateRef<any>;
}
@Component({
selector: 'needs-content-child-template-ref-app',
template: '<needs-content-child-template-ref>' +
'<template>OUTER<template>INNER</template></template>' +
'</needs-content-child-template-ref>'
})
class NeedsContentChildTemplateRefApp {
}
@Component({
selector: 'needs-view-children-read',
template: '<div #q text="va"></div><div #w text="vb"></div>',

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
import {Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
@ -119,7 +119,7 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(injector.get(token)).toEqual(tokenValue);
});
it('should support providers with an anonymous function as token', () => {
it('should support providers with an anonymous function', () => {
const token = () => true;
const tokenValue = 1;
const injector = createInjector([{provide: token, useValue: tokenValue}]);
@ -147,22 +147,6 @@ function declareTests({useJit}: {useJit: boolean}) {
const injector = createInjector([{provide: 'someToken', useValue: data}]);
expect(injector.get('someToken')).toEqual(data);
});
describe('ANALYZE_FOR_ENTRY_COMPONENTS providers', () => {
it('should support class instances', () => {
class SomeObject {
someMethod() {}
}
expect(
() => createInjector([
{provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: new SomeObject(), multi: true}
]))
.not.toThrow();
});
});
});
it('should allow logging a previous elements class binding via interpolation', () => {

View File

@ -190,8 +190,6 @@ export function main() {
class ChildNoDecorators extends Parent {}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.annotations(Parent)).toEqual([new ClassDecorator({value: 'parent'})]);
@ -201,11 +199,6 @@ export function main() {
expect(reflector.annotations(ChildNoDecorators)).toEqual([new ClassDecorator(
{value: 'parent'})]);
expect(reflector.annotations(NoDecorators)).toEqual([]);
expect(reflector.annotations(<any>{})).toEqual([]);
expect(reflector.annotations(<any>1)).toEqual([]);
expect(reflector.annotations(null)).toEqual([]);
});
it('should inherit parameters', () => {
@ -233,8 +226,6 @@ export function main() {
constructor(a: any, b: any, c: any) { super(null, null); }
}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.parameters(Parent)).toEqual([
[A, new ParamDecorator('a')], [B, new ParamDecorator('b')]
@ -251,11 +242,6 @@ export function main() {
expect(reflector.parameters(ChildWithCtorNoDecorator)).toEqual([
undefined, undefined, undefined
]);
expect(reflector.parameters(NoDecorators)).toEqual([]);
expect(reflector.parameters(<any>{})).toEqual([]);
expect(reflector.parameters(<any>1)).toEqual([]);
expect(reflector.parameters(null)).toEqual([]);
});
it('should inherit property metadata', () => {
@ -277,8 +263,6 @@ export function main() {
c: C;
}
class NoDecorators {}
// Check that metadata for Parent was not changed!
expect(reflector.propMetadata(Parent)).toEqual({
'a': [new PropDecorator('a')],
@ -290,11 +274,6 @@ export function main() {
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
'c': [new PropDecorator('c')]
});
expect(reflector.propMetadata(NoDecorators)).toEqual({});
expect(reflector.propMetadata(<any>{})).toEqual({});
expect(reflector.propMetadata(<any>1)).toEqual({});
expect(reflector.propMetadata(null)).toEqual({});
});
it('should inherit lifecycle hooks', () => {
@ -353,7 +332,7 @@ export function main() {
static ctorParameters = () =>
[{type: A, decorators: [{type: ParamDecorator, args: ['a']}]},
{type: B, decorators: [{type: ParamDecorator, args: ['b']}]},
]
];
}
class Child extends Parent {}

View File

@ -9,7 +9,7 @@
import * as mock_animation_player from './mock_animation_player';
import * as test_compiler from './test_compiler';
export const __core_private_testing__: {
export var __core_private_testing__: {
TestingCompiler: typeof test_compiler.TestingCompiler,
_TestingCompiler?: test_compiler.TestingCompiler,
TestingCompilerFactory: typeof test_compiler.TestingCompilerFactory,

View File

@ -135,6 +135,11 @@ export class TestBed implements Injector {
return TestBed;
}
static overrideTemplate(component: Type<any>, template: string): typeof TestBed {
getTestBed().overrideComponent(component, {set: {template, templateUrl: null}});
return TestBed;
}
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
return getTestBed().get(token, notFoundValue);
}
@ -262,7 +267,7 @@ export class TestBed implements Injector {
} catch (e) {
if (e.compType) {
throw new Error(
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
`This test module uses the component ${stringify(e.compType)} which is using a "templateUrl", but they were never compiled. ` +
`Please call "TestBed.compileComponents" before your test.`);
} else {
throw e;

View File

@ -29,4 +29,4 @@ if (_global.beforeEach) {
// TODO(juliemr): remove this, only used because we need to export something to have compilation
// work.
export const __core_private_testing_placeholder__ = '';
export var __core_private_testing_placeholder__ = '';

View File

@ -19,12 +19,12 @@ export {inject} from './test_bed';
export * from './logger';
export * from './ng_zone_mock';
export const proxy: ClassDecorator = (t: any) => t;
export var proxy: ClassDecorator = (t: any) => t;
const _global = <any>(typeof window === 'undefined' ? global : window);
export const afterEach: Function = _global.afterEach;
export const expect: (actual: any) => jasmine.Matchers = _global.expect;
export var afterEach: Function = _global.afterEach;
export var expect: (actual: any) => jasmine.Matchers = _global.expect;
const jsmBeforeEach = _global.beforeEach;
const jsmDescribe = _global.describe;

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 {$, ExpectedConditions, browser, by, element} from 'protractor';
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
function waitForElement(selector: string) {
const EC = ExpectedConditions;
// Waits for the element with id 'abc' to be present on the dom.
browser.wait(EC.presenceOf($(selector)), 20000);
}
describe('ngIf', () => {
const URL = 'common/ngIf/ts/';
afterEach(verifyNoBrowserErrors);
describe('ng-if-simple', () => {
let comp = 'ng-if-simple';
it('should hide/show content', () => {
browser.get(URL);
waitForElement(comp);
expect(element.all(by.css(comp)).get(0).getText()).toEqual('hide show = true\nText to show');
element(by.css(comp + ' button')).click();
expect(element.all(by.css(comp)).get(0).getText()).toEqual('show show = false');
});
});
describe('ng-if-else', () => {
let comp = 'ng-if-else';
it('should hide/show content', () => {
browser.get(URL);
waitForElement(comp);
expect(element.all(by.css(comp)).get(0).getText()).toEqual('hide show = true\nText to show');
element(by.css(comp + ' button')).click();
expect(element.all(by.css(comp)).get(0).getText())
.toEqual('show show = false\nAlternate text while primary text is hidden');
});
});
describe('ng-if-then-else', () => {
let comp = 'ng-if-then-else';
it('should hide/show content', () => {
browser.get(URL);
waitForElement(comp);
expect(element.all(by.css(comp)).get(0).getText())
.toEqual('hide Switch Primary show = true\nPrimary text to show');
element.all(by.css(comp + ' button')).get(1).click();
expect(element.all(by.css(comp)).get(0).getText())
.toEqual('hide Switch Primary show = true\nSecondary text to show');
element.all(by.css(comp + ' button')).get(0).click();
expect(element.all(by.css(comp)).get(0).getText())
.toEqual('show Switch Primary show = false\nAlternate text while primary text is hidden');
});
});
describe('ng-if-let', () => {
let comp = 'ng-if-let';
it('should hide/show content', () => {
browser.get(URL);
waitForElement(comp);
expect(element.all(by.css(comp)).get(0).getText())
.toEqual('Next User\nWaiting... (user is null)');
element(by.css(comp + ' button')).click();
expect(element.all(by.css(comp)).get(0).getText()).toEqual('Next User\nHello Smith, John!');
});
});
});

View File

@ -0,0 +1,128 @@
/**
* @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, OnInit, TemplateRef, ViewChild} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {Subject} from 'rxjs/Subject';
// #docregion NgIfSimple
@Component({
selector: 'ng-if-simple',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
show = {{show}}
<br>
<div *ngIf="show">Text to show</div>
`
})
class NgIfSimple {
show: boolean = true;
}
// #enddocregion
// #docregion NgIfElse
@Component({
selector: 'ng-if-else',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
show = {{show}}
<br>
<div *ngIf="show; else elseBlock">Text to show</div>
<template #elseBlock>Alternate text while primary text is hidden</template>
`
})
class NgIfElse {
show: boolean = true;
}
// #enddocregion
// #docregion NgIfThenElse
@Component({
selector: 'ng-if-then-else',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
<button (click)="switchPrimary()">Switch Primary</button>
show = {{show}}
<br>
<div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div>
<template #primaryBlock>Primary text to show</template>
<template #secondaryBlock>Secondary text to show</template>
<template #elseBlock>Alternate text while primary text is hidden</template>
`
})
class NgIfThenElse implements OnInit {
thenBlock: TemplateRef<any> = null;
show: boolean = true;
@ViewChild('primaryBlock')
primaryBlock: TemplateRef<any> = null;
@ViewChild('secondaryBlock')
secondaryBlock: TemplateRef<any> = null;
switchPrimary() {
this.thenBlock = this.thenBlock === this.primaryBlock ? this.secondaryBlock : this.primaryBlock;
}
ngOnInit() { this.thenBlock = this.primaryBlock; }
}
// #enddocregion
// #docregion NgIfLet
@Component({
selector: 'ng-if-let',
template: `
<button (click)="nextUser()">Next User</button>
<br>
<div *ngIf="userObservable | async; else loading; let user">
Hello {{user.last}}, {{user.first}}!
</div>
<template #loading let-user>Waiting... (user is {{user|json}})</template>
`
})
class NgIfLet {
userObservable = new Subject<{first: string, last: string}>();
first = ['John', 'Mike', 'Mary', 'Bob'];
firstIndex = 0;
last = ['Smith', 'Novotny', 'Angular'];
lastIndex = 0;
nextUser() {
let first = this.first[this.firstIndex++];
if (this.firstIndex >= this.first.length) this.firstIndex = 0;
let last = this.last[this.lastIndex++];
if (this.lastIndex >= this.last.length) this.lastIndex = 0;
this.userObservable.next({first, last});
}
}
// #enddocregion
@Component({
selector: 'example-app',
template: `
<ng-if-simple></ng-if-simple>
<hr>
<ng-if-else></ng-if-else>
<hr>
<ng-if-then-else></ng-if-then-else>
<hr>
<ng-if-let></ng-if-let>
<hr>
`
})
class ExampleApp {
}
@NgModule({
imports: [BrowserModule],
declarations: [ExampleApp, NgIfSimple, NgIfElse, NgIfThenElse, NgIfLet],
bootstrap: [ExampleApp]
})
export class AppModule {
}

View File

@ -14,6 +14,7 @@ import {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_sta
import {NgForm} from './directives/ng_form';
import {NgModel} from './directives/ng_model';
import {NgModelGroup} from './directives/ng_model_group';
import {NgNovalidate} from './directives/ng_novalidate_directive';
import {NumberValueAccessor} from './directives/number_value_accessor';
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
import {RangeValueAccessor} from './directives/range_value_accessor';
@ -44,6 +45,7 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
export const SHARED_FORM_DIRECTIVES: Type<any>[] = [
NgNovalidate,
NgSelectOption,
NgSelectMultipleOption,
DefaultValueAccessor,

View File

@ -0,0 +1,16 @@
/**
* @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 {Directive} from '@angular/core';
@Directive({
selector: 'form:not([ngNoForm])',
host: {'novalidate': ''},
})
export class NgNovalidate {
}

View File

@ -14,9 +14,8 @@ import {isPresent} from './facade/lang';
import {AbstractControl} from './model';
import {isPromise} from './private_import_core';
function isEmptyInputValue(value: any): boolean {
// we don't check for string here so it also works with arrays
return value == null || value.length === 0;
function isEmptyInputValue(value: any) {
return value == null || typeof value === 'string' && value.length === 0;
}
/**

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, EventEmitter, Input, Output, forwardRef} from '@angular/core';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, Validator, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -16,38 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
export function main() {
describe('reactive forms integration tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule],
declarations: [
FormControlComp,
FormGroupComp,
FormArrayComp,
FormArrayNestedGroup,
FormControlNameSelect,
FormControlNumberInput,
FormControlRangeInput,
FormControlRadioButtons,
WrappedValue,
WrappedValueForm,
MyInput,
MyInputForm,
FormGroupNgModel,
FormControlNgModel,
LoginIsEmptyValidator,
LoginIsEmptyWrapper,
ValidationBindingsForm,
UniqLoginValidator,
UniqLoginWrapper,
NestedFormGroupComp,
FormControlCheckboxRequiredValidator,
]
});
});
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]});
return TestBed.createComponent(component);
}
describe('basic functionality', () => {
it('should work with single controls', () => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old value');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -64,7 +41,7 @@ export function main() {
});
it('should work with formGroups (model -> view)', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
@ -72,8 +49,17 @@ export function main() {
expect(input.nativeElement.value).toEqual('loginValue');
});
it('should add novalidate by default to form', () => {
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.getAttribute('novalidate')).toEqual('');
});
it('work with formGroups (view -> model)', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('oldValue')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -90,7 +76,7 @@ export function main() {
describe('rebound form groups', () => {
it('should update DOM elements initially', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
fixture.detectChanges();
@ -102,7 +88,7 @@ export function main() {
});
it('should update model when UI changes', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
fixture.detectChanges();
@ -123,7 +109,7 @@ export function main() {
});
it('should work with radio buttons when reusing control', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const food = new FormControl('chicken');
fixture.componentInstance.form =
new FormGroup({'food': food, 'drink': new FormControl('')});
@ -141,7 +127,7 @@ export function main() {
});
it('should update nested form group model when UI changes', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp);
fixture.componentInstance.form = new FormGroup(
{'signin': new FormGroup({'login': new FormControl(), 'password': new FormControl()})});
fixture.detectChanges();
@ -169,7 +155,7 @@ export function main() {
});
it('should pick up dir validators from form controls', () => {
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -193,7 +179,7 @@ export function main() {
});
it('should pick up dir validators from nested form groups', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
'signin':
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
@ -213,7 +199,7 @@ export function main() {
});
it('should strip named controls that are not found', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
'signin':
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
@ -239,7 +225,7 @@ export function main() {
});
it('should strip array controls that are not found', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -268,7 +254,7 @@ export function main() {
it('should attach dir to control when leaf control changes', () => {
const form = new FormGroup({'login': new FormControl('oldValue')});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -291,7 +277,7 @@ export function main() {
});
it('should attach dirs to all child controls when group control changes', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
signin: new FormGroup(
{login: new FormControl('oldLogin'), password: new FormControl('oldPassword')})
@ -323,7 +309,7 @@ export function main() {
});
it('should attach dirs to all present child controls when array control changes', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -354,7 +340,7 @@ export function main() {
describe('form arrays', () => {
it('should support form arrays', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -377,7 +363,7 @@ export function main() {
});
it('should support pushing new controls to form arrays', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -393,7 +379,7 @@ export function main() {
});
it('should support form groups nested in form arrays', () => {
const fixture = TestBed.createComponent(FormArrayNestedGroup);
const fixture = initTest(FormArrayNestedGroup);
const cityArray = new FormArray([
new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}),
new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')})
@ -425,7 +411,7 @@ export function main() {
describe('programmatic changes', () => {
it('should update the value in the DOM when setValue() is called', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -441,7 +427,7 @@ export function main() {
describe('disabled controls', () => {
it('should add disabled attribute to an individual control when instantiated as disabled',
() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl({value: 'some value', disabled: true});
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -455,7 +441,7 @@ export function main() {
});
it('should add disabled attribute to formControlName when instantiated as disabled', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const control = new FormControl({value: 'some value', disabled: true});
fixture.componentInstance.form = new FormGroup({login: control});
fixture.componentInstance.control = control;
@ -471,7 +457,7 @@ export function main() {
it('should add disabled attribute to an individual control when disable() is called',
() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('some value');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -489,7 +475,7 @@ export function main() {
it('should add disabled attribute to child controls when disable() is called on group',
() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('login')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -507,7 +493,7 @@ export function main() {
it('should not add disabled attribute to custom controls when disable() is called', () => {
const fixture = TestBed.createComponent(MyInputForm);
const fixture = initTest(MyInputForm, MyInput);
const control = new FormControl('some value');
fixture.componentInstance.form = new FormGroup({login: control});
fixture.detectChanges();
@ -526,7 +512,7 @@ export function main() {
describe('user input', () => {
it('should mark controls as touched after interacting with the DOM control', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -544,7 +530,7 @@ export function main() {
describe('submit and reset events', () => {
it('should emit ngSubmit event with the original submit event on submit', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.componentInstance.event = null;
fixture.detectChanges();
@ -557,7 +543,7 @@ export function main() {
});
it('should mark formGroup as submitted on submit event', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
@ -572,7 +558,7 @@ export function main() {
});
it('should set value in UI when form resets to that value programmatically', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('some value');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -586,7 +572,7 @@ export function main() {
});
it('should clear value in UI when form resets programmatically', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('some value');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -604,7 +590,7 @@ export function main() {
describe('value changes and status changes', () => {
it('should mark controls as dirty before emitting a value change event', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
fixture.componentInstance.form = new FormGroup({'login': login});
fixture.detectChanges();
@ -619,7 +605,7 @@ export function main() {
it('should mark control as pristine before emitting a value change event when resetting ',
() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -640,7 +626,7 @@ export function main() {
describe('setting status classes', () => {
it('should work with single fields', () => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('', Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -661,7 +647,7 @@ export function main() {
});
it('should work with single fields and async validators', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('', null, uniqLoginAsyncValidator('good'));
fixture.debugElement.componentInstance.control = control;
fixture.detectChanges();
@ -682,7 +668,7 @@ export function main() {
}));
it('should work with single fields that combines async and sync validators', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control =
new FormControl('', Validators.required, uniqLoginAsyncValidator('good'));
fixture.debugElement.componentInstance.control = control;
@ -715,7 +701,7 @@ export function main() {
}));
it('should work with single fields in parent forms', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -736,7 +722,7 @@ export function main() {
});
it('should work with formGroup', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -765,7 +751,7 @@ export function main() {
it('should support <input> without type', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<input [formControl]="control">`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -782,7 +768,7 @@ export function main() {
});
it('should support <input type=text>', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('old')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -799,7 +785,7 @@ export function main() {
});
it('should ignore the change event for <input type=text>', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('oldValue')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -814,7 +800,7 @@ export function main() {
it('should support <textarea>', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<textarea [formControl]="control"></textarea>`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -833,7 +819,7 @@ export function main() {
it('should support <type=checkbox>', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<input type="checkbox" [formControl]="control">`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl(true);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -850,7 +836,7 @@ export function main() {
});
it('should support <select>', () => {
const fixture = TestBed.createComponent(FormControlNameSelect);
const fixture = initTest(FormControlNameSelect);
fixture.detectChanges();
// model -> view
@ -870,7 +856,7 @@ export function main() {
describe('should support <type=number>', () => {
it('with basic use case', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -887,7 +873,7 @@ export function main() {
});
it('when value is cleared in the UI', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10, Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -907,7 +893,7 @@ export function main() {
});
it('when value is cleared programmatically', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -922,7 +908,7 @@ export function main() {
describe('should support <type=radio>', () => {
it('should support basic functionality', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -949,7 +935,7 @@ export function main() {
});
it('should support an initial undefined value', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({'food': new FormControl(), 'drink': new FormControl()});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -960,7 +946,7 @@ export function main() {
});
it('should reset properly', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -975,7 +961,7 @@ export function main() {
});
it('should set value to null and undefined properly', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup(
{'food': new FormControl('chicken'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -996,7 +982,7 @@ export function main() {
});
it('should use formControlName to group radio buttons when name is absent', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const foodCtrl = new FormControl('fish');
const drinkCtrl = new FormControl('sprite');
fixture.componentInstance.form = new FormGroup({'food': foodCtrl, 'drink': drinkCtrl});
@ -1028,7 +1014,7 @@ export function main() {
});
it('should support removing controls from <type=radio>', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const showRadio = new FormControl('yes');
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
@ -1062,7 +1048,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({
food: new FormControl('fish'),
nested: new FormGroup({food: new FormControl('fish')})
@ -1091,7 +1077,7 @@ export function main() {
});
it('should disable all radio buttons when disable() is called', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({food: new FormControl('fish'), drink: new FormControl('cola')});
fixture.componentInstance.form = form;
@ -1123,7 +1109,7 @@ export function main() {
});
it('should disable all radio buttons when initially disabled', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({
food: new FormControl({value: 'fish', disabled: true}),
drink: new FormControl('cola')
@ -1142,7 +1128,7 @@ export function main() {
describe('should support <type=range>', () => {
it('with basic use case', () => {
const fixture = TestBed.createComponent(FormControlRangeInput);
const fixture = initTest(FormControlRangeInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1159,7 +1145,7 @@ export function main() {
});
it('when value is cleared in the UI', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10, Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1179,7 +1165,7 @@ export function main() {
});
it('when value is cleared programmatically', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1193,7 +1179,7 @@ export function main() {
describe('custom value accessors', () => {
it('should support basic functionality', () => {
const fixture = TestBed.createComponent(WrappedValueForm);
const fixture = initTest(WrappedValueForm, WrappedValue);
const form = new FormGroup({'login': new FormControl('aa')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -1216,7 +1202,7 @@ export function main() {
it('should support non builtin input elements that fire a change event without a \'target\' property',
() => {
const fixture = TestBed.createComponent(MyInputForm);
const fixture = initTest(MyInputForm, MyInput);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')});
fixture.detectChanges();
@ -1231,7 +1217,7 @@ export function main() {
});
it('should support custom accessors without setDisabledState - formControlName', () => {
const fixture = TestBed.createComponent(WrappedValueForm);
const fixture = initTest(WrappedValueForm, WrappedValue);
fixture.componentInstance.form = new FormGroup({
'login': new FormControl({value: 'aa', disabled: true}),
});
@ -1245,7 +1231,7 @@ export function main() {
TestBed.overrideComponent(
FormControlComp,
{set: {template: `<input type="text" [formControl]="control" wrapped-value>`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
fixture.componentInstance.control = new FormControl({value: 'aa', disabled: true});
fixture.detectChanges();
expect(fixture.componentInstance.control.status).toEqual('DISABLED');
@ -1258,7 +1244,7 @@ export function main() {
describe('ngModel interactions', () => {
it('should support ngModel for complex forms', fakeAsync(() => {
const fixture = TestBed.createComponent(FormGroupNgModel);
const fixture = initTest(FormGroupNgModel);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('')});
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
@ -1275,7 +1261,7 @@ export function main() {
}));
it('should support ngModel for single fields', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlNgModel);
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
@ -1292,7 +1278,7 @@ export function main() {
}));
it('should not update the view when the value initially came from the view', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlNgModel);
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
fixture.detectChanges();
tick();
@ -1313,7 +1299,7 @@ export function main() {
describe('validations', () => {
it('required validator should validate checkbox', () => {
const fixture = TestBed.createComponent(FormControlCheckboxRequiredValidator);
const fixture = initTest(FormControlCheckboxRequiredValidator);
const control = new FormControl(false, Validators.requiredTrue);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1331,7 +1317,7 @@ export function main() {
});
it('should use sync validators defined in html', () => {
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1376,7 +1362,7 @@ export function main() {
});
it('should use sync validators using bindings', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1424,7 +1410,7 @@ export function main() {
});
it('changes on bound properties should change the validation state of the form', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1499,7 +1485,7 @@ export function main() {
});
it('should support rebound controls with rebound validators', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1536,7 +1522,7 @@ export function main() {
});
it('should use async validators defined in the html', fakeAsync(() => {
const fixture = TestBed.createComponent(UniqLoginWrapper);
const fixture = initTest(UniqLoginWrapper, UniqLoginValidator);
const form = new FormGroup({'login': new FormControl('')});
tick();
fixture.componentInstance.form = form;
@ -1556,7 +1542,7 @@ export function main() {
}));
it('should use sync validators defined in the model', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('aa', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -1570,7 +1556,7 @@ export function main() {
});
it('should use async validators defined in the model', fakeAsync(() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const control =
new FormControl('', Validators.required, uniqLoginAsyncValidator('expected'));
const form = new FormGroup({'login': control});
@ -1601,7 +1587,7 @@ export function main() {
describe('errors', () => {
it('should throw if a form isn\'t passed into formGroup', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(`formGroup expects a FormGroup instance`));
@ -1615,7 +1601,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1632,7 +1618,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1651,7 +1637,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1668,7 +1654,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1687,7 +1673,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1703,7 +1689,7 @@ export function main() {
</div>`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1720,7 +1706,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges())
@ -1738,7 +1724,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges()).not.toThrowError();
@ -1756,7 +1742,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const myGroup = new FormGroup({person: new FormGroup({})});
fixture.componentInstance.myGroup = new FormGroup({person: new FormGroup({})});
@ -1777,7 +1763,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges())
@ -1794,7 +1780,7 @@ export function main() {
</form>`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'food': new FormControl('fish')});
expect(() => fixture.detectChanges())

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Input, forwardRef} from '@angular/core';
import {Component, Directive, Input, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, Validator} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
@ -16,37 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
export function main() {
describe('template-driven forms integration tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
StandaloneNgModel,
NgModelForm,
NgModelGroupForm,
NgModelValidBinding,
NgModelNgIfForm,
NgModelRadioForm,
NgModelRangeForm,
NgModelSelectForm,
NgNoFormComp,
InvalidNgModelNoName,
NgModelOptionsStandalone,
NgModelCustomComp,
NgModelCustomWrapper,
NgModelValidationBindings,
NgModelMultipleValidators,
NgAsyncValidator,
NgModelAsyncValidation,
NgModelSelectMultipleForm,
NgModelSelectWithNullForm,
NgModelCheckboxRequiredValidator,
],
imports: [FormsModule]
});
});
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule]});
return TestBed.createComponent(component);
}
describe('basic functionality', () => {
it('should support ngModel for standalone fields', fakeAsync(() => {
const fixture = TestBed.createComponent(StandaloneNgModel);
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = 'oldValue';
fixture.detectChanges();
@ -65,7 +43,7 @@ export function main() {
}));
it('should support ngModel registration with a parent form', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy';
fixture.detectChanges();
@ -76,8 +54,18 @@ export function main() {
expect(form.valid).toBe(false);
}));
it('should add novalidate by default to form element', fakeAsync(() => {
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.getAttribute('novalidate')).toEqual('');
}));
it('should support ngModelGroup', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
@ -100,7 +88,7 @@ export function main() {
}));
it('should add controls and control groups to form control model', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
@ -115,7 +103,7 @@ export function main() {
}));
it('should remove controls and control groups from form control model', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelNgIfForm);
const fixture = initTest(NgModelNgIfForm);
fixture.componentInstance.emailShowing = true;
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.email = 'some email';
@ -148,7 +136,7 @@ export function main() {
}));
it('should set status classes with ngModel', async(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'aa';
fixture.detectChanges();
fixture.whenStable().then(() => {
@ -171,7 +159,7 @@ export function main() {
it('should set status classes with ngModel and async validators', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelAsyncValidation);
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
fixture.whenStable().then(() => {
fixture.detectChanges();
@ -193,7 +181,7 @@ export function main() {
}));
it('should set status classes with ngModelGroup and ngForm', async(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = '';
fixture.detectChanges();
@ -228,27 +216,34 @@ export function main() {
}));
it('should not create a template-driven form when ngNoForm is used', () => {
const fixture = TestBed.createComponent(NgNoFormComp);
const fixture = initTest(NgNoFormComp);
fixture.detectChanges();
expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0);
});
it('should not add novalidate when ngNoForm is used', () => {
const fixture = initTest(NgNoFormComp);
fixture.detectChanges();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.hasAttribute('novalidate')).toBeFalsy();
});
});
describe('name and ngModelOptions', () => {
it('should throw if ngModel has a parent form but no name attr or standalone label', () => {
const fixture = TestBed.createComponent(InvalidNgModelNoName);
const fixture = initTest(InvalidNgModelNoName);
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(`name attribute must be set`));
});
it('should not throw if ngModel has a parent form, no name attr, and a standalone label',
() => {
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
const fixture = initTest(NgModelOptionsStandalone);
expect(() => fixture.detectChanges()).not.toThrow();
});
it('should not register standalone ngModels with parent form', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.one = 'some data';
fixture.componentInstance.two = 'should not show';
fixture.detectChanges();
@ -263,7 +258,7 @@ export function main() {
}));
it('should override name attribute with ngModelOptions name if provided', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.options = {name: 'override'};
fixture.componentInstance.name = 'some data';
fixture.detectChanges();
@ -276,7 +271,7 @@ export function main() {
describe('submit and reset events', () => {
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.event = null;
const form = fixture.debugElement.query(By.css('form'));
@ -287,7 +282,7 @@ export function main() {
}));
it('should mark NgForm as submitted on submit event', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
@ -301,7 +296,7 @@ export function main() {
}));
it('should reset the form to empty when reset event is fired', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'should be cleared';
fixture.detectChanges();
tick();
@ -324,7 +319,7 @@ export function main() {
}));
it('should reset the form submit state when reset button is clicked', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm);
const formEl = fixture.debugElement.query(By.css('form'));
@ -342,7 +337,7 @@ export function main() {
describe('valueChange and statusChange events', () => {
it('should emit valueChanges and statusChanges on init', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm);
fixture.componentInstance.name = 'aa';
fixture.detectChanges();
@ -363,7 +358,7 @@ export function main() {
}));
it('should mark controls dirty before emitting the value change event', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
fixture.detectChanges();
@ -380,7 +375,7 @@ export function main() {
it('should mark controls pristine before emitting the value change event when resetting ',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
@ -402,7 +397,7 @@ export function main() {
describe('disabled controls', () => {
it('should not consider disabled controls in value or validation', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.isDisabled = false;
fixture.componentInstance.first = '';
fixture.componentInstance.last = 'Drew';
@ -426,7 +421,7 @@ export function main() {
it('should add disabled attribute in the UI if disable() is called programmatically',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.isDisabled = false;
fixture.componentInstance.first = 'Nancy';
fixture.detectChanges();
@ -442,7 +437,7 @@ export function main() {
}));
it('should disable a custom control if disabled attr is added', async(() => {
const fixture = TestBed.createComponent(NgModelCustomWrapper);
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.componentInstance.isDisabled = true;
fixture.detectChanges();
@ -468,7 +463,7 @@ export function main() {
`,
}
});
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
@ -484,7 +479,7 @@ export function main() {
}));
it('should disable radio controls properly with programmatic call', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.detectChanges();
tick();
@ -520,7 +515,7 @@ export function main() {
describe('range control', () => {
it('should support <type=range>', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRangeForm);
const fixture = initTest(NgModelRangeForm);
// model -> view
fixture.componentInstance.val = 4;
fixture.detectChanges();
@ -540,7 +535,7 @@ export function main() {
describe('radio controls', () => {
it('should support <type=radio>', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.detectChanges();
tick();
@ -559,7 +554,7 @@ export function main() {
}));
it('should support multiple named <type=radio> groups', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.componentInstance.drink = 'sprite';
fixture.detectChanges();
@ -582,7 +577,7 @@ export function main() {
}));
it('should support initial undefined value', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.detectChanges();
tick();
@ -594,7 +589,7 @@ export function main() {
}));
it('should support resetting properly', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'chicken';
fixture.detectChanges();
tick();
@ -610,7 +605,7 @@ export function main() {
}));
it('should support setting value to null and undefined', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'chicken';
fixture.detectChanges();
tick();
@ -638,7 +633,7 @@ export function main() {
describe('select controls', () => {
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
@ -662,7 +657,7 @@ export function main() {
}));
it('when new options are added', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[1];
@ -681,7 +676,7 @@ export function main() {
}));
it('when options are removed', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[1];
@ -699,7 +694,7 @@ export function main() {
}));
it('when option values have same content, but different identities', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[0];
@ -716,7 +711,7 @@ export function main() {
}));
it('should work with null option', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
const fixture = initTest(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null;
@ -743,7 +738,7 @@ export function main() {
let comp: NgModelSelectMultipleForm;
beforeEach(() => {
fixture = TestBed.createComponent(NgModelSelectMultipleForm);
fixture = initTest(NgModelSelectMultipleForm);
comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
});
@ -804,7 +799,7 @@ export function main() {
describe('custom value accessors', () => {
it('should support standard writing to view and model', async(() => {
const fixture = TestBed.createComponent(NgModelCustomWrapper);
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.detectChanges();
fixture.whenStable().then(() => {
@ -829,7 +824,7 @@ export function main() {
describe('validation directives', () => {
it('required validator should validate checkbox', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelCheckboxRequiredValidator);
const fixture = initTest(NgModelCheckboxRequiredValidator);
fixture.detectChanges();
tick();
@ -865,7 +860,7 @@ export function main() {
}));
it('should support dir validators using bindings', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidationBindings);
const fixture = initTest(NgModelValidationBindings);
fixture.componentInstance.required = true;
fixture.componentInstance.minLen = 3;
fixture.componentInstance.maxLen = 3;
@ -909,7 +904,7 @@ export function main() {
}));
it('should support optional fields with string pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = '[a-z]+';
fixture.detectChanges();
@ -931,7 +926,7 @@ export function main() {
}));
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = /^[a-z]+$/;
fixture.detectChanges();
@ -953,7 +948,7 @@ export function main() {
}));
it('should support optional fields with minlength validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.minLen = 2;
fixture.detectChanges();
@ -976,7 +971,7 @@ export function main() {
it('changes on bound properties should change the validation state of the form',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidationBindings);
const fixture = initTest(NgModelValidationBindings);
fixture.detectChanges();
tick();
@ -1050,7 +1045,7 @@ export function main() {
describe('ngModel corner cases', () => {
it('should update the view when the model is set back to what used to be in the view',
fakeAsync(() => {
const fixture = TestBed.createComponent(StandaloneNgModel);
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = '';
fixture.detectChanges();
tick();
@ -1078,7 +1073,7 @@ export function main() {
}));
it('should not crash when validity is checked from a binding', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidBinding);
const fixture = initTest(NgModelValidBinding);
tick();
expect(() => fixture.detectChanges()).not.toThrowError();
}));

View File

@ -48,12 +48,6 @@ export function main() {
it('should accept zero as valid',
() => { expect(Validators.required(new FormControl(0))).toBeNull(); });
it('should error on an empty array',
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
it('should not error on a non-empty array',
() => expect(Validators.required(new FormControl([1, 2]))).toBeNull());
});
describe('requiredTrue', () => {

View File

@ -62,7 +62,15 @@ export class RequestOptions {
/**
* Search parameters to be included in a {@link Request}.
*/
search: URLSearchParams;
params: URLSearchParams;
/**
* @deprecated from 4.0.0. Use params instead.
*/
get search(): URLSearchParams { return this.params; }
/**
* @deprecated from 4.0.0. Use params instead.
*/
set search(params: URLSearchParams) { this.params = params; }
/**
* Enable use credentials for a {@link Request}.
*/
@ -72,15 +80,15 @@ export class RequestOptions {
*/
responseType: ResponseContentType;
// TODO(Dzmitry): remove search when this.search is removed
constructor(
{method, headers, body, url, search, withCredentials,
{method, headers, body, url, search, params, withCredentials,
responseType}: RequestOptionsArgs = {}) {
this.method = method != null ? normalizeMethodName(method) : null;
this.headers = headers != null ? headers : null;
this.body = body != null ? body : null;
this.url = url != null ? url : null;
this.search =
search != null ? (typeof search === 'string' ? new URLSearchParams(search) : search) : null;
this.params = this._mergeSearchParams(params || search);
this.withCredentials = withCredentials != null ? withCredentials : null;
this.responseType = responseType != null ? responseType : null;
}
@ -116,18 +124,49 @@ export class RequestOptions {
headers: options && options.headers != null ? options.headers : new Headers(this.headers),
body: options && options.body != null ? options.body : this.body,
url: options && options.url != null ? options.url : this.url,
search: options && options.search != null ?
(typeof options.search === 'string' ? new URLSearchParams(options.search) :
options.search.clone()) :
this.search,
params: options && this._mergeSearchParams(options.params || options.search),
withCredentials: options && options.withCredentials != null ? options.withCredentials :
this.withCredentials,
responseType: options && options.responseType != null ? options.responseType :
this.responseType
});
}
}
private _mergeSearchParams(params: string|URLSearchParams|
{[key: string]: any | any[]}): URLSearchParams {
if (!params) return this.params;
if (params instanceof URLSearchParams) {
return params.clone();
}
if (typeof params === 'string') {
return new URLSearchParams(params);
}
return this._parseParams(params);
}
private _parseParams(objParams: {[key: string]: any | any[]} = {}): URLSearchParams {
const params = new URLSearchParams();
Object.keys(objParams).forEach((key: string) => {
const value: any|any[] = objParams[key];
if (Array.isArray(value)) {
value.forEach((item: any) => this._appendParam(key, item, params));
} else {
this._appendParam(key, value, params);
}
});
return params;
}
private _appendParam(key: string, value: any, params: URLSearchParams): void {
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
params.append(key, value);
}
}
/**
* Subclass of {@link RequestOptions}, with default values.

View File

@ -48,7 +48,9 @@ export abstract class XSRFStrategy { abstract configureRequest(req: Request): vo
export interface RequestOptionsArgs {
url?: string;
method?: string|RequestMethod;
search?: string|URLSearchParams;
/** @deprecated from 4.0.0. Use params instead. */
search?: string|URLSearchParams|{[key: string]: any | any[]};
params?: string|URLSearchParams|{[key: string]: any | any[]};
headers?: Headers;
body?: any;
withCredentials?: boolean;

View File

@ -76,15 +76,15 @@ export class Request extends Body {
// TODO: assert that url is present
const url = requestOptions.url;
this.url = requestOptions.url;
if (requestOptions.search) {
const search = requestOptions.search.toString();
if (search.length > 0) {
if (requestOptions.params) {
const params = requestOptions.params.toString();
if (params.length > 0) {
let prefix = '?';
if (this.url.indexOf('?') != -1) {
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
}
// TODO: just delete search-query-looking string in url?
this.url = url + prefix + search;
this.url = url + prefix + params;
}
}
this._body = requestOptions.body;

View File

@ -25,5 +25,32 @@ export function main() {
const options2 = options1.merge(new RequestOptions({method: RequestMethod.Delete}));
expect(options2.method).toBe(RequestMethod.Delete);
});
it('should accept search params as object', () => {
const params = {a: 1, b: 'text', c: [1, 2, '3']};
const options = new RequestOptions({params});
expect(options.params.paramsMap.size).toBe(3);
expect(options.params.paramsMap.get('a')).toEqual(['1']);
expect(options.params.paramsMap.get('b')).toEqual(['text']);
expect(options.params.paramsMap.get('c')).toEqual(['1', '2', '3']);
});
it('should merge search params as object', () => {
const options1 = new BaseRequestOptions();
const params = {a: 1, b: 'text', c: [1, 2, '3']};
const options2 = options1.merge(new RequestOptions({params}));
expect(options2.params.paramsMap.size).toBe(3);
expect(options2.params.paramsMap.get('a')).toEqual(['1']);
expect(options2.params.paramsMap.get('b')).toEqual(['text']);
expect(options2.params.paramsMap.get('c')).toEqual(['1', '2', '3']);
});
it('should create a new headers object when calling merge', () => {
const options1 = new RequestOptions({headers: new Headers()});
const options2 = options1.merge();
expect(options2.headers).not.toBe(options1.headers);
});
});
}

View File

@ -114,78 +114,25 @@ export class MockConnection implements Connection {
* ### Example
*
* ```
* import {Injectable, ReflectiveInjector} from '@angular/core';
* import {async, fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
* import {MockBackend, MockConnection} from '@angular/http/testing';
*
* const HERO_ONE = 'HeroNrOne';
* const HERO_TWO = 'WillBeAlwaysTheSecond';
*
* @Injectable()
* class HeroService {
* constructor(private http: Http) {}
*
* getHeroes(): Promise<String[]> {
* return this.http.get('myservices.de/api/heroes')
* .toPromise()
* .then(response => response.json().data)
* .catch(e => this.handleError(e));
* }
*
* private handleError(error: any): Promise<any> {
* console.error('An error occurred', error);
* return Promise.reject(error.message || error);
* }
* }
*
* describe('MockBackend HeroService Example', () => {
* beforeEach(() => {
* this.injector = ReflectiveInjector.resolveAndCreate([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
* HeroService,
* ]);
* this.heroService = this.injector.get(HeroService);
* this.backend = this.injector.get(ConnectionBackend) as MockBackend;
* this.backend.connections.subscribe((connection: any) => this.lastConnection = connection);
* import {BaseRequestOptions, Http} from '@angular/http';
* import {MockBackend} from '@angular/http/testing';
* it('should get some data', inject([AsyncTestCompleter], (async) => {
* var connection;
* var injector = Injector.resolveAndCreate([
* MockBackend,
* {provide: Http, useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MockBackend, BaseRequestOptions]}]);
* var http = injector.get(Http);
* var backend = injector.get(MockBackend);
* //Assign any newly-created connection to local variable
* backend.connections.subscribe(c => connection = c);
* http.request('data.json').subscribe((res) => {
* expect(res.text()).toBe('awesome');
* async.done();
* });
*
* it('getHeroes() should query current service url', () => {
* this.heroService.getHeroes();
* expect(this.lastConnection).toBeDefined('no http service connection at all?');
* expect(this.lastConnection.request.url).toMatch(/api\/heroes$/, 'url invalid');
* });
*
* it('getHeroes() should return some heroes', fakeAsync(() => {
* let result: String[];
* this.heroService.getHeroes().then((heroes: String[]) => result = heroes);
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
* body: JSON.stringify({data: [HERO_ONE, HERO_TWO]}),
* })));
* tick();
* expect(result.length).toEqual(2, 'should contain given amount of heroes');
* expect(result[0]).toEqual(HERO_ONE, ' HERO_ONE should be the first hero');
* expect(result[1]).toEqual(HERO_TWO, ' HERO_TWO should be the second hero');
* }));
*
* it('getHeroes() while server is down', fakeAsync(() => {
* let result: String[];
* let catchedError: any;
* this.heroService.getHeroes()
* .then((heroes: String[]) => result = heroes)
* .catch((error: any) => catchedError = error);
* this.lastConnection.mockRespond(new Response(new ResponseOptions({
* status: 404,
* statusText: 'URL not Found',
* })));
* tick();
* expect(result).toBeUndefined();
* expect(catchedError).toBeDefined();
* }));
* });
* connection.mockRespond(new Response('awesome'));
* }));
* ```
*
* This method only exists in the mock implementation, not in real Backends.
@ -202,30 +149,27 @@ export class MockBackend implements ConnectionBackend {
* ### Example
*
* ```
* import {ReflectiveInjector} from '@angular/core';
* import {fakeAsync, tick} from '@angular/core/testing';
* import {BaseRequestOptions, ConnectionBackend, Http, RequestOptions} from '@angular/http';
* import {Response, ResponseOptions} from '@angular/http';
* import {MockBackend, MockConnection} from '@angular/http/testing';
* import {Http, BaseRequestOptions, Response} from '@angular/http';
* import {MockBackend} from '@angular/http/testing';
* import {Injector, provide} from '@angular/core';
*
* it('should get a response', fakeAsync(() => {
* let connection:
* MockConnection; // this will be set when a new connection is emitted from the
* // backend.
* let text: string; // this will be set from mock response
* let injector = ReflectiveInjector.resolveAndCreate([
* {provide: ConnectionBackend, useClass: MockBackend},
* {provide: RequestOptions, useClass: BaseRequestOptions},
* Http,
* ]);
* let backend = injector.get(ConnectionBackend);
* let http = injector.get(Http);
* backend.connections.subscribe((c: MockConnection) => connection = c);
* http.request('something.json').toPromise().then((res: any) => text = res.text());
* connection.mockRespond(new Response(new ResponseOptions({body: 'Something'})));
* tick();
* expect(text).toBe('Something');
* }));
* it('should get a response', () => {
* var connection; //this will be set when a new connection is emitted from the backend.
* var text; //this will be set from mock response
* var injector = Injector.resolveAndCreate([
* MockBackend,
* {provide: Http, useFactory: (backend, options) => {
* return new Http(backend, options);
* }, deps: [MockBackend, BaseRequestOptions]}]);
* var backend = injector.get(MockBackend);
* var http = injector.get(Http);
* backend.connections.subscribe(c => connection = c);
* http.request('something.json').subscribe(res => {
* text = res.text();
* });
* connection.mockRespond(new Response({body: 'Something'}));
* expect(text).toBe('Something');
* });
* ```
*
* This property only exists in the mock implementation, not in real Backends.

View File

@ -208,12 +208,13 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
private diagnoseExpression(ast: AST, offset: number, includeEvent: boolean) {
const scope = this.getExpressionScope(this.path, includeEvent);
this.diagnostics.push(
...getExpressionDiagnostics(scope, ast, this.info.template.query)
.map(d => ({
span: offsetSpan(d.ast.span, offset + this.info.template.span.start),
kind: d.kind,
message: d.message
})));
...getExpressionDiagnostics(scope, ast, this.info.template.query, {
event: includeEvent
}).map(d => ({
span: offsetSpan(d.ast.span, offset + this.info.template.span.start),
kind: d.kind,
message: d.message
})));
}
private push(ast: TemplateAst) { this.path.push(ast); }

View File

@ -16,9 +16,12 @@ import {TemplateAstChildVisitor, TemplateAstPath} from './template_path';
import {BuiltinType, CompletionKind, Definition, DiagnosticKind, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './types';
import {inSpan, spanOf} from './utils';
export interface ExpressionDiagnosticsContext { event?: boolean; }
export function getExpressionDiagnostics(
scope: SymbolTable, ast: AST, query: SymbolQuery): TypeDiagnostic[] {
const analyzer = new AstType(scope, query);
scope: SymbolTable, ast: AST, query: SymbolQuery,
context: ExpressionDiagnosticsContext = {}): TypeDiagnostic[] {
const analyzer = new AstType(scope, query, context);
analyzer.getDiagnostics(ast);
return analyzer.diagnostics;
}
@ -30,7 +33,7 @@ export function getExpressionCompletions(
const tail = path.tail;
let result: SymbolTable|undefined = scope;
function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); }
function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); }
// If the completion request is in a not in a pipe or property access then the global scope
// (that is the scope of the implicit receiver) is the right scope as the user is typing the
@ -88,7 +91,7 @@ export function getExpressionSymbol(
if (path.empty) return undefined;
const tail = path.tail;
function getType(ast: AST): Symbol { return new AstType(scope, query).getType(ast); }
function getType(ast: AST): Symbol { return new AstType(scope, query, {}).getType(ast); }
let symbol: Symbol = undefined;
let span: Span = undefined;
@ -189,13 +192,18 @@ export class TypeDiagnostic {
class AstType implements ExpressionVisitor {
public diagnostics: TypeDiagnostic[];
constructor(private scope: SymbolTable, private query: SymbolQuery) {}
constructor(
private scope: SymbolTable, private query: SymbolQuery,
private context: ExpressionDiagnosticsContext) {}
getType(ast: AST): Symbol { return ast.visit(this); }
getDiagnostics(ast: AST): TypeDiagnostic[] {
this.diagnostics = [];
ast.visit(this);
const type: Symbol = ast.visit(this);
if (this.context.event && type.callable) {
this.reportWarning('Unexpected callable expression. Expected a method call', ast);
}
return this.diagnostics;
}
@ -751,7 +759,7 @@ function refinedVariableType(
const ngForOfBinding = ngForDirective.inputs.find(i => i.directiveName == 'ngForOf');
if (ngForOfBinding) {
const bindingType =
new AstType(info.template.members, info.template.query).getType(ngForOfBinding.value);
new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value);
if (bindingType) {
return info.template.query.getElementType(bindingType);
}

View File

@ -29,17 +29,7 @@ import {createLanguageService} from './language_service';
import {ReflectorHost} from './reflector_host';
import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types';
// In TypeScript 2.1 these flags moved
// These helpers work for both 2.0 and 2.1.
const isPrivate = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Private)) :
((node: ts.Node) => !!(node.flags & (ts as any).NodeFlags.Private));
const isReferenceType = (ts as any).ObjectFlags ?
((type: ts.Type) =>
!!(type.flags & (ts as any).TypeFlags.Object &&
(type as any).objectFlags & (ts as any).ObjectFlags.Reference)) :
((type: ts.Type) => !!(type.flags & (ts as any).TypeFlags.Reference));
/**
* Create a `LanguageServiceHost`
@ -689,7 +679,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
const constructorDeclaration = constructor.declarations[0] as ts.ConstructorTypeNode;
for (const parameter of constructorDeclaration.parameters) {
const type = this.checker.getTypeAtLocation(parameter.type);
if (type.symbol.name == 'TemplateRef' && isReferenceType(type)) {
if (type.symbol.name == 'TemplateRef' && type.flags & ts.TypeFlags.Reference) {
const typeReference = type as ts.TypeReference;
if (typeReference.typeArguments.length === 1) {
return typeReference.typeArguments[0].symbol;
@ -814,7 +804,7 @@ class SymbolWrapper implements Symbol {
get public(): boolean {
// Symbols that are not explicitly made private are public.
return !isSymbolPrivate(this.symbol);
return !(getDeclarationFlagsFromSymbol(this.symbol) & ts.NodeFlags.Private);
}
get callable(): boolean { return typeCallable(this.tsType); }
@ -1106,8 +1096,10 @@ function getCombinedNodeFlags(node: ts.Node): ts.NodeFlags {
return flags;
}
function isSymbolPrivate(s: ts.Symbol): boolean {
return s.valueDeclaration && isPrivate(s.valueDeclaration);
function getDeclarationFlagsFromSymbol(s: ts.Symbol): ts.NodeFlags {
return s.valueDeclaration ?
getCombinedNodeFlags(s.valueDeclaration) :
s.flags & ts.SymbolFlags.Prototype ? ts.NodeFlags.Public | ts.NodeFlags.Static : 0;
}
function getBuiltinTypeFromTs(kind: BuiltinType, context: TypeContext): ts.Type {
@ -1265,4 +1257,4 @@ function typeKindOf(type: ts.Type): BuiltinType {
}
}
return BuiltinType.Other;
}
}

View File

@ -10,16 +10,17 @@ import 'reflect-metadata';
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Completions} from '../src/types';
import {Completions, Diagnostic, Diagnostics} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
import {MockTypescriptHost} from './test_utils';
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
describe('completions', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);

View File

@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Span} from '../src/types';
import {Completions, Diagnostic, Diagnostics, Span} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
@ -20,6 +20,7 @@ describe('definitions', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);

View File

@ -9,7 +9,7 @@
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Diagnostics} from '../src/types';
import {Completions, Diagnostic, Diagnostics} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
@ -19,6 +19,7 @@ describe('diagnostics', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);
@ -129,6 +130,39 @@ describe('diagnostics', () => {
});
});
it('should report a warning if an event results in a callable expression', () => {
const code =
` @Component({template: \`<div (click)="onClick"></div>\`}) export class MyComponent { onClick() { } }`;
addCode(code, (fileName, content) => {
const diagnostics = ngService.getDiagnostics(fileName);
includeDiagnostic(
diagnostics, 'Unexpected callable expression. Expected a method call', 'onClick',
content);
});
});
// #13412
it('should not report an error for using undefined', () => {
const code =
` @Component({template: \`<div *ngIf="something === undefined"></div>\`}) export class MyComponent { something = 'foo'; }})`;
addCode(code, fileName => {
const diagnostics = ngService.getDiagnostics(fileName);
onlyModuleDiagnostics(diagnostics);
});
});
// Issue #13326
it('should report a narrow span for invalid pipes', () => {
const code =
` @Component({template: '<p> Using an invalid pipe {{data | dat}} </p>'}) export class MyComponent { data = 'some data'; }`;
addCode(code, fileName => {
const diagnostic =
ngService.getDiagnostics(fileName).filter(d => d.message.indexOf('pipe') > 0)[0];
expect(diagnostic).not.toBeUndefined();
expect(diagnostic.span.end - diagnostic.span.start).toBeLessThan(11);
});
});
function addCode(code: string, cb: (fileName: string, content?: string) => void) {
const fileName = '/app/app.component.ts';
const originalContent = mockHost.getFileContent(fileName);

View File

@ -11,16 +11,17 @@ import 'reflect-metadata';
import * as ts from 'typescript';
import {createLanguageService} from '../src/language_service';
import {Hover} from '../src/types';
import {Hover, HoverTextSection} from '../src/types';
import {TypeScriptServiceHost} from '../src/typescript_host';
import {toh} from './test_data';
import {MockTypescriptHost} from './test_utils';
import {MockTypescriptHost, includeDiagnostic, noDiagnostics} from './test_utils';
describe('hover', () => {
let documentRegistry = ts.createDocumentRegistry();
let mockHost = new MockTypescriptHost(['/app/main.ts', '/app/parsing-cases.ts'], toh);
let service = ts.createLanguageService(mockHost, documentRegistry);
let program = service.getProgram();
let ngHost = new TypeScriptServiceHost(mockHost, service);
let ngService = createLanguageService(ngHost);
ngHost.setSite(ngService);

View File

@ -213,7 +213,7 @@ export class UnknownTrackBy {
'ng-if-cases.ts': `
import {Component} from '@angular/core';
@Component({template: '<div ~{implicit}*ngIf="show; let l"~{implicit-end}>Showing now!</div>'})
@Component({template: '<div ~{implicit}*ngIf="show; let l=unknown"~{implicit-end}>Showing now!</div>'})
export class ShowIf {
show = false;
}

View File

@ -19,7 +19,7 @@ export type MockData = string | MockDirectory;
export type MockDirectory = {
[name: string]: MockData | undefined;
};
}
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
const rxjsts = /rxjs\/(\w|\/)+\.tsx?$/;

View File

@ -186,7 +186,8 @@ describe('plugin', () => {
expectSemanticError('app/ng-if-cases.ts', locationMarker, message);
}
it('should report an implicit context reference', () => {
expectError('implicit', 'The template context does not have an implicit value');
expectError(
'implicit', 'The template context does not defined a member called \'unknown\'');
});
});
});

View File

@ -9,7 +9,7 @@
import {INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS} from './platform_providers';
import * as resource_loader from './resource_loader/resource_loader_impl';
export const __platform_browser_dynamic_private__: {
export var __platform_browser_dynamic_private__: {
INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: typeof INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
_ResourceLoaderImpl?: resource_loader.ResourceLoaderImpl,
ResourceLoaderImpl: typeof resource_loader.ResourceLoaderImpl

View File

@ -8,6 +8,6 @@
import {__core_private__ as r} from '@angular/core';
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;
export const Console: typeof r.Console = r.Console;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export var Console: typeof r.Console = r.Console;

View File

@ -8,6 +8,6 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
export var INTERNAL_BROWSER_PLATFORM_PROVIDERS: typeof _.INTERNAL_BROWSER_PLATFORM_PROVIDERS =
_.INTERNAL_BROWSER_PLATFORM_PROVIDERS;
export const getDOM: typeof _.getDOM = _.getDOM;
export var getDOM: typeof _.getDOM = _.getDOM;

View File

@ -11,6 +11,8 @@ import {TestComponentRenderer} from '@angular/core/testing';
import {DOCUMENT} from '@angular/platform-browser';
import {getDOM} from './private_import_platform-browser';
/**
* A DOM based implementation of the TestComponentRenderer.
*/

View File

@ -8,7 +8,7 @@
import * as testing from './dom_test_component_renderer';
export const __platform_browser_dynamic_private__:
export var __platform_browser_dynamic_private__:
{DOMTestComponentRenderer: typeof testing.DOMTestComponentRenderer} = {
DOMTestComponentRenderer: testing.DOMTestComponentRenderer
};

View File

@ -8,6 +8,6 @@
import {__platform_browser_dynamic_private__ as _} from '@angular/platform-browser-dynamic';
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
export var INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS:
typeof _.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS =
_.INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS;

View File

@ -8,4 +8,4 @@
import {__platform_browser_private__ as _} from '@angular/platform-browser';
export const getDOM: typeof _.getDOM = _.getDOM;
export var getDOM: typeof _.getDOM = _.getDOM;

View File

@ -14,6 +14,7 @@ import {WebAnimationsDriver} from '../src/dom/web_animations_driver';
import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
import {Meta} from './browser/meta';
import {BrowserGetTestability} from './browser/testability';
import {Title} from './browser/title';
import {ELEMENT_PROBE_PROVIDERS} from './dom/debug/ng_probe';
@ -46,7 +47,7 @@ export const BROWSER_SANITIZATION_PROVIDERS: Array<any> = [
/**
* @stable
*/
export const platformBrowser =
export const platformBrowser: (extraProviders?: Provider[]) => PlatformRef =
createPlatformFactory(platformCore, 'browser', INTERNAL_BROWSER_PLATFORM_PROVIDERS);
export function initDomAdapter() {
@ -58,6 +59,10 @@ export function errorHandler(): ErrorHandler {
return new ErrorHandler();
}
export function meta(): Meta {
return new Meta(getDOM());
}
export function _document(): any {
return getDOM().defaultDoc();
}
@ -76,7 +81,8 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
*/
@NgModule({
providers: [
BROWSER_SANITIZATION_PROVIDERS, {provide: ErrorHandler, useFactory: errorHandler, deps: []},
BROWSER_SANITIZATION_PROVIDERS,
{provide: ErrorHandler, useFactory: errorHandler, deps: []},
{provide: DOCUMENT, useFactory: _document, deps: []},
{provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
{provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
@ -85,8 +91,13 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
{provide: DomRootRenderer, useClass: DomRootRenderer_},
{provide: RootRenderer, useExisting: DomRootRenderer},
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver}, DomSharedStylesHost,
Testability, EventManager, ELEMENT_PROBE_PROVIDERS, Title
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
{provide: Meta, useFactory: meta},
DomSharedStylesHost,
Testability,
EventManager,
ELEMENT_PROBE_PROVIDERS,
Title,
],
exports: [CommonModule, ApplicationModule]
})

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