Compare commits

...

195 Commits

Author SHA1 Message Date
5c89d6bffa feat(core): support metadata reflection for native class types (#22356)
closes #21731

PR Close #22356
2018-02-21 16:09:27 -08:00
3e6a86fb0a fix(forms): set state before emitting a value from ngModelChange (#21514)
Closes #21513.

PR Close #21514
2018-02-21 15:59:33 -08:00
a7ebf5aadd fix(core): properly handle function without prototype in reflector (#22284)
closes #19978

PR Close #22284
2018-02-21 14:52:04 -08:00
b42921bbd2 docs(aio): updates directive event hooks real capabilities (#16654)
Minor documentation update to include event hooks that were assumed to only work on components.

Closes angular/angular#10221

PR Close #16654
2018-02-21 14:51:04 -08:00
722dec11b0 docs(aio): Wrong code example. Form status field was added later in the guide. (#21275)
PR Close #21275
2018-02-21 11:06:47 -08:00
9e6268ba59 docs(http): fix a typo in code comment (#22327)
PR Close #22327
2018-02-21 11:06:06 -08:00
435f6eecd2 build: make git revert messages valid (#22339)
`git revert` default message is "Revert <original message>" (no semi-colon)

PR Close #22339
2018-02-21 11:05:35 -08:00
1c1cbba04b docs: add ngStyle to cheat sheet (#22070)
PR Close #22070
2018-02-20 16:08:15 -08:00
3b692a55a7 docs(aio): fix incorrect quote mark usage (#22335)
PR Close #22335
2018-02-20 15:42:55 -08:00
69a0578e00 docs(aio): fix the css of the heroes component's buttons (#22333)
Fixes #22222

PR Close #22333
2018-02-20 15:41:56 -08:00
b5ca275590 docs(aio): Fix name of component (#22332)
PR Close #22332
2018-02-20 15:41:35 -08:00
519f022b02 docs(aio): update installed mobile tool list (#22331)
PR Close #22331
2018-02-20 15:41:15 -08:00
236a9320df build: update tsickle dep from compiler-cli (#22295)
PR Close #22295
2018-02-20 15:40:44 -08:00
28ac24444f fix(compiler-cli): add missing entry point to package, update tsickle (#22295)
PR Close #22295
2018-02-20 15:40:44 -08:00
99909bbf2c feat(ivy): generate pipe references and definitions (#22034)
PR Close #22034
2018-02-20 13:58:03 -08:00
ee60bb5b36 fix(ivy): pureFunction8 should update the right bindings (#22313)
PR Close #22313
2018-02-20 11:36:50 -08:00
f6120c09e7 docs(aio): add Nx and Angular Enterprise Playbook to resources (#22321)
PR Close #22321
2018-02-20 10:09:33 -08:00
e2bdef4cf6 test(language-service): fix minor typos (#21372)
PR Close #21372
2018-02-20 10:08:55 -08:00
8115edc82f fix(common): then and else template might be set to null (#22298)
PR Close #22298
2018-02-18 19:25:28 -08:00
a8b5465e24 fix(ivy): update master with renamings (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
9ce495b3d8 refactor(ivy): simplify interpolation instructions (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
d40263447d refactor(ivy): move get functions next to their underlying variable (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
31c5c1060a docs(ivy): update the API docs instructions to add details about removing attributes (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
c9ebd60435 refactor(ivy): interpolatiom instructions do not support NO_CHANGE at input. (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
5a14e2238f refactor(ivy): split the memory instruction into store and load (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
3ceee99e22 feat(compiler-cli): Check unvalidated combination of ngc and TypeScript (#22293)
closes #20669

PR Close #22293
2018-02-18 15:12:46 -08:00
28b23f954c docs(aio): add angular-buch to resources (#22163)
adds a link to the website of our book. second version of the text. thanks!

PR Close #22163
2018-02-18 15:12:13 -08:00
ad17e5e791 docs(aio): add angular-buch to resources (#22163)
adds a link to the website of our book. many thanks for reviewing this

PR Close #22163
2018-02-18 15:12:13 -08:00
c30d329faa fix(ivy): fix merge errors (master is broken) (#22291)
PR Close #22291
2018-02-18 15:03:39 -08:00
991300b86c feat(platform-browser): do not throw error when Hammer.js not loaded (#22257)
closes #16992

PR Close #22257
2018-02-18 13:29:14 -08:00
7c45db3a19 docs: correct grammar mistakes in CONTRIBUTING.md (#22285)
Various grammar mistakes were present in the contribution guidelines
This commit corrects some of them

PR Close #22285
2018-02-18 13:27:23 -08:00
67cf11d071 feat(common): better error message when non-template element used in NgIf (#22274)
closes #16410

PR Close #22274
2018-02-18 13:26:50 -08:00
49082d7ab2 feat(ivy): support host attributes (#22213)
PR Close #22213
2018-02-18 13:22:38 -08:00
6b627f67db test(ivy): add missing host listener and host attribute binding tests (#22213)
PR Close #22213
2018-02-18 13:22:38 -08:00
5c320b4c2a test(ivy): add missing host binding and query tests (#22213)
PR Close #22213
2018-02-18 13:22:34 -08:00
ac2b04a5ab test(ivy): Add small_app spec for sprint #3 (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
a63b764b54 test(ivy): move compiler canonical specs into a single directory (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
2654357c72 docs: update BAZEL.md crosstool error instructions (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
4ec40c6ab2 fix(aio): improve announcement-bar layout with wide logos (#22272)
PR Close #22272
2018-02-18 13:16:30 -08:00
80d424798e docs(aio): added ngconf announcement (#22272)
PR Close #22272
2018-02-18 13:16:30 -08:00
7fa2d4b503 fix: merge-pr script (#22290)
PR Close #22290
2018-02-18 13:13:29 -08:00
f4845fae12 build: use authenticated mode for the merge script (#22269)
`TOKEN` is the name with use for other GH scripts

PR Close #22269
2018-02-18 12:44:42 -08:00
f693be3996 feat(ivy): add pureFunction0 instruction (#22214)
PR Close #22214
2018-02-16 18:03:55 -08:00
a73d5308e0 refactor(ivy): rename objectLiteral to pureFn to prep for pipes (#22214)
PR Close #22214
2018-02-16 18:03:55 -08:00
e1bf067090 fix(animations): report correct totalTime value even during noOp animations (#22225)
This patch ensures that if the NoopAnimationsModule is used then it will
correctly report the associated `totalTime` property within the emitted
AnimationEvent instance when an animation event trigger is fired.

BREAKING CHANGE: When animation is trigged within a disabled zone, the
associated event (which an instance of AnimationEvent) will no longer
report the totalTime as 0 (it will emit the actual time of the
animation). To detect if an animation event is reporting a disabled
animation then the `event.disabled` property can be used instead.

PR Close #22225
2018-02-16 18:03:31 -08:00
884de18cba docs: replace plnkr with StackBlitz (#20365)
PR Close #20365
2018-02-16 15:12:10 -08:00
dfa2fb95d5 test(ivy): clean the imported renderer2 (#22255)
PR Close #22255
2018-02-16 15:11:23 -08:00
2639b4bffb fix(common): correct mapping of Observable methods (#20518)
fixes #20516
PR Close #20518
2018-02-16 15:10:31 -08:00
978f97cc59 test(aio): increase docs-test timeouts to prevent flakes on Travis (#22261)
PR Close #22261
2018-02-16 14:46:23 -08:00
f1a063298e feat(core): set preserveWhitespaces to false by default (#22046)
Fixes #22027

PR Close #22046
2018-02-16 09:06:14 -08:00
d241532488 docs(ivy): add a note about exporting top level variables (#22234)
PR Close #22234
2018-02-15 16:16:40 -08:00
f755db78dc fix(core): require factory to be provided for shakeable InjectionToken (#22207)
InjectionToken can be created with an ngInjectableDef, and previously
this allowed the full expressiveness of @Injectable. However, this
requires a runtime reflection system in order to generate factories
from expressed provider declarations.

Instead, this change requires scoped InjectionTokens to provide the
factory directly (likely using inject() for the arguments), bypassing
the need for a reflection system.

Fixes #22205

PR Close #22207
2018-02-15 16:16:16 -08:00
5dd2b5135d refactor(ivy): rename bindX() functions to interpolationX() (#22229)
rationale: remove the confusion with `bind()` and `bind0()`

PR Close #22229
2018-02-15 14:20:53 -08:00
7ac34e42a0 feat: allow direct scoping of @Injectables to the root injector (#22185)
@Injectable() supports a scope parameter which specifies the target module.
However, it's still difficult to specify that a particular service belongs
in the root injector. A developer attempting to ensure that must either
also provide a module intended for placement in the root injector or target
a module known to already be in the root injector (e.g. BrowserModule).
Both of these strategies are cumbersome and brittle.

Instead, this commit adds a token APP_ROOT_SCOPE which provides a
straightforward way of targeting the root injector directly, without
requiring special knowledge of modules within it.

PR Close #22185
2018-02-15 14:20:27 -08:00
029dbf0e18 feat(bazel): ng_module produces bundle index (#22176)
It creates the bundle index .d.ts and .metadata.json files.
The names are based on the ng_module target.

PR Close #22176
2018-02-15 14:08:53 -08:00
bba65e0f41 feat(bazel): introduce a binary stamping feature (#22176)
This grabs version control metadata and makes it available in the build, eg. to put in the version field for released artifacts

PR Close #22176
2018-02-15 14:08:53 -08:00
a069e08354 refactor(bazel): convert most ts_library to ng_module (#22176)
This is necessary so we can produce ng metadata for our packages that are published as libraries

PR Close #22176
2018-02-15 14:08:53 -08:00
03d93c96a3 Revert: "build: merge-pr new checks that all requested changes have been addressed (#21817)"
This reverts commit 4a4d749710.
2018-02-15 11:00:52 -08:00
020338230f style: fix typos boostrap to bootstrap (#21917)
PR Close #21917
2018-02-15 09:54:00 -08:00
a1d86daa71 refactor(ivy): assertion (#22189)
Encourage the use of message to explain the assertion

PR Close #22189
2018-02-15 09:53:05 -08:00
7078fbffb4 fix(aio): improve printing styles (#19651)
printfix

PR Close #19651
2018-02-15 09:52:32 -08:00
0aa9b46b79 Revert "build: allow bazel build ... (#22168)"
This reverts commit 265ac8a106.
2018-02-15 03:28:35 -08:00
831592c381 Revert "style: fix typos boostrap to bootstrap (#21917)"
This reverts commit 363498b6b4.
2018-02-14 22:56:44 -05:00
f628797d91 Revert "refactor(ivy): assertion (#22189)"
This reverts commit 0b683123d2.
2018-02-14 22:56:11 -05:00
47f51c2ead Revert "Revert "build: merge-pr new checks that all requested changes have been addressed (#21817)""
This reverts commit 5b8eb9c5c7.
2018-02-14 22:55:56 -05:00
ba9cd5bbc4 Revert "fix(aio): improve printing styles (#19651)"
This reverts commit b54ad053f9.
2018-02-14 22:55:24 -05:00
b54ad053f9 fix(aio): improve printing styles (#19651)
printfix

PR Close #19651
2018-02-14 18:49:58 -05:00
5b8eb9c5c7 Revert "build: merge-pr new checks that all requested changes have been addressed (#21817)"
This reverts commit 4a4d749710.
2018-02-14 18:48:45 -05:00
0b683123d2 refactor(ivy): assertion (#22189)
Encourage the use of message to explain the assertion

PR Close #22189
2018-02-14 18:42:04 -05:00
363498b6b4 style: fix typos boostrap to bootstrap (#21917)
PR Close #21917
2018-02-14 18:21:52 -05:00
a1bb56f739 docs: fix changelog errors (#22226)
PR Close #22226
2018-02-14 18:18:22 -05:00
5bb9fcad3e build: comment-out chromium version checking code temporarily (#22232)
Related #22231

PR Close #22232
2018-02-14 17:26:43 -05:00
Pat
f4697f351e docs: typo - components should be possessive (#22172)
PR Close #22172
2018-02-14 15:06:52 -05:00
1d571b299d feat(platform-browser): fix #19604, can config hammerOptions (#21979)
PR Close #21979
2018-02-14 15:02:58 -05:00
3a0b5a928c docs(aio): fix extraneous divs (#22069)
PR Close #22069
2018-02-14 15:02:36 -05:00
265ac8a106 build: allow bazel build ... (#22168)
Note, the reason this commit removes `firebase-tools` is:

1) firebase-tools has an optional dependency on
https://www.npmjs.com/package/@google-cloud/functions-emulator
2) yarn's `--ignore-optional` doesn't work for transitive deps, so
there's no way to yarn install without getting that functions-emulator
package
3) functions-emulator has a transitive dep on `grpc`
4) the version of `grpc` we get has `BUILD` files and no `WORKSPACE`
file so it always breaks `bazel build ...`

It could be solved by any of:
1) remove firebase-tools - this is what I did
2) fix yarn so you can omit optional deps of a transitive dep
3) make functions-emulator depend transitively on a more recent `grpc`
version
4) patch `grpc` after install by doing an `rm` command in our
postinstall or something

In its place we must install protobufjs. This is needed by the
ngc-wrapped test, which needs jasmine as well as bazel's worker mode
dependencies, and therefore cannot simply rely on
node_modules =
"@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules"

PR Close #22168
2018-02-14 15:01:41 -05:00
fa7d8907d0 docs: add changelog for 6.0.0-beta.4 2018-02-13 21:35:43 -08:00
0220ce7002 docs: add changelog for 5.2.5 2018-02-13 21:34:04 -08:00
3bd0b2ab28 release: cut the 6.0.0-beta.4 release 2018-02-13 21:31:14 -08:00
a589ca0adb test(ivy): clean up canonical spec (#22188)
PR Close #22188
2018-02-13 13:04:43 -08:00
72f8abd7b3 fix(compiler): make unary plus operator consistent to JavaScript (#22154)
fixes #22089

PR Close #22154
2018-02-13 13:04:30 -08:00
20a900b648 test: Add bundle symbol extractor tool (#22002)
This tool will be used for extracting symbols out of bundles so that
we can assert that only whitelisted symbols are allowed.

PR Close #22002
2018-02-13 11:28:54 -08:00
6435ecd3c6 fix(platform-browser): support 0/false/null values in transfer_state (#22179)
Issue #22178

PR Close #22179
2018-02-13 11:28:22 -08:00
16d1700a8e fix(core): add stacktrace in log when error during cleanup component in TestBed (#22162)
PR Close #22162
2018-02-13 11:28:08 -08:00
b75cf3f70b ci: update ngbot config file (#22173)
Fixes #22053
PR Close #22173
2018-02-13 10:26:06 -08:00
4f19491fec ci: remove conditional clause for bazel install (#22170)
No longer needed since we don't have a bazel job
PR Close #22170
2018-02-13 10:25:51 -08:00
8f36fd1374 ci: remove bazel job from Travis (#22170)
This saves us an executor on Travis.

Note that we still do a bazel build on travis when we run the integration tests under e2e_2.

We expect that CircleCI is the only place we'll ever consume bazel-built artifacts.

PR Close #22170
2018-02-13 10:25:51 -08:00
2de0d4c1db perf(ivy): use official build optimizer rollup plugin in int test (#22121)
PR Close #22121
2018-02-13 10:25:37 -08:00
5e4af7c550 fix(ivy): o2+ should work with multiple template instances (#22075)
Closes #22075
2018-02-13 10:24:41 -08:00
8ec21fc325 ci: enable bazel remote caching on CircleCI (#21784)
This should cause Bazel builds to be incremental, only re-building parts of Angular affected by changes since the last build.
It also fixes a potential version skew, where CI was running the Bazel linter binaries in the ngcontainer docker image, but developers built them using the versions in WORKSPACE

PR Close #21784
2018-02-13 10:10:41 -08:00
eb48750705 docs(aio): fix typo in "preserveWhitespaces" example (#22182)
Fixes #22147

PR Close #22182
2018-02-12 15:57:42 -08:00
be59c3a98c fix(common): weaken AsyncPipe transform signature (#22169)
The AsyncPipe type signature was changed to allow
deferred creation of promises and observalbes that
is supported by the implementation by allowing
`Promise<T>|null|undefined` and by allowing
`Observable<T>|null|undefined`.

PR Close #22169
2018-02-12 15:57:29 -08:00
b333919722 build(bazel): allow ng_modules to elide .ngsummary.closure.js files (#22107)
PR Close #22107
2018-02-12 15:57:17 -08:00
235a235fab feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking
of services which are never injected. Ordinarily, this tree-shaking is prevented
by the existence of a hard dependency on the service by the module in which it
is declared.

Firstly, @Injectable() is modified to accept a 'scope' parameter, which points
to an @NgModule(). This reverses the dependency edge, permitting the module to
not depend on the service which it "provides".

Secondly, the runtime is modified to understand the new relationship created
above. When a module receives a request to inject a token, and cannot find that
token in its list of providers, it will then look at the token for a special
ngInjectableDef field which indicates which module the token is scoped to. If
that module happens to be in the injector, it will behave as if the token
itself was in the injector to begin with.

Thirdly, the compiler is modified to read the @Injectable() metadata and to
generate the special ngInjectableDef field as part of TS compilation, using the
PartialModules system.

Additionally, this commit adds several unit and integration tests of various
flavors to test this change.

PR Close #22005
2018-02-12 14:34:59 -08:00
2d5e7d1b52 feat(compiler): mark @NgModules in provider lists for identification at runtime (#22005)
All of the providers in a module get compiled into a module definition in the
factory file. Some of these providers are for the actual module types, as those
are available for injection in Angular. For tree-shakeable tokens, the runtime
needs to be able to distinguish which modules are present in an injector.

This change adds a NodeFlag which tags those module providers for later
identification.

PR Close #22005
2018-02-12 14:34:59 -08:00
647b8595d0 build: update some ts_library rules to ng_module (#22005)
This is needed so the rules produce metadata.json files, which is essential
for building compiler/integration tests with Bazel.

PR Close #22005
2018-02-12 14:34:59 -08:00
0a1a397cd7 fix(platform-browser): add @Injectable where it was missing (#22005)
PR Close #22005
2018-02-12 14:34:59 -08:00
7f9b1b78f6 docs(aio): add angular-playground to resources (#22042)
PR Close #22042
2018-02-12 14:30:58 -08:00
1e9484673d docs(aio): add angular.schule to resources (#22164)
adds a link to our website. many thanks for reviewing this

PR Close #22164
2018-02-12 10:01:23 -08:00
88bec238ac fix(aio): remove broken span closing tag (#22146)
PR Close #22146
2018-02-12 10:01:10 -08:00
62e7b9da1e refactor(ivy): code simplification (#22082)
PR Close #22082
2018-02-12 10:00:56 -08:00
61341b2791 refactor(ivy): generatePropertyAliases (#22082)
PR Close #22082
2018-02-12 10:00:56 -08:00
92a5876f51 refactor(router): move activation to private method (#22033)
PR Close #22033
2018-02-12 10:00:36 -08:00
a57df4ee20 docs(aio): put structural directives back in the nav (#21856)
PR Close #21856
2018-02-12 10:00:14 -08:00
92d7060cb0 Revert "build(bazel): allow ng_modules to elide .ngsummary.closure.js files (#22107)"
This reverts commit 263a2eca88.
2018-02-09 20:08:41 -08:00
7e9b120452 build: update to latest bazel rules (#22127)
PR Close #22127
2018-02-09 17:21:54 -08:00
b081dfe705 fix(bazel): allow TS to read ambient typings (#21876)
Same fix as e70d7a2a7c
This is because the CompilerOptions needs to have directoryExists undefined in order to get the google3 behavior,
so we have to set the property outside the constructor.

Fixes #21872

PR Close #21876
2018-02-09 17:16:25 -08:00
4a4d749710 build: merge-pr new checks that all requested changes have been addressed (#21817)
PR Close #21817
2018-02-09 17:14:17 -08:00
c878d55397 docs: add VSCode interaction issue to bazel docs (#22128)
PR Close #22128
2018-02-09 17:13:06 -08:00
263a2eca88 build(bazel): allow ng_modules to elide .ngsummary.closure.js files (#22107)
PR Close #22107
2018-02-09 16:07:49 -08:00
44154e71fd fix(common): round currencies based on decimal digits in CurrencyPipe (#21783)
By default, we now round currencies based on the number of decimal digits available for that currency instead of using the rouding defined in the number formats.
More info about that can be found in http://www.unicode.org/cldr/charts/latest/supplemental/detailed_territory_currency_information.html#format_info

Fixes #10189

PR Close #21783
2018-02-09 14:42:23 -08:00
0b2f7d13d0 fix(common): regenerate i18n locale data files (#21783)
PR Close #21783
2018-02-09 14:42:23 -08:00
420cc7afc6 fix(common): add locale currency values (#21783)
we now use locale currency symbols, since they may be different in each locale (we were only using english data previously)

Fixes #20385

PR Close #21783
2018-02-09 14:42:23 -08:00
5fc77c90cb fix(aio): do not rewrite /styleguide URL in Service Worker (#22085)
This URL needs to be redirected via the server, so
we must exclude it from being rewitten.

Closes #22078

PR Close #22085
2018-02-09 13:10:35 -08:00
c3484450b8 docs: fix typo in http.md (#22058)
PR Close #22058
2018-02-09 13:10:23 -08:00
fbef94a8ee feat(aio): enable data driven homepage announcements (#22043)
PR Close #22043
2018-02-09 13:10:11 -08:00
aa456edafc refactor(ivy): validate that identifier identity in emitted output (#21877)
Modifies validation syntax to generate back references to ensure
that identifiers are used consistently.

Introduced … to allow validating constant definition and usage.

PR Close #21877
2018-02-09 13:06:10 -08:00
7007f51c35 feat(aio): first pass API docs redesign (#21874)
Includes:

* display ToC for API docs
* update dgeni-packages to 0.24.1
* add floating sidebar in API docs
* add breadcrumbs and structured data for Google crawler
* improved rendering of method overloads
* properties rendered in a table
* params rendered with docs
* removal of outdated "infobox" from all API docs

PR Close #21874
2018-02-09 13:05:16 -08:00
bc1e22922a docs(aio): several fix for ngmodule guides (#21517)
PR Close #21517
2018-02-09 13:03:47 -08:00
cf8d512e43 Revert "fix(forms): set state before emitting a value from ngModelChange (#21514)"
This reverts commit 9744a1c966.
2018-02-09 10:11:23 -08:00
0b1f5d2127 Revert "docs(common): add HttpParamsOptions to the public API (#20332)"
This reverts commit a9545aba4d.
2018-02-08 14:37:27 -08:00
dcf64a0d01 fix(bazel): improve error message for missing assets (#22096)
fixes #22095

PR Close #22096
2018-02-08 10:01:27 -08:00
a9545aba4d docs(common): add HttpParamsOptions to the public API (#20332)
Fixes #20276

PR Close #20332
2018-02-08 09:44:36 -08:00
d9ae70c699 test(ivy): normalize template names in canonical spec (#21815)
PR Close #21815
2018-02-08 08:55:40 -08:00
a751649c8d fix(core): use appropriate inert document strategy for Firefox & Safari (#17019)
Both Firefox and Safari are vulnerable to XSS if we use an inert document
created via `document.implementation.createHTMLDocument()`.

Now we check for those vulnerabilities and then use a DOMParser or XHR
strategy if needed.

Further the platform-server has its own library for parsing HTML, so we
sniff for that (by checking whether DOMParser exists) and fall back to
the standard strategy.

Thanks to @cure53 for the heads up on this issue.

PR Close #17019
2018-02-08 08:55:15 -08:00
3f5a3d6ea1 refactor(ivy): add internal isProceduralRenderer() (#22055)
PR Close #22055
2018-02-07 17:03:25 -08:00
10a014d89e refactor(ivy): prefix viewStart & viewEnd with embedded (#22055)
PR Close #22055
2018-02-07 17:03:25 -08:00
8feb8e5408 refactor(ivy): use long instruction format in tests (#22055)
PR Close #22055
2018-02-07 17:03:25 -08:00
16dada28f5 docs(ivy): Simplify & dedup API docs for canInsertNativeNode (#22055)
PR Close #22055
2018-02-07 17:03:25 -08:00
67cf7128ae docs(aio): remove ngATL banner from homepage (#22060)
Closes #22029

PR Close #22060
2018-02-07 16:10:17 -08:00
16e5b866d2 test(ivy): also track the size of the compressed hello world bundle (#22056)
PR Close #22056
2018-02-07 16:10:00 -08:00
83d43ac850 docs(aio): remove lifecycle hooks img (#21425)
PR Close #21425
2018-02-07 16:09:44 -08:00
cd25939be9 build(aio): update examples to CLI to 1.6.5 (#21222)
PR Close #21222
2018-02-07 16:09:26 -08:00
b58c3527e9 test(ivy): add canonical spec for object literals (#22045)
PR Close #22045
2018-02-07 12:10:16 -08:00
efc67ee5ef fix(ivy): make pipe invocation locality neutral (#22030)
PR Close #22030
2018-02-07 12:09:56 -08:00
7a406a3896 feat(aio): report logger.error calls to Google Analytics (#22011)
We have a number of observables that have `catch` handlers to recover
from errors without causing the stream to close, and breaking the app.
We also have some `try ... catch` blocks for synchronous code for a
similar reason.

In these cases we conventionally then call `logger.error` in the catch
handler. We are interested in these errors so we are going to capture them
by reporting them to Google Analytics via the new `ReportingErrorHandler`.

PR Close #22011
2018-02-07 12:09:38 -08:00
98001a065d feat(aio): report application errors to Google Analytics (#22011)
This is a basic implementation of error logging using the limited
facilities provided by Google Analytics.

Errors within the Angular app itself will be handled by a new
`ReportingErrorHandler` service, which overrides and extends the
built-in `ErrorHandler`.

Further, errors outside the app, which arrive at `window.onerror`
will also be reported to Google Analytics.

Closes #21943

PR Close #22011
2018-02-07 12:09:38 -08:00
e442881ead feat(bazel): allow explicit specification of factories (#22003)
The `ng_module` rule now has a factories attribute that
allows explicit specification of which files are expected
to generate factories. This allows avoiding generating
empty factory files (such as `.ngfactory.js`) begin
generated which might cause down-stream tools issues if
they have a limit on the number of files that can be
processed in a single bazel action.

PR Close #22003
2018-02-07 12:09:21 -08:00
b37cee36f9 fix(language-service): correct instructions to install the language service (#22000)
Fixes: #21956

PR Close #22000
2018-02-07 12:09:00 -08:00
e56de1025a fix(core): ensure initial value of QueryList length (#21980) (#21982)
Set initial value of `length` to `0`.

Fixes regression introduced by e544742156 (diff-a85dbe0991a7577ea24b49374e9ae90b) where the `length` property ceased to have initial value.

Closes #21980

PR Close #21982
2018-02-07 12:08:44 -08:00
64ae6d206e test(common): disable deprecated date pipe tests on chrome mobile (#21933)
Closes #21907
PR Close #21933
2018-02-07 12:07:31 -08:00
54a14312d1 test(forms): update test name with correct wording (#21833)
Use the term primitive value instead of standalone

Fixes #21831

PR Close #21833
2018-02-07 12:07:14 -08:00
7e95802cc1 fix(aio): ignore .header-link when selecting the heading text (#21695)
Implemented @maxkorz's
[suggestion](https://github.com/angular/angular/issues/21515#issuecomment-357453634).

Fixes #21515

PR Close #21695
2018-02-07 12:06:45 -08:00
e3e7044d06 fix(aio): prevent heading misplacement while styles load (#21695)
During the initial load of the page (probably until the icon styles are
loaded and/or applied), the `.header-link` element is wider, pushing the
heading text slightly to the right (for a brief moment).

This commit prevents this slight shift by explicitly setting the width
for the `.header-link` element.

PR Close #21695
2018-02-07 12:06:45 -08:00
eb3bfc25be fix(aio): ensure header-links are visible at <600px (#21695)
PR Close #21695
2018-02-07 12:06:45 -08:00
94d769de71 refactor(aio): simplify .header-link styles (#21695)
PR Close #21695
2018-02-07 12:06:45 -08:00
66191e8a37 fix(aio): reduce flicker and reflows for initial rendering (#21695)
For the initial rendering, where there is no transition from a previous
visual state to a new one, animations make little sense. The page should
load with as few reflows as possible.
Similarly, while we typically want to defer updating the SideNav state
(e.g. opened/closed) until the "leaving" document is animated out of the
page, on the initial rendering (where there is no "leaving" document)
this leads to the SideNav flashing (from closed to open).

These worked as expected before, but several parts (mostly related to
documents with a SideNav) have been accidentally broken in recent
commits (e.g. when upgraded to latest material, or enabled animations
for DocViewer transitions, etc.).

This commit restores the previous behavior by ensuring that (on the
initial rendering) the SideNav state is updated as soon as possible and
that there will be no animations when:

1. The hamburger button appears.
2. The SideNav is opened.
3. The main section's width is adjusted to make room for the SideNav.

PR Close #21695
2018-02-07 12:06:45 -08:00
bec188506c refactor(aio): preserve HttpClient asynchronicity in tests (#21695)
Previously, the mocked `HttpClient` was synchronous in tests (despite
the actual `HttpClient` being asynchronous). Although we use observables
(which generally make the implementation sync/async-agnostic), the fact
that we have no control over when Angular updates/checks views and calls
lifecycle hooks resulted in different behavior (and errors) in tests
(with sync `HttpClient`) vs actual app (with async `HttpClient`).

This commit ensures that the behavior (and errors) are consistent
between the tests and the actual app by making the mocked `HttpClient`
asynchronous.

PR Close #21695
2018-02-07 12:06:45 -08:00
4f869ff755 fix(aio): remove links from sub-menu toggles (#21695)
Navigating to a document while trying to expand or collapse a sub-menu
is undesirable and confusing. All sub-menu toggles should have no other
effect than expanding/collapsing the corresponding sub-menu.

PR Close #21695
2018-02-07 12:06:45 -08:00
8f6047340e docs(animations): fix typo (disbled --> disabled) (#21695)
PR Close #21695
2018-02-07 12:06:45 -08:00
9744a1c966 fix(forms): set state before emitting a value from ngModelChange (#21514)
Closes #21513.

PR Close #21514
2018-02-07 12:05:43 -08:00
0bcfae7cac fix(forms): prevent event emission on enable/disable when emitEvent is false (#12366) (#21018)
Previously, the emitEvent flag was only checked when emitting on the current control.
Thus, if  the control was part of a hierarchy, events were emitted on the parent and the childrens.
This fixes the issue by properly passing the emitEvent flag to both parent and childrens.

Fixes #12366

PR Close #21018
2018-02-07 12:05:26 -08:00
140e7c00d1 fix(forms): make Validators.email support optional controls (#20869)
Bring email validator in line with other validators so that empty values are ignored.

PR Close #20869
2018-02-07 12:05:08 -08:00
941e88ff79 feat(forms): multiple validators for array method (#20766)
Change array method signature so that array of validator and/or async
validatior functions can be passed.

Fixes #20665

PR Close #20766
2018-02-07 12:04:48 -08:00
71ea931df5 build(aio): blacklist unwanted URLs from the generated sitemap.xml (#22061)
Closes #22017

PR Close #22061
2018-02-07 12:02:01 -08:00
545fdf10e2 docs(aio): fix TOH inclusion of HeroesService. (#21228)
Change docs where the MessageService is referenced

Fixes #20398

PR Close #21228
2018-02-07 12:01:32 -08:00
7e928db204 docs(forms): Custom Validator example selector name incorrect. (#20464)
Added bobby e2e test for template form.

Fixes: #20206

PR Close #20464
2018-02-07 12:01:12 -08:00
cd4c0eab94 docs(forms): Custom Validator example selector name incorrect. (#20464)
Name of selector in ForbiddenName example is not consistent with Validator class nor Html selector example. Added the selector name 'appForbiddenName' as an alias name for the input of the Validator class, and updated the view accordingly.

Fixes: #20206

PR Close #20464
2018-02-07 12:01:12 -08:00
5b06069fd9 docs: add changelog for 6.0.0-beta.3 2018-02-07 11:25:45 -08:00
d0f3162e84 release: cut the 6.0.0-beta.3 release 2018-02-07 11:23:15 -08:00
81537cb161 docs: add changelog for 5.2.4 2018-02-07 11:20:14 -08:00
370ab66c4f build(ivy): create hello world rollup (#22004)
This is a customization of the rollup_bundle rule from rules_nodejs
which adds the build-optimizer as a plugin.

Add a functional test with fast round-trip that asserts the minified app
still works.

Publish the min.js artifact on circleCI so we can track its size.

PR Close #22004
2018-02-06 08:25:22 -08:00
2707012181 fix(forms): publish missing types (#19941)
PR Close #19941
2018-02-06 08:02:15 -08:00
4d62be69c5 feat(ivy): memoize array literals in render3 (#21973)
PR Close #21973
2018-02-06 08:01:52 -08:00
7e51e52f55 perf(ivy): improve Uglify configuration in hello world integration test (#21985)
PR Close #21985
2018-02-06 08:01:18 -08:00
e81606c97a fix(core): fix proper propagation of subscriptions in EventEmitter (#22016)
Closes #21999

PR Close #22016
2018-02-06 07:56:33 -08:00
f791e9f081 fix(core): fix #20582, don't need to wrap zone in location change listener (#20640)
PR Close #20640
2018-02-05 13:06:22 -08:00
3aa7e0228a docs(aio): fix swap value (#20905)
'http.get' has been swapped in for 'of'

PR Close #20905
2018-02-05 13:05:58 -08:00
9d3326caa7 docs: clarify npm/yarn commands, add blank lines to mix md/html in table (#21606)
PR Close #21606
2018-02-05 13:02:14 -08:00
1940b18124 docs: update browser support (#21606)
PR Close #21606
2018-02-05 13:02:13 -08:00
0846784b98 fix(ivy): improve bindV perf and memory usage (#21881)
- Fix the case when first dynamic values are NO_CHANGE
- Do not store the static texts (even indexes) as bindings,
- Do not diff static texts (they do not change),
- Do not stringify static texts,
- Remove superfluous values walking.

PR Close #21881
2018-02-05 13:01:37 -08:00
0d10b9002e refactor(ivy): simplify bind code (#21881)
PR Close #21881
2018-02-05 13:01:37 -08:00
0c9ec37e26 ci: mark PRs with rejection as not green (#21922)
PR Close #21922
2018-02-05 13:01:11 -08:00
9a0700f5bd build(aio): add API static members to search index (#21988)
Previously searching for `compose` did not include `Validators`
in the search results because we were not including all the
`static` members of API docs in the index.

PR Close #21988
2018-02-05 13:00:47 -08:00
ae7bc2238d ci: add config for g3 status (#21996)
Ref #21642
PR Close #21996
2018-02-05 12:59:59 -08:00
5df626bbe1 refactor(ivy): misc refactoring (#22001)
PR Close #22001
2018-02-05 12:59:34 -08:00
5a624fa1be feat(aio): dynamically, pre-emptively, add noindex (#21992)
These tags are removed when the doc is ready and valid, but this will
allow us to block indexing in the case that the Angular app fails to
bootstrap or load the document for some non-404 reason.

This should get around the problem with hardcoded tags. See
c3fb820473

Closes #21941

PR Close #21992
2018-02-05 12:58:27 -08:00
3a86940ca5 fix(core): should check Zone existance when scheduleMicroTask (#20656)
PR Close #20656
2018-02-02 07:53:55 -08:00
7b120b5f73 docs: consistency fix in describing a custom tag (#21747)
PR Close #21747
2018-02-02 07:53:18 -08:00
de25d1886e docs(aio): update docs changelog with links to ts-to-js guide (#21763)
PR Close #21763
2018-02-02 07:52:30 -08:00
d77444b88a fix(aio): update Firebase redirects and SW routes (#21763)
Closes #21377

PR Close #21763
2018-02-02 07:52:30 -08:00
240aed29e0 build(aio): test Service Worker "routing" configuration (#21763)
PR Close #21763
2018-02-02 07:52:30 -08:00
bf29936af9 build(aio): test Firebase hosting redirection configuration (#21763)
PR Close #21763
2018-02-02 07:52:30 -08:00
339ca83f9d build(aio): move test config and e2e tests into subfolders (#21763)
This is in preparation of putting firebase and service worker
deployment tests into the project.

PR Close #21763
2018-02-02 07:52:30 -08:00
447783e575 docs: add docs for IE (#21824)
PR Close #21824
2018-02-02 07:51:46 -08:00
743d8bc845 feat(ivy): add canonical example of a pipe. (#21834)
PR Close #21834
2018-02-02 07:51:23 -08:00
f816666ede fix(ivy): generate lifecycle pattern (#21865)
Implement the lifecycle pattern defined in #21793

PR Close #21865
2018-02-02 07:50:31 -08:00
d3c2aa5f95 docs: add missing underline (#21892)
PR Close #21892
2018-02-02 07:49:32 -08:00
3cc1d76ee7 fix(ivy): generate correct interpolations (#21946)
Ivy compile would generate the an incorrect interpolation if there
were more than 8 interpolations in a text block.

Fixes: #21927

PR Close #21946
2018-02-02 07:49:13 -08:00
124283982b build(aio): move zip and live-example generation to yarn predocs task (#21970)
This will prevent the confusing errors for first time users who
try to generate the docs with `yarn docs` and are told there are
dangling links.

Closes #21944

PR Close #21970
2018-02-02 07:48:42 -08:00
65cf1add97 fix(ivy): remove unnecessary parameter of NgOnChangesFeature (#21879)
PR Close #21879
2018-02-01 08:33:36 -08:00
8b14488827 fix(common): don't convert null to a string when flushing a mock request (#21417)
A bug in TestRequest caused null response bodies to be stringified. This
change causes null to be treated faithfully.

Fixes #20744

PR Close #21417
2018-02-01 08:32:43 -08:00
f9fa157a09 docs(aio): add missing closing <code-examle> tag (#21771)
PR Close #21771
2018-02-01 08:31:20 -08:00
eb8ddd2983 feat(compiler-cli): reflect static methods added to classes in metadata (#21926)
PR Close #21926
2018-02-01 08:30:58 -08:00
1aa2947f70 feat(ivy): add support for attributes on ng-content nodes (#21935)
By adding attributes on the <ng-content> element template authors
can decide how content should be re-projected (or, in other words:
which selectors should match re-projected content).

PR Close #21935
2018-02-01 08:30:26 -08:00
982 changed files with 24706 additions and 6729 deletions

25
.circleci/bazel.rc Normal file
View File

@ -0,0 +1,25 @@
# These options are enabled when running on CI
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
# See remote cache documentation in /docs/BAZEL.md
# Don't be spammy in the logs
build --noshow_progress
# Don't run manual tests
test --test_tag_filters=-manual
# Enable experimental CircleCI bazel remote cache proxy
# See remote cache documentation in /docs/BAZEL.md
build --experimental_remote_spawn_cache --remote_rest_cache=http://localhost:7643
# Prevent unstable environment variables from tainting cache keys
build --experimental_strict_action_env
# Workaround https://github.com/bazelbuild/bazel/issues/3645
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
# Limit Bazel to consuming resources that fit in CircleCI "medium" class which is the default:
# https://circleci.com/docs/2.0/configuration-reference/#resource_class
build --local_resources=3072,2.0,1.0
# Retry in the event of flakes, eg. https://circleci.com/gh/angular/angular/31309
test --flaky_test_attempts=2

View File

@ -15,6 +15,13 @@
var_1: &docker_image angular/ngcontainer:0.1.0
var_2: &cache_key angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.1.0
# See remote cache documentation in /docs/BAZEL.md
var_3: &setup-bazel-remote-cache
run:
name: Start up bazel remote cache proxy
command: ~/bazel-remote-proxy -backend circleci://
background: true
# Settings common to each job
anchor_1: &job_defaults
working_directory: ~/ng
@ -34,14 +41,16 @@ jobs:
steps:
- checkout:
<<: *post_checkout
# Check BUILD.bazel formatting before we have a node_modules directory
# Then we don't need any exclude pattern to avoid checking those files
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
# Run the skylark linter to check our Bazel rules
- run: 'find . -type f -name "*.bzl" |
xargs java -jar /usr/local/bin/Skylint_deploy.jar ||
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- run: 'yarn buildifier -mode=check ||
(echo -e "\nBUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
- run: 'yarn skylint ||
(echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)'
- restore_cache:
key: *cache_key
@ -54,6 +63,11 @@ jobs:
steps:
- checkout:
<<: *post_checkout
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- restore_cache:
key: *cache_key
@ -62,7 +76,16 @@ jobs:
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
# This avoids waiting for a build command to finish before running the first test
# See https://github.com/bazelbuild/bazel/issues/4257
- run: bazel query --output=label '//modules/... union //packages/... union //tools/...' | xargs bazel test --config=ci
- run: bazel query --output=label '//modules/... union //packages/... union //tools/...' | xargs bazel test
# CircleCI will allow us to go back and view/download these artifacts from past builds.
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
destination: packages/core/test/bundling/hello_world/bundle.min.js
- store_artifacts:
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.brotli
destination: packages/core/test/bundling/hello_world/bundle.min.js.brotli
- save_cache:
key: *cache_key

11
.circleci/setup_cache.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
# Install bazel remote cache proxy
# This is temporary until the feature is no longer experimental on CircleCI.
# See remote cache documentation in /docs/BAZEL.md
set -u -e
readonly DOWNLOAD_URL="https://5-116431813-gh.circle-artifacts.com/0/pkg/bazel-remote-proxy-$(uname -s)_$(uname -m)"
curl --fail -o ~/bazel-remote-proxy "$DOWNLOAD_URL"
chmod +x ~/bazel-remote-proxy

View File

@ -25,7 +25,7 @@ ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
## Minimal reproduction of the problem with instructions
<!--
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
-->
## What is the motivation / use case for changing the behavior?

View File

@ -13,6 +13,30 @@ merge:
# text to show when some checks are failing
failureText: "The following checks are failing:"
# the g3 status will be added to your pull requests if they include files that match the patterns
g3Status:
# set to true to disable
disabled: false
# the name of the status
context: "google3"
# text to show when the status is pending
pendingDesc: "Googler: test this change in google3 http://go/angular-g3sync"
# text to show when the status is success
successDesc: "Does not affect google3"
# list of patterns to check for the files changed by the PR
# this list must be manually kept in sync with google3/third_party/javascript/angular2/copy.bara.sky
include:
- "BUILD.bazel"
- "LICENSE"
- "WORKSPACE"
- "modules/**"
- "packages/**"
# list of patterns to ignore for the files changed by the PR
exclude:
- "packages/language-service/**"
- "**/.gitignore"
- "**/.gitkeep"
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
mergeConflictComment: "Hi @{{PRAuthor}}! This PR has merge conflicts due to recent upstream merges.
\nPlease help to unblock it by resolving these conflicts. Thanks!"
@ -26,13 +50,15 @@ merge:
noConflict: true
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
requiredLabels:
- "PR target:"
- "PR target: *"
- "cla: yes"
# list of labels that a PR shouldn't have, checked after the required labels with a regexp
forbiddenLabels:
- "PR target: TBD"
- "PR action: cleanup"
- "PR action: review"
- "PR state: blocked"
- "cla: no"
# list of PR statuses that need to be successful
@ -60,9 +86,15 @@ triage:
triagedLabels:
-
- "type: bug"
- "severity"
- "freq"
- "comp:"
- "severity*"
- "freq*"
- "comp: *"
-
- "type: feature"
- "comp:"
- "comp: *"
-
- "type: refactor"
- "comp: *"
-
- "type: RFC / Discussion / question"
- "comp: *"

View File

@ -44,6 +44,7 @@ groups:
all:
users: all
required: 1
rejection_value: -999
# In this group, your self-approval does not count
author_approval:
auto: false

View File

@ -56,7 +56,6 @@ env:
- CI_MODE=aio
- CI_MODE=aio_e2e AIO_SHARD=0
- CI_MODE=aio_e2e AIO_SHARD=1
- CI_MODE=bazel
matrix:
fast_finish: true

View File

@ -32,6 +32,7 @@ filegroup(
"reflect-metadata",
"source-map-support",
"minimist",
"tslib",
] for ext in [
"*.js",
"*.json",

View File

@ -1,3 +1,99 @@
<a name="6.0.0-beta.4"></a>
# [6.0.0-beta.4](https://github.com/angular/angular/compare/6.0.0-beta.3...6.0.0-beta.4) (2018-02-14)
### Bug Fixes
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([b081dfe](https://github.com/angular/angular/commit/b081dfe)), closes [#21872](https://github.com/angular/angular/issues/21872)
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([dcf64a0](https://github.com/angular/angular/commit/dcf64a0)), closes [#22095](https://github.com/angular/angular/issues/22095)
* **common:** add locale currency values ([#21783](https://github.com/angular/angular/issues/21783)) ([420cc7a](https://github.com/angular/angular/commit/420cc7a)), closes [#20385](https://github.com/angular/angular/issues/20385)
* **common:** round currencies based on decimal digits in `CurrencyPipe` ([#21783](https://github.com/angular/angular/issues/21783)) ([44154e7](https://github.com/angular/angular/commit/44154e7)), closes [#10189](https://github.com/angular/angular/issues/10189)
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([be59c3a](https://github.com/angular/angular/commit/be59c3a))
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([72f8abd](https://github.com/angular/angular/commit/72f8abd)), closes [#22089](https://github.com/angular/angular/issues/22089)
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([16d1700](https://github.com/angular/angular/commit/16d1700))
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [#21980](https://github.com/angular/angular/issues/21980)
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([a751649](https://github.com/angular/angular/commit/a751649))
* **forms:** make Validators.email support optional controls ([#20869](https://github.com/angular/angular/issues/20869)) ([140e7c0](https://github.com/angular/angular/commit/140e7c0))
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([0bcfae7](https://github.com/angular/angular/commit/0bcfae7))
* **forms:** set state before emitting a value from ngModelChange ([#21514](https://github.com/angular/angular/issues/21514)) ([9744a1c](https://github.com/angular/angular/commit/9744a1c)), closes [#21513](https://github.com/angular/angular/issues/21513)
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([b37cee3](https://github.com/angular/angular/commit/b37cee3))
* **platform-browser:** add @Injectable where it was missing ([#22005](https://github.com/angular/angular/issues/22005)) ([0a1a397](https://github.com/angular/angular/commit/0a1a397))
* **platform-browser:** support 0/false/null values in transfer_state ([#22179](https://github.com/angular/angular/issues/22179)) ([6435ecd](https://github.com/angular/angular/commit/6435ecd))
### Features
* **bazel:** allow explicit specification of factories ([#22003](https://github.com/angular/angular/issues/22003)) ([e442881](https://github.com/angular/angular/commit/e442881))
* **compiler:** mark @NgModules in provider lists for identification at runtime ([#22005](https://github.com/angular/angular/issues/22005)) ([2d5e7d1](https://github.com/angular/angular/commit/2d5e7d1))
* **forms:** multiple validators for array method ([#20766](https://github.com/angular/angular/issues/20766)) ([941e88f](https://github.com/angular/angular/commit/941e88f)), closes [#20665](https://github.com/angular/angular/issues/20665)
* change @Injectable() to support tree-shakeable tokens ([#22005](https://github.com/angular/angular/issues/22005)) ([235a235](https://github.com/angular/angular/commit/235a235))
<a name="5.2.5"></a>
## [5.2.5](https://github.com/angular/angular/compare/5.2.4...5.2.5) (2018-02-14)
### Bug Fixes
* **aio:** update Firebase redirects and SW routes ([#21763](https://github.com/angular/angular/pull/21763)) ([#22104](https://github.com/angular/angular/pull/22104)) ([15ff7ba](https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([d57fd0b](https://github.com/angular/angular/commit/d57fd0b)), closes [#21872](https://github.com/angular/angular/issues/21872)
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([c5ec8d9](https://github.com/angular/angular/commit/c5ec8d9)), closes [#22095](https://github.com/angular/angular/issues/22095)
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([c6bdc83](https://github.com/angular/angular/commit/c6bdc83))
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([1b8ea10](https://github.com/angular/angular/commit/1b8ea10)), closes [#22089](https://github.com/angular/angular/issues/22089)
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([c4f841f](https://github.com/angular/angular/commit/c4f841f))
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [#21980](https://github.com/angular/angular/issues/21980)
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([47b71d9](https://github.com/angular/angular/commit/47b71d9))
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([56b9591](https://github.com/angular/angular/commit/56b9591))
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([0b23573](https://github.com/angular/angular/commit/0b23573))
* **platform-browser:** support 0/false/null values in transfer_state ([#22179](https://github.com/angular/angular/issues/22179)) ([da6ab91](https://github.com/angular/angular/commit/da6ab91))
<a name="6.0.0-beta.3"></a>
# [6.0.0-beta.3](https://github.com/angular/angular/compare/6.0.0-beta.2...6.0.0-beta.3) (2018-02-07)
### Bug Fixes
* **common:** don't convert null to a string when flushing a mock request ([#21417](https://github.com/angular/angular/issues/21417)) ([8b14488](https://github.com/angular/angular/commit/8b14488)), closes [#20744](https://github.com/angular/angular/issues/20744)
* **core:** fix [#20582](https://github.com/angular/angular/issues/20582), don't need to wrap zone in location change listener ([#20640](https://github.com/angular/angular/issues/20640)) ([f791e9f](https://github.com/angular/angular/commit/f791e9f))
* **core:** fix proper propagation of subscriptions in EventEmitter ([#22016](https://github.com/angular/angular/issues/22016)) ([e81606c](https://github.com/angular/angular/commit/e81606c)), closes [#21999](https://github.com/angular/angular/issues/21999)
* **core:** should check Zone existance when scheduleMicroTask ([#20656](https://github.com/angular/angular/issues/20656)) ([3a86940](https://github.com/angular/angular/commit/3a86940))
* **forms:** publish missing types ([#19941](https://github.com/angular/angular/issues/19941)) ([2707012](https://github.com/angular/angular/commit/2707012))
* **ivy:** generate correct interpolations ([#21946](https://github.com/angular/angular/issues/21946)) ([3cc1d76](https://github.com/angular/angular/commit/3cc1d76))
* **ivy:** generate lifecycle pattern ([#21865](https://github.com/angular/angular/issues/21865)) ([f816666](https://github.com/angular/angular/commit/f816666))
* **ivy:** improve `bindV` perf and memory usage ([#21881](https://github.com/angular/angular/issues/21881)) ([0846784](https://github.com/angular/angular/commit/0846784))
* **ivy:** remove unnecessary parameter of NgOnChangesFeature ([#21879](https://github.com/angular/angular/issues/21879)) ([65cf1ad](https://github.com/angular/angular/commit/65cf1ad))
### Features
* **compiler-cli:** reflect static methods added to classes in metadata ([#21926](https://github.com/angular/angular/issues/21926)) ([eb8ddd2](https://github.com/angular/angular/commit/eb8ddd2))
* **ivy:** add canonical example of a pipe. ([#21834](https://github.com/angular/angular/issues/21834)) ([743d8bc](https://github.com/angular/angular/commit/743d8bc))
* **ivy:** add support for attributes on ng-content nodes ([#21935](https://github.com/angular/angular/issues/21935)) ([1aa2947](https://github.com/angular/angular/commit/1aa2947))
* **ivy:** memoize array literals in render3 ([#21973](https://github.com/angular/angular/issues/21973)) ([4d62be6](https://github.com/angular/angular/commit/4d62be6))
### Performance Improvements
* **ivy:** improve Uglify configuration in hello world integration test ([#21985](https://github.com/angular/angular/issues/21985)) ([7e51e52](https://github.com/angular/angular/commit/7e51e52))
<a name="5.2.4"></a>
## [5.2.4](https://github.com/angular/angular/compare/5.2.3...5.2.4) (2018-02-07)
### Bug Fixes
* **common:** don't convert null to a string when flushing a mock request ([#21417](https://github.com/angular/angular/issues/21417)) ([c4fb696](https://github.com/angular/angular/commit/c4fb696)), closes [#20744](https://github.com/angular/angular/issues/20744)
* **core:** fix [#20582](https://github.com/angular/angular/issues/20582), don't need to wrap zone in location change listener ([#22007](https://github.com/angular/angular/issues/22007)) ([ce51ea9](https://github.com/angular/angular/commit/ce51ea9))
* **core:** fix proper propagation of subscriptions in EventEmitter ([#22016](https://github.com/angular/angular/issues/22016)) ([c6645e7](https://github.com/angular/angular/commit/c6645e7)), closes [#21999](https://github.com/angular/angular/issues/21999)
* **core:** should check Zone existance when scheduleMicroTask ([#20656](https://github.com/angular/angular/issues/20656)) ([aa9ba7f](https://github.com/angular/angular/commit/aa9ba7f))
<a name="6.0.0-beta.2"></a>
# [6.0.0-beta.2](https://github.com/angular/angular/compare/6.0.0-beta.1...6.0.0-beta.2) (2018-01-31)

View File

@ -51,7 +51,7 @@ and help you to craft the change so that it is successfully accepted into the pr
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like:
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
- version of Angular used
- 3rd-party libraries and their versions
@ -61,7 +61,7 @@ A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
@ -173,12 +173,12 @@ The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
```
docs(changelog): update change log to beta.5
docs(changelog): update changelog to beta.5
```
```
fix(release): need to depend on latest rxjs and zone.js
@ -203,7 +203,7 @@ Must be one of the following:
* **test**: Adding missing tests or correcting existing tests
### Scope
The scope should be the name of the npm package affected (as perceived by person reading changelog generated from commit messages.
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
The following is the list of supported scopes:
@ -232,10 +232,10 @@ There are currently a few exceptions to the "use package name" rule:
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
### Subject
The subject contains succinct description of the change:
The subject contains a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize first letter
* don't capitalize the first letter
* no dot (.) at the end
### Body
@ -266,7 +266,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
* https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
* https://help.github.com/articles/about-commit-email-addresses/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.

View File

@ -1,11 +1,14 @@
workspace(name = "angular")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary
# programs produce source-mapped stack traces.
RULES_NODEJS_VERSION = "926349cea4cd360afcd5647ccdd09d2d2fb471aa"
git_repository(
http_archive(
name = "build_bazel_rules_nodejs",
remote = "https://github.com/bazelbuild/rules_nodejs.git",
commit = "230d39a391226f51c03448f91eb61370e2e58c42",
url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION,
strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION,
sha256 = "5ba3c8c209078c2e3f0c6aa4abd01a1a561f92a5bfda04e25604af5f4734d69d",
)
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
@ -13,10 +16,13 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi
check_bazel_version("0.9.0")
node_repositories(package_json = ["//:package.json"])
git_repository(
RULES_TYPESCRIPT_VERSION = "0.10.1"
http_archive(
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
commit = "eb3244363e1cb265c84e723b347926f28c29aa35"
url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION,
strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION,
sha256 = "a2c81776a4a492ff9f878f9705639f5647bef345f7f3e1da09c9eeb8dec80485",
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
@ -56,3 +62,23 @@ http_archive(
strip_prefix = "bazel-9755c72b48866ed034bd28aa033e9abd27431b1e",
sha256 = "5b8443fc3481b5fcd9e7f348e1dd93c1397f78b223623c39eb56494c55f41962",
)
# We have a source dependency on the Devkit repository, because it's built with
# Bazel.
# This allows us to edit sources and have the effect appear immediately without
# re-packaging or "npm link"ing.
# Even better, things like aspects will visit the entire graph including
# ts_library rules in the devkit repository.
http_archive(
name = "angular_devkit",
url = "https://github.com/angular/devkit/archive/v0.3.1.zip",
strip_prefix = "devkit-0.3.1",
sha256 = "31d4b597fe9336650acf13df053c1c84dcbe9c29c6a833bcac3819cd3fd8cad3",
)
http_archive(
name = "org_brotli",
url = "https://github.com/google/brotli/archive/v1.0.2.zip",
strip_prefix = "brotli-1.0.2",
sha256 = "b43d5d6bc40f2fa6c785b738d86c6bbe022732fe25196ebbe43b9653a025920d",
)

View File

@ -39,7 +39,7 @@
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
"config": "tests/e2e/protractor.conf.js"
}
},
"lint": [
@ -50,12 +50,12 @@
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
"project": "tests/e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
"config": "src/karma.conf.js"
}
},
"defaults": {

View File

@ -105,8 +105,7 @@ The general setup is as follows:
* Open a terminal, ensure the dependencies are installed; run an initial doc generation; then start the doc-viewer:
```bash
yarn
yarn docs
yarn setup
yarn start
```

View File

@ -15,6 +15,7 @@ describe('Form Validation Tests', function () {
});
tests('Template-Driven Form');
bobTests();
});
describe('Reactive form', () => {

View File

@ -20,7 +20,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
// #enddocregion directive-providers
})
export class ForbiddenValidatorDirective implements Validator {
@Input() forbiddenName: string;
@Input('appForbiddenName') forbiddenName: string;
validate(control: AbstractControl): {[key: string]: any} {
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)

View File

@ -12,7 +12,7 @@
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<input id="name" name="name" class="form-control"
required minlength="4" forbiddenName="bob"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion name-input -->

View File

@ -21,7 +21,14 @@ import { MessagesComponent } from './messages/messages.component';
FormsModule
],
// #docregion providers
providers: [ HeroService, MessageService ],
// #docregion providers-heroservice
providers: [
HeroService,
// #enddocregion providers-heroservice
MessageService
// #docregion providers-heroservice
],
// #enddocregion providers-heroservice
// #enddocregion providers
bootstrap: [ AppComponent ]
})

View File

@ -3,6 +3,6 @@
<h2>Messages</h2>
<button class="clear"
(click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
<div *ngFor="let message of messageService.messages"> {{message}} </div>
</div>

View File

@ -190,6 +190,29 @@ describe('Tutorial part 6', () => {
const maxId = heroesBefore[heroesBefore.length - 1].id;
expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName});
});
it('displays correctly styled buttons', async () => {
element.all(by.buttonText('x')).then(buttons => {
for (const button of buttons) {
// Inherited styles from styles.css
expect(button.getCssValue('font-family')).toBe('Arial');
expect(button.getCssValue('border')).toContain('none');
expect(button.getCssValue('padding')).toBe('5px 10px');
expect(button.getCssValue('border-radius')).toBe('4px');
// Styles defined in heroes.component.css
expect(button.getCssValue('left')).toBe('194px');
expect(button.getCssValue('top')).toBe('-32px');
}
});
const addButton = element(by.buttonText('add'));
// Inherited styles from styles.css
expect(addButton.getCssValue('font-family')).toBe('Arial');
expect(addButton.getCssValue('border')).toContain('none');
expect(addButton.getCssValue('padding')).toBe('5px 10px');
expect(addButton.getCssValue('border-radius')).toBe('4px');
});
});
describe('Progressive hero search', () => {

View File

@ -51,7 +51,7 @@
}
/* #docregion additions */
.button {
button {
background-color: #eee;
border: none;
padding: 5px 10px;

View File

@ -1254,7 +1254,7 @@ also encapsulate a style sheet within a specific component.
### Styles configuration
<code-example hideCopy path="ajs-quick-reference/.angular-cli.1.json" region="styles" linenums="false">
<code-example hideCopy path="ajs-quick-reference/.angular-cli.1.json" region="styles" linenums="false"></code-example>
With the Angular CLI, you can configure your global styles in the `.angular-cli.json` file.
You can rename the extension to `.scss` to use sass.

View File

@ -92,7 +92,7 @@ You can control your app compilation by providing template compiler options in t
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhiteSpaces": false,
"preserveWhitespaces": true,
...
}
}
@ -234,9 +234,7 @@ done manually.
### *preserveWhitespaces*
This option tells the compiler whether to remove blank text nodes from compiled templates.
This option is `true` by default.
*Note*: It is recommended to set this explicitly to `false` as it emits smaller template factory modules and might be set to `false` by default in the future.
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
### *allowEmptyCodegenFiles*

View File

@ -0,0 +1,175 @@
<div class="breadcrumb">
<a href="#">API<a> / <a href="#">@core<a>
</div>
<header class="api-header">
<h1><label class="api-status-label experimental">experimental</label><label class="api-type-label class">class</label>Class Name</h1>
</header>
<div class="page-actions">
<a href="#"><label class="raised page-label"><i class="material-icons">mode_edit</i>suggest edits</label></a>
<a href="#"><label class="raised page-label"><i class="material-icons">code</i>view source</label></a>
</div>
<p>Class description goes here. This is a short and to the point one or two sentence description that easily introduces the reader to the class.</p>
<div class="api-body">
<section>
<h2>Overview</h2>
<code-example language="ts" hidecopy="true" ng-version="5.2.0"><aio-code class="simple-code" ng-reflect-ng-class="[object Object]" ng-reflect-code="
class <a href=&quot;api/core/Compi" ng-reflect-hide-copy="true" ng-reflect-language="ts" ng-reflect-linenums="" ng-reflect-path="" ng-reflect-region="" ng-reflect-title=""><pre class="prettyprint lang-ts">
<code class="animated fadeIn"><span class="kwd">class</span><span class="pln"> </span><a href="api/core/Compiler" class="code-anchor"><span class="typ">Compiler</span></a><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#compileModuleSync"><span class="pln">compileModuleSync</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;):</span><span class="pln"> </span><span class="typ">NgModuleFactory</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;</span></a><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAsync"><span class="pln">compileModuleAsync</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="typ">NgModuleFactory</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;&gt;</span></a><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAndAllComponentsSync"><span class="pln">compileModuleAndAllComponentsSync</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;):</span><span class="pln"> </span><span class="typ">ModuleWithComponentFactories</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;</span></a><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#compileModuleAndAllComponentsAsync"><span class="pln">compileModuleAndAllComponentsAsync</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;(</span><span class="pln">moduleType</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;):</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">&lt;</span><span class="typ">ModuleWithComponentFactories</span><span class="pun">&lt;</span><span class="pln">T</span><span class="pun">&gt;&gt;</span></a><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#clearCache"><span class="pln">clearCache</span><span class="pun">():</span><span class="pln"> </span><span class="kwd">void</span></a><span class="pln">
</span><a class="code-anchor" href="api/core/Compiler#clearCacheFor"><span class="pln">clearCacheFor</span><span class="pun">(</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Type</span><span class="pun">&lt;</span><span class="pln">any</span><span class="pun">&gt;)</span></a><span class="pln">
</span><span class="pun">}</span></code>
</pre></aio-code></code-example>
</section>
<section>
<h2>Description</h2>
<p>The longer class description goes here which can include multiple paragraphs.</p>
</p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
<h3>Subclasses</h3>
<ul>
<li><a href="#">Subclass1</a></li>
<li><a href="#">Subclass2</a></li>
<li><a href="#">Subclass3</a></li>
</ul>
<h3>See Also</h3>
<ul>
<li><a href="#">Link1</a></li>
<li><a href="#">Link2</a></li>
</ul>
</section>
<section>
<h2>Constructor</h2>
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
<aio-code class="simple-code"><pre class="prettyprint lang-">
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
</pre></aio-code></code-example>
</section>
<section>
<h2>Properties</h2>
<table class="is-full-width list-table">
<thead>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code><strong>Property1</strong></code>
</td>
<td><label class="property-type-label type">Type</label></td>
<td>Description goes here</td>
</tr>
<tr>
<td>
<code><strong>Property2</strong></code>
</td>
<td>Type</td>
<td>Description goes here</td>
</tr>
<tr>
<td>
<code><strong>Property3</strong></code>
</td>
<td>Type</td>
<td>Description goes here</td>
</tr>
</tbody>
</table>
</section>
<section class="api-method">
<h2>Methods</h2>
<table class="is-full-width item-table">
<thead>
<tr>
<th>Method1Name( )</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>Description goes here</p>
<br>
<p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
</td>
</tr>
</tbody>
</table>
<table class="is-full-width api-method item-table">
<thead>
<tr>
<th>Method2Name( )</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>Description goes here</p>
<hr>
<h5>Declaration</h5>
<code-example language="ts" hidecopy="true" ng-version="5.2.0">
<aio-code class="simple-code"><pre class="prettyprint lang-ts">
<code class="animated fadeIn"><span class="kwd">class</span><span class="pln"> </span><a href="api/animations/AnimationBuilder" class="code-anchor"><span class="typ">AnimationBuilder</span></a><span class="pln"> </span><span class="pun">{</span><span class="pln"></span><a class="code-anchor" href="api/animations/AnimationBuilder#build"><span class="pln">build</span><span class="pun">(</span><span class="pln">animation</span><span class="pun">:</span><span class="pln"> </span><span class="typ">AnimationMetadata</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="typ">AnimationMetadata</span><span class="pun">[]):</span><span class="pln"> </span><span class="typ">AnimationFactory</span></a><span class="pln"></span><span class="pun">}</span></code></pre>
</aio-code>
</code-example>
<h6>Parameters</h6>
<h6>Returns</h6>
<p>Returns information and results goes here.</p>
<h6>Errors</h6>
<p>Error information goes here</p>
<hr>
<p>Further details provided as needed. Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball.</p><hr>
<h6>Overloads</h6>
<table class="is-full-width">
<tbody>
<tr>
<td>
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
<aio-code class="simple-code"><pre class="prettyprint lang-">
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
</pre></aio-code></code-example>
</td>
<td>Description goes here</td>
</tr>
<tr>
<td>
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
<aio-code class="simple-code"><pre class="prettyprint lang-">
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
</pre></aio-code></code-example>
</td>
<td>Description goes here</td>
</tr>
</tbody>
</table>
<hr>
<h5>Example: Descriptive Title of Method Example</h5>
<p>Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball. Ham hock picanha burgdoggen pork belly rump bacon cupim. Bacon kielbasa sirloin shank strip steak ground round. Bresaola cow salami meatloaf pork chop leberkas flank turducken biltong meatball chuck pork tri-tip chicken. Ribeye corned beef shoulder, meatloaf strip steak jerky porchetta capicola alcatra ham.</p>
</td>
</tr>
</tbody>
</table>
</section>
<section>
<h2>Example: Descriptive Title of Combined Example Goes Here</h2>
<p>Intro description text about what the example is and how it can be used.</p>
<code-example hidecopy="true" class="no-box api-heading" ng-version="5.2.0">
<aio-code class="simple-code"><pre class="prettyprint lang-">
<code class="animated fadeIn"><span class="kwd">constructor</span><span class="pun">(</span><span class="pln">element</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">,</span><span class="pln"> keyframes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">[</span><span class="pln">key</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">]:</span><span class="pln"> </span><span class="kwd">string</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}[],</span><span class="pln"> duration</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> delay</span><span class="pun">:</span><span class="pln"> number</span><span class="pun">,</span><span class="pln"> easing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">string</span><span class="pun">,</span><span class="pln"> previousPlayers</span><span class="pun">:</span><span class="pln"> any</span><span class="pun">[])</span></code>
</pre></aio-code></code-example>
<p>Further explanation provided as needed. Bacon ipsum dolor amet pork belly capicola sirloin venison alcatra ground round ham hock jowl turkey picanha bresaola pancetta brisket chicken fatback. Burgdoggen kevin salami jowl shoulder jerky leberkas meatball.</p>
</section>
</div>

View File

@ -186,9 +186,9 @@ template for our `HeroListComponent`:
<code-example path="architecture/src/app/hero-list.component.html" title="src/app/hero-list.component.html"></code-example>
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<hero-detail>` uses Angular's [template syntax](guide/template-syntax).
Although this template uses typical HTML elements like `<h2>` and `<p>`, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<app-hero-detail>` uses Angular's [template syntax](guide/template-syntax).
In the last line of the template, the `<hero-detail>` tag is a custom element that represents a new component, `HeroDetailComponent`.
In the last line of the template, the `<app-hero-detail>` tag is a custom element that represents a new component, `HeroDetailComponent`.
The `HeroDetailComponent` is a *different* component than the `HeroListComponent` you've been reviewing.
The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the
@ -197,7 +197,7 @@ The `HeroDetailComponent` is a **child** of the `HeroListComponent`.
<img src="generated/images/guide/architecture/component-tree.png" alt="Metadata" class="left">
Notice how `<hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
Notice how `<app-hero-detail>` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts.
<hr class="clear"/>
@ -230,8 +230,8 @@ information Angular needs to create and present the component and its view.
Here are a few of the most useful `@Component` configuration options:
* `selector`: CSS selector that tells Angular to create and insert an instance of this component
where it finds a `<hero-list>` tag in *parent* HTML.
For example, if an app's HTML contains `<hero-list></hero-list>`, then
where it finds a `<app-hero-list>` tag in *parent* HTML.
For example, if an app's HTML contains `<app-hero-list></app-hero-list>`, then
Angular inserts an instance of the `HeroListComponent` view between those tags.
* `templateUrl`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates).

View File

@ -2,188 +2,96 @@
Angular supports most recent browsers. This includes the following specific versions:
<table>
<tr>
<th>
<th>
Browser
</th>
<th>
Supported versions
</th>
</tr>
<tr>
<td>
Chrome
</th>
</td>
<th>
<td>
latest
</td>
</tr>
<tr>
<td>
Firefox
</th>
</td>
<th>
<td>
latest
</td>
</tr>
<tr>
<td>
Edge
</th>
</td>
<th>
<td>
2 most recent major versions
</td>
</tr>
<tr>
<td>
IE
</th>
<th>
Safari
</th>
<th>
iOS
</th>
<th>
Android
</th>
<th>
</td>
<td>
11<br>10<br>9
</td>
</tr>
<tr>
<tr>
<td>
IE Mobile
</th>
</tr>
<tr>
<td>
latest
</td>
<td>
latest
</td>
<td>
14
</td>
<td>
11
</td>
<td>
10
</td>
<td>
10
</td>
<td>
Nougat (7.0)<br>Marshmallow (6.0)
</td>
<td>
11
</td>
</tr>
<tr>
<td>
Safari
</td>
<td>
2 most recent major versions
</td>
</tr>
<tr>
<td>
iOS
</td>
<td>
2 most recent major versions
</td>
<td>
13
</td>
<td>
10
</td>
<td>
9
</td>
<td>
9
</td>
<td>
Lollipop<br>(5.0, 5.1)
</td>
<td>
</td>
</tr>
</tr>
<tr>
<td>
Android
</td>
<td>
Nougat (7.0)<br>Marshmallow (6.0)<br>Lollipop (5.0, 5.1)<br>KitKat (4.4)
</td>
<td>
</td>
<td>
9
</td>
<td>
8
</td>
<td>
8
</td>
<td>
KitKat<br>(4.4)
</td>
<td>
</td>
</tr>
<tr>
<td>
</td>
<td>
</td>
<td>
</td>
<td>
</td>
<td>
7
</td>
<td>
7
</td>
<td>
Jelly Bean<br>(4.1, 4.2, 4.3)
</td>
<td>
</td>
</tr>
</tr>
</table>
@ -219,21 +127,16 @@ the CLI created with your project.
This file incorporates the mandatory and many of the optional polyfills as JavaScript `import` statements.
The npm packages for the _mandatory_ polyfills (such as `zone.js`) were installed automatically for you when you created your project and
their corresponding `import` statements are ready to go.
You probably won't touch these.
The npm packages for the _mandatory_ polyfills (such as `zone.js`) were installed automatically for you when you created your project and their corresponding `import` statements are ready to go. You probably won't touch these.
But if you need an optional polyfill, you'll have to install its npm package with `npm` or `yarn`.
For example, [if you need the web animations polyfill](http://caniuse.com/#feat=web-animation),
you could install it with either of the following commands:
But if you need an optional polyfill, you'll have to install its npm package.
For example, [if you need the web animations polyfill](http://caniuse.com/#feat=web-animation), you could install it with `npm`, using the following command (or the `yarn` equivalent):
<code-example language="sh" class="code-shell">
npm install --save web-animations-js
yarn add web-animations-js
</code-example>
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement
as in the following example:
Then open the `polyfills.ts` file and un-comment the corresponding `import` statement as in the following example:
<code-example title="src/polyfills.ts">
/**
@ -243,7 +146,7 @@ as in the following example:
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
</code-example>
If you can't find the polyfill you want in `polyfills.ts`,
If you can't find the polyfill you want in `polyfills.ts`,
add it yourself, following the same pattern:
1. install the npm package
@ -283,6 +186,7 @@ These are the polyfills required to run an Angular application on each supported
<td>
[ES7/reflect](guide/browser-support#core-es7-reflect) (JIT only)
</td>
</tr>
@ -295,8 +199,8 @@ These are the polyfills required to run an Angular application on each supported
<td>
[ES6](guide/browser-support#core-es6)
</td>
</tr>
@ -309,7 +213,6 @@ These are the polyfills required to run an Angular application on each supported
<td>
[ES6<br>classList](guide/browser-support#classlist)
</td>
@ -323,8 +226,7 @@ These are the polyfills required to run an Angular application on each supported
Some features of Angular may require additional polyfills.
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today.
You'll need a polyfill to use animations in other browsers.
For example, the animations library relies on the standard web animation API, which is only available in Chrome and Firefox today. You'll need a polyfill to use animations in other browsers.
Here are the features which may require additional polyfills:
@ -351,19 +253,20 @@ Here are the features which may require additional polyfills:
<td>
[JIT compilation](guide/aot-compiler).
[JIT compilation](guide/aot-compiler).
Required to reflect for metadata.
</td>
<td>
[ES7/reflect](guide/browser-support#core-es7-reflect)
</td>
<td>
All current browsers.
Enabled by default.
Can remove If you always use AOT and only use Angular decorators.
All current browsers. Enabled by default.
Can remove if you always use AOT and only use Angular decorators.
</td>
</tr>
@ -373,12 +276,13 @@ Here are the features which may require additional polyfills:
<td>
[Animations](guide/animations)
</td>
<td>
[Web Animations](guide/browser-support#web-animations)
</td>
<td>
@ -391,13 +295,23 @@ Here are the features which may require additional polyfills:
<td>
If you use the following deprecated i18n pipes: [date](api/common/DeprecatedDatePipe), [currency](api/common/DeprecatedCurrencyPipe), [decimal](api/common/DeprecatedDecimalPipe) and [percent](api/common/DeprecatedPercentPipe)
If you use the following deprecated i18n pipes:
[date](api/common/DeprecatedDatePipe),
[currency](api/common/DeprecatedCurrencyPipe),
[decimal](api/common/DeprecatedDecimalPipe),
[percent](api/common/DeprecatedPercentPipe)
</td>
<td>
[Intl API](guide/browser-support#intl)
</td>
<td>
@ -410,13 +324,15 @@ Here are the features which may require additional polyfills:
<td>
[NgClass](api/common/NgClass) on SVG elements
[NgClass](api/common/NgClass)
on SVG elements
</td>
<td>
[classList](guide/browser-support#classlist)
</td>
<td>
@ -429,14 +345,19 @@ Here are the features which may require additional polyfills:
<td>
[Http](guide/http) when sending and receiving binary data
[Http](guide/http)
when sending and receiving binary data
</td>
<td>
[Typed&nbsp;Array](guide/browser-support#typedarray)<br>
[Blob](guide/browser-support#blob)<br>
[FormData](guide/browser-support#formdata)
</td>
<td>
@ -476,6 +397,7 @@ Below are the polyfills which are used to test the framework itself. They are a
<td>
<a id='core-es7-reflect' href="https://github.com/zloirock/core-js/blob/master/es7/reflect.js">ES7/reflect</a>
</td>
<td>
@ -491,7 +413,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='core-es6' href="https://github.com/zloirock/core-js">ES6</a>
</td>
<td>
@ -507,7 +431,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='classlist' href="https://github.com/eligrey/classList.js">classList</a>
</td>
<td>
@ -523,7 +449,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='intl' href="https://github.com/andyearnshaw/Intl.js">Intl</a>
</td>
<td>
@ -539,7 +467,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='web-animations' href="https://github.com/web-animations/web-animations-js">Web Animations</a>
</td>
<td>
@ -555,7 +485,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='typedarray' href="https://github.com/inexorabletash/polyfill/blob/master/typedarray.js">Typed Array</a>
</td>
<td>
@ -571,7 +503,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='blob' href="https://github.com/eligrey/Blob.js">Blob</a>
</td>
<td>
@ -587,7 +521,9 @@ Below are the polyfills which are used to test the framework itself. They are a
<tr>
<td>
<a id='formdata' href="https://github.com/francois2metz/html5-formdata">FormData</a>
</td>
<td>
@ -603,14 +539,13 @@ Below are the polyfills which are used to test the framework itself. They are a
</table>
\* Figures are for minified and gzipped code,
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.
{@a non-cli}
## Polyfills for non-CLI users
If you aren't using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
If you are not using the CLI, you should add your polyfill scripts directly to the host web page (`index.html`), perhaps like this.
<code-example title="src/index.html">
&lt;!-- pre-zone polyfills -->
@ -625,8 +560,13 @@ If you aren't using the CLI, you should add your polyfill scripts directly to th
// __Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// __Zone_disable_on_property = true; // disable patch onProperty such as onclick
// __zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
&lt;/script>
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// __Zone_enable_cross_context_check = true;
&lt;/script>
&lt;!-- zone.js required by Angular -->
&lt;script src="node_modules/zone.js/dist/zone.js">&lt;/script>

View File

@ -120,11 +120,13 @@ The documentation for the version prior to v.2.2.0 has been removed.
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
The updated TypeScript to JavaScript guide (removed August 2017, PR #18694)
explains how to write apps in ES6/7
The updated TypeScript to JavaScript guide explains how to write apps in ES6/7
by translating the common idioms in the TypeScript documentation examples
(and elsewhere on the web) to ES6/7 and ES5.
This was [removed in August 2017](https://github.com/angular/angular/pull/18694) but can still be
viewed in the [v2 documentation](https://v2.angular.io/docs/ts/latest/cookbook/ts-to-js.html).
## Sync with Angular v.2.1.1 (2016-10-21)
Docs and code samples updated and tested with Angular v.2.1.1.

View File

@ -140,6 +140,11 @@ is available to <code>declarations</code> of this module.</p>
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
</td>
</tr>
<tr>
<td><code>&lt;div <b>[ngStyle]</b>="{'property': 'value'}"&gt;</code><br><code>&lt;div <b>[ngStyle]</b>="dynamicStyles()"&gt;</code></td>
<td><p>Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.</p>
</td>
</tr>
</tbody></table>
<table class="is-full-width is-fixed-layout">

View File

@ -101,7 +101,7 @@ Begin the title with the markdown `#` character. Alternatively, you can write th
**Only one title (`<h1>`) per document!**
Title text should be in "Title Case", which means that you use capital letters to start the first words and all _principal_ words. Use lower case letters for _secondary words such as "in", "of", and "the".
Title text should be in "Title Case", which means that you use capital letters to start the first words and all _principal_ words. Use lower case letters for _secondary_ words such as "in", "of", and "the".
```html
# The Meat of the Matter

View File

@ -97,7 +97,7 @@ In fact, many libraries declare and export components you'll never use.
For example, a material design library will export all components because it doesnt know which ones you will use. However, it is unlikely that you will use them all.
For the ones you don't reference, the tree shaker drops these components from the final code package.
If a component isn't an _entry component_ or isn't found in a template,
If a component isn't an _entry component_ and isn't found in a template,
the tree shaker will throw it away. So, it's best to add only the components that are truly entry components to help keep your app
as trim as possible.

View File

@ -98,7 +98,7 @@ When the CLI generated the `CustomerDashboardComponent` for the feature module,
</code-example>
To see this HTML in the `AppComponent`, you first have to export the `CustomerDashboardComponent` in the `CustomerDashboardModule`. In `customer-dashboard.module.ts`, just beneath the declarations array, add an exports array containing `CustomerDashboardModule`:
To see this HTML in the `AppComponent`, you first have to export the `CustomerDashboardComponent` in the `CustomerDashboardModule`. In `customer-dashboard.module.ts`, just beneath the `declarations` array, add an `exports` array containing `CustomerDashboardModule`:
<code-example path="feature-modules/src/app/customer-dashboard/customer-dashboard.module.ts" region="component-exports" title="src/app/customer-dashboard/customer-dashboard.module.ts" linenums="false">
</code-example>

View File

@ -171,7 +171,7 @@ comes together:
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
</code-example>
Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector, `forbiddenName`, to any input element to activate it. For example:
Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector, `appForbiddenName`, to any input element to activate it. For example:
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" title="template/hero-form-template.component.html (forbidden-name-input)" linenums="false">

View File

@ -512,10 +512,6 @@ You rarely access Angular feature modules directly. You usually import them from
## NgModule
<div class="l-sub-section">
Helps you organize an application into cohesive blocks of functionality.
An NgModule identifies the components, directives, and pipes that the application uses along with the list of external NgModules that the application needs, such as `FormsModule`.
@ -526,7 +522,7 @@ For details and examples, see [NgModules](guide/ngmodules) and the
related files in that section.
</div>
{@a O}
@ -631,13 +627,10 @@ For more information, see the [Routing & Navigation](guide/router) page.
## Router module
<div class="l-sub-section">
A separate [NgModule](guide/glossary#ngmodule) that provides the necessary service providers and directives for navigating through application views.
For more information, see the [Routing & Navigation](guide/router) page.
</div>
## Routing component

View File

@ -358,7 +358,7 @@ subscribes without a callback.
The bare `.subscribe()` _seems_ pointless.
In fact, it is essential.
Merely calling `HeroService.addHero()` **does not initiate the DELETE request.**
Merely calling `HeroService.deleteHero()` **does not initiate the DELETE request.**
<code-example
path="http/src/app/heroes/heroes.component.ts"

View File

@ -57,7 +57,7 @@ You can also use the VS Quick Open (⌘+P) to search for the extension. When you
enter the following command:
```sh
ext install ng-template
ext install Angular.ng-template
```
Then click the install button to install the Angular Language Service.

View File

@ -1,7 +1,5 @@
# Lifecycle Hooks
<img src="generated/images/guide/lifecycle-hooks/hooks-in-sequence.png" alt="Us" class="left">
A component has a lifecycle managed by Angular.
Angular creates it, renders it, creates and renders its children,
@ -10,7 +8,7 @@ checks it when its data-bound properties change, and destroys it before removing
Angular offers **lifecycle hooks**
that provide visibility into these key life moments and the ability to act when they occur.
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
A directive has the same set of lifecycle hooks.
{@a hooks-overview}
@ -27,7 +25,7 @@ that Angular calls shortly after creating the component:
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" title="peek-a-boo.component.ts (excerpt)" linenums="false"></code-example>
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
No directive or component will implement all of the lifecycle hooks.
Angular only calls a directive/component hook method *if it is defined*.
{@a hooks-purpose-timing}
@ -88,12 +86,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular projects external content into the component's view.
Respond after Angular projects external content into the component's view / the view that a directive is in.
Called _once_ after the first `ngDoCheck()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -102,12 +98,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular checks the content projected into the component.
Respond after Angular checks the content projected into the directive/component.
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -116,12 +110,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular initializes the component's views and child views.
Respond after Angular initializes the component's views and child views / the view that a directive is in.
Called _once_ after the first `ngAfterContentChecked()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -130,12 +122,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular checks the component's views and child views.
Respond after Angular checks the component's views and child views / the view that a directive is in.
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>

View File

@ -27,7 +27,7 @@ JavaScript modules help you namespace, preventing accidental global variables.
## NgModules
<!-- KW-- perMisko: let's discuss. This does not answer the question why it is different. Also, last sentence is confusing.-->
NgModules are classes decorated with `@NgModule`. The `@NgModule` decorators `imports` array tells Angular what other NgModules the current module needs. The modules in the imports array are different than JavaScript modules because they are NgModules rather than regular JavaScript modules. Classes with an `@NgModule` decorator are by convention kept in their own files, but what makes them an `NgModule` isnt being in their own file, like JavaScript modules; its the presence of `@NgModule` and its metadata.
NgModules are classes decorated with `@NgModule`. The `@NgModule` decorators `imports` array tells Angular what other NgModules the current module needs. The modules in the `imports` array are different than JavaScript modules because they are NgModules rather than regular JavaScript modules. Classes with an `@NgModule` decorator are by convention kept in their own files, but what makes them an `NgModule` isnt being in their own file, like JavaScript modules; its the presence of `@NgModule` and its metadata.
The `AppModule` generated from the Angular CLI demonstrates both kinds of modules in action:
@ -53,7 +53,7 @@ export class AppModule { }
```
The NgModule classes differ from JavaScript module classes in the following key ways:
The NgModule classes differ from JavaScript module in the following key ways:
* An NgModule bounds [declarable classes](guide/ngmodule-faq#q-declarable) only.
Declarables are the only classes that matter to the [Angular compiler](guide/ngmodule-faq#q-angular-compiler).

View File

@ -45,7 +45,7 @@ NgModule metadata does the following:
* Declares which components, directives, and pipes belong to the module.
* Makes some of those components, directives, and pipes public so that other module's component templates can use them.
* Imports other modules with the components, directives, and pipes that components in the current module need.
* Provides services at the other application components can use.
* Provides services that the other application components can use.
Every Angular app has at least one module, the root module.
You [bootstrap](guide/bootstrapping) that module to launch the application.

View File

@ -496,7 +496,7 @@ Remember that impure pipes are called every few milliseconds.
If you're not careful, this pipe will punish the server with requests.
In the following code, the pipe only calls the server when the request URL changes and it caches the server response.
The code uses the [Angular http](guide/http) client to retrieve data</span>:
The code uses the [Angular http](guide/http) client to retrieve data:
<code-example path="pipes/src/app/fetch-json.pipe.ts" title="src/app/fetch-json.pipe.ts">

View File

@ -10,7 +10,7 @@ see the <live-example></live-example>.
<hr>
## Create a service
You can provide services to your app by using the providers array in an NgModule.
You can provide services to your app by using the `providers` array in an NgModule.
Consider the default app generated by the CLI. In order to add a user service to it,
you can generate one by entering the following command in the terminal window:
@ -20,7 +20,7 @@ ng generate service User
This creates a service called `UserService`. You now need to make the service available in your
app's injector. Update `app.module.ts` by importing it with your other import statements at the top
of the file and adding it to the providers array:
of the file and adding it to the `providers` array:
<code-example path="providers/src/app/app.module.ts" title="src/app/app.module.ts" linenums="false">
</code-example>
@ -28,7 +28,7 @@ of the file and adding it to the providers array:
## Provider scope
When you add a service provider to the providers array of the root module, its available throughout the app. Additionally, when you import a module that has providers, those providers are also available to all the classes in the app as long they have the lookup token. For example, if you import the `HttpClientModule` into your `AppModule`, its providers are then available to the entire app and you can make HTTP requests from anywhere in your app.
When you add a service provider to the `providers` array of the root module, its available throughout the app. Additionally, when you import a module that has providers, those providers are also available to all the classes in the app as long they have the lookup token. For example, if you import the `HttpClientModule` into your `AppModule`, its providers are then available to the entire app and you can make HTTP requests from anywhere in your app.
## Limiting provider scope by lazy loading modules

View File

@ -418,7 +418,7 @@ To see the form model, add the following line after the
closing `form` tag in the `hero-detail.component.html`:
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
</code-example>

View File

@ -350,3 +350,10 @@ It's the perfect place to reproduce a bug when you want to
<a href="https://github.com/angular/angular/issues/new" title="File an Angular issue">file an issue with Angular itself</a>.
For real development, we strongly recommend [developing locally](guide/setup#develop-locally).
## Appendix: develop locally with IE
If you develop angular locally with `ng serve`, there will be `websocket` connection being setup automatically between browser and local dev server, so when your code change, browser can automatically refresh.
In windows, by default one application can only have 6 websocket connections, <a href="https://msdn.microsoft.com/library/ee330736%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#websocket_maxconn" title="MSDN WebSocket settings">MSDN WebSocket Settings</a>.
So if IE was refreshed manunally or automatically by `ng serve`, sometimes, the websocket will not close properly, when websocket connections exceed limitations, `SecurityError` will be thrown, this error will not affect the angular application, you can just restart IE to clear this error, or modify the windows registry to update the limitations.

View File

@ -135,3 +135,12 @@ QuickStart identifies two *typings*, or `d.ts`, files:
you can view an example in the [webpack](guide/webpack) page.
QuickStart doesn't require these typings but many of the samples do.
{@a target}
### *target*
By default, the target is `es5`, you can configure the target to `es6` if you only want to deploy the application to
es6 compatible browser. But if you configure the target to `es6` in some old browser such as `IE`, `Syntax Error` will be thrown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,9 @@
[
{
"startDate": "2018-02-14",
"endDate": "2018-04-22",
"message": "Join us for ng-conf<br/>Apr 18th-20th, 2018",
"imageUrl": "generated/images/marketing/home/ng-conf.png",
"linkUrl": "http://ng-conf.org/"
}
]

View File

@ -20,7 +20,7 @@
<div class="feature">
<div class="feature-title">Native</div>
<p class="text-body">Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.</p>
<p class="text-body">Build native mobile apps with strategies from Cordova, Ionic, or NativeScript.</p>
</div>
<div class="feature">

View File

@ -29,14 +29,7 @@
<div class="home-rows">
<!-- Announcement Bar -->
<div class="homepage-container">
<div class="announcement-bar">
<img src="generated/images/marketing/home/ng-atl.png">
<p>Join us in Atlanta for ngATL<br>Jan 30 - Feb 2, 2018</p>
<a class="button" href="http://ng-atl.org/">Learn More</a>
</div>
</div>
<aio-announcement-bar></aio-announcement-bar>
<!-- Group 1 -->
<div layout="row" layout-xs="column" class="home-row homepage-container">

View File

@ -241,6 +241,19 @@
"rev": true,
"title": "NinjaCodeGen - Angular CRUD Generator",
"url": "https://ninjaCodeGen.com"
},
"angular-playground": {
"desc": "UI development environment for building, testing, and documenting Angular applications.",
"rev": true,
"title": "Angular Playground",
"url": "http://www.angularplayground.it/"
},
"nx": {
"desc": "Nx (Nrwl Extensions for Angular) is an open source toolkit built on top of Angular CLI to help enterprise teams develop Angular at scale.",
"rev": true,
"title": "Nx",
"logo": "https://nrwl.io/assets/nx-logo.png",
"url": "https://nrwl.io/nx"
}
}
},
@ -438,6 +451,13 @@
"rev": true,
"title": "Essential Angular",
"url": "https://gumroad.com/l/essential_angular"
},
"angular-buch": {
"desc": "The first German book about Angular. It gives you a detailed practical overview of the key concepts of the platform. In each chapter a sample application is built upon with a new Angular topic. All sources are available on GitHub.",
"logo": "https://angular-buch.com/assets/img/brand.svg",
"rev": true,
"title": "Angular-Buch (German)",
"url": "https://angular-buch.com/"
}
}
},
@ -482,6 +502,13 @@
"title": "CodeSchool: Accelerating Through Angular",
"url": "https://www.codeschool.com/courses/accelerating-through-angular-2"
},
"angular-playbook": {
"desc": "Learn advanced Angular best practices for enterprise teams, created by Nrwl.io.",
"logo": "https://nrwl.io/assets/logo_footer_2x.png",
"rev": true,
"title": "Angular Enterprise Playbook",
"url": "https://angularplaybook.com"
},
"a2b": {
"desc": "Hundreds of Angular courses for all skill levels",
"logo": "",
@ -652,6 +679,13 @@
"rev": true,
"title": "We Are One Sàrl",
"url": "https://weareone.ch/courses/angular/"
},
"angular-schule": {
"desc": "Angular onsite training and public workshops in Germany from the authors of the German Angular book. We also regularly post articles and videos on our blog (in English and German language).",
"logo": "https://angular.schule/assets/img/brand.svg",
"rev": true,
"title": "Angular.Schule (German)",
"url": "https://angular.schule/"
}
}
}

View File

@ -74,7 +74,6 @@
},
{
"url": "tutorial",
"title": "Tutorial",
"tooltip": "The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.",
"children": [
@ -122,7 +121,6 @@
},
{
"url": "guide/architecture",
"title": "Fundamentals",
"tooltip": "The fundamentals of Angular",
"children": [
@ -170,6 +168,11 @@
"title": "Attribute Directives",
"tooltip": "Attribute directives attach behavior to elements."
},
{
"url": "guide/structural-directives",
"title": "Structural Directives",
"tooltip": "Structural directives manipulate the layout of the page."
},
{
"url": "guide/pipes",
"title": "Pipes",

View File

@ -29,7 +29,7 @@ and annotate the component class with `@Component`.
The CLI generated three metadata properties:
1. `selector`&mdash; the components CSS element selector
1. `selector`&mdash; the component's CSS element selector
1. `templateUrl`&mdash; the location of the component's template file.
1. `styleUrls`&mdash; the location of the component's private CSS styles.

View File

@ -96,7 +96,7 @@ Bind the `HeroesComponent.selectedHero` to the element's `hero` property like th
`[hero]="selectedHero"` is an Angular [property binding](guide/template-syntax#property-binding).
It's a _one way_ data binding from
the `selectedHero` property of the `HeroComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
the `selectedHero` property of the `HeroesComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
Now when the user clicks a hero in the list, the `selectedHero` changes.
When the `selectedHero` changes, the _property binding_ updates `hero`

View File

@ -97,7 +97,7 @@ Since you did not, you'll have to provide it yourself.
Open the `AppModule` class, import the `HeroService`, and add it to the `@NgModule.providers` array.
<code-example path="toh-pt4/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers">
<code-example path="toh-pt4/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (providers)" region="providers-heroservice">
</code-example>
The `providers` array tells Angular to create a single, shared instance of `HeroService`
@ -105,6 +105,12 @@ and inject into any class that asks for it.
The `HeroService` is now ready to plug into the `HeroesComponent`.
<div class="alert is-important">
This is a interim code sample that will allow you to provide and use the `HeroService`. At this point, the code will differ from the `HeroService` in the ["final code review"](#final-code-review).
</div>
<div class="alert is-helpful">
Learn more about _providers_ in the [Providers](guide/providers) guide.
@ -423,6 +429,10 @@ Here are the code files discussed on this page and your app should look like thi
path="toh-pt4/src/app/messages/messages.component.css">
</code-pane>
<code-pane title="src/app/app.module.ts"
path="toh-pt4/src/app/app.module.ts">
</code-pane>
<code-pane title="src/app/app.component.html"
path="toh-pt4/src/app/app.component.html">
</code-pane>

View File

@ -135,7 +135,7 @@ Convert that method to use `HttpClient`
Refresh the browser. The hero data should successfully load from the
mock server.
You've swapped `http.get` for `of` and the app keeps working without any other changes
You've swapped `of` for `http.get` and the app keeps working without any other changes
because both functions return an `Observable<Hero[]>`.
### Http methods return one value

View File

@ -0,0 +1,28 @@
import { SitePage } from './app.po';
describe('site search', () => {
let page;
beforeEach(() => {
page = new SitePage();
page.navigateTo('');
});
it('should find pages when searching by a partial word in the title', () => {
page.enterSearch('ngCont');
expect(page.getSearchResults()).toContain('NgControl');
page.enterSearch('accessor');
expect(page.getSearchResults()).toContain('ControlValueAccessor');
});
it('should find API docs whose instance member name matches the search query', () => {
page.enterSearch('decode');
expect(page.getSearchResults()).toContain('HttpParameterCodec');
});
it('should find API docs whose static method name matches the search query', () => {
page.enterSearch('compose');
expect(page.getSearchResults()).toContain('Validators');
});
});

View File

@ -12,50 +12,98 @@
// make sure the routing RegExp in `ngsw-manifest.json` is updated accordingly.
//////////////////////////////////////////////////////////////////////////////////////////////
// cli-quickstart.html, glossary.html, quickstart.html, server-communication.html, style-guide.html
{"type": 301, "source": "/docs/ts/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/glossary.html", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/ts/latest/quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/ts/latest/guide/server-communication.html", "destination": "/guide/http"},
{"type": 301, "source": "/docs/ts/latest/guide/style-guide.html", "destination": "/guide/styleguide"},
// A random bad indexed page that used `api/api`
{"type": 301, "source": "/api/api/:rest*", "destination": "/api/:rest*"},
// guide/cli-quickstart, styleguide
// Guide renames
{"type": 301, "source": "/docs/*/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/*/latest/glossary.html", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/*/latest/quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/*/latest/guide/server-communication.html", "destination": "/guide/http"},
{"type": 301, "source": "/docs/*/latest/guide/style-guide.html", "destination": "/guide/styleguide"},
{"type": 301, "source": "/guide/cli-quickstart", "destination": "/guide/quickstart"},
{"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
// cookbook/a1-a2-quick-reference.html, cookbook/component-communication.html, cookbook/dependency-injection.html
{"type": 301, "source": "/docs/ts/latest/cookbook/a1-a2-quick-reference.html", "destination": "/guide/ajs-quick-reference"},
{"type": 301, "source": "/docs/ts/latest/cookbook/component-communication.html", "destination": "/guide/component-interaction"},
{"type": 301, "source": "/docs/ts/latest/cookbook/dependency-injection.html", "destination": "/guide/dependency-injection-in-action"},
// cookbook, cookbook/, cookbook/index.html
{"type": 301, "source": "/docs/ts/latest/cookbook", "destination": "/docs"},
{"type": 301, "source": "/docs/ts/latest/cookbook/", "destination": "/docs"},
{"type": 301, "source": "/docs/ts/latest/cookbook/index.html", "destination": "/docs"},
// cookbook/*.html
{"type": 301, "source": "/docs/ts/latest/cookbook/:cookbook.html", "destination": "/guide/:cookbook"},
// docs/ts/latest/api/<package>/index/*-<type>.html (+ special case for `NgFor` which has been renamed)
{"type": 301, "source": "/docs/ts/latest/api/common/index/NgFor-directive.html", "destination": "/api/common/NgForOf"},
{"type": 301, "source": "/docs/ts/latest/api/:package/index/:api-*.html", "destination": "/api/:package/:api"},
// docs/ts/latest
{"type": 301, "source": "/docs/ts/latest", "destination": "/docs"},
// guide/*, tutorial/*, **/*
{"type": 301, "source": "/docs/ts/latest/:any*", "destination": "/:any*"},
// aot-compiler.md and metadata.md combined into aot-compiler.md - issue #19510
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"},
// ngmodule.md renamed to ngmodules.md
{"type": 301, "source": "/guide/ngmodule", "destination": "/guide/ngmodules"},
// service-worker-getstart.md, service-worker-comm.md, service-worker-configref.md
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"}
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"},
// some top level guide pages on old site were moved below the guide folder
{"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
{"type": 301, "source": "/docs/styleguide", "destination": "/guide/styleguide"},
// news is now blog
{"type": 301, "source": "/news*", "destination": "https://blog.angular.io/"},
// cookbook guides were moved (and sometime renamed or removed)
{"type": 301, "source": "/docs/*/latest/cookbook", "destination": "/docs"},
{"type": 301, "source": "/docs/*/latest/cookbook/", "destination": "/docs"},
{"type": 301, "source": "/docs/*/latest/cookbook/index.html", "destination": "/docs"},
{"type": 301, "source": "/**/cookbook/ts-to-js*", "destination": "https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14"},
{"type": 301, "source": "/docs/*/latest/cookbook/a1-a2-quick-reference.html", "destination": "/guide/ajs-quick-reference"},
{"type": 301, "source": "/docs/*/latest/cookbook/component-communication.html", "destination": "/guide/component-interaction"},
{"type": 301, "source": "/docs/*/latest/cookbook/dependency-injection.html", "destination": "/guide/dependency-injection-in-action"},
{"type": 301, "source": "/docs/*/latest/cookbook/:cookbook.html", "destination": "/guide/:cookbook"},
// Forms related code was moved from the `common` to `forms` package (and NgFor was renamed to NgForOf)
{"type": 301, "source": "/**/NgFor-*", "destination": "/api/common/NgForOf"},
{"type": 301, "source": "/**/api/common/index/MaxLengthValidator-*", "destination": "/api/forms/MaxLengthValidator"},
{"type": 301, "source": "/**/api/common/ControlGroup*", "destination": "/api/forms/FormGroup"},
{"type": 301, "source": "/**/api/common/Control*", "destination": "/api/forms/FormControl"},
{"type": 301, "source": "/**/api/common/SelectControlValueAccessor-*", "destination": "/api/forms/SelectControlValueAccessor"},
{"type": 301, "source": "/**/api/common/NgModel", "destination": "/api/forms/NgModel"},
// Animations moves, renames and removals
{"type": 301, "source": "/api/animate/:rest*", "destination": "/api/animations/:rest*"},
// AnimationStateDeclarationMetadata was removed
{"type": 301, "source": "/**/AnimationStateDeclarationMetadata*", "destination": "/api/animations"},
// `AnimationDriver` was moved to the `animations/browser` package
{"type": 301, "source": "/api/platform-browser/AnimationDriver", "destination": "/api/animations/browser/AnimationDriver"},
// The `testing` package was renamed to `core/testing`
{"type": 301, "source": "/api/testing/:api-*", "destination": "/api/core/testing/:api"},
// CORE_DIRECTIVES & PLATFORM_PIPES were removed and are now in the CommonModule
{"type": 301, "source": "/**/CORE_DIRECTIVES*", "destination": "/api/common/CommonModule"},
{"type": 301, "source": "/**/PLATFORM_PIPES*", "destination": "/api/common/CommonModule"},
// DirectiveMetadata is now covered by the Directive decorator
{"type": 301, "source": "/**/DirectiveMetadata-*", "destination": "/api/core/Directive"},
// OptionalMetadata is now covered by the Optional decorator
{"type": 301, "source": "/**/OptionalMetadata-*", "destination": "/api/core/Optional"},
// HTTP_PROVIDERS was removed and is now provided in HttpModule
{"type": 301, "source": "/**/HTTP_PROVIDERS*", "destination": "/api/http/HttpModule"},
// URLs that use the old scheme of adding the type to the end (e.g. `SomeClass-class`)
{"type": 301, "source": "/api/:package/:api-*", "destination": "/api/:package/:api"},
{"type": 301, "source": "/api/:package/testing/index/:api-*", "destination": "/api/:package/testing/:api"},
{"type": 301, "source": "/api/:package/testing/:api-*", "destination": "/api/:package/testing/:api"},
{"type": 301, "source": "/api/upgrade/:package/index/:api-*", "destination": "/api/upgrade/:package/:api"},
{"type": 301, "source": "/api/upgrade/:package/:api-*", "destination": "/api/upgrade/:package/:api"},
// URLs that use the old scheme before we moved the docs to the angular/angular repo
{"type": 301, "source": "/docs/*/latest", "destination": "/docs"},
{"type": 301, "source": "/docs/*/latest/api/", "destination": "/api"},
{"type": 301, "source": "/docs/*/latest/api/:package", "destination": "/api/:package"},
{"type": 301, "source": "/docs/*/latest/api/testing/:api-*", "destination": "/api/core/testing/:api"},
{"type": 301, "source": "/docs/*/latest/api/:package/:api-*", "destination": "/api/:package/:api"},
{"type": 301, "source": "/docs/*/latest/api/:package/index/:api-*", "destination": "/api/:package/:api"},
{"type": 301, "source": "/docs/*/latest/api/:package/testing", "destination": "/api/:package/testing"},
{"type": 301, "source": "/docs/*/latest/api/:package/testing/index/:api-*", "destination": "/api/:package/testing/:api"},
{"type": 301, "source": "/docs/*/latest/api/platform-browser/animations/index/:api-*", "destination": "/api/platform-browser/animations/:api"},
{"type": 301, "source": "/docs/*/latest/api/upgrade/:package/:api-*", "destination": "/api/upgrade/:package/:api"},
{"type": 301, "source": "/docs/*/latest/api/upgrade/:package/index/:api-*", "destination": "/api/upgrade/:package/:api"},
{"type": 301, "source": "/docs/*/latest/glossary", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/*/latest/guide/", "destination": "/docs"},
{"type": 301, "source": "/docs/*/latest/guide/lifecycle-hooks", "destination": "/guide/lifecycle-hooks"},
{"type": 301, "source": "/docs/*/latest/:rest*", "destination": "/:rest*"},
{"type": 301, "source": "/docs/latest/:rest*", "destination": "/:rest*"},
{"type": 301, "source": "/docs/styleguide*", "destination": "/guide/styleguide"},
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"},
{"type": 301, "source": "/guide/ngmodule", "destination": "/guide/ngmodules"},
{"type": 301, "source": "/guide/learning-angular*", "destination": "/guide/quickstart"},
{"type": 301, "source": "/testing", "destination": "/guide/testing"},
{"type": 301, "source": "/testing/**", "destination": "/guide/testing"}
],
"rewrites": [
{

View File

@ -19,7 +19,7 @@
"routing": {
"index": "/index.html",
"routes": {
"^(?!/docs/ts/latest|/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref))/?$|/styleguide).*/(?!e?plnkr)[^/.]*$": {
"^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing|api/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?$|.*\\.[^\/.]+$)": {
"match": "regex"
}
}

View File

@ -15,7 +15,7 @@
"build": "yarn ~~build",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn ~~update-webdriver",
"e2e": "ng e2e --no-webdriver-update",
@ -23,7 +23,7 @@
"preinstall": "node ../tools/yarn/check-yarn.js",
"presetup": "yarn install --frozen-lockfile && yarn ~~check-env && yarn boilerplate:remove",
"setup": "yarn aio-use-npm && yarn example-use-npm",
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-stackblitz && yarn generate-zips && yarn docs",
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn docs",
"presetup-local": "yarn presetup",
"setup-local": "yarn aio-use-local && yarn example-use-local",
"postsetup-local": "yarn postsetup",
@ -39,11 +39,15 @@
"check-env": "yarn ~~check-env",
"postcheck-env": "yarn aio-check-local",
"payload-size": "scripts/payload.sh",
"predocs": "yarn generate-stackblitz && yarn generate-zips",
"docs": "dgeni ./tools/transforms/angular.io-package",
"docs-watch": "node tools/transforms/authors-package/watchr.js",
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
"docs-test": "node tools/transforms/test.js",
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js",
"deployment-config-test": "jasmine-ts tests/deployment/**/*.spec.ts",
"firebase-utils-test": "jasmine-ts tools/firebase-test-utils/*.spec.ts",
"tools-lint": "tslint -c \"tools/tslint.json\" \"tools/firebase-test-utils/**/*.ts\"",
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js && yarn firebase-utils-test",
"preserve-and-sync": "yarn docs",
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch --watch-only\" \"yarn start\"",
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
@ -97,12 +101,13 @@
"archiver": "^1.3.0",
"canonical-path": "^0.0.2",
"chalk": "^2.1.0",
"cjson": "^0.5.0",
"codelyzer": "~2.0.0",
"concurrently": "^3.4.0",
"cross-spawn": "^5.1.0",
"css-selector-parser": "^1.3.0",
"dgeni": "^0.4.7",
"dgeni-packages": "^0.24.0",
"dgeni-packages": "0.24.1",
"entities": "^1.1.1",
"eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0",
@ -117,6 +122,7 @@
"image-size": "^0.5.1",
"jasmine-core": "^2.8.0",
"jasmine-spec-reporter": "^4.1.0",
"jasmine-ts": "^0.2.1",
"jsdom": "^9.12.0",
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.1.1",
@ -146,6 +152,7 @@
"unist-util-visit-parents": "^1.1.1",
"vrsource-tslint-rules": "^4.0.1",
"watchr": "^3.0.1",
"xregexp": "^4.0.0",
"yargs": "^7.0.2"
}
}

View File

@ -4,7 +4,7 @@
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
</div>
<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
<mat-toolbar-row class="notification-container">
<aio-notification
icon="insert_comment"
@ -18,7 +18,7 @@
</aio-notification>
</mat-toolbar-row>
<mat-toolbar-row>
<button mat-button class="hamburger" (click)="sidenav.toggle()" title="Docs menu">
<button mat-button class="hamburger" [class.starting]="isStarting" (click)="sidenav.toggle()" title="Docs menu">
<mat-icon svgIcon="menu"></mat-icon>
</button>
<a class="nav-link home" href="/" [ngSwitch]="isSideBySide">
@ -29,6 +29,7 @@
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
</mat-toolbar-row>
</mat-toolbar>
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
@ -56,11 +57,11 @@
</mat-sidenav-container>
<div *ngIf="hasFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
<aio-toc></aio-toc>
</div>
<footer>
<footer class="no-print">
<aio-footer [nodes]="footerNodes" [versionInfo]="versionInfo" ></aio-footer>
</footer>

View File

@ -8,9 +8,12 @@ import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { timer } from 'rxjs/observable/timer';
import 'rxjs/add/operator/mapTo';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { DocumentService } from 'app/documents/document.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Deployment } from 'app/shared/deployment.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
@ -31,18 +34,30 @@ import { TocItem, TocService } from 'app/shared/toc.service';
const sideBySideBreakPoint = 992;
const hideToCBreakPoint = 800;
const startedDelay = 100;
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let documentService: DocumentService;
let docViewer: HTMLElement;
let docViewerComponent: DocViewerComponent;
let hamburger: HTMLButtonElement;
let locationService: MockLocationService;
let sidenav: MatSidenav;
let tocService: TocService;
const initializeTest = () => {
async function awaitDocRendered() {
const newDocPromise = new Promise(resolve => documentService.currentDocument.subscribe(resolve));
const docRenderedPromise = new Promise(resolve => docViewerComponent.docRendered.subscribe(resolve));
await newDocPromise; // Wait for the new document to be fetched.
fixture.detectChanges(); // Propagate document change to the view (i.e to `DocViewer`).
await docRenderedPromise; // Wait for the `docRendered` event.
};
function initializeTest(waitForDoc = true) {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
@ -50,21 +65,27 @@ describe('AppComponent', () => {
component.onResize(sideBySideBreakPoint + 1); // wide by default
const de = fixture.debugElement;
docViewer = de.query(By.css('aio-doc-viewer')).nativeElement;
const docViewerDe = de.query(By.css('aio-doc-viewer'));
documentService = de.injector.get(DocumentService) as DocumentService;
docViewer = docViewerDe.nativeElement;
docViewerComponent = docViewerDe.componentInstance;
hamburger = de.query(By.css('.hamburger')).nativeElement;
locationService = de.injector.get(LocationService) as any as MockLocationService;
locationService = de.injector.get(LocationService) as any;
sidenav = de.query(By.directive(MatSidenav)).componentInstance;
tocService = de.injector.get(TocService);
return waitForDoc && awaitDocRendered();
};
describe('with proper DocViewer', () => {
beforeEach(() => {
beforeEach(async () => {
DocViewerComponent.animationsEnabled = false;
createTestingModule('a/b');
initializeTest();
await initializeTest();
});
afterEach(() => DocViewerComponent.animationsEnabled = true);
@ -356,43 +377,43 @@ describe('AppComponent', () => {
let selectElement: DebugElement;
let selectComponent: SelectComponent;
function setupSelectorForTesting(mode?: string) {
async function setupSelectorForTesting(mode?: string) {
createTestingModule('a/b', mode);
initializeTest();
await initializeTest();
component.onResize(sideBySideBreakPoint + 1); // side-by-side
selectElement = fixture.debugElement.query(By.directive(SelectComponent));
selectComponent = selectElement.componentInstance;
}
it('should select the version that matches the deploy mode', () => {
setupSelectorForTesting();
it('should select the version that matches the deploy mode', async () => {
await setupSelectorForTesting();
expect(selectComponent.selected.title).toContain('stable');
setupSelectorForTesting('next');
await setupSelectorForTesting('next');
expect(selectComponent.selected.title).toContain('next');
setupSelectorForTesting('archive');
await setupSelectorForTesting('archive');
expect(selectComponent.selected.title).toContain('v4');
});
it('should add the current raw version string to the selected version', () => {
setupSelectorForTesting();
it('should add the current raw version string to the selected version', async () => {
await setupSelectorForTesting();
expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
setupSelectorForTesting('next');
await setupSelectorForTesting('next');
expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
setupSelectorForTesting('archive');
await setupSelectorForTesting('archive');
expect(selectComponent.selected.title).toContain(`(v${component.versionInfo.raw})`);
});
// Older docs versions have an href
it('should navigate when change to a version with a url', () => {
setupSelectorForTesting();
it('should navigate when change to a version with a url', async () => {
await setupSelectorForTesting();
const versionWithUrlIndex = component.docVersions.findIndex(v => !!v.url);
const versionWithUrl = component.docVersions[versionWithUrlIndex];
selectElement.triggerEventHandler('change', { option: versionWithUrl, index: versionWithUrlIndex});
expect(locationService.go).toHaveBeenCalledWith(versionWithUrl.url);
});
it('should not navigate when change to a version without a url', () => {
setupSelectorForTesting();
it('should not navigate when change to a version without a url', async () => {
await setupSelectorForTesting();
const versionWithoutUrlIndex = component.docVersions.length;
const versionWithoutUrl = component.docVersions[versionWithoutUrlIndex] = { title: 'foo' };
selectElement.triggerEventHandler('change', { option: versionWithoutUrl, index: versionWithoutUrlIndex });
@ -401,37 +422,39 @@ describe('AppComponent', () => {
});
describe('currentDocument', () => {
it('should display a guide page (guide/pipes)', () => {
locationService.go('guide/pipes');
fixture.detectChanges();
const navigateTo = async (path: string) => {
locationService.go(path);
await awaitDocRendered();
};
it('should display a guide page (guide/pipes)', async () => {
await navigateTo('guide/pipes');
expect(docViewer.textContent).toMatch(/Pipes/i);
});
it('should display the api page', () => {
locationService.go('api');
fixture.detectChanges();
it('should display the api page', async () => {
await navigateTo('api');
expect(docViewer.textContent).toMatch(/API/i);
});
it('should display a marketing page', () => {
locationService.go('features');
fixture.detectChanges();
it('should display a marketing page', async () => {
await navigateTo('features');
expect(docViewer.textContent).toMatch(/Features/i);
});
it('should update the document title', () => {
it('should update the document title', async () => {
const titleService = TestBed.get(Title);
spyOn(titleService, 'setTitle');
locationService.go('guide/pipes');
fixture.detectChanges();
await navigateTo('guide/pipes');
expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Pipes');
});
it('should update the document title, with a default value if the document has no title', () => {
it('should update the document title, with a default value if the document has no title', async () => {
const titleService = TestBed.get(Title);
spyOn(titleService, 'setTitle');
locationService.go('no-title');
fixture.detectChanges();
await navigateTo('no-title');
expect(titleService.setTitle).toHaveBeenCalledWith('Angular');
});
});
@ -509,7 +532,9 @@ describe('AppComponent', () => {
expect(scrollToTopSpy).not.toHaveBeenCalled();
locationService.go('guide/pipes');
tick(1); // triggers the HTTP response for the document
fixture.detectChanges(); // triggers the event that calls `onDocInserted`
expect(scrollToTopSpy).toHaveBeenCalled();
expect(scrollSpy).not.toHaveBeenCalled();
@ -658,18 +683,16 @@ describe('AppComponent', () => {
});
describe('deployment banner', () => {
it('should show a message if the deployment mode is "archive"', () => {
it('should show a message if the deployment mode is "archive"', async () => {
createTestingModule('a/b', 'archive');
initializeTest();
fixture.detectChanges();
await initializeTest();
const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
expect(banner.textContent).toContain('archived documentation for Angular v4');
});
it('should show no message if the deployment mode is not "archive"', () => {
it('should show no message if the deployment mode is not "archive"', async () => {
createTestingModule('a/b', 'stable');
initializeTest();
fixture.detectChanges();
await initializeTest();
const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
expect(banner.textContent!.trim()).toEqual('');
});
@ -678,7 +701,6 @@ describe('AppComponent', () => {
describe('search', () => {
describe('initialization', () => {
it('should initialize the search worker', inject([SearchService], (searchService: SearchService) => {
fixture.detectChanges(); // triggers ngOnInit
expect(searchService.initWorker).toHaveBeenCalled();
}));
});
@ -771,103 +793,103 @@ describe('AppComponent', () => {
describe('archive redirection', () => {
it('should redirect to `docs` if deployment mode is `archive` and not at a docs page', () => {
createTestingModule('', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
createTestingModule('resources', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).toHaveBeenCalledWith('docs');
createTestingModule('guide/aot-compiler', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial/toh-pt1', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('docs', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api/core/getPlatform', 'archive');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
});
it('should not redirect if deployment mode is `next`', () => {
createTestingModule('', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('resources', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('guide/aot-compiler', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial/toh-pt1', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('docs', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api/core/getPlatform', 'next');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
});
it('should not redirect to `docs` if deployment mode is `stable`', () => {
createTestingModule('', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('resources', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('guide/aot-compiler', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('tutorial/toh-pt1', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('docs', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
createTestingModule('api/core/getPlatform', 'stable');
initializeTest();
initializeTest(false);
expect(TestBed.get(LocationService).replace).not.toHaveBeenCalled();
});
});
@ -889,27 +911,68 @@ describe('AppComponent', () => {
});
describe('initial rendering', () => {
it('should initially add the starting class until a document is rendered', () => {
const getSidenavContainer = () => fixture.debugElement.query(By.css('mat-sidenav-container'));
beforeEach(jasmine.clock().install);
afterEach(jasmine.clock().uninstall);
initializeTest();
it('should initially disable Angular animations until a document is rendered', () => {
initializeTest(false);
jasmine.clock().tick(1); // triggers the HTTP response for the document
expect(component.isStarting).toBe(true);
expect(getSidenavContainer().classes['starting']).toBe(true);
expect(fixture.debugElement.properties['@.disabled']).toBe(true);
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(true);
expect(fixture.debugElement.properties['@.disabled']).toBe(true);
triggerDocViewerEvent('docRendered');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(false);
expect(getSidenavContainer().classes['starting']).toBe(false);
expect(fixture.debugElement.properties['@.disabled']).toBe(false);
});
it('should initially add the starting class until a document is rendered', () => {
initializeTest(false);
jasmine.clock().tick(1); // triggers the HTTP response for the document
const sidenavContainer = fixture.debugElement.query(By.css('mat-sidenav-container')).nativeElement;
expect(component.isStarting).toBe(true);
expect(hamburger.classList.contains('starting')).toBe(true);
expect(sidenavContainer.classList.contains('starting')).toBe(true);
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(true);
expect(hamburger.classList.contains('starting')).toBe(true);
expect(sidenavContainer.classList.contains('starting')).toBe(true);
triggerDocViewerEvent('docRendered');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(false);
expect(hamburger.classList.contains('starting')).toBe(false);
expect(sidenavContainer.classList.contains('starting')).toBe(false);
});
it('should initially disable animations on the DocViewer for the first rendering', () => {
initializeTest();
initializeTest(false);
jasmine.clock().tick(1); // triggers the HTTP response for the document
expect(component.isStarting).toBe(true);
expect(docViewer.classList.contains('no-animations')).toBe(true);
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(true);
expect(docViewer.classList.contains('no-animations')).toBe(true);
triggerDocViewerEvent('docRendered');
jasmine.clock().tick(startedDelay);
fixture.detectChanges();
expect(component.isStarting).toBe(false);
expect(docViewer.classList.contains('no-animations')).toBe(false);
@ -921,50 +984,63 @@ describe('AppComponent', () => {
afterEach(jasmine.clock().uninstall);
it('should set the transitioning class on `.app-toolbar` while a document is being rendered', () => {
const getToolbar = () => fixture.debugElement.query(By.css('.app-toolbar'));
initializeTest();
initializeTest(false);
jasmine.clock().tick(1); // triggers the HTTP response for the document
const toolbar = fixture.debugElement.query(By.css('.app-toolbar'));
// Initially, `isTransitoning` is true.
expect(component.isTransitioning).toBe(true);
expect(getToolbar().classes['transitioning']).toBe(true);
expect(toolbar.classes['transitioning']).toBe(true);
triggerDocViewerEvent('docRendered');
fixture.detectChanges();
expect(component.isTransitioning).toBe(false);
expect(getToolbar().classes['transitioning']).toBe(false);
expect(toolbar.classes['transitioning']).toBe(false);
// While a document is being rendered, `isTransitoning` is set to true.
triggerDocViewerEvent('docReady');
fixture.detectChanges();
expect(component.isTransitioning).toBe(true);
expect(getToolbar().classes['transitioning']).toBe(true);
expect(toolbar.classes['transitioning']).toBe(true);
triggerDocViewerEvent('docRendered');
fixture.detectChanges();
expect(component.isTransitioning).toBe(false);
expect(getToolbar().classes['transitioning']).toBe(false);
expect(toolbar.classes['transitioning']).toBe(false);
});
it('should update the sidenav state as soon as a new document is inserted', () => {
initializeTest();
it('should update the sidenav state as soon as a new document is inserted (but not before)', () => {
initializeTest(false);
jasmine.clock().tick(1); // triggers the HTTP response for the document
jasmine.clock().tick(0); // calls `updateSideNav()` for initial rendering
const updateSideNavSpy = spyOn(component, 'updateSideNav');
triggerDocViewerEvent('docReady');
jasmine.clock().tick(0);
expect(updateSideNavSpy).not.toHaveBeenCalled();
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(0);
expect(updateSideNavSpy).toHaveBeenCalledTimes(1);
updateSideNavSpy.calls.reset();
triggerDocViewerEvent('docReady');
jasmine.clock().tick(0);
expect(updateSideNavSpy).not.toHaveBeenCalled();
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(0);
expect(updateSideNavSpy).toHaveBeenCalledTimes(2);
expect(updateSideNavSpy).toHaveBeenCalledTimes(1);
});
});
describe('pageId', () => {
const navigateTo = (path: string) => {
locationService.go(path);
jasmine.clock().tick(1); // triggers the HTTP response for the document
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(0);
jasmine.clock().tick(0); // triggers `updateHostClasses()`
fixture.detectChanges();
};
@ -972,7 +1048,7 @@ describe('AppComponent', () => {
afterEach(jasmine.clock().uninstall);
it('should set the id of the doc viewer container based on the current doc', () => {
initializeTest();
initializeTest(false);
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
navigateTo('guide/pipes');
@ -989,7 +1065,7 @@ describe('AppComponent', () => {
});
it('should not be affected by changes to the query', () => {
initializeTest();
initializeTest(false);
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
navigateTo('guide/pipes');
@ -1002,8 +1078,9 @@ describe('AppComponent', () => {
describe('hostClasses', () => {
const triggerUpdateHostClasses = () => {
jasmine.clock().tick(1); // triggers the HTTP response for document
triggerDocViewerEvent('docInserted');
jasmine.clock().tick(0);
jasmine.clock().tick(0); // triggers `updateHostClasses()`
fixture.detectChanges();
};
const navigateTo = (path: string) => {
@ -1015,7 +1092,7 @@ describe('AppComponent', () => {
afterEach(jasmine.clock().uninstall);
it('should set the css classes of the host container based on the current doc and navigation view', () => {
initializeTest();
initializeTest(false);
navigateTo('guide/pipes');
checkHostClass('page', 'guide-pipes');
@ -1034,7 +1111,7 @@ describe('AppComponent', () => {
});
it('should set the css class of the host container based on the open/closed state of the side nav', async () => {
initializeTest();
initializeTest(false);
navigateTo('guide/pipes');
checkHostClass('sidenav', 'open');
@ -1059,7 +1136,7 @@ describe('AppComponent', () => {
it('should set the css class of the host container based on the initial deployment mode', () => {
createTestingModule('a/b', 'archive');
initializeTest();
initializeTest(false);
triggerUpdateHostClasses();
checkHostClass('mode', 'archive');
@ -1079,13 +1156,13 @@ describe('AppComponent', () => {
const HIDE_DELAY = 500;
const getProgressBar = () => fixture.debugElement.query(By.directive(MatProgressBar));
const initializeAndCompleteNavigation = () => {
initializeTest();
initializeTest(false);
triggerDocViewerEvent('docReady');
tick(HIDE_DELAY);
};
it('should initially be hidden', () => {
initializeTest();
initializeTest(false);
expect(getProgressBar()).toBeFalsy();
});
@ -1300,6 +1377,8 @@ class TestHttpClient {
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
data = { id, contents };
}
return of(data);
// Preserve async nature of `HttpClient`.
return timer(1).mapTo(data);
}
}

View File

@ -28,7 +28,7 @@ export class AppComponent implements OnInit {
currentDocument: DocumentContents;
currentDocVersion: NavigationNode;
currentNodes: CurrentNodes;
currentNodes: CurrentNodes = {};
currentPath: string;
docVersions: NavigationNode[];
dtOn = false;
@ -57,9 +57,11 @@ export class AppComponent implements OnInit {
@HostBinding('class')
hostClasses = '';
isFetching = false;
// Disable all Angular animations for the initial render.
@HostBinding('@.disabled')
isStarting = true;
isTransitioning = true;
isFetching = false;
isSideBySide = false;
private isFetchingTimeout: any;
private isSideNavDoc = false;
@ -118,12 +120,6 @@ export class AppComponent implements OnInit {
/* No need to unsubscribe because this root component never dies */
this.documentService.currentDocument.subscribe(doc => this.currentDocument = doc);
// Generally, we want to delay updating the host classes for the new document, until after the
// leaving document has been removed (to avoid having the styles for the new document applied
// prematurely).
// On the first document, though, (when we know there is no previous document), we want to
// ensure the styles are applied as soon as possible to avoid flicker.
this.documentService.currentDocument.first().subscribe(doc => this.updateHostClassesForDoc(doc));
this.locationService.currentPath.subscribe(path => {
// Redirect to docs if we are in archive mode and are not hitting a docs page
@ -175,11 +171,22 @@ export class AppComponent implements OnInit {
this.topMenuNarrowNodes = views['TopBarNarrow'] || this.topMenuNodes;
});
this.navigationService.versionInfo.subscribe( vi => this.versionInfo = vi );
this.navigationService.versionInfo.subscribe(vi => this.versionInfo = vi);
const hasNonEmptyToc = this.tocService.tocList.map(tocList => tocList.length > 0);
combineLatest(hasNonEmptyToc, this.showFloatingToc)
.subscribe(([hasToc, showFloatingToc]) => this.hasFloatingToc = hasToc && showFloatingToc);
// Generally, we want to delay updating the shell (e.g. host classes, sidenav state) for the new
// document, until after the leaving document has been removed (to avoid having the styles for
// the new document applied prematurely).
// For the first document, though, (when we know there is no previous document), we want to
// ensure the styles are applied as soon as possible to avoid flicker.
combineLatest(
this.documentService.currentDocument, // ...needed to determine host classes
this.navigationService.currentNodes) // ...needed to determine `sidenav` state
.first()
.subscribe(() => this.updateShell());
}
// Scroll to the anchor in the hash fragment or top of doc.
@ -205,14 +212,11 @@ export class AppComponent implements OnInit {
}
onDocInserted() {
// TODO: Find a better way to avoid `ExpressionChangedAfterItHasBeenChecked` error.
setTimeout(() => {
// Update the SideNav state (if necessary).
this.updateSideNav();
// Update the host classes to match the new document.
this.updateHostClassesForDoc(this.currentDocument);
});
// Update the shell (host classes, sidenav state) to match the new document.
// This may be called as a result of actions initiated by view updates.
// In order to avoid errors (e.g. `ExpressionChangedAfterItHasBeenChecked`), updating the view
// (e.g. sidenav, host classes) needs to happen asynchronously.
setTimeout(() => this.updateShell());
// Scroll 500ms after the new document has been inserted into the doc-viewer.
// The delay is to allow time for async layout to complete.
@ -220,7 +224,14 @@ export class AppComponent implements OnInit {
}
onDocRendered() {
this.isStarting = false;
if (this.isStarting) {
// In order to ensure that the initial sidenav-content left margin
// adjustment happens without animation, we need to ensure that
// `isStarting` remains `true` until the margin change is triggered.
// (Apparently, this happens with a slight delay.)
setTimeout(() => this.isStarting = false, 100);
}
this.isTransitioning = false;
}
@ -241,7 +252,7 @@ export class AppComponent implements OnInit {
// items in the top-bar, ensure the sidenav is closed.
// (This condition can only be met when the resize event changes the value of `isSideBySide`
// from `false` to `true` while on a non-sidenav doc.)
this.sideNavToggle(false);
this.sidenav.toggle(false);
}
}
@ -272,10 +283,6 @@ export class AppComponent implements OnInit {
return true;
}
sideNavToggle(value?: boolean) {
this.sidenav.toggle(value);
}
setPageId(id: string) {
// Special case the home page
this.pageId = (id === 'index') ? 'home' : id.replace('/', '-');
@ -300,7 +307,7 @@ export class AppComponent implements OnInit {
const sideNavOpen = `sidenav-${this.sidenav.opened ? 'open' : 'closed'}`;
const pageClass = `page-${this.pageId}`;
const folderClass = `folder-${this.folderId}`;
const viewClasses = Object.keys(this.currentNodes || {}).map(view => `view-${view}`).join(' ');
const viewClasses = Object.keys(this.currentNodes).map(view => `view-${view}`).join(' ');
const notificationClass = `aio-notification-${this.notification.showNotification}`;
const notificationAnimatingClass = this.notificationAnimating ? 'aio-notification-animating' : '';
@ -315,9 +322,13 @@ export class AppComponent implements OnInit {
].join(' ');
}
updateHostClassesForDoc(doc: DocumentContents) {
this.setPageId(doc.id);
this.setFolderId(doc.id);
updateShell() {
// Update the SideNav state (if necessary).
this.updateSideNav();
// Update the host classes.
this.setPageId(this.currentDocument.id);
this.setFolderId(this.currentDocument.id);
this.updateHostClasses();
}
@ -333,7 +344,7 @@ export class AppComponent implements OnInit {
}
// May be open or closed when wide; always closed when narrow.
this.sideNavToggle(this.isSideBySide && openSideNav);
this.sidenav.toggle(this.isSideBySide && openSideNav);
}
// Dynamically change height of table of contents container

View File

@ -23,14 +23,16 @@ describe('AppModule', () => {
});
it('should provide a list of eagerly-loaded embedded components', () => {
const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector]))!;
const selectorCount = eagerSelector.split(',').length;
expect(eagerSelector).not.toBeNull();
expect(selectorCount).toBe(componentsMap[eagerSelector].length);
const eagerConfig = Object.keys(componentsMap).filter(selector => Array.isArray(componentsMap[selector]));
expect(eagerConfig.length).toBeGreaterThan(0);
const eagerSelectors = eagerConfig.reduce<string[]>((selectors, config) => selectors.concat(config.split(',')), []);
expect(eagerSelectors.length).toBeGreaterThan(0);
// For example...
expect(eagerSelector).toContain('aio-toc');
expect(eagerSelectors).toContain('aio-toc');
expect(eagerSelectors).toContain('aio-announcement-bar');
});
it('should provide a list of lazy-loaded embedded components', () => {

View File

@ -1,5 +1,5 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@ -14,6 +14,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
import { ROUTES } from '@angular/router';
import { AnnouncementBarComponent } from 'app/embedded/announcement-bar/announcement-bar.component';
import { AppComponent } from 'app/app.component';
import { EMBEDDED_COMPONENTS, EmbeddedComponentsMap } from 'app/embed-components/embed-components.service';
import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry';
@ -31,6 +32,7 @@ import { TopMenuComponent } from 'app/layout/top-menu/top-menu.component';
import { FooterComponent } from 'app/layout/footer/footer.component';
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
import { ReportingErrorHandler } from 'app/shared/reporting-error-handler';
import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
@ -109,6 +111,7 @@ export const svgIconProviders = [
SharedModule
],
declarations: [
AnnouncementBarComponent,
AppComponent,
DocViewerComponent,
DtComponent,
@ -124,6 +127,7 @@ export const svgIconProviders = [
providers: [
Deployment,
DocumentService,
{ provide: ErrorHandler, useClass: ReportingErrorHandler },
GaService,
Logger,
Location,
@ -143,6 +147,7 @@ export const svgIconProviders = [
provide: EMBEDDED_COMPONENTS,
useValue: {
/* tslint:disable: max-line-length */
'aio-announcement-bar': [AnnouncementBarComponent],
'aio-toc': [TocComponent],
'aio-api-list, aio-contributor-list, aio-file-not-found-search, aio-resource-list, code-example, code-tabs, current-location, live-example': embeddedModulePath,
/* tslint:enable: max-line-length */
@ -156,7 +161,7 @@ export const svgIconProviders = [
multi: true,
},
],
entryComponents: [ TocComponent ],
entryComponents: [ AnnouncementBarComponent, TocComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {

View File

@ -0,0 +1,109 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Logger } from 'app/shared/logger.service';
import { MockLogger } from 'testing/logger.service';
import { AnnouncementBarComponent } from './announcement-bar.component';
const today = new Date();
const lastWeek = changeDays(today, -7);
const yesterday = changeDays(today, -1);
const tomorrow = changeDays(today, 1);
const nextWeek = changeDays(today, 7);
describe('AnnouncementBarComponent', () => {
let element: HTMLElement;
let fixture: ComponentFixture<AnnouncementBarComponent>;
let component: AnnouncementBarComponent;
let httpMock: HttpTestingController;
let mockLogger: MockLogger;
beforeEach(() => {
const injector = TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [AnnouncementBarComponent],
providers: [{ provide: Logger, useClass: MockLogger }]
});
httpMock = injector.get(HttpTestingController);
mockLogger = injector.get(Logger);
fixture = TestBed.createComponent(AnnouncementBarComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
});
it('should have no announcement when first created', () => {
expect(component.announcement).toBeUndefined();
});
describe('ngOnInit', () => {
it('should make a single request to the server', () => {
component.ngOnInit();
httpMock.expectOne('generated/announcements.json');
});
it('should set the announcement to the first "live" one in the list loaded from `announcements.json`', () => {
component.ngOnInit();
const request = httpMock.expectOne('generated/announcements.json');
request.flush([
{ startDate: lastWeek, endDate: yesterday, message: 'Test Announcement 0' },
{ startDate: tomorrow, endDate: nextWeek, message: 'Test Announcement 1' },
{ startDate: yesterday, endDate: tomorrow, message: 'Test Announcement 2' },
{ startDate: yesterday, endDate: tomorrow, message: 'Test Announcement 3' }
]);
expect(component.announcement.message).toEqual('Test Announcement 2');
});
it('should set the announcement to `undefined` if there are no announcements in `announcements.json`', () => {
component.ngOnInit();
const request = httpMock.expectOne('generated/announcements.json');
request.flush([]);
expect(component.announcement).toBeUndefined();
});
it('should handle invalid data in `announcements.json`', () => {
component.ngOnInit();
const request = httpMock.expectOne('generated/announcements.json');
request.flush('some random response');
expect(component.announcement).toBeUndefined();
expect(mockLogger.output.error[0][0]).toContain('generated/announcements.json contains invalid data:');
});
it('should handle a failed request for `announcements.json`', () => {
component.ngOnInit();
const request = httpMock.expectOne('generated/announcements.json');
request.error(new ErrorEvent('404'));
expect(component.announcement).toBeUndefined();
expect(mockLogger.output.error[0][0]).toContain('generated/announcements.json request failed:');
});
});
describe('rendering', () => {
beforeEach(() => {
component.announcement = {
imageUrl: 'link/to/image',
linkUrl: 'link/to/website',
message: 'this is an <b>important</b> message',
endDate: '2018-03-01',
startDate: '2018-02-01'
};
fixture.detectChanges();
});
it('should display the message as HTML', () => {
expect(element.innerHTML).toContain('this is an <b>important</b> message');
});
it('should display an image', () => {
expect(element.querySelector('img')!.src).toContain('link/to/image');
});
it('should display a link', () => {
expect(element.querySelector('a')!.href).toContain('link/to/website');
});
});
});
function changeDays(initial: Date, days: number) {
return (new Date(initial.valueOf()).setDate(initial.getDate() + days));
}

View File

@ -0,0 +1,82 @@
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Logger } from 'app/shared/logger.service';
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
const announcementsPath = CONTENT_URL_PREFIX + 'announcements.json';
export interface Announcement {
imageUrl: string;
message: string;
linkUrl: string;
startDate: string;
endDate: string;
}
/**
* Display the latest live announcement. This is used on the homepage.
*
* The data for the announcements is kept in `aio/content/marketing/announcements.json`.
*
* The format for that data file looks like:
*
* ```
* [
* {
* "startDate": "2018-02-01",
* "endDate": "2018-03-01",
* "message": "This is an <b>important</b> announcement",
* "imageUrl": "url/to/image",
* "linkUrl": "url/to/website"
* },
* ...
* ]
* ```
*
* Only one announcement will be shown at any time. This is determined as the first "live"
* announcement in the file, where "live" means that its start date is before today, and its
* end date is after today.
*
* **Security Note:**
* The `message` field can contain unsanitized HTML but this field should only updated by
* verified members of the Angular team.
*/
@Component({
selector: 'aio-announcement-bar',
template: `
<div class="homepage-container" *ngIf="announcement">
<div class="announcement-bar">
<img [src]="announcement.imageUrl">
<p [innerHTML]="announcement.message"></p>
<a class="button" [href]="announcement.linkUrl">Learn More</a>
</div>
</div>`
})
export class AnnouncementBarComponent implements OnInit {
announcement: Announcement;
constructor(private http: HttpClient, private logger: Logger) {}
ngOnInit() {
this.http.get<Announcement[]>(announcementsPath)
.catch(error => {
this.logger.error(`${announcementsPath} request failed: ${error.message}`);
return [];
})
.map(announcements => this.findCurrentAnnouncement(announcements))
.catch(error => {
this.logger.error(`${announcementsPath} contains invalid data: ${error.message}`);
return [];
})
.subscribe(announcement => this.announcement = announcement);
}
/**
* Get the first date in the list that is "live" now
*/
private findCurrentAnnouncement(announcements: Announcement[]) {
return announcements
.filter(announcement => new Date(announcement.startDate).valueOf() < Date.now())
.filter(announcement => new Date(announcement.endDate).valueOf() > Date.now())
[0];
}
}

View File

@ -29,7 +29,7 @@ const defaultLineNumsCount = 10; // by default, show linenums over this number
selector: 'aio-code',
template: `
<pre class="prettyprint lang-{{language}}">
<button *ngIf="!hideCopy" class="material-icons copy-button"
<button *ngIf="!hideCopy" class="material-icons copy-button no-print"
title="Copy code snippet"
[attr.aria-label]="ariaLabel"
(click)="doCopy()">

View File

@ -1,4 +1,4 @@
<div *ngIf="type !== 'None'" class="toc-inner" [class.collapsed]="isCollapsed">
<div *ngIf="type !== 'None'" class="toc-inner no-print" [class.collapsed]="isCollapsed">
<div *ngIf="type === 'EmbeddedSimple'" class="toc-heading embedded">
Contents

View File

@ -30,6 +30,9 @@ export class GaService {
}
ga(...args: any[]) {
(this.window as any)['ga'](...args);
const gaFn = (this.window as any)['ga'];
if (gaFn) {
gaFn(...args);
}
}
}

View File

@ -0,0 +1,46 @@
import { ErrorHandler, ReflectiveInjector } from '@angular/core';
import { Logger } from './logger.service';
describe('logger service', () => {
let logSpy: jasmine.Spy;
let warnSpy: jasmine.Spy;
let logger: Logger;
let errorHandler: ErrorHandler;
beforeEach(() => {
logSpy = spyOn(console, 'log');
warnSpy = spyOn(console, 'warn');
const injector = ReflectiveInjector.resolveAndCreate([
Logger,
{ provide: ErrorHandler, useClass: MockErrorHandler }
]);
logger = injector.get(Logger);
errorHandler = injector.get(ErrorHandler);
});
describe('log', () => {
it('should delegate to console.log', () => {
logger.log('param1', 'param2', 'param3');
expect(console.log).toHaveBeenCalledWith('param1', 'param2', 'param3');
});
});
describe('warn', () => {
it('should delegate to console.warn', () => {
logger.warn('param1', 'param2', 'param3');
expect(console.warn).toHaveBeenCalledWith('param1', 'param2', 'param3');
});
});
describe('error', () => {
it('should delegate to ErrorHandler', () => {
logger.error('param1', 'param2', 'param3');
expect(errorHandler.handleError).toHaveBeenCalledWith('param1 param2 param3');
});
});
});
class MockErrorHandler implements ErrorHandler {
handleError = jasmine.createSpy('handleError');
}

View File

@ -1,10 +1,12 @@
import { Injectable } from '@angular/core';
import { ErrorHandler, Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
@Injectable()
export class Logger {
constructor(private errorHandler: ErrorHandler) {}
log(value: any, ...rest: any[]) {
if (!environment.production) {
console.log(value, ...rest);
@ -12,7 +14,8 @@ export class Logger {
}
error(value: any, ...rest: any[]) {
console.error(value, ...rest);
const message = [value, ...rest].join(' ');
this.errorHandler.handleError(message);
}
warn(value: any, ...rest: any[]) {

View File

@ -0,0 +1,64 @@
import { ErrorHandler, ReflectiveInjector } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { WindowToken } from 'app/shared/window';
import { AppModule } from 'app/app.module';
import { ReportingErrorHandler } from './reporting-error-handler';
describe('ReportingErrorHandler service', () => {
let handler: ReportingErrorHandler;
let superHandler: jasmine.Spy;
let onerrorSpy: jasmine.Spy;
beforeEach(() => {
onerrorSpy = jasmine.createSpy('onerror');
superHandler = spyOn(ErrorHandler.prototype, 'handleError');
const injector = ReflectiveInjector.resolveAndCreate([
{ provide: ErrorHandler, useClass: ReportingErrorHandler },
{ provide: WindowToken, useFactory: () => ({ onerror: onerrorSpy }) }
]);
handler = injector.get(ErrorHandler);
});
it('should be registered on the AppModule', () => {
handler = TestBed.configureTestingModule({ imports: [AppModule] }).get(ErrorHandler);
expect(handler).toEqual(jasmine.any(ReportingErrorHandler));
});
describe('handleError', () => {
it('should call the super class handleError', () => {
const error = new Error();
handler.handleError(error);
expect(superHandler).toHaveBeenCalledWith(error);
});
it('should cope with the super handler throwing an error', () => {
const error = new Error('initial error');
superHandler.and.throwError('super handler error');
handler.handleError(error);
expect(onerrorSpy).toHaveBeenCalledTimes(2);
// Error from super handler is reported first
expect(onerrorSpy.calls.argsFor(0)[0]).toEqual('super handler error');
expect(onerrorSpy.calls.argsFor(0)[4]).toEqual(jasmine.any(Error));
// Then error from initial exception
expect(onerrorSpy.calls.argsFor(1)[0]).toEqual('initial error');
expect(onerrorSpy.calls.argsFor(1)[4]).toEqual(error);
});
it('should send an error object to window.onerror', () => {
const error = new Error('this is an error message');
handler.handleError(error);
expect(onerrorSpy).toHaveBeenCalledWith(error.message, undefined, undefined, undefined, error);
});
it('should send an error string to window.onerror', () => {
const error = 'this is an error message';
handler.handleError(error);
expect(onerrorSpy).toHaveBeenCalledWith(error);
});
});
});

View File

@ -0,0 +1,37 @@
import { ErrorHandler, Inject, Injectable } from '@angular/core';
import { WindowToken } from './window';
/**
* Extend the default error handling to report errors to an external service - e.g Google Analytics.
*
* Errors outside the Angular application may also be handled by `window.onerror`.
*/
@Injectable()
export class ReportingErrorHandler extends ErrorHandler {
constructor(@Inject(WindowToken) private window: Window) {
super();
}
/**
* Send error info to Google Analytics, in addition to the default handling.
* @param error Information about the error.
*/
handleError(error: string | Error) {
try {
super.handleError(error);
} catch (e) {
this.reportError(e);
}
this.reportError(error);
}
private reportError(error: string | Error) {
if (typeof error === 'string') {
this.window.onerror(error);
} else {
this.window.onerror(error.message, undefined, undefined, undefined, error);
}
}
}

View File

@ -31,6 +31,14 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="translucent">
<script>
// Dynamically, pre-emptively, add `noindex`, which will be removed when the doc is ready and valid
var tag = document.createElement('meta'); tag.name = 'googlebot'; tag.content = 'noindex';
document.head.appendChild(tag);
tag = document.createElement('meta'); tag.name = 'robots'; tag.content = 'noindex';
document.head.appendChild(tag);
</script>
<!-- Google Analytics -->
<script>
// Note this is a customised version of the GA tracking snippet
@ -44,6 +52,38 @@
</script>
<!-- End Google Analytics -->
<script>
// Report fatal errors to Google Analytics
window.onerror = function() {
ga('send', 'exception', {exDescription: formatError.apply(null, arguments), exFatal: true});
function formatError(msg, url, line, col, e) {
var stack;
msg = msg.replace(/^Error: /, '');
if (e) {
stack = e.stack
// strip the leading "Error: " from the stack trace
.replace(/^Error: /, '')
// strip the message from the stack trace, if present
.replace(msg + '\n', '')
// strip leading spaces
.replace(/^ +/gm, '')
// strip all leading "at " for each frame
.replace(/^at /gm, '')
// replace long urls with just the last segment: `filename:line:column`
.replace(/(?: \(|@)http.+\/([^/)]+)\)?(?:\n|$)/gm, '@$1\n')
// replace "eval code" in Edge
.replace(/ *\(eval code(:\d+:\d+)\)(?:\n|$)/gm, '@???$1\n')
} else {
line = line || '?';
col = col || '?';
stack = url + ':' + line + ':' + col;
}
return (msg + '\n' + stack).substr(0, 150);
}
};
</script>
<script>
if (window.document.documentMode) {
// polyfill IE11 in a blocking way

View File

@ -143,13 +143,13 @@ th {
text-align: left;
}
p > code, li > code, table code {
p > code, li > code, td > code, th > code {
font-family: $code-font;
font-size: 85%;
color: $darkgray;
letter-spacing: 0;
line-height: 1;
padding: 2px 6px;
padding: 2px 0;
background-color: $backgroundgray;
border-radius: 4px;
}

View File

@ -16,7 +16,7 @@
text-transform: none;
padding: 8px 24px;
}
tbody {
pre {
white-space: normal;
@ -36,34 +36,44 @@
}
}
.api-body {
.api-header label {
border-radius: 4px;
padding: 4px 16px;
display: inline;
font-size: 14px;
color: white;
margin: 0 8px 0 16px;
font-weight: 500;
text-transform: uppercase;
@media screen and (max-width: 600px) {
display: block;
margin: 8px 0;
}
max-width: 1200px;
&.api-status-label {
background-color: $mediumgray;
}
table {
&.api-type-label {
background-color: $accentblue;
th {
text-transform: none;
font-size: 16px;
font-weight: bold;
}
@each $name, $symbol in $api-symbols {
&.#{$name} {
background: map-get($symbol, background);
tr {
border-bottom: 1px solid $lightgray;
}
td {
vertical-align: middle;
}
hr {
margin: 16px 0;
}
tr:last-child {
border-bottom: none;
}
&.item-table {
td {
padding: 32px;
}
}
&.list-table {
td {
padding: 16px 24px;
}
}
}
}

View File

@ -37,3 +37,11 @@ aio-shell.page-docs {
margin: 24px 0px;
background: $lightgray;
}
.page-actions {
display: flex;
flex-direction: column;
position: absolute;
top: 80px;
right: 24px;
}

View File

@ -12,3 +12,4 @@
@import 'sidenav';
@import 'table-of-contents';
@import 'top-menu';
@import 'print-layout';

View File

@ -125,6 +125,7 @@ section#intro {
.announcement-bar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
max-width: 50vw;

View File

@ -0,0 +1,110 @@
@media print {
// General Adjustments
* {
box-shadow: none !important;
}
h1 {
height: 40px !important;
color: $darkgray !important;
}
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
}
ul, ol, img, code-example, table, tr, .alert, .l-subsection, .feature {
page-break-inside: avoid;
}
table tbody tr:last-child td {
border-bottom: 1px solid $lightgray !important;
}
img {
max-width: 100% !important;
}
p {
widows: 4;
}
p > code, li > code, table code {
color: $blue !important;
}
// No Print Class
.no-print {
display: none !important;
}
// Custom No Print for Sidenav Menu
mat-sidenav.sidenav.mat-sidenav {
display: none !important;
}
// Custom No Print Element Adjustments
.mat-sidenav-content {
margin: 0 !important;
}
mat-sidenav-container.sidenav-container {
min-width: 100vw;
}
.sidenav-content {
overflow: visible;
}
.filetree {
max-width: 100%;
}
aio-code code {
border: none !important;
}
code-example {
pre.lang-bash code span {
color: $mediumgray !important;
}
pre.lang-sh code span {
color: $darkgray !important;
}
header {
border: 0.5px solid $lightgray;
color: $darkgray;
}
}
.content code {
border: 0.5px solid $lightgray;
}
.mat-tab-labels {
div.mat-tab-label {
&:not(.mat-tab-label-active) span {
font-style: italic;
}
&.mat-tab-label-active span {
font-weight: bold;
}
}
}
.api-header label {
color: $darkgray !important;
font-weight: bold !important;
margin: 2px !important;
padding: 0 !important;
display: block !important;
}
.feature-section img {
max-width: 70px !important;
}
}

View File

@ -1,11 +1,6 @@
// Disable sidenav animations while starting the app
// See https://github.com/angular/material2/blob/master/src/lib/sidenav/sidenav-transitions.scss
.starting.mat-sidenav-transition {
.mat-sidenav,
.mat-sidenav-content,
.mat-sidenav-backdrop.mat-sidenav-shown {
transition: none;
}
// Disable sidenav animations for the initial render.
.starting.mat-drawer-transition .mat-drawer-content {
transition: none;
}
aio-nav-menu {
@ -42,19 +37,19 @@ mat-sidenav.mat-sidenav.sidenav {
}
mat-sidenav-container.sidenav-container {
min-height: 100%;
height: auto !important;
max-width: 100%;
margin: 0;
transform: none;
min-height: 100%;
height: auto !important;
max-width: 100%;
margin: 0;
transform: none;
&.has-floating-toc {
max-width: 82%;
}
&.has-floating-toc {
max-width: 82%;
}
}
mat-sidenav-container div.mat-sidenav-content {
height: auto;
height: auto;
}
.vertical-menu-item {
@ -128,6 +123,12 @@ button.vertical-menu-item {
transition-timing-function: ease-out;
}
.no-animations {
.heading-children.expanded, .heading-children.collapsed {
transition: none! important;
}
}
.level-1 {
font-family: $main-font;
font-size: 14px;
@ -159,7 +160,6 @@ button.vertical-menu-item {
.level-1:not(.expanded) .mat-icon, .level-2:not(.expanded) .mat-icon {
@include rotate(0deg);
// margin: 4px;
}
aio-nav-menu.top-menu {

View File

@ -68,9 +68,12 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
height: 100%;
margin: $hamburgerShownMargin;
padding: 0;
transition-duration: .4s;
transition-property: color, margin;
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
&:not(.starting) {
transition-duration: .4s;
transition-property: color, margin;
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
}
@media (min-width: 992px) {
// Hamburger hidden by default on large screens.

View File

@ -1,23 +1,102 @@
.api-info-bar {
max-width: 800px;
text-align: left;
.api-body {
span {
margin: 0 16px 0 0;
.class-overview {
position: relative;
@media screen and (max-width: 600px) {
display: block;
code-example {
clear: left;
}
}
.sidebar {
box-shadow: 0 2px 2px rgba(10, 16, 20, 0.24), 0 0 2px rgba(10, 16, 20, 0.12);
border-radius: 2px;
background: #FAFAFA;
float: right;
margin: 20px;
padding: 0 24px 14px;
h2 {
margin: 18px 0 4px;
}
ul {
margin: 0;
padding-left: 14px;
}
}
.inline-sidebar {
display: none;
}
@media (max-width: 1200px) {
.sidebar {
display: none;
}
.inline-sidebar {
display: block;
}
}
.method-table {
h3 {
margin: 6px 0;
font-weight: bold;
}
h4 {
font-size: 14px;
font-weight: bold;
margin-top: 12px;
}
}
.api-heading {
padding: 5px 0;
font-size: 16px;
}
.properties-table {
font-size: 14px;
thead th {
&:nth-child(1) {
width: 20%;
}
&:nth-child(2) {
width: 20%;
}
}
}
.parameters-table {
margin-top: 0;
font-size: 14px;
td:nth-child(1) {
width: 20%;
}
}
details.overloads {
margin-left: -8px;
summary {
height: inherit;
padding: 8px 12px;
h4 {
margin: 0;
clear: left;
}
}
}
.api-section aio-code {
background-color: rgba(241, 241, 241, 0.2);
}
.from-constructor {
font-style: italic;
color: $blue;
}
}
.api-heading {
margin-top: 24px;
margin-bottom: 18px;
font-size: 16px;
}
.overloads .detail-contents {
padding-top: 0;
}

View File

@ -69,7 +69,7 @@ code-tabs mat-tab-body-content .fadeIn {
aio-code pre {
display: flex;
min-height: 32px;
margin: 16px 32px;
margin: 16px 24px;
white-space: pre-wrap;
align-items: center;
@ -85,7 +85,6 @@ aio-code pre {
.copy-button {
display: inline-block;
position: absolute;
top: -8px;
right: -32px;

View File

@ -25,16 +25,13 @@ summary {
display: none; // Remove the built in details marker in webkit
}
&::after {
&::before {
content: '\E5CE'; // See https://material.io/icons/#ic_expand_less
font-family: 'Material Icons';
font-size: 24px;
-webkit-font-smoothing: antialiased;
@include rotate(0deg); // We will rotate 180 degrees when details is open
position: absolute;
top: 12px;
right: 22px;
float: right;
}
}
@ -45,7 +42,7 @@ details {
padding: 16px 24px;
}
&[open] > summary::after {
&[open] > summary::before {
@include rotate(180deg); // Rotate the icon
}
}

View File

@ -5,25 +5,26 @@
display: none;
}
.mat-icon, .material-icons {
visibility: hidden;
display: inline-block;
.header-link {
box-sizing: border-box;
color: $mediumgray;
margin: 0 8px;
}
display: inline-block;
margin-left: -42px;
padding: 0 8px;
text-decoration: none;
user-select: none;
vertical-align: middle;
visibility: hidden;
width: 40px;
&:hover {
.mat-icon, .material-icons {
visibility: visible;
@media (max-width: 600px) {
float: right;
margin-left: 0;
}
}
a.header-link {
text-decoration: none;
padding-left: 8px;
margin-left: -50px;
display: inline-block;
vertical-align: middle;
&:hover .header-link {
visibility: visible;
}
}

View File

@ -0,0 +1,55 @@
label.raised, .api-header label {
border-radius: 4px;
padding: 4px 16px;
display: inline;
font-size: 14px;
color: white;
margin-right: 8px;
font-weight: 500;
text-transform: uppercase;
@media screen and (max-width: 600px) {
display: block;
margin: 8px 0;
}
&.page-label {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: $mist;
color: $mediumgray;
margin-bottom: 8px;
width: 140px;
.material-icons {
margin-right: 8px;
}
}
&.property-type-label {
font-size: 12px;
background-color: $darkgray;
color: $white;
text-transform: none;
}
}
.api-header label {
&.api-status-label {
background-color: $mediumgray;
}
&.api-type-label {
background-color: $accentblue;
@each $name, $symbol in $api-symbols {
&.#{$name} {
background: map-get($symbol, background);
}
}
}
}

View File

@ -30,3 +30,4 @@
@import 'select-menu';
@import 'deploy-theme';
@import 'notification';
@import 'label';

View File

@ -12,7 +12,7 @@ table {
table-layout: fixed;
}
thead {
thead > {
vertical-align: middle;
border-color: inherit;
@ -21,20 +21,20 @@ table {
border-color: inherit;
}
th {
tr > th {
background: rgba($lightgray, 0.2);
border-bottom: 1px solid $lightgray;
color: $darkgray;
font-size: 12px;
font-weight: 500;
padding: 8px 32px;
padding: 8px 24px;
text-align: left;
text-transform: uppercase;
line-height: 28px;
}
}
tbody {
tbody > tr > {
th,
td {
border-bottom: 1px solid $lightgray;
@ -70,7 +70,7 @@ table {
max-width: 100px;
}
tr:last-child td {
&:last-child td {
border: none;
@media (max-width: 480px) {

View File

@ -30,7 +30,7 @@
box-shadow: 0 2px 2px rgba(0,0,0,0.24), 0 0 2px rgba(0,0,0,0.12);
}
table tbody th{
table > tbody > tr > th {
border: 1px solid rgba(mat-color($foreground, secondary-text), .03);
}

View File

@ -0,0 +1,182 @@
/api/api/core/ElementRef /api/core/ElementRef
/api/common/Control-class /api/forms/FormControl
/api/common/CORE_DIRECTIVES /api/common/CommonModule
/api/common/DatePipe-class /api/common/DatePipe
/api/common/NgClass-directive.html /api/common/NgClass
/api/common/NgFor-directive.html /api/common/NgForOf
/api/common/NgModel-directive /api/forms/NgModel
/api/common/SelectControlValueAccessor-directive /api/forms/SelectControlValueAccessor
/api/common/SlicePipe-class.html /api/common/SlicePipe
/api/core/AnimationStateDeclarationMetadata /api/animations
/api/core/DoCheck-interface.html /api/core/DoCheck
/api/core/HostBinding-var /api/core/HostBinding
/api/core/OpaqueToken-class /api/core/OpaqueToken
/api/core/OptionalMetadata-class /api/core/Optional
/api/core/PLATFORM_PIPES /api/common/CommonModule
/api/core/Provider-class.html /api/core/Provider
/api/core/Renderer-class /api/core/Renderer
/api/core/testing/async-function /api/core/testing/async
/api/core/testing/index/async-function /api/core/testing/async
/api/core/testing/index/TestBed-class.html /api/core/testing/TestBed
/api/core/testing/inject-function /api/core/testing/inject
/api/core/testing/inject-function.html /api/core/testing/inject
/api/http/Headers-class /api/http/Headers
/api/http/Headers-class.html /api/http/Headers
/api/http/HTTP_PROVIDERS-let /api/http/HttpModule
/api/http/testing/index/MockBackend-class /api/http/testing/MockBackend
/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend
/api/platform-browser-dynamic/testing/index/platformBrowserDynamicTesting-let.html /api/platform-browser-dynamic/testing/platformBrowserDynamicTesting
/api/platform-browser/AnimationDriver /api/animations/browser/AnimationDriver
/api/router/Route-class /api/router/Route
/api/router/RouterLink-directive /api/router/RouterLink
/api/testing/tick-function /api/core/testing/tick
/api/upgrade/static/downgradeComponent-function /api/upgrade/static/downgradeComponent
/api/upgrade/static/downgradeComponent-function.html /api/upgrade/static/downgradeComponent
/api/upgrade/static/index/downgradeComponent-function /api/upgrade/static/downgradeComponent
/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule
/docs/js/latest/api/ /api
/docs/js/latest/api/animate/AnimationBuilder-class /api/animations/AnimationBuilder
/docs/js/latest/api/animate/CssAnimationBuilder-class.html /api/animations/CssAnimationBuilder
/docs/js/latest/api/animations/index/trigger-function /api/animations/trigger
/docs/js/latest/api/common/ControlGroup-class.html /api/forms/FormGroup
/docs/js/latest/api/common/index/DatePipe-pipe /api/common/DatePipe
/docs/js/latest/api/common/index/Location-class.html /api/common/Location
/docs/js/latest/api/common/index/MaxLengthValidator-directive.html /api/forms/MaxLengthValidator
/docs/js/latest/api/common/index/NgFor-directive /api/common/NgForOf
/docs/js/latest/api/common/index/NgFor-directive.html /api/common/NgForOf
/docs/js/latest/api/common/index/NgFor-type-alias /api/common/NgForOf
/docs/js/latest/api/common/index/NgFor-type-alias.html /api/common/NgForOf
/docs/js/latest/api/common/index/NgTemplateOutlet-directive /api/common/NgTemplateOutlet
/docs/js/latest/api/common/NgStyle-directive /api/common/NgStyle
/docs/js/latest/api/core/DynamicComponentLoader-class.html /api/core/DynamicComponentLoader
/docs/js/latest/api/core/HostListener-var /api/core/HostListener
/docs/js/latest/api/core/index/AfterViewChecked-class /api/core/AfterViewChecked
/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-class.html /api/core/AnimationStateTransitionMetadata
/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-type-alias /api/core/AnimationStateTransitionMetadata
/docs/js/latest/api/core/index/ApplicationModule-class /api/core/ApplicationModule
/docs/js/latest/api/core/index/ApplicationRef-class /api/core/ApplicationRef
/docs/js/latest/api/core/index/ChangeDetectorRef-class /api/core/ChangeDetectorRef
/docs/js/latest/api/core/index/ComponentFactory-class /api/core/ComponentFactory
/docs/js/latest/api/core/index/DebugNode-class /api/core/DebugNode
/docs/js/latest/api/core/index/destroyPlatform-function /api/core/destroyPlatform
/docs/js/latest/api/core/index/DirectiveMetadata-class /api/core/Directive
/docs/js/latest/api/core/index/ErrorHandler-class /api/core/ErrorHandler
/docs/js/latest/api/core/index/EventEmitter-class /api/core/EventEmitter
/docs/js/latest/api/core/index/EventEmitter-class.html /api/core/EventEmitter
/docs/js/latest/api/core/index/getPlatform-function /api/core/getPlatform
/docs/js/latest/api/core/index/NgModule-interface /api/core/NgModule
/docs/js/latest/api/core/index/NgModuleRef-class /api/core/NgModuleRef
/docs/js/latest/api/core/index/NgModuleRef-class.html /api/core/NgModuleRef
/docs/js/latest/api/core/index/OnInit-class /api/core/OnInit
/docs/js/latest/api/core/index/OnInit-class.html /api/core/OnInit
/docs/js/latest/api/core/index/OnInit-interface /api/core/OnInit
/docs/js/latest/api/core/index/PlatformRef-class /api/core/PlatformRef
/docs/js/latest/api/core/index/QueryList-class.html /api/core/QueryList
/docs/js/latest/api/core/index/RenderComponentType-class /api/core/RenderComponentType
/docs/js/latest/api/core/index/Renderer2-class.html /api/core/Renderer2
/docs/js/latest/api/core/index/state-function.html /api/core/state
/docs/js/latest/api/core/index/transition-function.html /api/core/transition
/docs/js/latest/api/core/index/trigger-function /api/core/trigger
/docs/js/latest/api/core/index/trigger-function.html /api/core/trigger
/docs/js/latest/api/core/index/ViewChild-decorator /api/core/ViewChild
/docs/js/latest/api/core/index/wtfLeave-let /api/core/wtfLeave
/docs/js/latest/api/core/Injector-class.html /api/core/Injector
/docs/js/latest/api/core/Query-var /api/core/Query
/docs/js/latest/api/core/ResolvedProvider-interface.html /api/core/ResolvedProvider
/docs/js/latest/api/core/testing/index/TestBed-class /api/core/testing/TestBed
/docs/js/latest/api/core/testing/index/tick-function /api/core/testing/tick
/docs/js/latest/api/core/testing/index/tick-function.html /api/core/testing/tick
/docs/js/latest/api/core/ViewContainerRef-class.html /api/core/ViewContainerRef
/docs/js/latest/api/forms/index/AbstractControl-class /api/forms/AbstractControl
/docs/js/latest/api/forms/index/AbstractControl-class.html /api/forms/AbstractControl
/docs/js/latest/api/forms/index/FormArray-class.html /api/forms/FormArray
/docs/js/latest/api/forms/index/FormBuilder-class.html /api/forms/FormBuilder
/docs/js/latest/api/forms/index/NG_VALIDATORS-let /api/forms/NG_VALIDATORS
/docs/js/latest/api/forms/index/Validator-interface.html /api/forms/Validator
/docs/js/latest/api/http/ConnectionBackend-class /api/http/ConnectionBackend
/docs/js/latest/api/http/index/Http-class.html /api/http/Http
/docs/js/latest/api/http/index/Jsonp-class.html /api/http/Jsonp
/docs/js/latest/api/http/index/ResponseOptions-class.html /api/http/ResponseOptions
/docs/js/latest/api/http/index/URLSearchParams-class /api/http/URLSearchParams
/docs/js/latest/api/http/index/XHRConnection-class /api/http/XHRConnection
/docs/js/latest/api/http/index/XHRConnection-class.html /api/http/XHRConnection
/docs/js/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection
/docs/js/latest/api/http/testing/MockBackend-class /api/http/testing/MockBackend
/docs/js/latest/api/platform-browser-dynamic/index/platformBrowserDynamic-let.html /api/platform-browser-dynamic/platformBrowserDynamic
/docs/js/latest/api/platform-browser-dynamic/testing/index/BrowserDynamicTestingModule-class.html /api/platform-browser-dynamic/testing/BrowserDynamicTestingModule
/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class /api/platform-browser/animations/BrowserAnimationsModule
/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class.html /api/platform-browser/animations/BrowserAnimationsModule
/docs/js/latest/api/platform-browser/animations/index/NoopAnimationsModule-class.html /api/platform-browser/animations/NoopAnimationsModule
/docs/js/latest/api/platform-browser/index/DomSanitizer-class /api/platform-browser/DomSanitizer
/docs/js/latest/api/platform-browser/index/Meta-class /api/platform-browser/Meta
/docs/js/latest/api/platform-browser/index/MetaDefinition-type-alias /api/platform-browser/MetaDefinition
/docs/js/latest/api/platform-browser/index/SafeUrl-interface /api/platform-browser/SafeUrl
/docs/js/latest/api/platform-browser/index/Title-class /api/platform-browser/Title
/docs/js/latest/api/platform-browser/index/Title-class.html /api/platform-browser/Title
/docs/js/latest/api/platform-server/index/PlatformState-class /api/platform-server/PlatformState
/docs/js/latest/api/platform-server/index/PlatformState-class.html /api/platform-server/PlatformState
/docs/js/latest/api/platform-webworker /api/platform-webworker
/docs/js/latest/api/platform-webworker/index/MessageBus-class /api/platform-webworker/MessageBus
/docs/js/latest/api/router/index/ActivatedRoute-interface /api/router/ActivatedRoute
/docs/js/latest/api/router/index/CanActivate-interface.html /api/router/CanActivate
/docs/js/latest/api/router/index/CanActivateChild-interface /api/router/CanActivateChild
/docs/js/latest/api/router/index/CanDeactivate-interface.html /api/router/CanDeactivate
/docs/js/latest/api/router/index/CanLoad-interface /api/router/CanLoad
/docs/js/latest/api/router/index/CanLoad-interface.html /api/router/CanLoad
/docs/js/latest/api/router/index/NavigationEnd-class /api/router/NavigationEnd
/docs/js/latest/api/router/index/provideRoutes-function /api/router/provideRoutes
/docs/js/latest/api/router/index/provideRoutes-function.html /api/router/provideRoutes
/docs/js/latest/api/router/index/Router-class /api/router/Router
/docs/js/latest/api/router/index/RouterLink-directive /api/router/RouterLink
/docs/js/latest/api/router/index/Routes-type-alias /api/router/Routes
/docs/js/latest/api/router/index/UrlSegment-class /api/router/UrlSegment
/docs/js/latest/api/router/index/UrlSerializer-class.html /api/router/UrlSerializer
/docs/js/latest/api/router/Instruction-class /api/router/Instruction
/docs/js/latest/api/router/Location-class /api/router/Location
/docs/js/latest/api/router/OnActivate-interface /api/router/OnActivate
/docs/js/latest/api/router/Redirect-class.html /api/router/Redirect
/docs/js/latest/api/router/RouteDefinition-interface.html /api/router/RouteDefinition
/docs/js/latest/api/router/RouteParams-class /api/router/RouteParams
/docs/js/latest/api/router/RouteParams-class.html /api/router/RouteParams
/docs/js/latest/api/router/Router-class.html /api/router/Router
/docs/js/latest/api/router/testing /api/router/testing
/docs/js/latest/api/upgrade/index/UpgradeAdapter-class.html /api/upgrade/UpgradeAdapter
/docs/js/latest/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule
/docs/js/latest/api/upgrade/static/UpgradeModule-class.html /api/upgrade/static/UpgradeModule
/docs/js/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14
/docs/js/latest/glossary /guide/glossary
/docs/js/latest/guide/ /docs
/docs/js/latest/guide/lifecycle-hooks /guide/lifecycle-hooks
/docs/js/latest/guide/ngmodule /guide/ngmodules
/docs/js/latest/resources /resources
/docs/latest/tutorial /tutorial
/styleguide /guide/styleguide
/docs/styleguide /guide/styleguide
/docs/styleguide.html /guide/styleguide
/docs/ts/latest/api/core/HostBinding-var.html /api/core/HostBinding
/docs/ts/latest/api/core/index/BaseException-class.html /api/core/BaseException
/docs/ts/latest/api/core/index/PLATFORM_PIPES-let.html /api/common/CommonModule
/docs/ts/latest/api/core/OnInit-interface.html /api/core/OnInit
/docs/ts/latest/api/core/OpaqueToken-class.html /api/core/OpaqueToken
/docs/ts/latest/api/core/OptionalMetadata-class.html /api/core/Optional
/docs/ts/latest/api/core/testing/index/async-function.html /api/core/testing/async
/docs/ts/latest/api/core/testing/index/fakeAsync-function.html /api/core/testing/fakeAsync
/docs/ts/latest/api/core/testing/index/TestComponentRenderer-class.html /api/core/testing/TestComponentRenderer
/docs/ts/latest/api/core/testing/index/tick-function.html /api/core/testing/tick
/docs/ts/latest/api/http/Connection-class.html /api/http/Connection
/docs/ts/latest/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend
/docs/ts/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection
/docs/ts/latest/api/platform-browser-dynamic/index/workerAppDynamicPlatform-let.html /api/platform-browser-dynamic/workerAppDynamicPlatform
/docs/ts/latest/api/testing/fakeAsync-function.html /api/core/testing/fakeAsync
/docs/ts/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14
/guide/cli-quickstart /guide/quickstart
/guide/learning-angular /guide/quickstart
/guide/learning-angular.html /guide/quickstart
/guide/metadata /guide/aot-compiler
/guide/service-worker-getstart /guide/service-worker-getting-started
/guide/service-worker-comm /guide/service-worker-communications
/guide/service-worker-configref /guide/service-worker-config
/news https://blog.angular.io/
/news.html https://blog.angular.io/
/testing /guide/testing
/testing/first-app-tests.html /guide/testing

View File

@ -0,0 +1,49 @@
const { readFileSync } = require('fs');
const path = require('canonical-path');
const cjson = require('cjson');
import { FirebaseRedirector, FirebaseRedirectConfig } from '../../tools/firebase-test-utils/FirebaseRedirector';
export function getRedirector() {
return new FirebaseRedirector(loadRedirects());
}
export function loadRedirects(): FirebaseRedirectConfig[] {
const pathToFirebaseJSON = path.resolve(__dirname, '../../firebase.json');
const contents = cjson.load(pathToFirebaseJSON);
return contents.hosting.redirects;
}
export function loadSitemapUrls() {
const pathToSiteMap = path.resolve(__dirname, '../../src/generated/sitemap.xml');
const xml = readFileSync(pathToSiteMap, 'utf8');
const urls: string[] = [];
xml.replace(/<loc>([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace('%%DEPLOYMENT_HOST%%', '')));
return urls;
}
export function loadLegacyUrls() {
const pathToLegacyUrls = path.resolve(__dirname, 'URLS_TO_REDIRECT.txt');
const urls = readFileSync(pathToLegacyUrls, 'utf8').split('\n').map(line => line.split('\t'));
return urls;
}
export function loadSWRoutes() {
const pathToSWManifest = path.resolve(__dirname, '../../ngsw-manifest.json');
const contents = cjson.load(pathToSWManifest);
const routes = contents.routing.routes;
return Object.keys(routes).map(route => {
const routeConfig = routes[route];
switch (routeConfig.match) {
case 'exact':
return (url) => url === route;
case 'prefix':
return (url) => url.startsWith(route);
case 'regex':
const regex = new RegExp(route);
return (url) => regex.test(url);
default:
throw new Error(`unknown route config: ${route} - ${routeConfig.match}`);
}
});
}

View File

@ -0,0 +1,31 @@
import { getRedirector, loadLegacyUrls, loadRedirects, loadSitemapUrls } from './helpers';
describe('firebase.json redirect config', () => {
describe('with sitemap urls', () => {
loadSitemapUrls().forEach(url => {
it('should not redirect any urls in the sitemap', () => {
expect(getRedirector().redirect(url)).toEqual(url);
});
});
});
describe('with legacy urls', () => {
loadLegacyUrls().forEach(urlPair => {
it('should redirect the legacy urls', () => {
const redirector = getRedirector();
expect(redirector.redirect(urlPair[0])).not.toEqual(urlPair[0]);
if (urlPair[1]) {
expect(redirector.redirect(urlPair[0])).toEqual(urlPair[1]);
}
});
});
describe('destinations', () => {
loadRedirects().forEach(redirect => {
it('should match pattern "^(https?:/)?/.*"', () => {
expect(redirect.destination).toMatch(/^(https?:\/)?\/.*/);
});
});
});
});
});

View File

@ -0,0 +1,33 @@
import { loadLegacyUrls, loadSitemapUrls, loadSWRoutes } from './helpers';
describe('service-worker routes', () => {
loadSitemapUrls().forEach(url => {
it('should process URLs in the Sitemap', () => {
const routes = loadSWRoutes();
expect(routes.some(test => test(url))).toBeTruthy(url);
});
});
loadLegacyUrls().forEach(urlPair => {
const url = urlPair[0];
it('should ignore legacy URLs that will be redirected', () => {
const routes = loadSWRoutes();
expect(routes.some(test => test(url))).toBeFalsy(url);
});
});
it('should ignore stackblitz URLs', () => {
const routes = loadSWRoutes();
expect(routes.some(test => test('/generated/live-examples/toh-pt6/stackblitz.html'))).toBeFalsy();
expect(routes.some(test => test('/generated/live-examples/toh-pt6/stackblitz'))).toBeFalsy();
});
it('should ignore URLs to files with extensions', () => {
const routes = loadSWRoutes();
expect(routes.some(test => test('/generated/zips/animations/animations.zip'))).toBeFalsy();
expect(routes.some(test => test('/generated/images/guide/animations/animation_auto.gif'))).toBeFalsy();
expect(routes.some(test => test('/generated/ie-polyfills.min.js'))).toBeFalsy();
expect(routes.some(test => test('/generated/docs/guide/animations.json'))).toBeFalsy();
});
});

View File

@ -24,7 +24,7 @@ export class ApiPage extends SitePage {
//
// and we want to be able to pull out the code elements from only the first level
// if `onlyDirect` is set to `true`.
const selector = `.descendants.${docType} ${onlyDirect ? '>' : ''} li > :not(ul) code`;
const selector = `.inline-sidebar .descendants.${docType} ${onlyDirect ? '>' : ''} ul > li > code`;
return element.all(by.css(selector)).map<string>(item => item && item.getText());
}

View File

@ -1,4 +1,4 @@
import { browser, by, element } from 'protractor';
import { browser, by, element, ElementFinder } from 'protractor';
import { SitePage } from './app.po';
describe('site App', function() {
@ -11,7 +11,7 @@ describe('site App', function() {
it('should show features text after clicking "Features"', () => {
page.navigateTo('');
page.getTopMenuLink('features').click();
page.click(page.getTopMenuLink('features'));
expect(page.getDocViewerText()).toMatch(/Progressive web apps/i);
});
@ -19,28 +19,74 @@ describe('site App', function() {
page.navigateTo('');
expect(browser.getTitle()).toBe('Angular');
page.getTopMenuLink('features').click();
page.click(page.getTopMenuLink('features'));
expect(browser.getTitle()).toBe('Angular - FEATURES & BENEFITS');
page.homeLink.click();
page.click(page.homeLink);
expect(browser.getTitle()).toBe('Angular');
});
it('should not navigate when clicking on nav-item headings (sub-menu toggles)', () => {
// Show the sidenav.
page.navigateTo('docs');
expect(page.locationPath()).toBe('/docs');
// Get the top-level nav-item headings (sub-menu toggles).
const navItemHeadings = page.getNavItemHeadings(page.sidenav, 1);
// Test all headings (and sub-headings).
expect(navItemHeadings.count()).toBeGreaterThan(0);
navItemHeadings.each(heading => testNavItemHeading(heading!, 1));
// Helpers
function expectToBeCollapsed(element: ElementFinder) {
expect(element.getAttribute('class')).toMatch(/\bcollapsed\b/);
expect(element.getAttribute('class')).not.toMatch(/\bexpanded\b/);
}
function expectToBeExpanded(element: ElementFinder) {
expect(element.getAttribute('class')).not.toMatch(/\bcollapsed\b/);
expect(element.getAttribute('class')).toMatch(/\bexpanded\b/);
}
function testNavItemHeading(heading: ElementFinder, level: number) {
const children = page.getNavItemHeadingChildren(heading, level);
// Headings are initially collapsed.
expectToBeCollapsed(children);
// Ensure heading does not cause navigation when expanding.
page.click(heading);
expectToBeExpanded(children);
expect(page.locationPath()).toBe('/docs');
// Recursively test child-headings (while this heading is expanded).
const nextLevel = level + 1;
const childNavItemHeadings = page.getNavItemHeadings(children, nextLevel);
childNavItemHeadings.each(childHeading => testNavItemHeading(childHeading!, nextLevel));
// Ensure heading does not cause navigation when collapsing.
page.click(heading);
expectToBeCollapsed(children);
expect(page.locationPath()).toBe('/docs');
}
});
it('should show the tutorial index page at `/tutorial` after jitterbugging through features', () => {
// check that we can navigate directly to the tutorial page
page.navigateTo('tutorial');
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
// navigate to a different page
page.getTopMenuLink('features').click();
page.click(page.getTopMenuLink('features'));
expect(page.getDocViewerText()).toMatch(/Progressive web apps/i);
// Show the menu
page.docsMenuLink.click();
page.click(page.docsMenuLink);
// Tutorial folder should still be expanded because this test runs in wide mode
// Navigate to the tutorial introduction via a link in the sidenav
page.getNavItem(/introduction/i).click();
page.click(page.getNavItem(/introduction/i));
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
});
@ -57,8 +103,7 @@ describe('site App', function() {
page.scrollToBottom();
expect(page.getScrollTop()).toBeGreaterThan(0);
page.getNavItem(/api/i).click();
browser.waitForAngular();
page.click(page.getNavItem(/api/i));
expect(page.locationPath()).toBe('/api');
expect(page.getScrollTop()).toBe(0);
});
@ -69,8 +114,7 @@ describe('site App', function() {
page.scrollToBottom();
expect(page.getScrollTop()).toBeGreaterThan(0);
page.getNavItem(/security/i).click();
browser.waitForAngular();
page.click(page.getNavItem(/security/i));
expect(page.locationPath()).toBe('/guide/security');
expect(page.getScrollTop()).toBe(0);
});
@ -102,7 +146,7 @@ describe('site App', function() {
it('should call ga with new URL on navigation', done => {
let path: string;
page.navigateTo('');
page.getTopMenuLink('features').click();
page.click(page.getTopMenuLink('features'));
page.locationPath()
.then(p => path = p)
.then(() => page.ga())
@ -115,18 +159,6 @@ describe('site App', function() {
});
});
describe('search', () => {
it('should find pages when searching by a partial word in the title', () => {
page.navigateTo('');
page.enterSearch('ngCont');
expect(page.getSearchResults()).toContain('NgControl');
page.enterSearch('accessor');
expect(page.getSearchResults()).toContain('ControlValueAccessor');
});
});
describe('404 page', () => {
it('should add or remove the "noindex" meta tag depending upon the validity of the page', () => {
page.navigateTo('');
@ -137,7 +169,7 @@ describe('site App', function() {
expect(element(by.css('meta[name="googlebot"][content="noindex"]')).isPresent()).toBeTruthy();
expect(element(by.css('meta[name="robots"][content="noindex"]')).isPresent()).toBeTruthy();
page.getTopMenuLink('features').click();
page.click(page.getTopMenuLink('features'));
expect(element(by.css('meta[name="googlebot"]')).isPresent()).toBeFalsy();
expect(element(by.css('meta[name="robots"]')).isPresent()).toBeFalsy();
});

View File

@ -7,6 +7,7 @@ export class SitePage {
links = element.all(by.css('md-toolbar a'));
homeLink = element(by.css('a.home'));
docsMenuLink = element(by.cssContainingText('aio-top-menu a', 'Docs'));
sidenav = element(by.css('mat-sidenav'));
docViewer = element(by.css('aio-doc-viewer'));
codeExample = element.all(by.css('aio-doc-viewer pre > code'));
ghLink = this.docViewer
@ -24,7 +25,17 @@ export class SitePage {
.filter(element => element.getText().then(text => pattern.test(text)))
.first();
}
getNavItemHeadings(parent: ElementFinder, level: number) {
const targetSelector = `aio-nav-item .vertical-menu-item.heading.level-${level}`;
return parent.all(by.css(targetSelector));
}
getNavItemHeadingChildren(heading: ElementFinder, level: number) {
const targetSelector = `.heading-children.level-${level}`;
const script = `return arguments[0].parentNode.querySelector('${targetSelector}');`;
return element(() => browser.executeScript(script, heading));
}
getTopMenuLink(path) { return element(by.css(`aio-top-menu a[href="${path}"]`)); }
ga() { return browser.executeScript('return window["ga"].q') as promise.Promise<any[][]>; }
locationPath() { return browser.executeScript('return document.location.pathname') as promise.Promise<string>; }
@ -53,6 +64,10 @@ export class SitePage {
return browser.executeScript('window.scrollTo(0, document.body.scrollHeight)');
}
click(element: ElementFinder) {
return element.click().then(() => browser.waitForAngular());
}
enterSearch(query: string) {
const input = element(by.css('.search-container input[type=search]'));
input.clear();

View File

@ -0,0 +1,203 @@
import { browser } from 'protractor';
import { SitePage } from './app.po';
/* tslint:disable:max-line-length */
describe('onerror handler', function() {
let page: SitePage;
beforeAll(() => {
page = new SitePage();
page.navigateTo('');
});
it('(called without an error object) should call ga with a payload based on the message, url, row and column arguments', async () => {
const message1 = await callOnError('Error: some error message', 'some-file.js', 12, 3, undefined);
expect(message1).toEqual('some error message\nsome-file.js:12:3');
const message2 = await callOnError('Error: some error message', undefined, undefined, undefined, undefined);
expect(message2).toEqual('some error message\nnull:?:?');
});
it('(called without an error object) should call ga with a payload that is no longer that 150 characters', async () => {
const message = await callOnError(
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' +
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz',
'some-file.js', 12, 3, undefined);
expect(message).toEqual(
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' +
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst');
});
it('(called with a Firefox on android style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `AppComponent@https://example.com/app/app.component.ts:31:29
createClass@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20
createDirectiveInstance@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37
createViewNodes@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53
createRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5
callWithDebugContext@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:39
debugCreateRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12
ComponentFactory_.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:37
ComponentFactoryBoundToModule.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:16
ApplicationRef.prototype.bootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:40
PlatformRef.prototype._moduleDoBootstrap/<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:74
PlatformRef.prototype._moduleDoBootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:13
PlatformRef.prototype.bootstrapModuleFactory/</</<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5417:21
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:392:17
forkInnerZoneWithAngularBehavior/zone._inner<.onInvoke@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4665:24
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:391:17
Zone.prototype.run@https://example.com/packages/zone.js@0.8.18/dist/zone.js:142:24
scheduleResolveOrReject/<@https://example.com/packages/zone.js@0.8.18/dist/zone.js:873:52
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:425:17
forkInnerZoneWithAngularBehavior/zone._inner<.onInvokeTask@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4656:24
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:424:17
Zone.prototype.runTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:192:28
drainMicroTaskQueue@https://example.com/packages/zone.js@0.8.18/dist/zone.js:602:25` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
AppComponent@app.component.ts:31:29
createClass@core.umd.js:12200:20
createDirectiveInstance@core.umd.j`);
});
it('(called with a Safari 11 style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `AppComponent
createClass
createDirectiveInstance
createViewNodes
createRootView
callWithDebugContext
create
bootstrap
forEach@[native code]
_moduleDoBootstrap
onInvoke
run
onInvokeTask
runTask
drainMicroTaskQueue
promiseReactionJob@[native code]` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
AppComponent
createClass
createDirectiveInstance
createViewNodes
createRootView
callWithDebugContext
cr`);
});
it('(called with a Opera 50 style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `Error: something terrible has happened. oh no. oh no.
at new AppComponent (https://example.com/app/app.component.ts:31:29)
at createClass (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20)
at createDirectiveInstance (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37)
at createViewNodes (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53)
at createRootView (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5)
at callWithDebugContext (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:42)
at Object.debugCreateRootView [as createRootView] (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12)
at ComponentFactory_.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:46)
at ComponentFactoryBoundToModule.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:29)
at ApplicationRef.bootstrap (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:57)` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
new AppComponent@app.component.ts:31:29
createClass@core.umd.js:12200:20
createDirectiveInstance@core.u`);
});
it('(called with a Chrome 64 style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `Error: something terrible has happened. oh no. oh no.
at new AppComponent (https://example.com/app/app.component.ts:31:29)
at createClass (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20)
at createDirectiveInstance (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37)
at createViewNodes (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53)
at createRootView (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5)
at callWithDebugContext (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:42)
at Object.debugCreateRootView [as createRootView] (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12)
at ComponentFactory_.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:46)
at ComponentFactoryBoundToModule.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:29)
at ApplicationRef.bootstrap (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:57)` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
new AppComponent@app.component.ts:31:29
createClass@core.umd.js:12200:20
createDirectiveInstance@core.u`);
});
it('(called with a Firefox 58 style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `AppComponent@https://example.com/app/app.component.ts:31:29
createClass@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20
createDirectiveInstance@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37
createViewNodes@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53
createRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5
callWithDebugContext@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:39
debugCreateRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12
ComponentFactory_.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:37
ComponentFactoryBoundToModule.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:16
ApplicationRef.prototype.bootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:40
PlatformRef.prototype._moduleDoBootstrap/<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:74
PlatformRef.prototype._moduleDoBootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:13
PlatformRef.prototype.bootstrapModuleFactory/</</<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5417:21
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:392:17
onInvoke@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4665:24
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:391:17
Zone.prototype.run@https://example.com/packages/zone.js@0.8.18/dist/zone.js:142:24
scheduleResolveOrReject/<@https://example.com/packages/zone.js@0.8.18/dist/zone.js:873:52
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:425:17
onInvokeTask@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4656:24
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:424:17
Zone.prototype.runTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:192:28
drainMicroTaskQueue@https://example.com/packages/zone.js@0.8.18/dist/zone.js:602:25` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
AppComponent@app.component.ts:31:29
createClass@core.umd.js:12200:20
createDirectiveInstance@core.umd.j`);
});
it('(called with a Edge 16 style error) should call ga with a payload based on the error object', async () => {
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
stack: `Error: something terrible has happened. oh no. oh no.
at AppComponent (eval code:31:21)
at createClass (eval code:12200:13)
at createDirectiveInstance (eval code:12049:5)
at createViewNodes (eval code:13487:21)
at createRootView (eval code:13377:5)
at callWithDebugContext (eval code:14778:9)
at debugCreateRootView (eval code:14079:5)
at ComponentFactory_.prototype.create (eval code:10998:9)
at ComponentFactoryBoundToModule.prototype.create (eval code:3958:9)
at ApplicationRef.prototype.bootstrap (eval code:5769:9)` });
expect(message).toEqual(`something terrible has happened. oh no. oh no.
AppComponent@???:31:21
createClass@???:12200:13
createDirectiveInstance@???:12049:5
createViewNodes@???`);
});
async function callOnError(message, url, line, column, error) {
await browser.executeScript(function() {
// reset the ga queue
(window as any).ga.q.length = 0;
// post the error to the handler
window.onerror(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
}, message, url, line, column, error);
const gaCalls = await page.ga();
const exceptionCall = gaCalls.find(call => call[0] === 'send' && call[1] === 'exception');
if (exceptionCall) {
const payload = exceptionCall[2];
expect(payload.exFatal).toBe(true);
return payload.exDescription;
}
}
});

View File

@ -6,7 +6,7 @@ const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
'./*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome',
@ -26,7 +26,7 @@ exports.config = {
},
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
project: 'tests/e2e/tsconfig.e2e.json'
});
},
onPrepare() {

View File

@ -1,7 +1,7 @@
{
"extends": "../tsconfig.json",
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"outDir": "../../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",

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