Compare commits

...

185 Commits

Author SHA1 Message Date
2c294d5dff docs(changelog): add changelog for 4.0.0-beta.3 2017-01-11 14:42:37 -08:00
e1af25d93e docs(changelog): add changelog for 2.4.3 2017-01-11 14:42:27 -08:00
123943a6e0 chore(release): cut the 4.0.0-beta.3 release 2017-01-11 14:25:16 -08:00
3a4b54daa4 docs(release-schedule): add release schedule doc (#13827)
While authoring this document we agreed to pushing off the 4.0.0 release by one week by adding one more RC week (compared to the original plan).
We announced that RC would take 1 month, but then I did the calendar math incorrectly.
This schedule change will give community more time to test the release before we call it done and report any potential regression or unforeseen issues.
2017-01-11 13:22:50 -08:00
95cbca20a5 chore(owners): configure pullapprove.com 2017-01-10 20:56:52 -05:00
aeed7373af fix(compiler-cli): avoid handling functions in loadChildren as lazy load routes paths
The change avoids the compiler CLI internal API from mismatching the following case as lazy loading

```
import { NonLazyLoadedModule } from './non-lazy-loaded/non-lazy-loaded.module';

export function getNonLazyLoadedModule() { return NonLazyLoadedModule; }

export const routes = [
{ path: '/some-path', loadChildren: getNonLazyLoadedModule }
];
```

The output of the check is later passed to `RouteDef.fromString()`, so, it makes sense to be only a string.

Fixes angular/angular-cli#3204
2017-01-10 14:31:45 -05:00
2e3ac70e0a refactor(common): remove some facade usages 2017-01-10 14:31:30 -05:00
9aeb8c5357 refactor(test): <template>/<ng-container>/*-directives
- remove outer `<div>` in tests,
- use `<ng-container>` instead of `<template>` where possible,
- use *... instead of template (tag or attr) where possible.

Fixes #13816
2017-01-09 19:33:38 -05:00
424e6c4cb9 fix(i18n): translate attributes inside elements marked for translation
fixes #13796
fixes #13814
2017-01-09 19:33:03 -05:00
5cb2008e6c docs(NgPlural): fix API docs
Fixes #13786
2017-01-09 19:32:42 -05:00
78f42c7aa1 refactor(Compiler): misc cleanup 2017-01-09 19:32:01 -05:00
d4d3782d45 feat(Router): call resolver when upstream params change (#12942)
With this change the resolver is called when the parameter for the activated and any parent routes change.
ie, switching from `/teams/10/players/5` to `/teams/12/players/5` will now trigger any `PlayerResolver`.
2017-01-09 18:56:58 -05:00
46cb04d575 fix(router): throw an error when navigate to null/undefined path
Closes #10560

Fixes #13384
2017-01-09 18:56:47 -05:00
8c7e93bebe fix(core): Add type information to differs
CHANGES:

- Remove unused `onDestroy` method on the `KeyValueDiffer` and
  `IterableDiffer`.

DEPRECATION:

- `CollectionChangeRecord` is renamed to `IterableChangeRecord`.
  `CollectionChangeRecord` is aliased to `IterableChangeRecord` and is
  marked as `@deprecated`. It will be removed in `v5.x.x`.
- Deprecate `DefaultIterableDiffer` as it is private class which
  was erroneously exposed.
- Deprecate `KeyValueDiffers#factories` as it is private field which
  was erroneously exposed.
- Deprecate `IterableDiffers#factories` as it is private field which
  was erroneously exposed.

BREAKING CHANGE:

- `IterableChangeRecord` is now an interface and parameterized on `<V>`.
  This should not be an issue unless your code does
  `new IterableChangeRecord` which it should not have a reason to do.
- `KeyValueChangeRecord` is now an interface and parameterized on `<V>`.
  This should not be an issue unless your code does
  `new IterableChangeRecord` which it should not have a reason to do.

Original PR #12570

Fixes #13382
2017-01-09 18:56:34 -05:00
5d9cbd7d6f fix(compiler-cli): add support for more than 2 levels of nested lazy routes
This change adds Compiler CLI support for any level of nesting for lazy routes.

For example `{app-root}/lazy-loaded-module-1/lazy-loaded-module-2/lazy-loaded-module-3`

Where `lazy-loaded-module-3` is lazy loaded from `lazy-loaded-module-2`,
and `lazy-loaded-module-2` is lazy loaded from module `lazy-loaded-module-1`,
and `lazy-loaded-module-1` is lazy loaded from `AppModule`

Fixes angular/angular-cli#3663
2017-01-09 17:43:14 -05:00
d061adc02d fix(compiler): avoid evaluating arguments to unknown decorators
Fixes #13605
2017-01-09 16:30:31 -05:00
6d29faefea fix(Router): fix checking for object intersection 2017-01-09 16:30:14 -05:00
99aa49ab6c feat(language-service): support TS2.2 plugin model 2017-01-09 15:00:40 -05:00
e5c6bb4286 fix(Compiler): fix template binding parsing (*directive="-...")
fixes #13800
2017-01-09 15:00:40 -05:00
d9a22dae4f fix(router): RouterLink mirrors input target as attribute
Closes #13837
2017-01-09 15:00:40 -05:00
fb6c4582a1 chore(ngComponentOutlet): add missing semicolon 2017-01-09 11:54:25 -08:00
8578682dcf feat(NgComponentOutlet): add NgComponentOutlet directive
Add NgComponentOutlet directive that can be used to dynamically create
host views from a supplied component.

Closes #11168
Takes over PR #11235
2017-01-06 19:30:38 -05:00
c0178de0e2 feat(NgTemplateOutlet): Make NgTemplateOutlet compatible with * syntax
BREAKING CHANGE:

- Deprecate `ngOutletContext`. Use `ngTemplateOutletContext` instead
2017-01-06 19:30:20 -05:00
31322e73b7 fix: correctly show error when karma fails to load 2017-01-06 19:30:09 -05:00
9211a22039 feat(animations): support function types in transitions
Closes #13538
Closes #13537
2017-01-06 19:29:46 -05:00
3f67ab074a feat(animations): expose the triggerName within the transition event
Closes #13600
2017-01-06 19:29:45 -05:00
4bae4b3bb5 feat(animations): expose the element value within transition events 2017-01-06 19:29:45 -05:00
02dd90faed docs(changelog): cherry-pick 2.4.2 release notes into the master branch 2017-01-06 12:57:54 -08:00
1c85e99588 chore(tsc-wrapped): bump version number to 4.0.0-beta.2
This was done in order for us to be able to publish tsc-wrapped as @next tag on npm.

The next step is to change the build scripts to version and release @angular/tsc-wrapped
together with all the other packages. I'll create an issue/PR for this.
2017-01-05 17:53:10 -08:00
ccb65893bf docs(changelog): release notes for 4.0.0-beta.2 2017-01-05 17:18:21 -08:00
3e90ffd293 chore(release): cut the 4.0.0-beta.2 release 2017-01-05 16:58:31 -08:00
8063b0d9a2 fix(language-service): support TypeScript 2.1 (#13655)
@angular/language-service now supports using TypeScript 2.1 as the
the TypeScript host. TypeScript 2.1 is now also partially supported
in `ngc` but is not recommended as Tsickle does not yet support 2.1.
2017-01-05 11:34:42 -08:00
21030e9a1c fix(core): animations no longer silently exits if the element is not apart of the DOM (#13763) 2017-01-05 11:33:40 -08:00
889b48d85f fix(core): animations should blend in all previously transitioned styles into next animation if interrupted (#13148) 2017-01-05 11:32:52 -08:00
1bd04e95de refactor: remove unused imports 2017-01-05 11:18:34 -08:00
f88cd2f22e fix(Common): allow null/undefined values for NgForTrackBy
Reverts a breaking change introduced in 2.4.1 by #13420
fixes #13641
2017-01-05 11:18:34 -08:00
f822f9599c docs(common): add an example how to bind multiple classes based on a single parameter (#13779)
Closes #13778
2017-01-05 10:21:38 -08:00
9898d8f6d9 fix(forms): Validators.required properly validate arrays (#13362)
Closes #12274
2017-01-05 09:25:20 -08:00
2dd6280ab8 fix(common): do not override locale provided on bootstrap (#13654)
Closes #13607
2017-01-05 09:24:37 -08:00
35f9a1c2cb docs(developer): add linting section and correct command to verify API changes 2017-01-03 14:08:52 -08:00
465516b905 refactor(core): remove backwards compatibility of SimpleChange
BREAKING CHANGE:
`SimnpleChange` now takes an additional argument that defines
whether this is the first change or not.
2017-01-03 13:05:05 -08:00
db49d422f2 refactor(compiler): generate less code for bindings to DOM elements
Detailed changes:
- remove `UNINITIALIZED`, initialize change detection fields with `undefined`.
  * we use `view.numberOfChecks === 0` now everywhere
    as indicator whether we are in the first change detection cycle
    (previously we used this only in a couple of places).
  * we keep the initialization itself as change detection get slower without it.
- remove passing around `throwOnChange` in various generated calls,
  and store it on the view as property instead.
- change generated code for bindings to DOM elements as follows:
  Before:
  ```
  var currVal_10 = self.context.bgColor;
  if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) {
    self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString()));
    self._expr_10 = currVal_10;
  }
  var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' ');
  if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) {
    self.renderer.setText(self._text_1,currVal_11);
    self._expr_11 = currVal_11;
  }
  ```,
  After:
  ```
  var currVal_10 = self.context.bgColor;
  jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21);
  var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' ');
  jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false);
  ```

Performance impact:
- None seen (checked against internal latency lab)

Part of #13651
2017-01-03 13:05:05 -08:00
8ed92d75b0 refactor(benchmarks): make ftl benchmarks use their own version of checkBinding 2017-01-03 13:05:05 -08:00
50e5cb15dd feat(benchmarks): add detectChanges test for ng2 tree benchmark 2017-01-03 13:05:05 -08:00
c5c53f3666 fix(core): Remove reference to "Angular 2" in dev mode warning (#13751) 2017-01-03 10:03:58 -08:00
bb0d23f82b Typo (#13698) 2016-12-29 09:41:21 -08:00
1e6440e81b docs(Http): fix and extend samples for testing/MockBackend (#13689)
Fix samples for MockBackend and MockBackend.connections that were outdated. Also extend central sample for MockBackend to ease getting started.
2016-12-29 09:39:00 -08:00
6b02b80a03 fix(compiler): improve error message for undefined providers (#13546)
Closes #10835
2016-12-27 17:05:14 -08:00
2c0c86e3ce fix(compiler): improve the error when template is not a string
Closes #8708
Closes #13377
2016-12-27 17:04:16 -08:00
5b4bea24de refactor(compiler): clean up directive normalizer 2016-12-27 17:03:58 -08:00
7690d02133 fix(compiler): don’t throw when using ANALYZE_FOR_ENTRY_COMPONENTS with user classes (#13679)
Fixed #13565
2016-12-27 16:58:52 -08:00
b2ae7b607e docs(Core): fix API docs for ContentChild and ViewChildren (#13656)
Move the documentations of the ContentChild and ViewChildren decorators
so that they appear correctly on angular.io.

Closes #13625
2016-12-27 16:58:33 -08:00
7c210645a3 fix(compiler): query <template> elements before their children. (#13677)
Fixes #13118
Closes #13167
2016-12-27 16:28:54 -08:00
07e0fce8fc fix(router): update route snapshot before emit new values (#13558)
Closes #12912
2016-12-27 15:57:22 -08:00
0ac8e102de test(i18n): add extraction to integration specs
Closes #13648.
2016-12-27 15:32:54 -08:00
e74d8aaf92 fix(i18n): parse ICU messages while normalizing templates
Fixes:
- Inject the i18n specific HtmlParser into the directive normalizer,
- Parse ICU messages while normalizing templates,
- Normalize (visit) the content of ICU messages.

🎄🎁🎅
2016-12-27 15:32:43 -08:00
881eb894bc fix(Compiler): allow "." in attribute selectors (#13653)
fixes #13645
2016-12-27 15:23:49 -08:00
0eca960494 fix(router): fix lazy loaded module with wildcard route (#13649)
Closes #12955
2016-12-27 15:22:57 -08:00
eed83443b8 chore(tslint): update tslint to 4.x (#13603) 2016-12-27 14:55:58 -08:00
e5c4e5801f fix(upgrade): fix/improve support for lifecycle hooks (#13020)
With the exception of `$onChanges()`, all lifecycle hooks in ng1 are called on
the controller, regardless if it is the binding destination or not (i.e.
regardless of the value of `bindToController`).

This change makes `upgrade` mimic that behavior when calling lifecycle hooks.

Additionally, calling the `$onInit()` hook has been moved before calling the
linking functions, which also mimics the ng1 behavior.
2016-12-27 14:42:53 -08:00
69fa3bbc03 feat(router): add an extra argument to CanDeactivate interface (#13560)
Adds a `nextState` argument to access the future url from `CanDeactivate`.

BEFORE:

    canDeactivate(component: T, route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean;

AFTER:

    canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean>|Promise<boolean>|boolean;

Closes #9853
2016-12-27 14:08:06 -08:00
445ed43b9a fix(compiler): throw an error for invalid provider (#13544)
Closes #8870
2016-12-27 14:02:28 -08:00
174334dec3 fix(router): routerLink support of null/undefined (#13380)
Closes #6971
2016-12-27 13:45:16 -08:00
9c697030e6 feat(compiler): generate proper reexports in .ngfactory.ts files to not need transitive deps for compiling .ngfactory.ts files. (#13524)
Note: This checks the constructors of `@Injectable` classes more strictly.
E.g this will fail now as the constructor argument has no `@Inject` nor is
the type of the argument a DI token.

```
@Injectable()
class MyService {
  constructor(dep: string) {}
}
```

Last part of #12787
Closes #12787
2016-12-27 09:36:47 -08:00
697690349f fix(common): add link to trackBy docs (#13634) 2016-12-22 13:25:51 -08:00
0448e80704 docs(examples): fix example path (#13635) 2016-12-22 13:25:21 -08:00
e85232afd2 docs(ngIf): fix typos (#13630) 2016-12-22 12:36:47 -08:00
e7ece6c8ce fixed minor typo (#13626) 2016-12-22 12:36:24 -08:00
67380d4b28 fix(testing): improve misleading error message when don't call compileComponents (#13543)
Closes #11301
2016-12-22 12:35:57 -08:00
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
384 changed files with 14572 additions and 6512 deletions

32
.pullapprove.yml Normal file
View File

@ -0,0 +1,32 @@
# Configuration for pullapprove.com
# See ownership spreadsheet:
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
version: 2
group_defaults:
required: 1
reset_on_reopened:
enabled: true
approve_by_comment:
enabled: true
approve_regex: '^(Approved|:\+1:|LGTM)'
groups:
config:
conditions:
files:
- "*.yml"
- "*.json"
teams:
- repoowners
compiler:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "modules/@angular/compiler/*"
- "modules/@angular/compiler-cli/*"
teams:
- compiler-owners
- repoowners

View File

@ -1,3 +1,241 @@
<a name="4.0.0-beta.3"></a>
# [4.0.0-beta.3](https://github.com/angular/angular/compare/4.0.0-beta.2...4.0.0-beta.3) (2017-01-11)
### Bug Fixes
* **compiler:** avoid evaluating arguments to unknown decorators ([d061adc](https://github.com/angular/angular/commit/d061adc)), closes [#13605](https://github.com/angular/angular/issues/13605)
* **compiler:** fix template binding parsing (`*directive="-..."`) ([e5c6bb4](https://github.com/angular/angular/commit/e5c6bb4)), closes [#13800](https://github.com/angular/angular/issues/13800)
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([5d9cbd7](https://github.com/angular/angular/commit/5d9cbd7)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([aeed737](https://github.com/angular/angular/commit/aeed737)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
* **core:** Add type information to differs ([8c7e93b](https://github.com/angular/angular/commit/8c7e93b)), closes [#13382](https://github.com/angular/angular/issues/13382)
* **i18n:** translate attributes inside elements marked for translation ([424e6c4](https://github.com/angular/angular/commit/424e6c4)), closes [#13796](https://github.com/angular/angular/issues/13796) [#13814](https://github.com/angular/angular/issues/13814)
* **router:** RouterLink mirrors input `target` as attribute ([d9a22da](https://github.com/angular/angular/commit/d9a22da)), closes [#13837](https://github.com/angular/angular/issues/13837)
* **router:** throw an error when navigate to null/undefined path ([46cb04d](https://github.com/angular/angular/commit/46cb04d)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
* **router:** fix checking for object intersection ([6d29fae](https://github.com/angular/angular/commit/6d29fae))
### Features
* **animations:** expose the `element` value within transition events ([4bae4b3](https://github.com/angular/angular/commit/4bae4b3))
* **animations:** expose the `triggerName` within the transition event ([3f67ab0](https://github.com/angular/angular/commit/3f67ab0)), closes [#13600](https://github.com/angular/angular/issues/13600)
* **animations:** support function types in transitions ([9211a22](https://github.com/angular/angular/commit/9211a22)), closes [#13538](https://github.com/angular/angular/issues/13538) [#13537](https://github.com/angular/angular/issues/13537)
* **language-service:** support TS2.2 plugin model ([99aa49a](https://github.com/angular/angular/commit/99aa49a))
* **NgComponentOutlet:** add NgComponentOutlet directive ([8578682](https://github.com/angular/angular/commit/8578682)), closes [#11168](https://github.com/angular/angular/issues/11168)
* **NgTemplateOutlet:** Make NgTemplateOutlet compatible with * syntax ([c0178de](https://github.com/angular/angular/commit/c0178de))
* **router:** call resolver when upstream params change ([#12942](https://github.com/angular/angular/issues/12942)) ([d4d3782](https://github.com/angular/angular/commit/d4d3782))
### BREAKING CHANGES
* core: - `IterableChangeRecord` is now an interface and parameterized on `<V>`.
This should not be an issue unless your code does
`new IterableChangeRecord` which it should not have a reason to do.
- `KeyValueChangeRecord` is now an interface and parameterized on `<V>`.
This should not be an issue unless your code does
`new IterableChangeRecord` which it should not have a reason to do.
### DEPRECATION
* Deprecate `ngOutletContext`. Use `ngTemplateOutletContext` instead.
* `CollectionChangeRecord` is renamed to `IterableChangeRecord`.
`CollectionChangeRecord` is aliased to `IterableChangeRecord` and is
marked as `@deprecated`. It will be removed in `v6.x.x`.
* Deprecate `DefaultIterableDiffer` as it is private class which
was erroneously exposed.
* Deprecate `KeyValueDiffers#factories` as it is private field which
was erroneously exposed.
* Deprecate `IterableDiffers#factories` as it is private field which
was erroneously exposed.
<a name="2.4.3"></a>
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
### Bug Fixes
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
<a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
### Bug Fixes
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645)
* **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))
<a name="4.0.0-beta.2"></a>
# [4.0.0-beta.2](https://github.com/angular/angular/compare/4.0.0-beta.1...4.0.0-beta.2) (2017-01-06)
### Features
* **compiler:** generate less code for bindings to DOM elements ([db49d42](https://github.com/angular/angular/commit/db49d42))
* **compiler:** generate proper reexports in `.ngfactory.ts` files to not need transitive deps for compiling `.ngfactory.ts` files. ([#13524](https://github.com/angular/angular/issues/13524)) ([9c69703](https://github.com/angular/angular/commit/9c69703)), closes [#12787](https://github.com/angular/angular/issues/12787)
* **router:** add an extra argument to CanDeactivate interface ([#13560](https://github.com/angular/angular/issues/13560)) ([69fa3bb](https://github.com/angular/angular/commit/69fa3bb)), closes [#9853](https://github.com/angular/angular/issues/9853)
### Bug Fixes
* **compiler:** improve error message for undefined providers ([#13546](https://github.com/angular/angular/issues/13546)) ([6b02b80](https://github.com/angular/angular/commit/6b02b80)), closes [#10835](https://github.com/angular/angular/issues/10835)
* **compiler:** improve the error when template is not a string ([2c0c86e](https://github.com/angular/angular/commit/2c0c86e)), closes [#8708](https://github.com/angular/angular/issues/8708) [#13377](https://github.com/angular/angular/issues/13377)
* **compiler:** throw an error for invalid provider ([#13544](https://github.com/angular/angular/issues/13544)) ([445ed43](https://github.com/angular/angular/commit/445ed43)), closes [#8870](https://github.com/angular/angular/issues/8870)
* **i18n:** parse ICU messages while normalizing templates ([e74d8aa](https://github.com/angular/angular/commit/e74d8aa))
Note: 4.0.0-beta.2 release also contains all the changes present in the 2.4.2 release.
### BREAKING CHANGES
* **core**: `SimpleChange` now takes an additional argument that defines whether this is the first
change or not. This is a low profile API and we don't expect anyone to be affected by this change.
If you are impacted by this change please file an issue. ([465516b](https://github.com/angular/angular/commit/465516b))
<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
* **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)
### Bug Fixes
* **animations:** always quote string map key values in AOT code ([#13602](https://github.com/angular/angular/issues/13602)) ([6a5e46c](https://github.com/angular/angular/commit/6a5e46c))
* **animations:** always recover from a failed animation step ([#13604](https://github.com/angular/angular/issues/13604)) ([d788c67](https://github.com/angular/angular/commit/d788c67))
* **compiler:** ignore `@import` in comments ([#13368](https://github.com/angular/angular/issues/13368)) ([6316e5d](https://github.com/angular/angular/commit/6316e5d)), closes [#12196](https://github.com/angular/angular/issues/12196)
* **core:** improve error message when component factory cannot be found ([#13541](https://github.com/angular/angular/issues/13541)) ([b9e979e](https://github.com/angular/angular/commit/b9e979e)), closes [#12678](https://github.com/angular/angular/issues/12678)
* **router:** should reset location if a navigation by location is successful ([#13545](https://github.com/angular/angular/issues/13545)) ([a38f14b](https://github.com/angular/angular/commit/a38f14b)), closes [#13491](https://github.com/angular/angular/issues/13491)
<a name="2.4.0"></a>
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
### Bug Fixes
* **animations:** allow players to be destroyed before initialized ([#13346](https://github.com/angular/angular/issues/13346)) ([b36f4bc](https://github.com/angular/angular/commit/b36f4bc)), closes [#13293](https://github.com/angular/angular/issues/13293)
* **build:** use bash string comparison operator ([#13502](https://github.com/angular/angular/issues/13502)) ([50afbe0](https://github.com/angular/angular/commit/50afbe0))
* **compiler:** do not lex `}}` when interpolation is disabled ([#13531](https://github.com/angular/angular/issues/13531)) ([9b87bb6](https://github.com/angular/angular/commit/9b87bb6)), closes [#13525](https://github.com/angular/angular/issues/13525)
* **compiler-cli:** produce metadata for .d.ts files without metadata ([#13526](https://github.com/angular/angular/issues/13526)) ([debb0c9](https://github.com/angular/angular/commit/debb0c9)), closes [#13307](https://github.com/angular/angular/issues/13307) [#13473](https://github.com/angular/angular/issues/13473) [#13521](https://github.com/angular/angular/issues/13521)
* **i18n:** add a default example to xmb placeholders ([#13507](https://github.com/angular/angular/issues/13507)) ([3f17841](https://github.com/angular/angular/commit/3f17841))
* **upgrade:** fix `registerForNg1Tests` ([#13522](https://github.com/angular/angular/issues/13522)) ([c26c24c](https://github.com/angular/angular/commit/c26c24c))
### Features
* 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)
### Bug Fixes
* **animations:** always cleanup players after they have finished internally ([#13334](https://github.com/angular/angular/issues/13334)) ([a26e054](https://github.com/angular/angular/commit/a26e054)), closes [#13333](https://github.com/angular/angular/issues/13333)
* **animations:** throw errors and normalize offset beyond the range of [0,1] ([6557bc3](https://github.com/angular/angular/commit/6557bc3)), closes [#13348](https://github.com/angular/angular/issues/13348) [#13440](https://github.com/angular/angular/issues/13440)
* **compiler:** emit quoted object literal keys if the source is quoted ([ecfad46](https://github.com/angular/angular/commit/ecfad46)), closes [#13249](https://github.com/angular/angular/issues/13249) [#13356](https://github.com/angular/angular/issues/13356)
* **compiler:** fix merge error in compiler_host ([69b52eb](https://github.com/angular/angular/commit/69b52eb))
* **compiler:** fix PR 13322 ([#13331](https://github.com/angular/angular/issues/13331)) ([79728b4](https://github.com/angular/angular/commit/79728b4))
* **compiler:** fix simplify a reference without a name ([1c279b3](https://github.com/angular/angular/commit/1c279b3)), closes [#13470](https://github.com/angular/angular/issues/13470)
* **compiler:** fix transpiled ES5 code ([#13322](https://github.com/angular/angular/issues/13322)) ([6c1d790](https://github.com/angular/angular/commit/6c1d790)), closes [#13301](https://github.com/angular/angular/issues/13301)
* **compiler:** generated CSS files suffixed with ngstyle. ([#13353](https://github.com/angular/angular/issues/13353)) ([c8a9b70](https://github.com/angular/angular/commit/c8a9b70)), closes [#13141](https://github.com/angular/angular/issues/13141)
* **compiler:** make sure provider values with `name` property dont break. ([efa2d80](https://github.com/angular/angular/commit/efa2d80)), closes [#13394](https://github.com/angular/angular/issues/13394) [#13445](https://github.com/angular/angular/issues/13445)
* **compiler:** narrow the span reported for invalid pipes ([307d305](https://github.com/angular/angular/commit/307d305)), closes [#13326](https://github.com/angular/angular/issues/13326) [#13411](https://github.com/angular/angular/issues/13411)
* **compiler:** propagate exports when upgrading metadata to v2 ([f6ef7d6](https://github.com/angular/angular/commit/f6ef7d6))
* **compiler:** resolver should merge host bindings and listeners ([#13474](https://github.com/angular/angular/issues/13474)) ([6aeaca3](https://github.com/angular/angular/commit/6aeaca3)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
* **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
* **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
* **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
* **core:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
* **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
* **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
* **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364)
* **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18))
* **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980)
* **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414)
* **compiler**: better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
* **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403)
* **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373)
* **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155))
* **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac))
* **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675)
* **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675)
### Performance Improvements
* **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440)
### Note ###
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
<a name="2.3.0"></a>
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
@ -15,7 +253,7 @@
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
* **router:** allow specifying a matcher wihtout specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))

View File

@ -1,6 +1,6 @@
# Building and Testing Angular 2 for JS
# Building and Testing Angular
This document describes how to set up your development environment to build and test Angular 2 JS version.
This document describes how to set up your development environment to build and test Angular.
It also explains the basic mechanics of using `git`, `node`, and `npm`.
* [Prerequisite Software](#prerequisite-software)
@ -74,6 +74,15 @@ use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Installing Bower Modules
Now run `bower` to install additional dependencies:
```shell
# Install other Angular project dependencies (bower.json)
bower install
```
## Windows only
In order to create the right symlinks, run **as administrator**:
@ -124,7 +133,8 @@ If you happen to modify the public API of Angular, API golden files must be upda
$ gulp public-api:update
```
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
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.
## <a name="clang-format"></a> Formatting your source code
@ -137,4 +147,32 @@ 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.
Rather than requiring them to pull your Pull Request and build Angular locally, you can
publish the `*-builds` snapshots just like our Travis build does.
First time, you need to create the github repositories:
``` shell
$ export TOKEN=[get one from https://github.com/settings/tokens]
$ CREATE_REPOS=1 ./scripts/publish/publish-build-artifacts.sh [github username]
```
For subsequent snapshots, just run
``` shell
$ ./scripts/publish/publish-build-artifacts.sh [github username]
```
The script will publish the build snapshot to a branch with the same name as your current branch,
and create it if it doesn't exist.

View File

@ -24,8 +24,9 @@ with it.
* `comp: forms`: `@kara`
* `comp: http`: `@jeffbcross`
* `comp: i18n`: `@vicb`
* `comp: language service`: `@chuckjaz`
* `comp: metadata-extractor`: `@chuckjaz`
* `comp: router`: `@vsavkin`
* `comp: router`: `@vicb`
* `comp: testing`: `@juliemr`
* `comp: upgrade`: `@mhevery`
* `comp: web-worker`: `@vicb`

View File

@ -36,7 +36,7 @@ var CIconfiguration = {
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
@ -44,10 +44,10 @@ var customLaunchers = {
'DartiumWithWebPlatform':
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'},
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '50'},
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
@ -96,7 +96,7 @@ var customLaunchers = {
'BS_IE10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
browser_version: '10.1',
os: 'Windows',
os_version: '8'
},

82
docs/RELEASE_SCHEDULE.md Normal file
View File

@ -0,0 +1,82 @@
# Angular Release Schedule
This document contains historic record of past Angular releases and future release schedule.
The purpose of this document is to assist coordination among the Angular team, Angular contributors, Angular application teams, and Angular community projects.
We'll keep this doc up to date when unplanned releases or other schedule changes occur.
## Schedule Caveats and Exceptions
The dates listed here are approximate last minute issues, team or community events, etc. can cause us to release a few days sooner or later.
This page contains only planned and past unplanned releases.
Due to serious regressions or other important reasons we reserve the right to release an unplanned patch or minor release.
In such case, we'll update this document accordingly.
The dates past March 2017 are just a guidance and might be adjusted slightly if necessary.
## Tentative Schedule Until March 2017
<!--
The table below is formatted so that it's easy to read and edit in both markdown and rendered html form.
In order to deal with undesirable line breaks, two special characters are occasionally used:
- non-breaking hyphen: "" http://www.fileformat.info/info/unicode/char/2011/index.htm
- non-breaking space: " " http://www.fileformat.info/info/unicode/char/00a0/index.htm
If you see undesirable wrapping issues in the rendered form, please copy&paste the quoted characters and use them in the table below where needed.
-->
Week Of | Stable Release<br>(@latest npm tag) | Beta/RC Release<br>(@next npm tag) | Note
------------- | ----------------------------------- | ---------------------------------- | ---------------------
2016-09-14 | 2.0.0 | - | Major Version Release
2016-09-21 | 2.0.1 | 2.1.0-beta.0 |
20160928 | - | - | Angular Connect
2016-10-05 | 2.0.2 | 2.1.0-rc.0 |
2016-10-12 | 2.1.0 | - | Minor Version Release
2016-10-19 | 2.1.1 | 2.2.0-beta.0 |
2016-10-26 | 2.1.2 | 2.2.0-beta.1 |
2016-11-02 | 2.1.3 | 2.2.0-rc.0 |
2016-11-09 | 2.2.0 | - | Minor Version Release
2016-11-16 | 2.2.1 | 2.3.0-beta.0 |
2016-11-23 | 2.2.2 | 2.3.0-beta.1 |
*2016-11-23* | *2.2.3* | - | *Unplanned Patch Release to fix regressions*
2016-11-30 | 2.2.4 | 2.3.0-rc.0 |
2016-12-07 | 2.3.0 | - | Minor Version Release
2016-12-14 | 2.3.1 | 4.0.0-beta.0 |
*2016-12-21* | *2.4.0* | - | *Unplanned Minor Release due to release of RxJS 5.0.0*
2016-12-21 | 2.4.1 | 4.0.0-beta.1 |
2016-12-28 | - | - | Holiday Break
2017-01-04 | 2.4.2 | 4.0.0-beta.2 |
2017-01-11 | 2.4.3 | 4.0.0-beta.3 |
2017-01-18 | 2.4.4 | 4.0.0-beta.4 |
2017-01-25 | 2.4.5 | 4.0.0-beta.5 |
2017-02-01 | 2.4.6 | 4.0.0-beta.6 |
2017-02-08 | 2.4.7 | 4.0.0-rc.0 |
2017-02-15 | 2.4.8 | 4.0.0-rc.1 |
2017-02-22 | 2.4.9 | 4.0.0-rc.2 |
2017-03-01 | 2.4.10 | 4.0.0-rc.3 |
2017-03-08 | 4.0.0 + 2.4.11 | - | Major Version Release
## Tentative Schedule After March 2017
Date | Stable Release | Compatibility`*`
---------------------- | -------------- | ----------------
September/October 2017 | 5.0.0 | ^4.0.0
March 2018 | 6.0.0 | ^5.0.0
September/October 2018 | 7.0.0 | ^6.0.0
`*` The goal of the backwards compatibility promise, is to ensure that changes in the core framework and tooling don't break the existing ecosystem of components and applications and don't put undue upgrade/migration burden on Angular application and component authors.
## More Info & Resources
In [September 2016 we announced](http://angularjs.blogspot.com/2016/10/versioning-and-releasing-angular.html) that Angular is fully adopting [semantic versioning](http://semver.org/) and that we'll be releasing patch versions on a weekly basis (~50 per year), minor versions monthly for 3 months following a major version release, and every 6 months we'll release a major version that will be backwards compatible with the previous release for most developers, but might remove APIs that have been deprecated two major versions ago (6 or more months ago).
In [December 2016 we clarified this message](http://angularjs.blogspot.com/2016/12/ok-let-me-explain-its-going-to-be.html), and provided additional details about the plans to release Angular 4.0.0 in March 2017.
This document contains updates to the schedule that happened since then.

View File

@ -24,17 +24,25 @@ module.exports = function(config) {
'node_modules/core-js/client/core.js',
// include Angular v1 for upgrade module testing
'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.js',
'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces.
'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js',
'shims_for_IE.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false}, {
'node_modules/reflect-metadata/Reflect.js',
'tools/build/file2modulename.js',
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{
pattern: 'modules/@angular/platform-browser/test/static_assets/**',
included: false,
watched: false
@ -48,13 +56,15 @@ module.exports = function(config) {
exclude: [
'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/router/**',
'dist/all/@angular/**/*node_only_spec.js',
'dist/all/@angular/benchpress/**',
'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'dist/all/@angular/benchpress/**',
'dist/all/@angular/examples/**/e2e_test/*',
'dist/all/@angular/language-service/**',
'dist/all/angular1_router.js',
'dist/all/@angular/router/**',
'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/all/angular1_router.js',
'dist/examples/**/e2e_test/**',
],

View File

@ -7,7 +7,7 @@
"dependencies": {
"@angular/core": "^2.0.0-rc.7",
"reflect-metadata": "^0.1.2",
"rxjs": "5.0.0-rc.4",
"rxjs": "^5.0.1",
"jpm": "1.1.4",
"firefox-profile": "0.4.0",
"selenium-webdriver": "^2.53.3"

View File

@ -12,9 +12,8 @@
* 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 {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
export {VERSION} from './version';
export {Version} from '@angular/core';

View File

@ -9,6 +9,7 @@
import {Provider} from '@angular/core';
import {NgClass} from './ng_class';
import {NgComponentOutlet} from './ng_component_outlet';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgPlural, NgPluralCase} from './ng_plural';
@ -18,6 +19,7 @@ import {NgTemplateOutlet} from './ng_template_outlet';
export {
NgClass,
NgComponentOutlet,
NgFor,
NgIf,
NgPlural,
@ -29,12 +31,14 @@ export {
NgTemplateOutlet
};
/**
* A collection of Angular directives that are likely to be used in each and every Angular
* application.
*/
export const COMMON_DIRECTIVES: Provider[] = [
NgClass,
NgComponentOutlet,
NgFor,
NgIf,
NgTemplateOutlet,

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection';
import {isPresent, stringify} from '../facade/lang';
import {stringify} from '../facade/lang';
/**
* @ngModule CommonModule
@ -25,6 +25,8 @@ 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
@ -39,8 +41,8 @@ import {isPresent, stringify} from '../facade/lang';
*/
@Directive({selector: '[ngClass]'})
export class NgClass implements DoCheck {
private _iterableDiffer: IterableDiffer;
private _keyValueDiffer: KeyValueDiffer;
private _iterableDiffer: IterableDiffer<string>;
private _keyValueDiffer: KeyValueDiffer<string, any>;
private _initialClasses: string[] = [];
private _rawClass: string[]|Set<string>|{[klass: string]: any};
@ -76,39 +78,35 @@ export class NgClass implements DoCheck {
ngDoCheck(): void {
if (this._iterableDiffer) {
const changes = this._iterableDiffer.diff(this._rawClass);
if (changes) {
this._applyIterableChanges(changes);
const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]);
if (iterableChanges) {
this._applyIterableChanges(iterableChanges);
}
} else if (this._keyValueDiffer) {
const changes = this._keyValueDiffer.diff(this._rawClass);
if (changes) {
this._applyKeyValueChanges(changes);
const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any});
if (keyValueChanges) {
this._applyKeyValueChanges(keyValueChanges);
}
}
}
private _cleanupClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}): void {
private _cleanupClasses(rawClassVal: string[]|{[klass: string]: any}): void {
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}
private _applyKeyValueChanges(changes: any): void {
changes.forEachAddedItem(
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
changes.forEachChangedItem(
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
changes.forEachRemovedItem((record: KeyValueChangeRecord) => {
private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void {
changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue));
changes.forEachRemovedItem((record) => {
if (record.previousValue) {
this._toggleClass(record.key, false);
}
});
}
private _applyIterableChanges(changes: any): void {
changes.forEachAddedItem((record: CollectionChangeRecord) => {
private _applyIterableChanges(changes: IterableChanges<string>): void {
changes.forEachAddedItem((record) => {
if (typeof record.item === 'string') {
this._toggleClass(record.item, true);
} else {
@ -117,8 +115,7 @@ export class NgClass implements DoCheck {
}
});
changes.forEachRemovedItem(
(record: CollectionChangeRecord) => this._toggleClass(record.item, false));
changes.forEachRemovedItem((record) => this._toggleClass(record.item, false));
}
private _applyInitialClasses(isCleanup: boolean) {
@ -126,23 +123,23 @@ export class NgClass implements DoCheck {
}
private _applyClasses(
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
rawClassVal: string[]|Set<string>|{[klass: string]: any}, isCleanup: boolean) {
if (rawClassVal) {
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else {
Object.keys(rawClassVal).forEach(klass => {
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
});
}
}
}
private _toggleClass(klass: string, enabled: boolean): void {
private _toggleClass(klass: string, enabled: any): void {
klass = klass.trim();
if (klass) {
klass.split(/\s+/g).forEach(
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, enabled); });
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, !!enabled); });
}
}
}

View File

@ -0,0 +1,86 @@
/**
* @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 {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnChanges, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
/**
* Instantiates a single {@link Component} type and inserts its Host View into current View.
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
*
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
* any existing component will get destroyed.
*
* ### Fine tune control
*
* You can control the component creation process by using the following optional attributes:
*
* * `ngOutletInjector`: Optional custom {@link Injector} that will be used as parent for the
* Component.
* Defaults to the injector of the current view container.
*
* * `ngOutletProviders`: Optional injectable objects ({@link Provider}) that are visible to the
* component.
*
* * `ngOutletContent`: Optional list of projectable nodes to insert into the content
* section of the component, if exists. ({@link NgContent}).
*
*
* ### Syntax
*
* Simple
* ```
* <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
* ```
*
* Customized
* ```
* <ng-container *ngComponentOutlet="componentTypeExpression;
* injector: injectorExpression;
* content: contentNodesExpression">
* </ng-container>
* ```
*
* # Example
*
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
*
* A more complete example with additional options:
*
* {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
*
* @experimental
*/
@Directive({selector: '[ngComponentOutlet]'})
export class NgComponentOutlet implements OnChanges {
@Input() ngComponentOutlet: Type<any>;
@Input() ngComponentOutletInjector: Injector;
@Input() ngComponentOutletContent: any[][];
componentRef: ComponentRef<any>;
constructor(
private _cmpFactoryResolver: ComponentFactoryResolver,
private _viewContainerRef: ViewContainerRef) {}
ngOnChanges(changes: SimpleChanges) {
if (this.componentRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this.componentRef.hostView));
}
this._viewContainerRef.clear();
this.componentRef = null;
if (this.ngComponentOutlet) {
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
this.componentRef = this._viewContainerRef.createComponent(
this._cmpFactoryResolver.resolveComponentFactory(this.ngComponentOutlet),
this._viewContainerRef.length, injector, this.ngComponentOutletContent);
}
}
}

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} from '@angular/core';
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang';
@ -89,9 +89,23 @@ export class NgForRow {
@Directive({selector: '[ngFor][ngForOf]'})
export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any;
@Input() ngForTrackBy: TrackByFn;
@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.`);
}
}
this._trackByFn = fn;
}
private _differ: IterableDiffer = null;
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
private _differ: IterableDiffer<any> = null;
private _trackByFn: TrackByFn;
constructor(
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
@ -119,17 +133,17 @@ export class NgFor implements DoCheck, OnChanges {
}
}
ngDoCheck() {
ngDoCheck(): void {
if (this._differ) {
const changes = this._differ.diff(this.ngForOf);
if (changes) this._applyChanges(changes);
}
}
private _applyChanges(changes: DefaultIterableDiffer) {
private _applyChanges(changes: IterableChanges<any>) {
const insertTuples: RecordViewTuple[] = [];
changes.forEachOperation(
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
if (item.previousIndex == null) {
const view = this._viewContainer.createEmbeddedView(
this._template, new NgForRow(null, null, null), currentIndex);
@ -161,7 +175,7 @@ export class NgFor implements DoCheck, OnChanges {
});
}
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: IterableChangeRecord<any>) {
view.context.$implicit = record.item;
}
}

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 truthy or falsy respectively. Typically the:
* - `then` template is the inline template of `ngIf` unless bound to a different value.
* - `else` template is blank unless it is bound.
*
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
* # Most common usage
*
* The most common usage of the `ngIf` directive 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 this example.
*
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
*
* # Storing conditional result in a variable
*
* A common pattern 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 `userStream`. One for each `async` pipe, or two in the
* example above.
* - We cannot 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 then be bound repeatedly in a more efficient way.
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
* display the data if `userStream` returns a value.
* - We can display an alternative template while waiting for the data.
*
* ### Syntax
*
* Simple form:
* - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>`
* - `<template [ngIf]="condition"><div>...</div></template>`
*
* Form with an else block:
* ```
* <div *ngIf="condition; else elseBlock">...</div>
* <template #elseBlock>...</template>
* ```
*
* Form with a `then` and `else` block:
* ```
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
* <template #thenBlock>...</template>
* <template #elseBlock>...</template>
* ```
*
* Form with storing the value locally:
* ```
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
* <template #elseBlock>...</template>
* ```
*
* @stable
*/
@Directive({selector: '[ngIf]'})
export class NgIf {
private _hasView = false;
private _context: NgIfContext = new NgIfContext();
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
}
@Input()
set ngIf(condition: any) {
if (condition && !this._hasView) {
this._hasView = true;
this._viewContainer.createEmbeddedView(this._template);
} else if (!condition && this._hasView) {
this._hasView = false;
this._viewContainer.clear();
this._context.$implicit = condition;
this._updateView();
}
@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any.
this._updateView();
}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any.
this._updateView();
}
private _updateView() {
if (this._context.$implicit) {
if (!this._thenViewRef) {
this._viewContainer.clear();
this._elseViewRef = null;
if (this._thenTemplateRef) {
this._thenViewRef =
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
}
}
} else {
if (!this._elseViewRef) {
this._viewContainer.clear();
this._thenViewRef = null;
if (this._elseTemplateRef) {
this._elseViewRef =
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
}
}
}
}
}
export class NgIfContext { public $implicit: any = null; }

View File

@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
* <template ngPluralCase="=0">there is nothing</template>
* <template ngPluralCase="=1">there is one</template>
* <template ngPluralCase="few">there are a few</template>
* </some-element>
* ```
*
@ -90,8 +89,8 @@ export class NgPlural {
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">...</ng-container>
* <ng-container *ngPluralCase="'other'">...</ng-container>
* <template ngPluralCase="=0">...</template>
* <template ngPluralCase="other">...</template>
* </some-element>
*```
*

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {Directive, DoCheck, ElementRef, Input, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
/**
* @ngModule CommonModule
@ -33,7 +33,7 @@ import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDif
@Directive({selector: '[ngStyle]'})
export class NgStyle implements DoCheck {
private _ngStyle: {[key: string]: string};
private _differ: KeyValueDiffer;
private _differ: KeyValueDiffer<string, string|number>;
constructor(
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
@ -55,20 +55,16 @@ export class NgStyle implements DoCheck {
}
}
private _applyChanges(changes: any): void {
changes.forEachRemovedItem((record: KeyValueChangeRecord) => this._setStyle(record.key, null));
changes.forEachAddedItem(
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem(
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
}
private _setStyle(nameAndUnit: string, value: string): void {
private _setStyle(nameAndUnit: string, value: string|number): void {
const [name, unit] = nameAndUnit.split('.');
value = value && unit ? `${value}${unit}` : value;
value = value != null && unit ? `${value}${unit}` : value;
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value as string);
}
}

View File

@ -15,42 +15,47 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef
*
* @howToUse
* ```
* <template [ngTemplateOutlet]="templateRefExpression"
* [ngOutletContext]="objectExpression">
* </template>
* <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
* ```
*
* @description
*
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
* available within the `TemplateRef`.
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.
* `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding
* by the local template `let` declarations.
*
* Note: using the key `$implicit` in the context object will set it's value as default.
*
* # Example
*
* {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
*
* @experimental
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet implements OnChanges {
private _viewRef: EmbeddedViewRef<any>;
private _context: Object;
private _templateRef: TemplateRef<any>;
@Input() public ngTemplateOutletContext: Object;
@Input() public ngTemplateOutlet: TemplateRef<any>;
constructor(private _viewContainerRef: ViewContainerRef) {}
/**
* @deprecated v4.0.0 - Renamed to ngTemplateOutletContext.
*/
@Input()
set ngOutletContext(context: Object) { this._context = context; }
@Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
set ngOutletContext(context: Object) { this.ngTemplateOutletContext = context; }
ngOnChanges(changes: SimpleChanges) {
if (this._viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
}
if (this._templateRef) {
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
if (this.ngTemplateOutlet) {
this._viewRef = this._viewContainerRef.createEmbeddedView(
this.ngTemplateOutlet, this.ngTemplateOutletContext);
}
}
}

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

@ -17,6 +17,8 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/**
* @whatItDoes Use URL hash for storing application location data.
* @description
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
@ -27,18 +29,7 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
*
* ### Example
*
* ```
* import {Component, NgModule} from '@angular/core';
* import {
* LocationStrategy,
* HashLocationStrategy
* } from '@angular/common';
*
* @NgModule({
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
* })
* class AppModule {}
* ```
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
*
* @stable
*/

View File

@ -12,7 +12,8 @@ import {LocationStrategy} from './location_strategy';
/**
* `Location` is a service that applications can use to interact with a browser's URL.
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
* @description
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
* to the URL's path or the URL's hash segment.
*
@ -28,19 +29,7 @@ import {LocationStrategy} from './location_strategy';
* - `/my/app/user/123/` **is not** normalized
*
* ### Example
*
* ```
* import {Component} from '@angular/core';
* import {Location} from '@angular/common';
*
* @Component({selector: 'app-component'})
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
* ```
*
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
* @stable
*/
@Injectable()

View File

@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
/**
* `LocationStrategy` is responsible for representing and reading route state
* from the browser's URL. Angular provides two strategies:
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
* {@link HashLocationStrategy} and {@link PathLocationStrategy}.
*
* This is used under the hood of the {@link Location} service.
*

View File

@ -17,14 +17,13 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/**
* @whatItDoes Use URL for storing application location data.
* @description
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
* browser's URL.
*
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link ROUTER_PROVIDERS}.
*
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
* or add a base element to the document. This URL prefix that will be preserved
* when generating and recognizing URLs.
@ -37,6 +36,10 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* ### Example
*
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
*
* @stable
*/
@Injectable()

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 common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe' }
*
* @stable
*/
@Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}
/**
* Helper method to transform a single word to titlecase.
*
* @stable
*/
function titleCaseWord(word: string) {
if (!word) return word;
return word[0].toUpperCase() + word.substr(1).toLowerCase();
}
/**
* Transforms text to titlecase.
*
* @stable
*/
@Pipe({name: 'titlecase'})
export class TitleCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(TitleCasePipe, value);
}
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
}
}
/**
* Transforms text to uppercase.
*
* @stable
*/
@Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
if (typeof value !== 'string') {
throw new InvalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -7,11 +7,14 @@
*/
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {DateFormatter} from '../facade/intl';
import {NumberWrapper, isDate} from '../facade/lang';
import {DateFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,184 @@
/**
* @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 {CommonModule} from '@angular/common';
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
import {Component, ComponentRef, Inject, Injector, NO_ERRORS_SCHEMA, NgModule, OpaqueToken, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('insert/remove', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
it('should do nothing if component is null', async(() => {
const template = `<template *ngComponentOutlet="currentComponent"></template>`;
TestBed.overrideComponent(TestComponent, {set: {template: template}});
let fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.currentComponent = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
}));
it('should insert content specified by a component', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
}));
it('should emit a ComponentRef once a component was created', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
expect(fixture.componentInstance.cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
}));
it('should clear view if component becomes null', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentComponent = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
}));
it('should swap content if component changes', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentComponent = InjectedComponentAgain;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
}));
it('should use the injector, if one supplied', async(() => {
let fixture = TestBed.createComponent(TestComponent);
const uniqueValue = {};
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.injector = ReflectiveInjector.resolveAndCreate(
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBe(uniqueValue);
}));
it('should resolve a with injector', async(() => {
let fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.cmpRef = null;
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.detectChanges();
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
expect(cmpRef.instance.testToken).toBeNull();
}));
it('should render projectable nodes, if supplied', async(() => {
const template = `<template>projected foo</template>${TEST_CMP_TEMPLATE}`;
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
TestBed
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
let fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentComponent = InjectedComponent;
fixture.componentInstance.projectables =
[fixture.componentInstance.vcRef
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
.rootNodes];
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('projected foo');
}));
});
}
const TEST_TOKEN = new OpaqueToken('TestToken');
@Component({selector: 'injected-component', template: 'foo'})
class InjectedComponent {
constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
}
@Component({selector: 'injected-component-again', template: 'bar'})
class InjectedComponentAgain {
}
const TEST_CMP_TEMPLATE =
`<template *ngComponentOutlet="currentComponent; injector: injector; content: projectables"></template>`;
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
class TestComponent {
currentComponent: Type<any>;
injector: Injector;
projectables: any[][];
get cmpRef(): ComponentRef<any> { return this.ngComponentOutlet.componentRef; }
set cmpRef(value: ComponentRef<any>) { this.ngComponentOutlet.componentRef = value; }
@ViewChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
@ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
constructor(public vcRef: ViewContainerRef) {}
}
@NgModule({
imports: [CommonModule],
declarations: [TestComponent, InjectedComponent, InjectedComponentAgain],
exports: [TestComponent, InjectedComponent, InjectedComponentAgain],
entryComponents: [InjectedComponent, InjectedComponentAgain]
})
export class TestModule {
}

View File

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

View File

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

View File

@ -33,11 +33,10 @@ export function main() {
});
it('should display the template according to the exact value', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>';
'</ul>';
fixture = createTestComponent(template);
@ -51,10 +50,9 @@ export function main() {
// https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul></div>';
'</ul>';
fixture = createTestComponent(template);
@ -64,11 +62,10 @@ export function main() {
it('should be applicable to <ng-container> elements', async(() => {
const template = '<div>' +
'<ng-container [ngPlural]="switchValue">' +
const template = '<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container></div>';
'</ng-container>';
fixture = createTestComponent(template);
@ -80,11 +77,10 @@ export function main() {
}));
it('should display the template according to the category', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>';
'</ul>';
fixture = createTestComponent(template);
@ -96,11 +92,10 @@ export function main() {
}));
it('should default to other when no matches are found', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>';
'</ul>';
fixture = createTestComponent(template);
@ -109,11 +104,10 @@ export function main() {
}));
it('should prioritize value matches over category matches', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
const template = '<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>';
'</ul>';
fixture = createTestComponent(template);

View File

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

View File

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

View File

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

View File

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

View File

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

@ -11,3 +11,7 @@ export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeComp
export {Extractor} from './src/extractor';
export * from '@angular/tsc-wrapped';
export {VERSION} from './src/version';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'

View File

@ -0,0 +1,3 @@
:host {
background-color: blue;
}

View File

@ -0,0 +1,5 @@
<div>
<h1 i18n>hello world</h1>
<a [routerLink]="['lazy']">lazy</a>
<router-outlet></router-outlet>
</div>

View File

@ -0,0 +1,19 @@
/**
* @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, ViewEncapsulation} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
}

View File

@ -0,0 +1,32 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouterModule} from '@angular/router';
import {AppComponent} from './app.component';
@Component({selector: 'home-view', template: 'home!'})
export class HomeView {
}
@NgModule({
declarations: [AppComponent, HomeView],
imports: [
BrowserModule, RouterModule.forRoot([
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
{path: 'feature2', loadChildren: 'feature2/feature2.module#Feature2Module'},
{path: '', component: HomeView}
])
],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,26 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'feature-component', template: 'foo.html'})
export class FeatureComponent {
}
@NgModule({
declarations: [FeatureComponent],
imports: [RouterModule.forChild([
{path: '', component: FeatureComponent}, {path: 'd', loadChildren: './default.module'} {
path: 'e',
loadChildren: 'feature/feature.module#FeatureModule'
}
])]
})
export class Feature2Module {
}

View File

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

View File

@ -0,0 +1,19 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
}
}

View File

@ -8,6 +8,10 @@
import {AUTO_STYLE, Component, animate, state, style, transition, trigger} from '@angular/core';
export function anyToAny(stateA: string, stateB: string): boolean {
return Math.random() != Math.random();
}
@Component({
selector: 'animate-cmp',
animations: [trigger(
@ -16,7 +20,7 @@ import {AUTO_STYLE, Component, animate, state, style, transition, trigger} from
state('*', style({height: AUTO_STYLE, color: 'black', borderColor: 'black'})),
state('closed, void', style({height: '0px', color: 'maroon', borderColor: 'maroon'})),
state('open', style({height: AUTO_STYLE, borderColor: 'green', color: 'green'})),
transition('* => *', animate(500))
transition(anyToAny, animate('1s')), transition('* => *', animate(500))
])],
template: `
<button (click)="setAsOpen()">Open</button>

View File

@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)>
]>
<messagebundle>
<msg id="3772663375917578720">other-3rdP-component</msg>
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
<msg id="3492007542396725315">Welcome</msg>
<msg id="3772663375917578720">other-3rdP-component</msg>
</messagebundle>
`;
@ -44,10 +44,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
<source>translate me</source>
<target/>
@ -58,6 +54,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<source>Welcome</source>
<target/>
</trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,217 @@
#!/usr/bin/env node
/**
* @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
*/
/* tslint:disable:no-console */
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
const glob = require('glob');
/**
* Main method.
* Standalone program that executes codegen using the ngtools API and tests that files were
* properly read and wrote.
*/
function main() {
console.log(`testing ngtools API...`);
Promise.resolve()
.then(() => codeGenTest())
.then(() => i18nTest())
.then(() => lazyRoutesTest())
.then(() => {
console.log('All done!');
process.exit(0);
})
.catch((err) => {
console.error(err.stack);
console.error('Test failed');
process.exit(1);
});
}
function codeGenTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const hostContext = new NodeCompilerHostContext();
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
console.log(`>>> running codegen for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.codeGen({
basePath,
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
// i18n options.
i18nFormat: null,
i18nFile: null,
locale: null,
readResource: (fileName: string) => {
readResources.push(fileName);
return hostContext.readResource(fileName);
}
})
.then(() => {
console.log(`>>> codegen done, asserting read and wrote files`);
// Assert for each file that it has been read and each `ts` has a written file associated.
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.module\.ts$/)) {
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
throw e;
});
}
function i18nTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const hostContext = new NodeCompilerHostContext();
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
console.log(`>>> running i18n extraction for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.extractI18n({
basePath,
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
i18nFormat: 'xlf',
readResource: (fileName: string) => {
readResources.push(fileName);
return hostContext.readResource(fileName);
},
})
.then(() => {
console.log(`>>> i18n extraction done, asserting read and wrote files`);
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
assert(
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
`Expected the bundle file to be "message.xlf".`);
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Extraction failed');
throw e;
});
}
function lazyRoutesTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const config = tsc.readConfiguration(project, basePath);
const host = ts.createCompilerHost(config.parsed.options, true);
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
program,
host,
angularCompilerOptions: config.ngOptions,
entryModule: 'app.module#AppModule'
});
const expectations: {[route: string]: string} = {
'./lazy.module#LazyModule': 'lazy.module.ts',
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
'./feature.module#FeatureModule': 'feature/feature.module.ts',
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
'./default.module': 'feature2/default.module.ts',
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
};
Object.keys(lazyRoutes).forEach((route: string) => {
assert(route in expectations, `Found a route that was not expected: "${route}".`);
assert(
lazyRoutes[route] == path.join(basePath, expectations[route]),
`Route "${route}" does not point to the expected absolute path ` +
`"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`);
});
// Verify that all expectations were met.
assert.deepEqual(
Object.keys(lazyRoutes), Object.keys(expectations), `Expected routes listed to be: \n` +
` ${JSON.stringify(Object.keys(expectations))}\n` +
`Actual:\n` +
` ${JSON.stringify(Object.keys(lazyRoutes))}\n`);
}
main();

View File

@ -27,9 +27,14 @@ function main() {
const basePath = path.resolve(__dirname, '..');
const project = path.resolve(basePath, 'tsconfig-build.json');
const readFiles: string[] = [];
const writtenFiles: {fileName: string, content: string}[] = [];
class AssertingHostContext extends NodeCompilerHostContext {
readFile(fileName: string): string {
if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName)) {
// Only allow to read summaries from node_modules
return null;
}
readFiles.push(path.relative(basePath, fileName));
return super.readFile(fileName);
}
@ -45,16 +50,29 @@ function main() {
config.ngOptions.generateCodeForLibraries = false;
console.log(`>>> running codegen for ${project}`);
codegen(config, (host) => new AssertingHostContext())
codegen(
config,
(host) => {
host.writeFile = (fileName: string, content: string) => {
fileName = path.relative(basePath, fileName);
writtenFiles.push({fileName, content});
};
return new AssertingHostContext();
})
.then((exitCode: any) => {
console.log(`>>> codegen done, asserting read files`);
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
console.log(`>>> asserting written files`);
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
console.log(`done, no errors.`);
process.exit(exitCode);
})
@ -97,4 +115,11 @@ function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
}
function assertWrittenFile(
files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) {
assert(
files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)),
`Expected some written files for ${filePattern} and content ${contentPattern}`);
}
main();

View File

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

View File

@ -14,13 +14,16 @@
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
"baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
},
"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",

View File

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

View File

@ -11,7 +11,6 @@
* 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';
@ -19,11 +18,8 @@ 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_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_META_FILES = /\.json$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular 2 template compiler.
@ -102,14 +98,8 @@ export class CodeGenerator {
debug: options.debug === true,
translations: transContent,
i18nFormat: cliOptions.i18nFormat,
locale: cliOptions.locale,
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
GENERATED_FILES
locale: cliOptions.locale
});
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
}
}
export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
}

View File

@ -15,7 +15,9 @@ import * as ts from 'typescript';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
export interface CompilerHostContext extends ts.ModuleResolutionHost {
readResource(fileName: string): Promise<string>;
@ -28,6 +30,7 @@ export class CompilerHost implements AotCompilerHost {
protected basePath: string;
private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>();
protected resolveModuleNameHost: CompilerHostContext;
constructor(
protected program: ts.Program, protected options: AngularCompilerOptions,
@ -38,12 +41,31 @@ export class CompilerHost implements AotCompilerHost {
const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
this.resolveModuleNameHost = Object.create(this.context);
// When calling ts.resolveModuleName,
// additional allow checks for .d.ts files to be done based on
// checks for .ngsummary.json files,
// so that our codegen depends on fewer inputs and requires to be called
// less often.
// This is needed as we use ts.resolveModuleName in reflector_host
// and it should be able to resolve summary file names.
this.resolveModuleNameHost.fileExists = (fileName: string): boolean => {
if (this.context.fileExists(fileName)) {
return true;
}
if (DTS.test(fileName)) {
const base = fileName.substring(0, fileName.length - 5);
return this.context.fileExists(base + '.ngsummary.json');
}
return false;
};
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string) {
moduleNameToFileName(m: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
@ -53,7 +75,8 @@ export class CompilerHost implements AotCompilerHost {
}
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
ts.resolveModuleName(
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
.resolvedModule;
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
};
@ -158,6 +181,12 @@ export class CompilerHost implements AotCompilerHost {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath);
} else {
// If there is a .d.ts file but no metadata file we need to produce a
// v3 metadata from the .d.ts file as v3 includes the exports we need
// to resolve symbols.
return [this.upgradeVersion1Metadata(
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
}
} else {
const sf = this.getSourceFile(filePath);
@ -173,32 +202,13 @@ export class CompilerHost implements AotCompilerHost {
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadatas = metadataOrMetadatas ?
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
const v1Metadata = metadatas.find((m: any) => m['version'] === 1);
let v2Metadata = metadatas.find((m: any) => m['version'] === 2);
if (!v2Metadata && v1Metadata) {
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
// as the only difference between the versions is whether all exports are contained in
// the metadata and the `extends` clause.
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
if (v1Metadata.exports) {
v2Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const sourceText = this.context.readFile(dtsFilePath);
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v2Metadata.metadata[prop]) {
v2Metadata.metadata[prop] = exports.metadata[prop];
}
}
}
metadatas.push(v2Metadata);
const v1Metadata = metadatas.find(m => m.version === 1);
let v3Metadata = metadatas.find(m => m.version === 3);
if (!v3Metadata && v1Metadata) {
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
@ -208,13 +218,49 @@ export class CompilerHost implements AotCompilerHost {
}
}
private upgradeVersion1Metadata(v1Metadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
// patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file
// as the only difference between the versions is whether all exports are contained in
// the metadata and the `extends` clause.
let v3Metadata: ModuleMetadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
if (v1Metadata.exports) {
v3Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v3Metadata.metadata[prop]) {
v3Metadata.metadata[prop] = exports.metadata[prop];
}
}
if (exports.exports) {
v3Metadata.exports = exports.exports;
}
}
return v3Metadata;
}
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
loadSummary(filePath: string): string { return this.context.readFile(filePath); }
loadSummary(filePath: string): string|null {
if (this.context.fileExists(filePath)) {
return this.context.readFile(filePath);
}
}
getOutputFileName(sourceFilePath: string): string {
return sourceFilePath.replace(EXT, '') + '.d.ts';
}
isSourceFile(filePath: string): boolean {
const excludeRegex =
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
return !excludeRegex.test(filePath);
}
}
export class CompilerHostContextAdapter {

View File

@ -14,41 +14,15 @@
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {Extractor} from './extractor';
function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) {
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
return (bundlePromise).then(messageBundle => {
let ext: string;
let serializer: compiler.Serializer;
const format = (cliOptions.i18nFormat || 'xlf').toLowerCase();
switch (format) {
case 'xmb':
ext = 'xmb';
serializer = new compiler.Xmb();
break;
case 'xliff':
case 'xlf':
default:
ext = 'xlf';
serializer = new compiler.Xliff();
break;
}
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
host.writeFile(dstPath, messageBundle.write(serializer), false);
});
program: ts.Program, host: ts.CompilerHost): Promise<void> {
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
}
// Entry point

View File

@ -15,29 +15,74 @@ import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {excludeFilePattern} from './codegen';
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export class Extractor {
constructor(
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
private program: ts.Program) {}
extract(): Promise<compiler.MessageBundle> {
return this.ngExtractor.extract(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
extract(formatName: string): Promise<void> {
// Checks the format and returns the extension
const ext = this.getExtension(formatName);
const promiseBundle = this.extractBundle();
return promiseBundle.then(bundle => {
const content = this.serialize(bundle, ext);
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
this.host.writeFile(dstPath, content, false);
});
}
extractBundle(): Promise<compiler.MessageBundle> {
const files = this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
return this.ngExtractor.extract(files);
}
serialize(bundle: compiler.MessageBundle, ext: string): string {
let serializer: compiler.Serializer;
switch (ext) {
case 'xmb':
serializer = new compiler.Xmb();
break;
case 'xlf':
default:
serializer = new compiler.Xliff();
}
return bundle.write(serializer);
}
getExtension(formatName: string): string {
const format = (formatName || 'xlf').toLowerCase();
if (format === 'xmb') return 'xmb';
if (format === 'xlf' || format === 'xlif') return 'xlf';
throw new Error('Unsupported format "${formatName}"');
}
static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost)
ngCompilerHost =
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
const {extractor: ngExtractor} = compiler.Extractor.create(
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
return new Extractor(ngExtractor, ngCompilerHost, program);
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
new CompilerHost(program, options, context);
}
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost);
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
}
}

View File

@ -14,6 +14,7 @@ import 'reflect-metadata';
import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import {SyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen';
function codegen(
@ -28,7 +29,7 @@ export function main(
const cliOptions = new tsc.NgcCliOptions(args);
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
if (e instanceof tsc.UserError) {
if (e instanceof tsc.UserError || e instanceof SyntaxError) {
consoleError(e.message);
return Promise.resolve(1);
} else {

View File

@ -0,0 +1,150 @@
/**
* @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
*/
/**
* This is a private API for the ngtools toolkit.
*
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
* something else.
*/
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CodeGenerator} from './codegen';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {Extractor} from './extractor';
import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string;
compilerOptions: ts.CompilerOptions;
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
// i18n options.
i18nFormat: string;
i18nFile: string;
locale: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.
}
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
entryModule: string;
// Every new property under this line should be optional.
}
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
basePath: string;
compilerOptions: ts.CompilerOptions;
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
i18nFormat: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.
}
/**
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
* passed in the interface.
*/
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
constructor(
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
super(host);
}
readResource(path: string) { return this._readResource(path); }
}
/**
* @internal
* @private
*/
export class NgTools_InternalApi_NG_2 {
/**
* @internal
* @private
*/
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<void> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
const cliOptions: NgcCliOptions = {
i18nFormat: options.i18nFormat,
i18nFile: options.i18nFile,
locale: options.locale,
basePath: options.basePath
};
// Create the Code Generator.
const codeGenerator = CodeGenerator.create(
options.angularCompilerOptions, cliOptions, options.program, options.host, hostContext);
return codeGenerator.codegen();
}
/**
* @internal
* @private
*/
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
NgTools_InternalApi_NG_2_LazyRouteMap {
const angularCompilerOptions = options.angularCompilerOptions;
const program = options.program;
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
const usePathMapping =
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
const ngCompilerHost: AotCompilerHost = usePathMapping ?
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
return Object.keys(routeMap).reduce(
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
acc[route] = routeMap[route].absoluteFilePath;
return acc;
},
{});
}
/**
* @internal
* @private
*/
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<void> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
// Create the i18n extractor.
const extractor = Extractor.create(
options.angularCompilerOptions, options.program, options.host, hostContext);
return extractor.extract(options.i18nFormat);
}
}

View File

@ -0,0 +1,211 @@
/**
* @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
*/
/**
* This is a private API for the ngtools toolkit.
*
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
* something else.
*/
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
import {NgModule} from '@angular/core';
// We cannot depend directly to @angular/router.
type Route = any;
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
// LazyRoute information between the extractors.
export interface LazyRoute {
routeDef: RouteDef;
absoluteFilePath: string;
}
export type LazyRouteMap = {
[route: string]: LazyRoute
};
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
// the user, and this is a helper class to extract information from it.
export class RouteDef {
private constructor(public readonly path: string, public readonly className: string = null) {}
toString() {
return (this.className === null || this.className == 'default') ?
this.path :
`${this.path}#${this.className}`;
}
static fromString(entry: string): RouteDef {
const split = entry.split('#');
return new RouteDef(split[0], split[1] || null);
}
}
/**
*
* @returns {LazyRouteMap}
* @private
*/
export function listLazyRoutesOfModule(
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
const entryRouteDef = RouteDef.fromString(entryModule);
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
const className = entryRouteDef.className;
// List loadChildren of this single module.
const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
const allLazyRoutes = lazyRoutes.reduce(
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
LazyRouteMap {
const route: string = lazyRoute.routeDef.toString();
_assertRoute(allRoutes, lazyRoute);
allRoutes[route] = lazyRoute;
// StaticReflector does not support discovering annotations like `NgModule` on default
// exports
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
// we cannot parse its routes to see if they have loadChildren or not.
if (!lazyRoute.routeDef.className) {
return allRoutes;
}
const lazyModuleSymbol = reflector.findDeclaration(
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
const subRoutes =
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
},
{});
return allLazyRoutes;
}
/**
* Try to resolve a module, and returns its absolute path.
* @private
*/
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
const result = host.moduleNameToFileName(modulePath, containingFile);
if (!result) {
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
}
return result;
}
/**
* Throw an exception if a route is in a route map, but does not point to the same module.
* @private
*/
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
const r = route.routeDef.toString();
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
throw new Error(
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
'load the proper one.');
}
}
/**
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
* module and all statically referred modules.
* @private
*/
function _extractLazyRoutesFromStaticModule(
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
ROUTES: StaticSymbol): LazyRoute[] {
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
const allRoutes: any =
(moduleMetadata.imports || [])
.filter(i => 'providers' in i)
.reduce((mem: Route[], m: any) => {
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
const lazyRoutes: LazyRoute[] =
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
const routeDef = RouteDef.fromString(route);
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
acc.push({routeDef, absoluteFilePath});
return acc;
}, []);
const importedSymbols = ((moduleMetadata.imports || []) as any[])
.filter(i => i instanceof StaticSymbol) as StaticSymbol[];
return importedSymbols
.reduce(
(acc: LazyRoute[], i: StaticSymbol) => {
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
},
[])
.concat(lazyRoutes);
}
/**
* Get the NgModule Metadata of a symbol.
* @private
*/
function _getNgModuleMetadata(staticSymbol: StaticSymbol, reflector: StaticReflector): NgModule {
const ngModules = reflector.annotations(staticSymbol).filter((s: any) => s instanceof NgModule);
if (ngModules.length === 0) {
throw new Error(`${staticSymbol.name} is not an NgModule`);
}
return ngModules[0];
}
/**
* Return the routes from the provider list.
* @private
*/
function _collectRoutes(
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
return providers.reduce((routeList: Route[], p: any) => {
if (p.provide === ROUTES) {
return routeList.concat(p.useValue);
} else if (Array.isArray(p)) {
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
} else {
return routeList;
}
}, []);
}
/**
* Return the loadChildren values of a list of Route.
* @private
*/
function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => {
if (r.loadChildren && typeof r.loadChildren === 'string') {
return m.concat(r.loadChildren);
} else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r));
} else if (r.children) {
return m.concat(_collectLoadChildren(r.children));
} else {
return m;
}
}, []);
}

View File

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

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ModuleMetadata} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
@ -66,11 +67,14 @@ describe('CompilerHost', () => {
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
'/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.shim.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.sass.ngstyle');
});
it('should import application from factory', () => {
@ -83,6 +87,12 @@ describe('CompilerHost', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other.css.shim');
});
});
@ -141,12 +151,14 @@ describe('CompilerHost', () => {
it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}}
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
dummyMetadata
]);
});
it('should be able to read empty metadata ', () => {
@ -157,23 +169,36 @@ describe('CompilerHost', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
});
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
it('should add missing v3 metadata from v1 metadata and .d.ts files', () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: 2,
version: 3,
metadata: {
foo: {__symbolic: 'class'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}
}
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
it('should upgrade a missing metadata file into v3', () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([
{__symbolic: 'module', version: 3, metadata: {}, exports: [{from: './lib/utils'}]}
]);
});
});
const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: 3,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
@ -193,7 +218,7 @@ const FILES: Entry = {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`,
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
@ -202,6 +227,11 @@ const FILES: Entry = {
},
'metadata_versions': {
'v1.d.ts': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export declare class Bar {
ngOnInit() {}
}
@ -209,6 +239,9 @@ const FILES: Entry = {
`,
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`
}
}
}

View File

@ -29,13 +29,17 @@ describe('compiler-cli', () => {
"types": [],
"outDir": "built",
"declaration": true,
"module": "es2015"
"module": "es2015",
"moduleResolution": "node"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
const nodeModulesPath = path.resolve(basePath, 'node_modules');
fs.mkdirSync(nodeModulesPath);
fs.symlinkSync(path.resolve(__dirname, '..', '..'), path.resolve(nodeModulesPath, '@angular'));
});
// Restore reflector since AoT compiler will update it with a new static reflector

View File

@ -122,11 +122,9 @@ 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 var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;

View File

@ -32,7 +32,9 @@ export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol';
export * from './src/aot/static_symbol_resolver';
export * from './src/aot/summary_resolver';
export * from './src/summary_resolver';
export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory';
export * from './src/url_resolver';
@ -60,4 +62,5 @@ export * from './src/style_compiler';
export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler';
export {AnimationParser} from './src/animation/animation_parser';
export {SyntaxError} from './src/util';
// This file only reexports content of the `src` folder. Keep it that way.

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '../aot/static_symbol';
export abstract class AnimationAst {
public startTime: number = 0;
@ -49,6 +50,10 @@ export class AnimationStateTransitionExpression {
constructor(public fromState: string, public toState: string) {}
}
export class AnimationStateTransitionFnExpression extends AnimationStateTransitionExpression {
constructor(public fn: Function|StaticSymbol) { super(null, null); }
}
export class AnimationStateTransitionAst extends AnimationStateAst {
constructor(
public stateChanges: AnimationStateTransitionExpression[],

View File

@ -12,7 +12,7 @@ import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionFnExpression, AnimationStepAst, AnimationStylesAst} from './animation_ast';
export class AnimationEntryCompileResult {
constructor(public name: string, public statements: o.Statement[], public fnExp: o.Expression) {}
@ -66,7 +66,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
ast.styles.forEach(entry => {
const entries =
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
stylesArr.push(o.literalMap(entries));
stylesArr.push(o.literalMap(entries, null, true));
});
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
@ -162,16 +162,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
const stateChangePreconditions: o.Expression[] = [];
ast.stateChanges.forEach(stateChange => {
stateChangePreconditions.push(
_compareToAnimationStateExpr(_ANIMATION_CURRENT_STATE_VAR, stateChange.fromState)
.and(_compareToAnimationStateExpr(_ANIMATION_NEXT_STATE_VAR, stateChange.toState)));
if (stateChange instanceof AnimationStateTransitionFnExpression) {
stateChangePreconditions.push(o.importExpr({reference: stateChange.fn}).callFn([
_ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR
]));
} else {
stateChangePreconditions.push(
_compareToAnimationStateExpr(_ANIMATION_CURRENT_STATE_VAR, stateChange.fromState)
.and(_compareToAnimationStateExpr(_ANIMATION_NEXT_STATE_VAR, stateChange.toState)));
if (stateChange.fromState != ANY_STATE) {
context.stateMap.registerState(stateChange.fromState);
}
if (stateChange.fromState != ANY_STATE) {
context.stateMap.registerState(stateChange.fromState);
}
if (stateChange.toState != ANY_STATE) {
context.stateMap.registerState(stateChange.toState);
if (stateChange.toState != ANY_STATE) {
context.stateMap.registerState(stateChange.toState);
}
}
});
@ -199,8 +205,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
'getAnimationPlayers',
[
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_FACTORY_ELEMENT_VAR,
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
.conditional(o.NULL_EXPR, o.literal(this.animationName))
]))
.toDeclStmt());
@ -293,8 +300,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
statements.push(new o.ReturnStatement(
o.importExpr(createIdentifier(Identifiers.AnimationTransition)).instantiate([
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR,
_ANIMATION_TIME_VAR
_ANIMATION_PLAYER_VAR, _ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR, _ANIMATION_TIME_VAR
])));
return o.fn(
@ -321,12 +328,13 @@ class _AnimationBuilder implements AnimationAstVisitor {
if (isPresent(value)) {
const styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
variableValue = o.literalMap(styleMap);
variableValue = o.literalMap(styleMap, null, true);
}
lookupMap.push([stateName, variableValue]);
});
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
const compiledStatesMapStmt =
this._statesMapVar.set(o.literalMap(lookupMap, null, true)).toDeclStmt();
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);

View File

@ -6,16 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import {StaticSymbol} from '../aot/static_symbol';
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStateTransitionFnExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
import {StylesCollection} from './styles_collection';
const _INITIAL_KEYFRAME = 0;
@ -35,7 +35,7 @@ export class AnimationEntryParseResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
}
@Injectable()
@CompilerInjectable()
export class AnimationParser {
constructor(private _schema: ElementSchemaRegistry) {}
@ -111,9 +111,12 @@ function _parseAnimationStateTransition(
errors: AnimationParseError[]): AnimationStateTransitionAst {
const styles = new StylesCollection();
const transitionExprs: AnimationStateTransitionExpression[] = [];
const transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
const stateChangeExpr = transitionStateMetadata.stateChangeExpr;
const transitionStates: Array<Function|StaticSymbol|string> = typeof stateChangeExpr == 'string' ?
(<string>stateChangeExpr).split(/\s*,\s*/) :
[<Function|StaticSymbol>stateChangeExpr];
transitionStates.forEach(
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
expr => transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)));
const entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
const animation = _normalizeStyleSteps(entry, stateStyles, schema, errors);
const animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
@ -142,25 +145,32 @@ function _parseAnimationAlias(alias: string, errors: AnimationParseError[]): str
}
function _parseAnimationTransitionExpr(
eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
transitionValue: string | Function | StaticSymbol,
errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
const expressions: AnimationStateTransitionExpression[] = [];
if (eventStr[0] == ':') {
eventStr = _parseAnimationAlias(eventStr, errors);
}
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
if (!isPresent(match) || match.length < 4) {
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
return expressions;
}
if (typeof transitionValue == 'string') {
let eventStr = <string>transitionValue;
if (eventStr[0] == ':') {
eventStr = _parseAnimationAlias(eventStr, errors);
}
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
if (!isPresent(match) || match.length < 4) {
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
return expressions;
}
const fromState = match[1];
const separator = match[2];
const toState = match[3];
expressions.push(new AnimationStateTransitionExpression(fromState, toState));
const fromState = match[1];
const separator = match[2];
const toState = match[3];
expressions.push(new AnimationStateTransitionExpression(fromState, toState));
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
if (separator[0] == '<' && !isFullAnyStateExpr) {
expressions.push(new AnimationStateTransitionExpression(toState, fromState));
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
if (separator[0] == '<' && !isFullAnyStateExpr) {
expressions.push(new AnimationStateTransitionExpression(toState, fromState));
}
} else {
expressions.push(
new AnimationStateTransitionFnExpression(<Function|StaticSymbol>transitionValue));
}
return expressions;
}
@ -174,6 +184,11 @@ function _normalizeStyleMetadata(
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
schema: ElementSchemaRegistry, errors: AnimationParseError[],
permitStateReferences: boolean): {[key: string]: string | number}[] {
const offset = entry.offset;
if (offset > 1 || offset < 0) {
errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`));
}
const normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') {

View File

@ -6,13 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, identifierName} from '../compile_metadata';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {ListWrapper} from '../facade/collection';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
@ -20,34 +17,35 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerOptions} from './compiler_options';
import {AotCompilerHost} from './compiler_host';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {AotSummaryResolver} from './summary_resolver';
import {filterFileByPatterns} from './utils';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {serializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName} from './util';
export class AotCompiler {
private _animationCompiler = new AnimationCompiler();
constructor(
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: AotSummaryResolver, private _localeId: string,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
private _translationFormat: string, private _animationParser: AnimationParser,
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {}
private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -56,27 +54,22 @@ export class AotCompiler {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules));
file.ngModules, file.injectables));
return ListWrapper.flatten(sourceModules);
});
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
injectables: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = [];
const exportedVars: string[] = [];
const generatedFiles: GeneratedFile[] = [];
// write summary files
const summaries: CompileTypeSummary[] = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref))
];
generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries));
generatedFiles.push(this._createSummary(
srcFileUrl, directives, pipes, ngModules, injectables, statements, exportedVars));
// compile all ng modules
exportedVars.push(
@ -115,12 +108,34 @@ export class AotCompiler {
});
if (statements.length > 0) {
const srcModule = this._codegenSourceModule(
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
srcFileUrl, ngfactoryFilePath(srcFileUrl), statements, exportedVars);
generatedFiles.unshift(srcModule);
}
return generatedFiles;
}
private _createSummary(
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[], injectables: StaticSymbol[], targetStatements: o.Statement[],
targetExportedVars: string[]): GeneratedFile {
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl)
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
const typeSummaries = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)),
...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref))
];
const {json, exportAs} = serializeSummaries(
this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
exportAs.forEach((entry) => {
targetStatements.push(
o.variable(entry.exportAs).set(o.importExpr({reference: entry.symbol})).toDeclStmt());
targetExportedVars.push(entry.exportAs);
});
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
const providers: CompileProviderMetadata[] = [];
@ -140,12 +155,6 @@ export class AotCompiler {
}
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
appCompileResult.dependencies.forEach((dep) => {
dep.placeholder.reference = this._staticReflector.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
});
targetStatements.push(...appCompileResult.statements);
return appCompileResult.ngModuleFactoryVar;
}
@ -162,13 +171,12 @@ export class AotCompiler {
private _compileComponentFactory(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string {
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
const hostMeta = createHostComponentMeta(
this._staticReflector.getStaticSymbol(
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
compMeta);
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
const hostViewFactoryVar = this._compileComponent(
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
const compFactoryVar = _componentFactoryName(compMeta.type);
const compFactoryVar = componentFactoryName(compMeta.type.reference);
targetStatements.push(
o.variable(compFactoryVar)
.set(o.importExpr(
@ -206,16 +214,16 @@ export class AotCompiler {
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
if (componentStyles) {
targetStatements.push(
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix));
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
}
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult));
targetStatements.push(...viewResult.statements);
return viewResult.viewClassVar;
}
private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix);
_resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
return this._codegenSourceModule(
fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
@ -231,29 +239,8 @@ export class AotCompiler {
}
}
function _resolveViewStatements(
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
if (dep instanceof ViewClassDependency) {
const vfd = <ViewClassDependency>dep;
vfd.placeholder.reference =
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(vfd.comp)), dep.name);
} else if (dep instanceof ComponentFactoryDependency) {
const cfd = <ComponentFactoryDependency>dep;
cfd.placeholder.reference = reflector.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(cfd.comp)), _componentFactoryName(cfd.comp));
} else if (dep instanceof DirectiveWrapperDependency) {
const dwd = <DirectiveWrapperDependency>dep;
dwd.placeholder.reference =
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(dwd.dir)), dwd.name);
}
});
return compileResult.statements;
}
function _resolveStyleStatements(
reflector: StaticReflector, compileResult: CompiledStylesheet,
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
fileSuffix: string): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
@ -262,17 +249,8 @@ function _resolveStyleStatements(
return compileResult.statements;
}
function _ngfactoryModuleUrl(dirUrl: string): string {
const urlWithSuffix = _splitTypescriptSuffix(dirUrl);
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
}
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
return `${identifierName(comp)}NgFactory`;
}
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`;
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
}
function _assertComponent(meta: CompileDirectiveMetadata) {
@ -282,20 +260,6 @@ function _assertComponent(meta: CompileDirectiveMetadata) {
}
}
function _splitTypescriptSuffix(path: string): string[] {
if (path.endsWith('.d.ts')) {
return [path.slice(0, -5), '.ts'];
}
const lastDot = path.lastIndexOf('.');
if (lastDot !== -1) {
return [path.substring(0, lastDot), path.substring(lastDot)];
}
return [path, ''];
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
@ -303,26 +267,27 @@ export interface NgAnalyzedModules {
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[]
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}>;
symbolsMissingModule?: StaticSymbol[];
}
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
_createNgModules(programStaticSymbols, host, metadataResolver);
return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
const result = analyzeNgModules(programStaticSymbols, host, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
@ -332,16 +297,27 @@ export function analyzeAndValidateNgModules(
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[],
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const ngPipesByFile = new Map<string, StaticSymbol[]>();
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Make sure we produce an analyzed file for each input file
programSymbols.forEach((symbol) => {
const filePath = symbol.filePath;
filePaths.add(filePath);
if (metadataResolver.isInjectable(symbol)) {
ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol));
}
});
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
@ -369,17 +345,20 @@ function _analyzeNgModules(
});
});
const files:
{srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[]}[] = [];
const files: {
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const pipes = ngPipesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules});
const injectables = ngInjectablesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules, injectables});
});
return {
@ -392,29 +371,20 @@ function _analyzeNgModules(
}
export function extractProgramSymbols(
staticReflector: StaticReflector, files: string[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
staticSymbolResolver: StaticSymbolResolver, files: string[],
host: NgAnalyzeModulesHost): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = [];
files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => {
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
if (!moduleMetadata) {
console.error(`WARNING: no metadata found for ${sourceFile}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
const symbolMeta = resolvedSymbol.metadata;
if (symbolMeta) {
if (symbolMeta.__symbolic != 'error') {
// Ignore symbols that are only included to record error information.
staticSymbols.push(resolvedSymbol.symbol);
}
}
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
}
});
});
return staticSymbols;
@ -424,8 +394,7 @@ export function extractProgramSymbols(
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -433,7 +402,7 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) {
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
return false;
}
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);

View File

@ -34,8 +34,12 @@ import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {AotSummaryResolver} from './summary_resolver';
/**
* Creates a new AotCompiler based on options and a host.
*/
@ -44,7 +48,10 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(compilerHost);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({
@ -60,17 +67,22 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const console = new Console();
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
staticReflector);
symbolCache, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const importResolver = {
getImportAs: (symbol: StaticSymbol) => symbolResolver.getImportAs(symbol),
fileNameToModuleName: (fileName: string, containingFilePath: string) =>
compilerHost.fileNameToModuleName(fileName, containingFilePath)
};
const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver),
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
new NgModuleCompiler(), new TypeScriptEmitter(importResolver), summaryResolver,
options.locale, options.i18nFormat, new AnimationParser(elementSchemaRegistry),
symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -6,18 +6,23 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolverHost} from './static_symbol_resolver';
import {AotSummaryResolverHost} from './summary_resolver';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver,
AotSummaryResolverHost {
export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*
* See ImportResolver.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
/*|null*/;
/**
* Loads a resource (e.g. html / css)
*/

View File

@ -11,6 +11,4 @@ export interface AotCompilerOptions {
locale?: string;
i18nFormat?: string;
translations?: string;
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}

View File

@ -8,6 +8,7 @@
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) {
@ -42,7 +43,7 @@ export class StaticAndDynamicReflectionCapabilities {
method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
resolveIdentifier(name: string, moduleUrl: string, runtime: any) {
return this.staticDelegate.resolveIdentifier(name, moduleUrl, runtime);
return this.staticDelegate.resolveIdentifier(name, moduleUrl);
}
resolveEnum(enumIdentifier: any, name: string): any {
if (isStaticType(enumIdentifier)) {

View File

@ -7,10 +7,12 @@
*/
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
const SUPPORTED_SCHEMA_VERSION = 2;
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
@ -20,66 +22,22 @@ const ANGULAR_IMPORT_LOCATIONS = {
provider: '@angular/core/src/di/provider'
};
/**
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface StaticReflectorHost {
/**
* Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string;
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}
const HIDDEN_KEY = /^\$.*\$$/;
/**
* A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol;
constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) {
@ -92,12 +50,26 @@ export class StaticReflector implements ReflectorReader {
}
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.findDeclaration(moduleUrl, name, '');
resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
return this.findDeclaration(moduleUrl, name);
}
findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
return this.findSymbolDeclaration(
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
}
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
return this.findSymbolDeclaration(resolvedSymbol.metadata);
} else {
return symbol;
}
}
resolveEnum(enumIdentifier: any, name: string): any {
@ -111,8 +83,11 @@ export class StaticReflector implements ReflectorReader {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
if (classMetadata['extends']) {
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
annotations.push(...parentAnnotations);
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType && (parentType instanceof StaticSymbol)) {
const parentAnnotations = this.annotations(parentType);
annotations.push(...parentAnnotations);
}
}
if (classMetadata['decorators']) {
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
@ -126,13 +101,16 @@ export class StaticReflector implements ReflectorReader {
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
if (classMetadata['extends']) {
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentPropMetadata = this.propMetadata(parentType);
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
}
}
const members = classMetadata['members'] || {};
@ -184,7 +162,10 @@ export class StaticReflector implements ReflectorReader {
parameters.push(nestedResult);
});
} else if (classMetadata['extends']) {
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
parameters = this.parameters(parentType);
}
}
if (!parameters) {
parameters = [];
@ -201,13 +182,16 @@ export class StaticReflector implements ReflectorReader {
private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
if (classMetadata['extends']) {
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
const parentType = this.simplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) {
const parentMethodNames = this._methodNames(parentType);
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
}
}
const members = classMetadata['members'] || {};
@ -304,7 +288,7 @@ export class StaticReflector implements ReflectorReader {
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
@ -315,96 +299,6 @@ export class StaticReflector implements ReflectorReader {
}
}
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
this.reportError(
new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`),
null, filePath);
}
return resolvedModulePath;
};
const cacheKey = `${filePath}|${symbolName}`;
let staticSymbol = this.declarationCache.get(cacheKey);
if (staticSymbol) {
return staticSymbol;
}
const metadata = this.getModuleMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata['metadata'][symbolName]) {
staticSymbol = this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (!staticSymbol && metadata['exports']) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata['exports']) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find((symbol: any) => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
staticSymbol =
this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
break;
}
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) {
staticSymbol = candidateSymbol;
break;
}
}
}
}
}
}
}
this.declarationCache.set(cacheKey, staticSymbol);
return staticSymbol;
}
findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol {
try {
const filePath = this.host.moduleNameToFileName(module, containingFile);
let symbol: StaticSymbol;
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
symbol = this.getStaticSymbol(module, symbolName);
} else {
symbol = this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
return symbol;
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
@ -412,93 +306,42 @@ export class StaticReflector implements ReflectorReader {
const calling = new Map<StaticSymbol, boolean>();
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol;
if (expression['module']) {
staticSymbol =
self.findDeclaration(expression['module'], expression['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
}
return staticSymbol;
}
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
const declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue;
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
return resolvedSymbol ? resolvedSymbol.metadata : null;
}
function isOpaqueToken(context: StaticSymbol, value: any): boolean {
if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression;
if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), self.opaqueToken);
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
}
return false;
}
function simplifyCall(expression: any) {
let callContext: {[name: string]: string}|undefined = undefined;
if (expression['__symbolic'] == 'call') {
const target = expression['expression'];
let functionSymbol: StaticSymbol;
let targetFunction: any;
if (target) {
switch (target.__symbolic) {
case 'reference':
// Find the function to call.
callContext = {name: target.name};
functionSymbol = resolveReference(context, target);
targetFunction = resolveReferenceValue(functionSymbol);
break;
case 'select':
// Find the static method to call
if (target.expression.__symbolic == 'reference') {
functionSymbol = resolveReference(context, target.expression);
const classData = resolveReferenceValue(functionSymbol);
if (classData && classData.statics) {
targetFunction = classData.statics[target.member];
}
}
break;
}
}
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
// Determine the arguments
const args: any[] =
(expression['arguments'] || []).map((arg: any) => simplify(arg));
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
} finally {
calling.delete(functionSymbol);
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
}
} finally {
calling.delete(functionSymbol);
}
}
@ -509,7 +352,7 @@ export class StaticReflector implements ReflectorReader {
return {__symbolic: 'ignore'};
}
return simplify(
{__symbolic: 'error', message: 'Function call not supported', context: callContext});
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
}
function simplify(expression: any): any {
@ -538,7 +381,18 @@ export class StaticReflector implements ReflectorReader {
return result;
}
if (expression instanceof StaticSymbol) {
return expression;
// Stop simplification at builtin symbols
if (expression === self.opaqueToken || self.conversionMap.has(expression)) {
return expression;
} else {
const staticSymbol = expression;
const declarationValue = resolveReferenceValue(staticSymbol);
if (declarationValue) {
return simplifyInContext(staticSymbol, declarationValue, depth + 1);
} else {
return staticSymbol;
}
}
}
if (expression) {
if (expression['__symbolic']) {
@ -616,47 +470,33 @@ export class StaticReflector implements ReflectorReader {
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case 'select':
const member = expression['member'];
let selectContext = context;
let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) {
// Access to a static instance variable
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
const declarationValue = resolveReferenceValue(selectTarget);
const members = selectTarget.members.concat(member);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
if (declarationValue && declarationValue.statics) {
selectTarget = declarationValue.statics;
const declarationValue = resolveReferenceValue(selectContext);
if (declarationValue) {
return simplifyInContext(selectContext, declarationValue, depth + 1);
} else {
return selectContext;
}
}
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
return null;
case 'reference':
if (!expression.module) {
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
// Note: This only has to deal with variable references,
// as symbol references have been converted into StaticSymbols already
// in the StaticSymbolResolver!
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
staticSymbol = resolveReference(context, expression);
let result: any = staticSymbol;
let declarationValue = resolveReferenceValue(result);
if (declarationValue) {
if (isOpaqueToken(staticSymbol, declarationValue)) {
// If the referenced symbol is initalized by a new OpaqueToken we can keep the
// reference to the symbol.
return staticSymbol;
}
result = simplifyInContext(staticSymbol, declarationValue, depth + 1);
}
return result;
break;
case 'class':
return context;
case 'function':
@ -664,26 +504,26 @@ export class StaticReflector implements ReflectorReader {
case 'new':
case 'call':
// Determine if the function is a built-in conversion
let target = expression['expression'];
if (target['module']) {
staticSymbol =
self.findDeclaration(target['module'], target['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
}
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
let args: any[] = expression['arguments'];
if (!args) {
args = [];
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.opaqueToken) {
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
// but rather return the symbol to which the OpaqueToken is assigned to.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, argExpressions);
}
return converter(
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
}
// Determine if the function is one we can simplify.
return simplifyCall(expression);
break;
case 'error':
let message = produceErrorMessage(expression);
if (expression['line']) {
@ -704,7 +544,9 @@ export class StaticReflector implements ReflectorReader {
try {
return simplify(value);
} catch (e) {
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`;
const members = context.members.length ? `.${context.members.join('.')}` : '';
const message =
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column);
}
@ -728,40 +570,10 @@ export class StaticReflector implements ReflectorReader {
return result;
}
/**
* @param module an absolute path to a module file.
*/
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
this.reportError(
new Error(
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`),
null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const moduleMetadata = this.getModuleMetadata(type.filePath);
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
}
}
@ -806,7 +618,11 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
Object.keys(input).forEach((key) => {
const value = transform(input[key], key);
if (!shouldIgnore(value)) {
result[key] = value;
if (HIDDEN_KEY.test(key)) {
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
} else {
result[key] = value;
}
}
});
return result;
@ -848,10 +664,6 @@ class PopulatedScope extends BindingScope {
}
}
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b || (a.name == b.name && a.filePath == b.filePath);
}
function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
}

View File

@ -12,5 +12,32 @@
* This token is unique for a filePath and name and can be used as a hash table key.
*/
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {}
constructor(public filePath: string, public name: string, public members: string[]) {}
assertNoMembers() {
if (this.members.length) {
throw new Error(
`Illegal state: symbol without members expected, but got ${JSON.stringify(this)}.`);
}
}
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
members = members || [];
const memberSuffix = members.length ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}

View File

@ -0,0 +1,343 @@
/**
* @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 {SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
export class ResolvedStaticSymbol {
constructor(public symbol: StaticSymbol, public metadata: any) {}
}
/**
* The host of the SymbolResolverHost disconnects the implementation from TypeScript / other
* language
* services and from underlying file systems.
*/
export interface StaticSymbolResolverHost {
/**
* Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/;
}
const SUPPORTED_SCHEMA_VERSION = 3;
/**
* This class is responsible for loading metadata per symbol,
* and normalizing references between symbols.
*
* Internally, it only uses symbols without members,
* and deduces the values for symbols with members based
* on these symbols.
*/
export class StaticSymbolResolver {
private metadataCache = new Map<string, {[key: string]: any}>();
// Note: this will only contain StaticSymbols without members!
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
private resolvedFilePaths = new Set<string>();
// Note: this will only contain StaticSymbols without members!
private importAs = new Map<StaticSymbol, StaticSymbol>();
constructor(
private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
private summaryResolver: SummaryResolver<StaticSymbol>,
private errorRecorder?: (error: any, fileName: string) => void) {}
resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
if (staticSymbol.members.length > 0) {
return this._resolveSymbolMembers(staticSymbol);
}
let result = this.resolvedSymbols.get(staticSymbol);
if (result) {
return result;
}
result = this._resolveSymbolFromSummary(staticSymbol);
if (result) {
return result;
}
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
this._createSymbolsOf(staticSymbol.filePath);
result = this.resolvedSymbols.get(staticSymbol);
return result;
}
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
if (staticSymbol.members.length) {
const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name);
const baseImportAs = this.getImportAs(baseSymbol);
return baseImportAs ?
this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) :
null;
}
let result = this.summaryResolver.getImportAs(staticSymbol);
if (!result) {
result = this.importAs.get(staticSymbol);
}
return result;
}
private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const members = staticSymbol.members;
const baseResolvedSymbol =
this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name));
if (!baseResolvedSymbol) {
return null;
}
const baseMetadata = baseResolvedSymbol.metadata;
if (baseMetadata instanceof StaticSymbol) {
return new ResolvedStaticSymbol(
staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
} else if (baseMetadata && baseMetadata.__symbolic === 'class') {
if (baseMetadata.statics && members.length === 1) {
return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]);
}
} else {
let value = baseMetadata;
for (let i = 0; i < members.length && value; i++) {
value = value[members[i]];
}
return new ResolvedStaticSymbol(staticSymbol, value);
}
return null;
}
private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const summary = this.summaryResolver.resolveSummary(staticSymbol);
return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null;
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
getSymbolsOf(filePath: string): StaticSymbol[] {
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
let symbols = new Set<StaticSymbol>(this.summaryResolver.getSymbolsOf(filePath));
this._createSymbolsOf(filePath);
this.resolvedSymbols.forEach((resolvedSymbol) => {
if (resolvedSymbol.symbol.filePath === filePath) {
symbols.add(resolvedSymbol.symbol);
}
});
return Array.from(symbols);
}
private _createSymbolsOf(filePath: string) {
if (this.resolvedFilePaths.has(filePath)) {
return;
}
this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = [];
const metadata = this.getModuleMetadata(filePath);
if (metadata['metadata']) {
// handle direct declarations of the symbol
const topLevelSymbolNames =
new Set<string>(Object.keys(metadata['metadata']).map(unescapeIdentifier));
Object.keys(metadata['metadata']).forEach((metadataKey) => {
const symbolMeta = metadata['metadata'][metadataKey];
resolvedSymbols.push(this.createResolvedSymbol(
this.getStaticSymbol(filePath, unescapeIdentifier(metadataKey)), topLevelSymbolNames,
symbolMeta));
});
}
// handle the symbols in one of the re-export location
if (metadata['exports']) {
for (const moduleExport of metadata['exports']) {
// handle the symbols in the list of explicitly re-exported symbols.
if (moduleExport.export) {
moduleExport.export.forEach((exportSymbol: any) => {
let symbolName: string;
if (typeof exportSymbol === 'string') {
symbolName = exportSymbol;
} else {
symbolName = exportSymbol.as;
}
symbolName = unescapeIdentifier(symbolName);
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = unescapeIdentifier(exportSymbol.name);
}
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
resolvedSymbols.push(this.createExport(sourceSymbol, targetSymbol));
}
});
} else {
// handle the symbols via export * directives.
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const nestedExports = this.getSymbolsOf(resolvedModule);
nestedExports.forEach((targetSymbol) => {
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
resolvedSymbols.push(this.createExport(sourceSymbol, targetSymbol));
});
}
}
}
}
resolvedSymbols.forEach(
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
}
private createResolvedSymbol(
sourceSymbol: StaticSymbol, topLevelSymbolNames: Set<string>,
metadata: any): ResolvedStaticSymbol {
const self = this;
class ReferenceTransformer extends ValueTransformer {
visitStringMap(map: {[key: string]: any}, functionParams: string[]): any {
const symbolic = map['__symbolic'];
if (symbolic === 'function') {
const oldLen = functionParams.length;
functionParams.push(...(map['parameters'] || []));
const result = super.visitStringMap(map, functionParams);
functionParams.length = oldLen;
return result;
} else if (symbolic === 'reference') {
const module = map['module'];
const name = map['name'] ? unescapeIdentifier(map['name']) : map['name'];
if (!name) {
return null;
}
let filePath: string;
if (module) {
filePath = self.resolveModule(module, sourceSymbol.filePath);
if (!filePath) {
return {
__symbolic: 'error',
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
};
}
return self.getStaticSymbol(filePath, name);
} else if (functionParams.indexOf(name) >= 0) {
// reference to a function parameter
return {__symbolic: 'reference', name: name};
} else {
if (topLevelSymbolNames.has(name)) {
return self.getStaticSymbol(sourceSymbol.filePath, name);
}
// ambient value
null;
}
} else {
return super.visitStringMap(map, functionParams);
}
}
}
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
if (transformedMeta instanceof StaticSymbol) {
return this.createExport(sourceSymbol, transformedMeta);
}
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
}
private createExport(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol):
ResolvedStaticSymbol {
sourceSymbol.assertNoMembers();
targetSymbol.assertNoMembers();
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath)) {
// This case is for an ng library importing symbols from a plain ts library
// transitively.
// Note: We rely on the fact that we discover symbols in the direction
// from source files to library files
this.importAs.set(targetSymbol, this.getImportAs(sourceSymbol) || sourceSymbol);
}
return new ResolvedStaticSymbol(sourceSymbol, targetSymbol);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(error, (context && context.filePath) || path);
} else {
throw error;
}
}
/**
* @param module an absolute path to a module file.
*/
private getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
const filePath = this.resolveModule(module, containingFile);
if (!filePath) {
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
}
return this.getStaticSymbol(filePath, symbolName);
}
private resolveModule(module: string, containingFile: string): string {
try {
return this.host.moduleNameToFileName(module, containingFile);
} catch (e) {
console.error(`Could not resolve module '${module}' relative to file ${containingFile}`);
this.reportError(new e, null, containingFile);
}
}
}
// Remove extra underscore from escaped identifier.
// See https://github.com/Microsoft/TypeScript/blob/master/src/compiler/utilities.ts
export function unescapeIdentifier(identifier: string): string {
return identifier.startsWith('___') ? identifier.substr(1) : identifier;
}

View File

@ -5,22 +5,23 @@
* 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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {filterFileByPatterns} from './utils';
import {Summary, SummaryResolver} from '../summary_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {deserializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, stripNgFactory, summaryFileName} from './util';
export interface AotSummaryResolverHost {
/**
* Loads an NgModule/Directive/Pipe summary file
*/
loadSummary(filePath: string): string;
loadSummary(filePath: string): string /*|null*/;
/**
* Returns whether a file is a source file or not.
*/
isSourceFile(sourceFilePath: string): boolean;
/**
* Returns the output file path of a source file.
* E.g.
@ -29,96 +30,67 @@ export interface AotSummaryResolverHost {
getOutputFileName(sourceFilePath: string): string;
}
export interface AotSummaryResolverOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
// Note: this will only contain StaticSymbols without members!
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
private loadedFilePaths = new Set<string>();
// Note: this will only contain StaticSymbols without members!
private importAs = new Map<StaticSymbol, StaticSymbol>();
export class AotSummaryResolver implements SummaryResolver {
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
constructor(
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
private options: AotSummaryResolverOptions) {}
serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile {
const jsonReplacer = (key: string, value: any) => {
if (value instanceof StaticSymbol) {
// We convert the source filenames into output filenames,
// as the generated summary file will be used when the current
// compilation unit is used as a library
return {
'__symbolic__': 'symbol',
'name': value.name,
'path': this.host.getOutputFileName(value.filePath),
'members': value.members
};
}
return value;
};
const allSummaries = summaries.slice();
summaries.forEach((summary) => {
if (summary.summaryKind === CompileSummaryKind.NgModule) {
const moduleMeta = <CompileNgModuleSummary>summary;
moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => {
if (!filterFileByPatterns(id.reference.filePath, this.options)) {
allSummaries.push(this.resolveSummary(id.reference));
}
});
}
});
return new GeneratedFile(
srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer));
isLibraryFile(filePath: string): boolean {
// Note: We need to strip the .ngfactory. file path,
// so this method also works for generated files
// (for which host.isSourceFile will always return false).
return !this.host.isSourceFile(stripNgFactory(filePath));
}
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
getLibraryFileName(filePath: string) { return this.host.getOutputFileName(filePath); }
resolveSummary(staticSymbol: StaticSymbol): any {
const filePath = staticSymbol.filePath;
const name = staticSymbol.name;
const cacheKey = this._cacheKey(staticSymbol);
if (!filterFileByPatterns(filePath, this.options)) {
let summary = this.summaryCache[cacheKey];
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
staticSymbol.assertNoMembers();
let summary = this.summaryCache.get(staticSymbol);
if (!summary) {
this._loadSummaryFile(staticSymbol.filePath);
summary = this.summaryCache.get(staticSymbol);
}
return summary;
}
getSymbolsOf(filePath: string): StaticSymbol[] {
this._loadSummaryFile(filePath);
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
}
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
staticSymbol.assertNoMembers();
return this.importAs.get(staticSymbol);
}
private _loadSummaryFile(filePath: string) {
if (this.loadedFilePaths.has(filePath)) {
return;
}
this.loadedFilePaths.add(filePath);
if (this.isLibraryFile(filePath)) {
const summaryFilePath = summaryFileName(filePath);
if (!summary) {
try {
const jsonReviver = (key: string, value: any) => {
if (value && value['__symbolic__'] === 'symbol') {
// Note: We can't use staticReflector.findDeclaration here:
// Summary files can contain symbols of transitive compilation units
// (via the providers), and findDeclaration needs .metadata.json / .d.ts files,
// but we don't want to depend on these for transitive dependencies.
return this.staticReflector.getStaticSymbol(
value['path'], value['name'], value['members']);
} else {
return value;
}
};
const readSummaries: CompileTypeSummary[] =
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
readSummaries.forEach((summary) => {
const filePath = summary.type.reference.filePath;
this.summaryCache[this._cacheKey(summary.type.reference)] = summary;
});
summary = this.summaryCache[cacheKey];
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
let json: string;
try {
json = this.host.loadSummary(summaryFilePath);
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
if (!summary) {
throw new Error(
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
if (json) {
const {summaries, importAs} = deserializeSummaries(this.staticSymbolCache, json);
summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary));
importAs.forEach((importAs) => {
this.importAs.set(
importAs.symbol,
this.staticSymbolCache.get(ngfactoryFilePath(filePath), importAs.importAs));
});
}
return summary;
} else {
return null;
}
}
}
function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}

View File

@ -0,0 +1,192 @@
/**
* @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 {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
export function serializeSummaries(
summaryResolver: SummaryResolver<StaticSymbol>, symbolResolver: StaticSymbolResolver,
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]):
{json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const serializer = new Serializer(symbolResolver, summaryResolver);
// for symbols, we use everything except for the class metadata itself
// (we keep the statics though), as the class metadata is contained in the
// CompileTypeSummary.
symbols.forEach(
(resolvedSymbol) => serializer.addOrMergeSummary(
{symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}));
// Add summaries that are referenced by the given symbols (transitively)
// Note: the serializer.symbols array might be growing while
// we execute the loop!
for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) {
const symbol = serializer.symbols[processedIndex];
if (summaryResolver.isLibraryFile(symbol.filePath)) {
let summary = summaryResolver.resolveSummary(symbol);
if (!summary) {
// some symbols might originate from a plain typescript library
// that just exported .d.ts and .metadata.json files, i.e. where no summary
// files were created.
const resolvedSymbol = symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol) {
summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata};
}
}
if (summary) {
serializer.addOrMergeSummary(summary);
}
}
}
// Add type summaries.
// Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols,
// as the type summaries already contain the transitive data that they require
// (in a minimal way).
types.forEach((typeSummary) => {
serializer.addOrMergeSummary(
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
const symbol: StaticSymbol = id.reference;
if (summaryResolver.isLibraryFile(symbol.filePath)) {
const summary = summaryResolver.resolveSummary(symbol);
if (summary) {
serializer.addOrMergeSummary(summary);
}
}
});
}
});
return serializer.serialize();
}
export function deserializeSummaries(symbolCache: StaticSymbolCache, json: string):
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
const deserializer = new Deserializer(symbolCache);
return deserializer.deserialize(json);
}
class Serializer extends ValueTransformer {
// Note: This only contains symbols without members.
symbols: StaticSymbol[] = [];
private indexBySymbol = new Map<StaticSymbol, number>();
// This now contains a `__symbol: number` in the place of
// StaticSymbols, but otherwise has the same shape as the original objects.
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
private processedSummaries: any[] = [];
constructor(
private symbolResolver: StaticSymbolResolver,
private summaryResolver: SummaryResolver<StaticSymbol>) {
super();
}
addOrMergeSummary(summary: Summary<StaticSymbol>) {
let symbolMeta = summary.metadata;
if (symbolMeta && symbolMeta.__symbolic === 'class') {
// For classes, we only keep their statics, but not the metadata
// of the class itself as that has been captured already via other summaries
// (e.g. DirectiveSummary, ...).
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics};
}
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
if (!processedSummary) {
processedSummary = this.processValue({symbol: summary.symbol});
this.processedSummaries.push(processedSummary);
this.processedSummaryBySymbol.set(summary.symbol, processedSummary);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.metadata == null && symbolMeta != null) {
processedSummary.metadata = this.processValue(symbolMeta);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.type == null && summary.type != null) {
processedSummary.type = this.processValue(summary.type);
}
}
serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
const exportAs: {symbol: StaticSymbol, exportAs: string}[] = [];
const json = JSON.stringify({
summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => {
symbol.assertNoMembers();
let importAs: string;
if (this.summaryResolver.isLibraryFile(symbol.filePath)) {
importAs = `${symbol.name}_${index}`;
exportAs.push({symbol, exportAs: importAs});
}
return {
__symbol: index,
name: symbol.name,
// We convert the source filenames tinto output filenames,
// as the generated summary file will be used when teh current
// compilation unit is used as a library
filePath: this.summaryResolver.getLibraryFileName(symbol.filePath),
importAs: importAs
};
})
});
return {json, exportAs};
}
private processValue(value: any): any { return visitValue(value, this, null); }
visitOther(value: any, context: any): any {
if (value instanceof StaticSymbol) {
const baseSymbol = this.symbolResolver.getStaticSymbol(value.filePath, value.name);
let index = this.indexBySymbol.get(baseSymbol);
// Note: == by purpose to compare with undefined!
if (index == null) {
index = this.indexBySymbol.size;
this.indexBySymbol.set(baseSymbol, index);
this.symbols.push(baseSymbol);
}
return {__symbol: index, members: value.members};
}
}
}
class Deserializer extends ValueTransformer {
private symbols: StaticSymbol[];
constructor(private symbolCache: StaticSymbolCache) { super(); }
deserialize(json: string):
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
this.symbols = [];
data.symbols.forEach((serializedSymbol) => {
const symbol = this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name);
this.symbols.push(symbol);
if (serializedSymbol.importAs) {
importAs.push({symbol: symbol, importAs: serializedSymbol.importAs});
}
});
const summaries = visitValue(data.summaries, this, null);
return {summaries, importAs};
}
visitStringMap(map: {[key: string]: any}, context: any): any {
if ('__symbol' in map) {
const baseSymbol = this.symbols[map['__symbol']];
const members = map['members'];
return members.length ? this.symbolCache.get(baseSymbol.filePath, baseSymbol.name, members) :
baseSymbol;
} else {
return super.visitStringMap(map, context);
}
}
}

View File

@ -0,0 +1,37 @@
/**
* @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
*/
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export function ngfactoryFilePath(filePath: string): string {
const urlWithSuffix = splitTypescriptSuffix(filePath);
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
}
export function stripNgFactory(filePath: string): string {
return filePath.replace(/\.ngfactory\./, '.');
}
export function splitTypescriptSuffix(path: string): string[] {
if (path.endsWith('.d.ts')) {
return [path.slice(0, -5), '.ts'];
}
const lastDot = path.lastIndexOf('.');
if (lastDot !== -1) {
return [path.substring(0, lastDot), path.substring(lastDot)];
}
return [path, ''];
}
export function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}

View File

@ -1,19 +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
*/
export function filterFileByPatterns(
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
if (options.includeFilePattern) {
match = match && !!options.includeFilePattern.exec(fileName);
}
if (options.excludeFilePattern) {
match = match && !options.excludeFilePattern.exec(fileName);
}
return match;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ChangeDetectionStrategy, ComponentFactory, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {ListWrapper} from './facade/collection';
@ -39,7 +39,11 @@ export class CompileAnimationStateDeclarationMetadata extends CompileAnimationSt
}
export class CompileAnimationStateTransitionMetadata extends CompileAnimationStateMetadata {
constructor(public stateChangeExpr: string, public steps: CompileAnimationMetadata) { super(); }
constructor(
public stateChangeExpr: string|StaticSymbol|((stateA: string, stateB: string) => boolean),
public steps: CompileAnimationMetadata) {
super();
}
}
export abstract class CompileAnimationMetadata {}
@ -112,13 +116,31 @@ export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata
return reflector.importUri(ref);
}
export function viewClassName(compType: any, embeddedTemplateIndex: number): string {
return `View_${identifierName({reference: compType})}_${embeddedTemplateIndex}`;
}
export function hostViewClassName(compType: any): string {
return `HostView_${identifierName({reference: compType})}`;
}
export function dirWrapperClassName(dirType: any) {
return `Wrapper_${identifierName({reference: dirType})}`;
}
export function componentFactoryName(compType: any): string {
return `${identifierName({reference: compType})}NgFactory`;
}
export interface ProxyClass { setDelegate(delegate: any): void; }
export interface CompileIdentifierMetadata { reference: any; }
export enum CompileSummaryKind {
Template,
Pipe,
Directive,
NgModule
NgModule,
Injectable
}
/**
@ -126,9 +148,10 @@ export enum CompileSummaryKind {
* in other modules / components. However, this data is not enough to compile
* the directive / module itself.
*/
export interface CompileSummary { summaryKind: CompileSummaryKind; }
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; }
export interface CompileTypeSummary {
summaryKind: CompileSummaryKind;
type: CompileTypeMetadata;
}
export interface CompileDiDependencyMetadata {
isAttribute?: boolean;
@ -210,7 +233,7 @@ export class CompileStylesheetMetadata {
/**
* Summary Metadata regarding compilation of a template.
*/
export interface CompileTemplateSummary extends CompileSummary {
export interface CompileTemplateSummary {
animations: string[];
ngContentSelectors: string[];
encapsulation: ViewEncapsulation;
@ -240,7 +263,7 @@ export class CompileTemplateMetadata {
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[],
animations?: CompileAnimationEntryMetadata[],
interpolation?: [string, string]
interpolation?: [string, string],
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
@ -258,14 +281,18 @@ export class CompileTemplateMetadata {
toSummary(): CompileTemplateSummary {
return {
summaryKind: CompileSummaryKind.Template,
animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation
encapsulation: this.encapsulation,
};
}
}
export interface CompileEntryComponentMetadata {
componentType: any;
componentFactory: StaticSymbol|ComponentFactory<any>;
}
// Note: This should only use interfaces as nested data types
// as we need to be able to serialize this from/to JSON!
export interface CompileDirectiveSummary extends CompileTypeSummary {
@ -281,9 +308,12 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
entryComponents: CompileIdentifierMetadata[];
entryComponents: CompileEntryComponentMetadata[];
changeDetection: ChangeDetectionStrategy;
template: CompileTemplateSummary;
wrapperType: StaticSymbol|ProxyClass;
componentViewType: StaticSymbol|ProxyClass;
componentFactory: StaticSymbol|ComponentFactory<any>;
}
/**
@ -292,7 +322,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
export class CompileDirectiveMetadata {
static create(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
providers, viewProviders, queries, viewQueries, entryComponents, template}: {
providers, viewProviders, queries, viewQueries, entryComponents, template, wrapperType,
componentViewType, componentFactory}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
@ -306,8 +337,11 @@ export class CompileDirectiveMetadata {
viewProviders?: CompileProviderMetadata[],
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[],
template?: CompileTemplateMetadata
entryComponents?: CompileEntryComponentMetadata[],
template?: CompileTemplateMetadata,
wrapperType?: StaticSymbol|ProxyClass,
componentViewType?: StaticSymbol|ProxyClass,
componentFactory?: StaticSymbol|ComponentFactory<any>,
} = {}): CompileDirectiveMetadata {
const hostListeners: {[key: string]: string} = {};
const hostProperties: {[key: string]: string} = {};
@ -359,6 +393,9 @@ export class CompileDirectiveMetadata {
viewQueries,
entryComponents,
template,
wrapperType,
componentViewType,
componentFactory,
});
}
isHost: boolean;
@ -376,32 +413,39 @@ export class CompileDirectiveMetadata {
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
entryComponents: CompileIdentifierMetadata[];
entryComponents: CompileEntryComponentMetadata[];
template: CompileTemplateMetadata;
constructor(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries,
viewQueries, entryComponents, template}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: {[key: string]: string},
outputs?: {[key: string]: string},
hostListeners?: {[key: string]: string},
hostProperties?: {[key: string]: string},
hostAttributes?: {[key: string]: string},
providers?: CompileProviderMetadata[],
viewProviders?: CompileProviderMetadata[],
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[],
template?: CompileTemplateMetadata,
} = {}) {
wrapperType: StaticSymbol|ProxyClass;
componentViewType: StaticSymbol|ProxyClass;
componentFactory: StaticSymbol|ComponentFactory<any>;
constructor({isHost, type, isComponent, selector, exportAs,
changeDetection, inputs, outputs, hostListeners, hostProperties,
hostAttributes, providers, viewProviders, queries, viewQueries,
entryComponents, template, wrapperType, componentViewType, componentFactory}: {
isHost?: boolean,
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: {[key: string]: string},
outputs?: {[key: string]: string},
hostListeners?: {[key: string]: string},
hostProperties?: {[key: string]: string},
hostAttributes?: {[key: string]: string},
providers?: CompileProviderMetadata[],
viewProviders?: CompileProviderMetadata[],
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileEntryComponentMetadata[],
template?: CompileTemplateMetadata,
wrapperType?: StaticSymbol|ProxyClass,
componentViewType?: StaticSymbol|ProxyClass,
componentFactory?: StaticSymbol|ComponentFactory<any>,
} = {}) {
this.isHost = !!isHost;
this.type = type;
this.isComponent = isComponent;
@ -418,8 +462,11 @@ export class CompileDirectiveMetadata {
this.queries = _normalizeArray(queries);
this.viewQueries = _normalizeArray(viewQueries);
this.entryComponents = _normalizeArray(entryComponents);
this.template = template;
this.wrapperType = wrapperType;
this.componentViewType = componentViewType;
this.componentFactory = componentFactory;
}
toSummary(): CompileDirectiveSummary {
@ -439,7 +486,10 @@ export class CompileDirectiveMetadata {
queries: this.queries,
entryComponents: this.entryComponents,
changeDetection: this.changeDetection,
template: this.template && this.template.toSummary()
template: this.template && this.template.toSummary(),
wrapperType: this.wrapperType,
componentViewType: this.componentViewType,
componentFactory: this.componentFactory
};
}
}
@ -448,11 +498,12 @@ export class CompileDirectiveMetadata {
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
*/
export function createHostComponentMeta(
typeReference: any, compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata {
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
hostViewType: StaticSymbol | ProxyClass): CompileDirectiveMetadata {
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({
isHost: true,
type: {reference: typeReference, diDeps: [], lifecycleHooks: []},
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
template: new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template: template,
@ -471,7 +522,8 @@ export function createHostComponentMeta(
providers: [],
viewProviders: [],
queries: [],
viewQueries: []
viewQueries: [],
componentViewType: hostViewType
});
}
@ -517,7 +569,7 @@ export interface CompileNgModuleSummary extends CompileTypeSummary {
exportedPipes: CompileIdentifierMetadata[];
// Note: This is transitive.
entryComponents: CompileIdentifierMetadata[];
entryComponents: CompileEntryComponentMetadata[];
// Note: This is transitive.
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[];
// Note: This is transitive.
@ -534,7 +586,7 @@ export class CompileNgModuleMetadata {
declaredPipes: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
entryComponents: CompileIdentifierMetadata[];
entryComponents: CompileEntryComponentMetadata[];
bootstrapComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[];
@ -555,7 +607,7 @@ export class CompileNgModuleMetadata {
exportedDirectives?: CompileIdentifierMetadata[],
declaredPipes?: CompileIdentifierMetadata[],
exportedPipes?: CompileIdentifierMetadata[],
entryComponents?: CompileIdentifierMetadata[],
entryComponents?: CompileEntryComponentMetadata[],
bootstrapComponents?: CompileIdentifierMetadata[],
importedModules?: CompileNgModuleSummary[],
exportedModules?: CompileNgModuleSummary[],
@ -603,7 +655,7 @@ export class TransitiveCompileNgModuleMetadata {
modulesSet = new Set<any>();
modules: CompileTypeMetadata[] = [];
entryComponentsSet = new Set<any>();
entryComponents: CompileIdentifierMetadata[] = [];
entryComponents: CompileEntryComponentMetadata[] = [];
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = [];
@ -641,10 +693,10 @@ export class TransitiveCompileNgModuleMetadata {
this.modules.push(id);
}
}
addEntryComponent(id: CompileIdentifierMetadata) {
if (!this.entryComponentsSet.has(id.reference)) {
this.entryComponentsSet.add(id.reference);
this.entryComponents.push(id);
addEntryComponent(ec: CompileEntryComponentMetadata) {
if (!this.entryComponentsSet.has(ec.componentType)) {
this.entryComponentsSet.add(ec.componentType);
this.entryComponents.push(ec);
}
}
}

View File

@ -6,12 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';
import {ConvertPropertyBindingResult} from './expression_converter';
export class CheckBindingField {
constructor(public expression: o.ReadPropExpr, public bindingId: string) {}
}
@ -21,28 +18,14 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
const fieldExpr = createBindFieldExpr(bindingId);
// private is fine here as no child view will reference the cached value...
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name).set(o.literal(undefined)).toStmt());
return new CheckBindingField(fieldExpr, bindingId);
}
export function createCheckBindingStmt(
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
throwOnChangeVar, fieldExpr, evalResult.currValExpr
]);
if (evalResult.forceUpdate) {
condition = evalResult.forceUpdate.or(condition);
}
return [
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
]))
];
}
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
}
export function isFirstViewCheck(view: o.Expression): o.Expression {
return o.not(view.prop('numberOfChecks'));
}

View File

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

View File

@ -7,75 +7,69 @@
*/
import {SecurityContext} from '@angular/core';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {isFirstViewCheck} from './binding_util';
import {ConvertPropertyBindingResult} from './expression_converter';
import {createEnumExpression} from './identifier_util';
export function writeToRenderer(
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
renderValue: o.Expression, logBindingUpdate: boolean,
export function createCheckRenderBindingStmt(
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
securityContextExpression?: o.Expression): o.Statement[] {
const updateStmts: o.Statement[] = [];
const renderer = view.prop('renderer');
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
const checkStmts: o.Statement[] = [...evalResult.stmts];
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
switch (boundProp.type) {
case PropertyBindingType.Property:
if (logBindingUpdate) {
updateStmts.push(
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
}
updateStmts.push(
renderer
.callMethod(
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderProperty))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr),
evalResult.forceUpdate || o.literal(false), securityContext
])
.toStmt());
break;
case PropertyBindingType.Attribute:
renderValue =
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
updateStmts.push(
renderer
.callMethod(
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderAttribute))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr),
evalResult.forceUpdate || o.literal(false), securityContext
])
.toStmt());
break;
case PropertyBindingType.Class:
updateStmts.push(
renderer
.callMethod(
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
checkStmts.push(
o.importExpr(createIdentifier(Identifiers.checkRenderClass))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false)
])
.toStmt());
break;
case PropertyBindingType.Style:
let strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
renderer
.callMethod(
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
checkStmts.push(
o.importExpr(createIdentifier(Identifiers.checkRenderStyle))
.callFn([
view, renderElement, o.literal(boundProp.name), o.literal(boundProp.unit), oldValue,
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false),
securityContext
])
.toStmt());
break;
case PropertyBindingType.Animation:
throw new Error('Illegal state: Should not come here!');
}
return updateStmts;
return checkStmts;
}
function sanitizedValue(
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
securityContextExpression?: o.Expression): o.Expression {
function calcSecurityContext(
boundProp: BoundElementPropertyAst, securityContextExpression?: o.Expression): o.Expression {
if (boundProp.securityContext === SecurityContext.NONE) {
return renderValue; // No sanitization needed.
return o.NULL_EXPR; // No sanitization needed.
}
if (!boundProp.needsRuntimeSecurityContext) {
securityContextExpression =
@ -84,15 +78,13 @@ function sanitizedValue(
if (!securityContextExpression) {
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
}
const ctx = view.prop('viewUtils').prop('sanitizer');
const args = [securityContextExpression, renderValue];
return ctx.callMethod('sanitize', args);
return securityContextExpression;
}
export function triggerAnimation(
export function createCheckAnimationBindingStmts(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
lastRenderValue: o.Expression) {
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
@ -104,43 +96,60 @@ export function triggerAnimation(
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
view, renderElement,
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
view, renderElement, isFirstViewCheck(view).conditional(emptyStateValue, oldValue),
evalResult.currValExpr
]))
.toDeclStmt());
updateStmts.push(oldValue.set(evalResult.currValExpr).toStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
detachStmts.push(animationTransitionVar
.set(animationFnExpr.callFn(
[view, renderElement, evalResult.currValExpr, emptyStateValue]))
.toDeclStmt());
const registerStmts = [
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt(),
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt(),
const registerStmts: o.Statement[] = [];
const animationStartMethodExists = boundOutputs.find(
event => event.isAnimation && event.name == animationName && event.phase == 'start');
if (animationStartMethodExists) {
registerStmts.push(
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt());
}
];
const animationDoneMethodExists = boundOutputs.find(
event => event.isAnimation && event.name == animationName && event.phase == 'done');
if (animationDoneMethodExists) {
registerStmts.push(
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt());
}
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);
return {updateStmts, detachStmts};
const checkUpdateStmts: o.Statement[] = [
...evalResult.stmts,
new o.IfStmt(
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
view, oldValue, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)
]),
updateStmts)
];
const checkDetachStmts: o.Statement[] = [...evalResult.stmts, ...detachStmts];
return {checkUpdateStmts, checkDetachStmts};
}

View File

@ -6,11 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Injectable, ViewEncapsulation} from '@angular/core';
import {ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang';
import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config';
@ -18,7 +19,7 @@ import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util';
import {SyncAsyncResult, SyntaxError} from './util';
export interface PrenormalizedTemplateMetadata {
componentType: any;
@ -32,7 +33,7 @@ export interface PrenormalizedTemplateMetadata {
animations?: CompileAnimationEntryMetadata[];
}
@Injectable()
@CompilerInjectable()
export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>();
@ -40,9 +41,9 @@ export class DirectiveNormalizer {
private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver,
private _htmlParser: HtmlParser, private _config: CompilerConfig) {}
clearCache() { this._resourceLoaderCache.clear(); }
clearCache(): void { this._resourceLoaderCache.clear(); }
clearCacheFor(normalizedDirective: CompileDirectiveMetadata) {
clearCacheFor(normalizedDirective: CompileDirectiveMetadata): void {
if (!normalizedDirective.isComponent) {
return;
}
@ -64,13 +65,21 @@ export class DirectiveNormalizer {
SyncAsyncResult<CompileTemplateMetadata> {
let normalizedTemplateSync: CompileTemplateMetadata = null;
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
if (isPresent(prenormData.template)) {
if (prenormData.template != null) {
if (typeof prenormData.template !== 'string') {
throw new SyntaxError(
`The template specified for component ${stringify(prenormData.componentType)} is not a string`);
}
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
} else if (prenormData.templateUrl) {
if (typeof prenormData.templateUrl !== 'string') {
throw new SyntaxError(
`The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`);
}
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else {
throw new Error(
throw new SyntaxError(
`No template specified for component ${stringify(prenormData.componentType)}`);
}
@ -101,11 +110,12 @@ export class DirectiveNormalizer {
templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), false, interpolationConfig);
template, stringify(prenomData.componentType), true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`);
throw new SyntaxError(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles,
styleUrls: prenomData.styleUrls,
@ -118,7 +128,7 @@ export class DirectiveNormalizer {
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
let encapsulation = prenomData.encapsulation;
if (isBlank(encapsulation)) {
if (encapsulation == null) {
encapsulation = this._config.defaultEncapsulation;
}
@ -227,9 +237,13 @@ class TemplatePreparseVisitor implements html.Visitor {
return null;
}
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
html.visitAll(this, ast.expression);
}
visitComment(ast: html.Comment, context: any): any { return null; }
visitAttribute(ast: html.Attribute, context: any): any { return null; }
visitText(ast: html.Text, context: any): any { return null; }
visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
}

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util';
/*
* Resolve a `Type` for {@link Directive}.
*
@ -21,7 +21,7 @@ import {splitAtColon} from './util';
*
* See {@link Compiler}
*/
@Injectable()
@CompilerInjectable()
export class DirectiveResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
@ -75,9 +75,8 @@ export class DirectiveResolver {
outputs.push(propName);
}
}
const hostBinding =
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostBinding);
if (hostBinding) {
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
hostBindings.forEach(hostBinding => {
if (hostBinding.hostPropertyName) {
const startWith = hostBinding.hostPropertyName[0];
if (startWith === '(') {
@ -90,13 +89,12 @@ export class DirectiveResolver {
} else {
host[`[${propName}]`] = propName;
}
}
const hostListener =
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostListener);
if (hostListener) {
});
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
hostListeners.forEach(hostListener => {
const args = hostListener.args || [];
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
}
});
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
if (query) {
queries[propName] = query;

View File

@ -6,20 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser';
import {Identifiers, createIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
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, isDefaultChangeDetectionStrategy} from './private_import_core';
import {Console, LifecycleHooks} 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';
@ -33,8 +32,8 @@ const CHANGES_FIELD_NAME = '_changes';
const CHANGED_FIELD_NAME = '_changed';
const EVENT_HANDLER_FIELD_NAME = '_eventHandler';
const CHANGE_VAR = o.variable('change');
const CURR_VALUE_VAR = o.variable('currValue');
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
const FORCE_UPDATE_VAR = o.variable('forceUpdate');
const VIEW_VAR = o.variable('view');
const COMPONENT_VIEW_VAR = o.variable('componentView');
@ -51,12 +50,8 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
*
* So far, only `@Input` and the lifecycle hooks have been implemented.
*/
@Injectable()
@CompilerInjectable()
export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) {
return `Wrapper_${identifierName(id)}`;
}
constructor(
private compilerConfig: CompilerConfig, private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
@ -70,7 +65,7 @@ export class DirectiveWrapperCompiler {
addCheckInputMethod(inputFieldName, builder);
});
addNgDoCheckMethod(builder);
addCheckHostMethod(hostParseResult.hostProps, builder);
addCheckHostMethod(hostParseResult.hostProps, hostParseResult.hostListeners, builder);
addHandleEventMethod(hostParseResult.hostListeners, builder);
addSubscribeMethod(dirMeta, builder);
@ -129,14 +124,14 @@ class DirectiveWrapperBuilder implements ClassBuilder {
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
];
const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]),
];
const ctorStmts: o.Statement[] =
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
const ctorStmts: o.Statement[] = [
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt(),
];
if (this.genChanges) {
fields.push(new o.ClassField(
CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private]));
@ -150,7 +145,7 @@ class DirectiveWrapperBuilder implements ClassBuilder {
.toStmt());
return createClassStmt({
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type),
name: dirWrapperClassName(this.dirMeta.type.reference),
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
builders: [{fields, ctorStmts, methods}, this]
});
@ -185,14 +180,14 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
if (builder.ngOnInit) {
lifecycleStmts.push(new o.IfStmt(
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
isFirstViewCheck(VIEW_VAR),
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
}
if (builder.ngDoCheck) {
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
}
if (lifecycleStmts.length > 0) {
stmts.push(new o.IfStmt(o.not(THROW_ON_CHANGE_VAR), lifecycleStmts));
stmts.push(new o.IfStmt(o.not(VIEW_VAR.prop('throwOnChange')), lifecycleStmts));
}
stmts.push(new o.ReturnStatement(changedVar));
@ -202,7 +197,6 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
new o.FnParam(
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
],
stmts, o.BOOL_TYPE));
}
@ -212,30 +206,42 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
const onChangeStatements: o.Statement[] = [
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(),
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
field.expression.set(CURR_VALUE_VAR).toStmt()
];
let methodBody: o.Statement[];
if (builder.genChanges) {
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
.key(o.literal(input))
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
.instantiate([field.expression, CURR_VALUE_VAR]))
.toStmt());
onChangeStatements.push(
o.THIS_EXPR.prop(CHANGES_FIELD_NAME).key(o.literal(input)).set(CHANGE_VAR).toStmt());
methodBody = [
CHANGE_VAR
.set(o.importExpr(createIdentifier(Identifiers.checkBindingChange)).callFn([
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
]))
.toDeclStmt(),
new o.IfStmt(CHANGE_VAR, onChangeStatements)
];
} else {
methodBody = [new o.IfStmt(
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
]),
onChangeStatements)];
}
const methodBody: o.Statement[] = createCheckBindingStmt(
{currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression,
THROW_ON_CHANGE_VAR, onChangeStatements);
builder.methods.push(new o.ClassMethod(
`check_${input}`,
[
new o.FnParam(
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE)
],
methodBody));
}
function addCheckHostMethod(
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
hostProps: BoundElementPropertyAst[], hostEvents: BoundEventAst[],
builder: DirectiveWrapperBuilder) {
const stmts: o.Statement[] = [];
const methodParams: o.FnParam[] = [
new o.FnParam(
@ -244,7 +250,6 @@ function addCheckHostMethod(
COMPONENT_VIEW_VAR.name,
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
];
hostProps.forEach((hostProp, hostPropIdx) => {
const field = createCheckBindingField(builder);
@ -259,23 +264,18 @@ function addCheckHostMethod(
methodParams.push(new o.FnParam(
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
}
let checkBindingStmts: o.Statement[];
if (hostProp.isAnimation) {
const {updateStmts, detachStmts} = triggerAnimation(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
.or(o.importExpr(createIdentifier(Identifiers.noop))),
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
checkBindingStmts = updateStmts;
builder.detachStmts.push(...detachStmts);
RENDER_EL_VAR, field.expression, evalResult);
builder.detachStmts.push(...checkDetachStmts);
stmts.push(...checkUpdateStmts);
} else {
checkBindingStmts = writeToRenderer(
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
builder.compilerConfig.logBindingUpdate, securityContextExpr);
stmts.push(...createCheckRenderBindingStmt(
VIEW_VAR, RENDER_EL_VAR, hostProp, field.expression, evalResult, securityContextExpr));
}
stmts.push(...createCheckBindingStmt(
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
});
builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
}
@ -385,20 +385,19 @@ export class DirectiveWrapperExpressions {
return dirWrapper.prop(CONTEXT_FIELD_NAME);
}
static ngDoCheck(
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
throwOnChange: o.Expression): o.Expression {
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
static ngDoCheck(dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression, ):
o.Expression {
return dirWrapper.callMethod('ngDoCheck', [view, renderElement]);
}
static checkHost(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
componentView: o.Expression, renderElement: o.Expression,
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
if (hostProps.length) {
return [dirWrapper
.callMethod(
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
runtimeSecurityContexts))
'checkHost',
[view, componentView, renderElement].concat(runtimeSecurityContexts))
.toStmt()];
} else {
return [];

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {NumberWrapper, isPresent} from '../facade/lang';
import {NumberWrapper} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
export enum TokenType {
Character,
@ -22,7 +22,7 @@ export enum TokenType {
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
@Injectable()
@CompilerInjectable()
export class Lexer {
tokenize(text: string): Token[] {
const scanner = new _Scanner(text);
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
return new Token(index, TokenType.Error, 0, message);
}
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
class _Scanner {
length: number;
@ -241,7 +241,7 @@ class _Scanner {
this.advance();
str += two;
}
if (isPresent(threeCode) && this.peek == threeCode) {
if (threeCode != null && this.peek == threeCode) {
this.advance();
str += three;
}

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
@ -31,7 +30,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
return new RegExp(pattern, 'g');
}
@Injectable()
@CompilerInjectable()
export class Parser {
private errors: ParserError[] = [];
@ -344,7 +343,7 @@ export class _ParseAST {
while (this.optionalCharacter(chars.$COLON)) {
args.push(this.parseExpression());
}
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args);
result = new BindingPipe(this.span(result.span.start), result, name, args);
} while (this.optionalOperator('|'));
}

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

@ -14,7 +14,9 @@ import {ViewEncapsulation} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbolCache} from '../aot/static_symbol';
import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver';
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
import {CompileDirectiveMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config';
@ -26,23 +28,17 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {NgModuleResolver} from '../ng_module_resolver';
import {ParseError} from '../parse_util';
import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {I18NHtmlParser} from './i18n_html_parser';
import {MessageBundle} from './message_bundle';
export interface ExtractorOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
/**
* The host of the Extractor disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost {
export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Loads a resource (e.g. html / css)
*/
@ -51,14 +47,13 @@ export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHo
export class Extractor {
constructor(
private options: ExtractorOptions, public host: ExtractorHost,
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
private metadataResolver: CompileMetadataResolver) {}
public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver,
private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {}
extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options);
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -91,12 +86,14 @@ export class Extractor {
});
}
static create(host: ExtractorHost, options: ExtractorOptions):
{extractor: Extractor, staticReflector: StaticReflector} {
static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} {
const htmlParser = new I18NHtmlParser(new HtmlParser());
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(host);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(host, symbolCache);
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(staticSymbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new CompilerConfig({
@ -111,13 +108,13 @@ export class Extractor {
const elementSchemaRegistry = new DomElementSchemaRegistry();
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options),
elementSchemaRegistry, normalizer, staticReflector);
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
symbolCache, staticReflector);
// TODO(vicb): implicit tags & attributes
const messageBundle = new MessageBundle(htmlParser, [], {});
const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver);
const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver);
return {extractor, staticReflector};
}
}

View File

@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
const _I18N_ATTR = 'i18n';
const _I18N_ATTR_PREFIX = 'i18n-';
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
const MEANING_SEPARATOR = '|';
const ID_SEPARATOR = '@@';
/**
* Extract translatable messages from an html AST
@ -53,20 +55,22 @@ enum _VisitorMode {
* @internal
*/
class _Visitor implements html.Visitor {
private _depth: number;
// <el i18n>...</el>
private _inI18nNode: boolean;
private _depth: number;
private _inImplicitNode: boolean;
// <!--i18n-->...<!--/i18n-->
private _inI18nBlock: boolean;
private _blockMeaningAndDesc: string;
private _blockChildren: html.Node[];
private _blockStartDepth: number;
private _inI18nBlock: boolean;
// {<icu message>}
private _inIcu: boolean;
// set to void 0 when not in a section
private _msgCountAtSectionStart: number;
private _errors: I18nError[];
private _mode: _VisitorMode;
@ -77,7 +81,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[]}) {}
@ -208,50 +212,31 @@ class _Visitor implements html.Visitor {
this._depth++;
const wasInI18nNode = this._inI18nNode;
const wasInImplicitNode = this._inImplicitNode;
let childNodes: html.Node[];
let childNodes: html.Node[] = [];
let translatedChildNodes: html.Node[];
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
// message
// Extract:
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
// - ICU messages
const i18nAttr = _getI18nAttr(el);
const i18nMeta = i18nAttr ? i18nAttr.value : '';
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
!this._isInTranslatableSection;
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit;
this._inImplicitNode = wasInImplicitNode || isImplicit;
if (!this._isInTranslatableSection && !this._inIcu) {
if (i18nAttr) {
// explicit translation
if (i18nAttr || isTopLevelImplicit) {
this._inI18nNode = true;
const message = this._addMessage(el.children, i18nAttr.value);
childNodes = this._translateMessage(el, message);
} else if (isTopLevelImplicit) {
// implicit translation
this._inI18nNode = true;
const message = this._addMessage(el.children);
childNodes = this._translateMessage(el, message);
const message = this._addMessage(el.children, i18nMeta);
translatedChildNodes = this._translateMessage(el, message);
}
if (this._mode == _VisitorMode.Extract) {
const isTranslatable = i18nAttr || isTopLevelImplicit;
if (isTranslatable) {
this._openTranslatableSection(el);
}
if (isTranslatable) this._openTranslatableSection(el);
html.visitAll(this, el.children);
if (isTranslatable) {
this._closeTranslatableSection(el, el.children);
}
}
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
if (isTranslatable) this._closeTranslatableSection(el, el.children);
}
} else {
if (i18nAttr || isTopLevelImplicit) {
@ -263,19 +248,18 @@ class _Visitor implements html.Visitor {
// Descend into child nodes for extraction
html.visitAll(this, el.children);
}
}
if (this._mode == _VisitorMode.Merge) {
// Translate attributes in ICU messages
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
}
if (this._mode === _VisitorMode.Merge) {
const visitNodes = translatedChildNodes || el.children;
visitNodes.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
}
this._visitAttributesOf(el);
@ -285,7 +269,6 @@ class _Visitor implements html.Visitor {
this._inImplicitNode = wasInImplicitNode;
if (this._mode === _VisitorMode.Merge) {
// There are no childNodes in translatable sections - those nodes will be replace anyway
const translatedAttrs = this._translateAttributes(el);
return new html.Element(
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
@ -330,15 +313,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 +351,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 +365,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) {
@ -420,7 +403,7 @@ class _Visitor implements html.Visitor {
}
/**
* Marks the start of a section, see `_endSection`
* Marks the start of a section, see `_closeTranslatableSection`
*/
private _openTranslatableSection(node: html.Node): void {
if (this._isInTranslatableSection) {
@ -432,7 +415,7 @@ class _Visitor implements html.Visitor {
/**
* A translatable section could be:
* - a translatable element,
* - the content of translatable element,
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
*/
private get _isInTranslatableSection(): boolean {
@ -496,8 +479,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};
}

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