Compare commits

..

17 Commits

Author SHA1 Message Date
1f5dce2128 docs: add changelog for 4.1.1 2017-05-04 14:22:43 -07:00
14e7e43ad8 release: cut the 4.1.1 release 2017-05-04 14:18:24 -07:00
54d4b893fd test(compiler-cli): add test for missingTranslation parameter 2017-05-04 14:05:17 -07:00
4670cf51cc test: cleanup rxjs custom build
The latest rxjs release works with closure compiler out of the box.
We no longer need to compile our own.

Also put closure options into a file rather than using a shell script.
2017-05-04 14:05:16 -07:00
dd4e501999 fix(upgrade): initialize all inputs in time for ngOnChanges()
Previously, non-bracketed inputs (e.g. `xyz="foo"`) on downgraded components
were initialized using `attrs.$observe()` (which uses `$evalAsync()` under the
hood), while bracketed inputs (e.g. `[xyz]="'foo'"`) were initialized using
`$watch()`. If the downgraded component was created during a `$digest` (e.g. by
an `ng-if` watcher), the non-bracketed inputs were not initialized in time for
the initial call to `ngOnChanges()` and `ngOnInit()`.

This commit fixes it by using `$watch()` to initialize all inputs. `$observe()`
is still used for subsequent updates on non-bracketed inputs, because it is more
performant.

Fixes #16212
2017-05-04 14:05:16 -07:00
9124994849 build: update concurrently to latest version
The regression in `concurrently` v3.2.0 (which made us roll back to v3.1.0
in #14378) has been fixed in v3.3.0 (see kimmobrunfeldt/concurrently#89).
2017-05-04 14:05:16 -07:00
4fbc61469f docs: fix links in api docs 2017-05-04 14:05:16 -07:00
8a883f24f6 refactor(compiler): simplify AOT tests 2017-05-04 14:05:15 -07:00
07cef367ac fix(core): don’t stop change detection because of errors
- prevents unsubscribing from the zone on error
- prevents unsubscribing from directive `EventEmitter`s on error
- prevents detaching views in dev mode if there on error
- ensures that `ngOnInit` is only called 1x (also in prod mode)

Fixes #9531
Fixes #2413
Fixes #15925
2017-05-04 14:05:15 -07:00
c060110695 fix(language-service): remove asserts for non-null expressions (#16422)
Reworked some of the code so asserts are no longer necessary.
Added additional and potentially redundant checks
Added checks where the null checker found real problems.

PR Close #16422
2017-05-04 14:05:15 -07:00
93ff3166ab docs(common): fix API docs for NgComponentOutlet (#16411)
fixes #16373

PR Close #16411
2017-05-04 14:05:14 -07:00
85a1b54c6e fix(core): don’t set ng-version for dynamically created components (#16394)
Angular uses the `ng-version` attribute to indicate which elements
were used to bootstrap an application. However, after 4.0 we also
added this attribute for all dynamically created components.

Fixes #15880

PR Close #16394
2017-05-04 14:05:14 -07:00
acf83b90bc fix(core): allow to detach OnPush components (#16394)
Fixes #9720
2017-05-04 14:05:14 -07:00
f66e59ebe4 fix(core): allow directives to inject the component’s ChangeDetectorRef. (#16394)
When a directive lives on the same element as a component
(e.g. `<my-comp myDir>`), the directive was not able to get hold
of the `ChangeDetectorRef` of the component on that element. However,
as directives are supposed to decorate components, this is incorrect.

This commit enables this use case.

Closes #12816
2017-05-04 14:05:14 -07:00
d932e724ab ci(language-service): update ci tests to official 2.3 build (#16415)
PR Close #16415
2017-05-04 14:05:13 -07:00
dcaa11a88b fix: public API golden files (#16414) 2017-05-04 14:05:13 -07:00
427d63a422 fix: strictNullCheck support. (#16389) (#16389)
Fix #16357

Workaround for https://github.com/Microsoft/TypeScript/issues/10078

Closes #16389

PR Close #16389
2017-05-04 14:05:13 -07:00
1190 changed files with 27489 additions and 33020 deletions

View File

@ -136,7 +136,6 @@ groups:
users: users:
- alexeagle - alexeagle
- chuckjaz - chuckjaz
- vicb
- tbosch - tbosch
- IgorMinar #fallback - IgorMinar #fallback
- mhevery #fallback - mhevery #fallback

View File

@ -32,11 +32,11 @@ env:
global: global:
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine> # GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery. # This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc=" - secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
# FIREBASE_TOKEN # FIREBASE_TOKEN
# This is needed for publishing builds to the "aio-staging" and "angular-io" firebase projects. # This is needed for publishing builds to the "aio-staging" firebase site.
# This token was generated using the aio-deploy@angular.io account using `firebase login:ci` and password from valentine # TODO(i): the token was generated using the iminar@google account, we should switch to a shared/role-base account.
- secure: "L5CyQmpwWtoR4Qi4xlWQh/cL1M6ZeJL4W4QAr4HdKFMgYt9h+Whqkymyh2NxwmCbPvWa7yUd+OiLQUDCY7L2VIg16hTwoe2CgYDyQA0BEwLzxtRrJXl93TfwMlrUx5JSIzAccD6D4sjtz8kSFMomK2Nls33xOXOukwyhVMjd0Cg=" - secure: "MPx3UM77o5IlhT75PKHL0FXoB5tSXDc3vnCXCd1sRy4XUTZ9vjcV6nNuyqEf+SOw659bGbC1FI4mACGx1Q+z7MQDR85b1mcA9uSgHDkh+IR82CnCVdaX9d1RXafdJIArahxfmorbiiPPLyPIKggo7ituRm+2c+iraoCkE/pXxYg="
matrix: matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete. # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=e2e - CI_MODE=e2e

View File

@ -1,126 +1,3 @@
<a name="4.2.0-rc.1"></a>
# [4.2.0-rc.1](https://github.com/angular/angular/compare/4.2.0-rc.0...4.2.0-rc.1) (2017-05-26)
### Bug Fixes
* **animations:** repair flicker issues with WA polyfill ([#16937](https://github.com/angular/angular/issues/16937)) ([e7d9fd8](https://github.com/angular/angular/commit/e7d9fd8)), closes [#16919](https://github.com/angular/angular/issues/16919) [#16918](https://github.com/angular/angular/issues/16918)
* **animations:** use a lightweight renderer for non-animation components ([#17003](https://github.com/angular/angular/issues/17003)) ([3ab86bd](https://github.com/angular/angular/commit/3ab86bd))
* **compiler:** compile `.ngfactory.ts` files even if nobody references them. ([#16899](https://github.com/angular/angular/issues/16899)) ([573b861](https://github.com/angular/angular/commit/573b861)), closes [#16741](https://github.com/angular/angular/issues/16741)
* **compiler:** do not report type errors for arguments with `@Inject` ([#16222](https://github.com/angular/angular/issues/16222)) ([27761b4](https://github.com/angular/angular/commit/27761b4)), closes [#15424](https://github.com/angular/angular/issues/15424)
* **core:** make decorators closure safe ([#16905](https://github.com/angular/angular/issues/16905)) ([a80ac0a](https://github.com/angular/angular/commit/a80ac0a)), closes [#16889](https://github.com/angular/angular/issues/16889)
* **tsc-wrapped:** ignore `|null` and `|undefined` when collecting types ([#16222](https://github.com/angular/angular/issues/16222)) ([1651a8f](https://github.com/angular/angular/commit/1651a8f))
* **tsc-wrapped:** resolve short-hand literal values to locals ([#16873](https://github.com/angular/angular/issues/16873)) ([11c10b2](https://github.com/angular/angular/commit/11c10b2))
### Features
* **compiler:** add location note to extracted xliff2 files ([#16791](https://github.com/angular/angular/issues/16791)) ([08dfe91](https://github.com/angular/angular/commit/08dfe91)), closes [#16531](https://github.com/angular/angular/issues/16531)
* **core:** update zone.js to 0.8.10 and expose the flush method ([#16860](https://github.com/angular/angular/issues/16860)) ([85d4c4b](https://github.com/angular/angular/commit/85d4c4b))
* **tsc-wrapped:** support template literals in metadata collection ([#16880](https://github.com/angular/angular/issues/16880)) ([6e41add](https://github.com/angular/angular/commit/6e41add))
<a name="4.2.0-rc.0"></a>
# [4.2.0-rc.0](https://github.com/angular/angular/compare/4.2.0-beta.0...4.2.0-rc.0) (2017-05-19)
### Bug Fixes
* **animations:** make sure reuseable animation subtitutions work without default params ([#16875](https://github.com/angular/angular/issues/16875)) ([7d9f96a](https://github.com/angular/angular/commit/7d9f96a))
* **animations:** only require one flushMicrotasks call when testing animations ([6cb93c1](https://github.com/angular/angular/commit/6cb93c1))
* **compiler:** avoid a `...null` spread in extraction ([#16547](https://github.com/angular/angular/issues/16547)) ([e0a8376](https://github.com/angular/angular/commit/e0a8376))
* **compiler-cli:** allow '==' to compare nullable types ([#16731](https://github.com/angular/angular/issues/16731)) ([d761059](https://github.com/angular/angular/commit/d761059))
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([f0f6544](https://github.com/angular/angular/commit/f0f6544)), closes [#15578](https://github.com/angular/angular/issues/15578)
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([fcc91d8](https://github.com/angular/angular/commit/fcc91d8)), closes [#14321](https://github.com/angular/angular/issues/14321)
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9da6340](https://github.com/angular/angular/commit/9da6340)), closes [#15521](https://github.com/angular/angular/issues/15521)
* **http:** honor RequestArgs.search and RequestArgs.params map type ([aef5245](https://github.com/angular/angular/commit/aef5245)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([7ae7a84](https://github.com/angular/angular/commit/7ae7a84)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([5d4b36f](https://github.com/angular/angular/commit/5d4b36f)), closes [#16310](https://github.com/angular/angular/issues/16310)
### Features
* **animations:** introduce a wave of new animation features ([16c8167](https://github.com/angular/angular/commit/16c8167))
* **animations:** introduce routeable animation support ([f1a9e3c](https://github.com/angular/angular/commit/f1a9e3c))
* add .ngsummary.ts files to support AOT unit tests ([547c363](https://github.com/angular/angular/commit/547c363))
* introduce `TestBed.overrideProvider` ([#16725](https://github.com/angular/angular/issues/16725)) ([39b92f7](https://github.com/angular/angular/commit/39b92f7))
* **compiler:** support a non-null postfix assert ([#16672](https://github.com/angular/angular/issues/16672)) ([b9521b5](https://github.com/angular/angular/commit/b9521b5))
* **core:** introduce fixture.whenRenderingDone for testing ([#16732](https://github.com/angular/angular/issues/16732)) ([38c524d](https://github.com/angular/angular/commit/38c524d))
### Performance Improvements
* **animations:** reduce size of animations bundle ([712630c](https://github.com/angular/angular/commit/712630c))
<a name="4.1.3"></a>
## [4.1.3](https://github.com/angular/angular/compare/4.1.2...4.1.3) (2017-05-17)
### Bug Fixes
* add typescript 2.3.2 typings test ([#16738](https://github.com/angular/angular/issues/16738)) ([a5bdbed](https://github.com/angular/angular/commit/a5bdbed)), closes [#16663](https://github.com/angular/angular/issues/16663)
* **compiler-cli:** import routing module with forRoot ([#16438](https://github.com/angular/angular/issues/16438)) ([b7f8581](https://github.com/angular/angular/commit/b7f8581))
* **platform-server:** wait for async app initializers to complete before removing server side styles ([#16712](https://github.com/angular/angular/issues/16712)) ([0a82f7d](https://github.com/angular/angular/commit/0a82f7d)), closes [#15716](https://github.com/angular/angular/issues/15716)
* **router:** Wrap Promise-like instances in native Promises ([#16759](https://github.com/angular/angular/issues/16759)) ([883ca28](https://github.com/angular/angular/commit/883ca28))
* **upgrade:** Prevent renaming of $inject property ([#16706](https://github.com/angular/angular/issues/16706)) ([afb7540](https://github.com/angular/angular/commit/afb7540))
* **upgrade:** use quote to prevent ClossureCompiler obfuscating $event. ([#16724](https://github.com/angular/angular/issues/16724)) ([47df3d6](https://github.com/angular/angular/commit/47df3d6))
<a name="4.2.0-beta.1"></a>
# [4.2.0-beta.1](https://github.com/angular/angular/compare/4.2.0-beta.0...4.2.0-beta.1) (2017-05-10)
### Features
* add .ngsummary.ts files to support AOT unit tests ([547c363](https://github.com/angular/angular/commit/547c363))
<a name="4.1.2"></a>
## [4.1.2](https://github.com/angular/angular/compare/4.1.1...4.1.2) (2017-05-10)
### Bug Fixes
* **compiler:** avoid a `...null` spread in extraction ([#16547](https://github.com/angular/angular/issues/16547)) ([d0e1688](https://github.com/angular/angular/commit/d0e1688))
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([ee6705a](https://github.com/angular/angular/commit/ee6705a)), closes [#15578](https://github.com/angular/angular/issues/15578)
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([9218812](https://github.com/angular/angular/commit/9218812)), closes [#14321](https://github.com/angular/angular/issues/14321)
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9c70a3c](https://github.com/angular/angular/commit/9c70a3c)), closes [#15521](https://github.com/angular/angular/issues/15521)
* **http:** honor RequestArgs.search and RequestArgs.params map type ([63066f7](https://github.com/angular/angular/commit/63066f7)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([ec3b6e9](https://github.com/angular/angular/commit/ec3b6e9)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([915eae5](https://github.com/angular/angular/commit/915eae5)), closes [#16310](https://github.com/angular/angular/issues/16310)
<a name="4.2.0-beta.0"></a>
# [4.2.0-beta.0](https://github.com/angular/angular/compare/4.1.0...4.2.0-beta.0) (2017-05-04)
### Bug Fixes
* **core**: strictNullCheck support. ([#16389](https://github.com/angular/angular/issues/16389)) ([#16389](https://github.com/angular/angular/issues/16389)) ([8c09d10](https://github.com/angular/angular/commit/8c09d10)), closes [#16357](https://github.com/angular/angular/issues/16357)
* **core:** allow directives to inject the components `ChangeDetectorRef`. ([#16394](https://github.com/angular/angular/issues/16394)) ([392d584](https://github.com/angular/angular/commit/392d584)), closes [#12816](https://github.com/angular/angular/issues/12816)
* **core:** allow to detach `OnPush` components ([#16394](https://github.com/angular/angular/issues/16394)) ([aa8bba4](https://github.com/angular/angular/commit/aa8bba4)), closes [#9720](https://github.com/angular/angular/issues/9720)
* **core:** dont set `ng-version` for dynamically created components ([#16394](https://github.com/angular/angular/issues/16394)) ([a4de214](https://github.com/angular/angular/commit/a4de214)), closes [#15880](https://github.com/angular/angular/issues/15880)
* **core:** dont stop change detection because of errors ([e263e19](https://github.com/angular/angular/commit/e263e19)), closes [#9531](https://github.com/angular/angular/issues/9531) [#2413](https://github.com/angular/angular/issues/2413) [#15925](https://github.com/angular/angular/issues/15925)
* **language-service:** remove asserts for non-null expressions ([#16422](https://github.com/angular/angular/issues/16422)) ([253345c](https://github.com/angular/angular/commit/253345c))
* **upgrade:** initialize all inputs in time for `ngOnChanges()` ([b3e63c0](https://github.com/angular/angular/commit/b3e63c0)), closes [#16212](https://github.com/angular/angular/issues/16212)
### Features
* **compiler-cli:** add param to set MissingTranslationStrategy on ngc ([#15987](https://github.com/angular/angular/issues/15987)) ([6e2abcd](https://github.com/angular/angular/commit/6e2abcd)), closes [#15808](https://github.com/angular/angular/issues/15808)
* **core:** add `begin` and `end` renderer methods to track change detection ([7f9c589](https://github.com/angular/angular/commit/7f9c589))
* **core:** allow custom selector when bootstrapping components ([#15668](https://github.com/angular/angular/issues/15668)) ([900a88b](https://github.com/angular/angular/commit/900a88b)), closes [#7136](https://github.com/angular/angular/issues/7136)
* **core:** upgrade dep on zone.js to 0.8.9 ([#16401](https://github.com/angular/angular/issues/16401)) ([065b76d](https://github.com/angular/angular/commit/065b76d))
* **forms:** introduce min and max validators ([#15813](https://github.com/angular/angular/issues/15813)) ([81925fa](https://github.com/angular/angular/commit/81925fa))
* **language-service:** provide external file list to TypeScript ([#16417](https://github.com/angular/angular/issues/16417)) ([f4b771a](https://github.com/angular/angular/commit/f4b771a))
<a name="4.1.1"></a> <a name="4.1.1"></a>
## [4.1.1](https://github.com/angular/angular/compare/4.1.0...4.1.1) (2017-05-04) ## [4.1.1](https://github.com/angular/angular/compare/4.1.0...4.1.1) (2017-05-04)
@ -143,10 +20,35 @@
### Bug Fixes ### Bug Fixes
* **aio:** AppComponent should scroll only once when location changes ([ac5e6ba](https://github.com/angular/angular/commit/ac5e6ba))
* **aio:** copy button placement fix ([23e6502](https://github.com/angular/angular/commit/23e6502))
* **aio:** fix URL redirection for API pages ([54e587a](https://github.com/angular/angular/commit/54e587a))
* **aio:** header anchor placement ([b0c5d21](https://github.com/angular/angular/commit/b0c5d21))
* **aio:** resource nav ([35a2dfc](https://github.com/angular/angular/commit/35a2dfc))
* **aio:** strip leading slashes from path (and improve DRY-ness) ([#16238](https://github.com/angular/angular/issues/16238)) ([9c1318d](https://github.com/angular/angular/commit/9c1318d)), closes [#16230](https://github.com/angular/angular/issues/16230)
* **aio:** use SVG icons for page load sensitive UI ([c3fa880](https://github.com/angular/angular/commit/c3fa880)), closes [#16100](https://github.com/angular/angular/issues/16100)
* **router:** forward the query parameters in the ng1 -> ng2 url sync ([#16249](https://github.com/angular/angular/issues/16249)) ([2f97731](https://github.com/angular/angular/commit/2f97731)), closes [#16067](https://github.com/angular/angular/issues/16067) * **router:** forward the query parameters in the ng1 -> ng2 url sync ([#16249](https://github.com/angular/angular/issues/16249)) ([2f97731](https://github.com/angular/angular/commit/2f97731)), closes [#16067](https://github.com/angular/angular/issues/16067)
* **upgrade:** use correct attribute name for upgraded component's bindings ([#16128](https://github.com/angular/angular/issues/16128)) ([d1fb066](https://github.com/angular/angular/commit/d1fb066)), closes [#8856](https://github.com/angular/angular/issues/8856) * **upgrade:** use correct attribute name for upgraded component's bindings ([#16128](https://github.com/angular/angular/issues/16128)) ([d1fb066](https://github.com/angular/angular/commit/d1fb066)), closes [#8856](https://github.com/angular/angular/issues/8856)
### Features
* **aio:** api page column layout ([64ef69f](https://github.com/angular/angular/commit/64ef69f))
* **aio:** api page styles ([cf034f7](https://github.com/angular/angular/commit/cf034f7))
* **aio:** api pages styling ([bb52e22](https://github.com/angular/angular/commit/bb52e22))
* **aio:** boilerplate:add cleans (removes) before adding ([d8e2829](https://github.com/angular/angular/commit/d8e2829))
* **aio:** copy code snackbar and design updates ([e7c37d7](https://github.com/angular/angular/commit/e7c37d7))
* **aio:** don't animate sidenav on launch. ([11b2f62](https://github.com/angular/angular/commit/11b2f62))
* **aio:** dont set query params during search [#16125](https://github.com/angular/angular/issues/16125) ([#16217](https://github.com/angular/angular/issues/16217)) ([7520ddc](https://github.com/angular/angular/commit/7520ddc))
* **aio:** layout max width and design cleanup ([710b4a3](https://github.com/angular/angular/commit/710b4a3))
### Performance Improvements
* **aio:** improve unit test rebuild time ([d7719aa](https://github.com/angular/angular/commit/d7719aa))
<a name="4.1.0-rc.0"></a> <a name="4.1.0-rc.0"></a>
# [4.1.0-rc.0](https://github.com/angular/angular/compare/4.1.0-beta.0...4.1.0-rc.0) (2017-04-21) # [4.1.0-rc.0](https://github.com/angular/angular/compare/4.1.0-beta.0...4.1.0-rc.0) (2017-04-21)

View File

@ -9,11 +9,10 @@
"outDir": "dist", "outDir": "dist",
"assets": [ "assets": [
"assets", "assets",
"generated", "content",
"app/search/search-worker.js", "app/search/search-worker.js",
"favicon.ico", "favicon.ico",
"pwa-manifest.json", "pwa-manifest.json"
"google385281288605d160.html"
], ],
"index": "index.html", "index": "index.html",
"main": "main.ts", "main": "main.ts",
@ -22,11 +21,12 @@
"tsconfig": "tsconfig.app.json", "tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json", "testTsconfig": "tsconfig.spec.json",
"prefix": "aio", "prefix": "aio",
"serviceWorker": false, "serviceWorker": true,
"styles": [ "styles": [
"styles.scss" "styles.scss"
], ],
"scripts": [ "scripts": [
], ],
"environmentSource": "environments/environment.ts", "environmentSource": "environments/environment.ts",
"environments": { "environments": {

2
aio/.gitignore vendored
View File

@ -3,7 +3,7 @@
# compiled output # compiled output
/dist /dist
/out-tsc /out-tsc
/src/generated /src/content
/tmp /tmp
# dependencies # dependencies

View File

@ -30,19 +30,6 @@ Here are the most important tasks you might need to use:
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs. * `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs. * `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
## Using ServiceWorker locally
Since abb36e3cb, running `yarn start -- --prod` will no longer set up the ServiceWorker, which
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
with webpack serving the files from memory).
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
with `yarn http-server -- dist -p 4200`.
For more details see #16745.
## Guide to authoring ## Guide to authoring

View File

@ -51,7 +51,6 @@ ENV AIO_BUILDS_DIR=$AIO_BUILDS_DIR TEST_AIO_BUILDS_DIR=$TEST
AIO_UPLOAD_HOSTNAME=$AIO_UPLOAD_HOSTNAME TEST_AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME \ AIO_UPLOAD_HOSTNAME=$AIO_UPLOAD_HOSTNAME TEST_AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME \
AIO_UPLOAD_MAX_SIZE=$AIO_UPLOAD_MAX_SIZE TEST_AIO_UPLOAD_MAX_SIZE=$TEST_AIO_UPLOAD_MAX_SIZE \ AIO_UPLOAD_MAX_SIZE=$AIO_UPLOAD_MAX_SIZE TEST_AIO_UPLOAD_MAX_SIZE=$TEST_AIO_UPLOAD_MAX_SIZE \
AIO_UPLOAD_PORT=$AIO_UPLOAD_PORT TEST_AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT \ AIO_UPLOAD_PORT=$AIO_UPLOAD_PORT TEST_AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT \
AIO_WWW_USER=www-data \
NODE_ENV=production NODE_ENV=production
@ -64,7 +63,6 @@ RUN apt-get update -y && apt-get install -y curl
RUN curl --silent --show-error --location https://deb.nodesource.com/setup_6.x | bash - RUN curl --silent --show-error --location https://deb.nodesource.com/setup_6.x | bash -
RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/backports.list
# Install packages # Install packages
@ -73,11 +71,11 @@ RUN apt-get update -y && apt-get install -y \
cron \ cron \
dnsmasq \ dnsmasq \
nano \ nano \
nginx \
nodejs \ nodejs \
openssl \ openssl \
rsyslog \ rsyslog \
yarn yarn
RUN apt-get install -t jessie-backports -y nginx
RUN yarn global add pm2@2 RUN yarn global add pm2@2
@ -111,31 +109,31 @@ RUN update-ca-certificates
# Set up nginx (for production and testing) # Set up nginx (for production and testing)
RUN sed -i -E "s|^user\s+\S+;|user $AIO_WWW_USER;|" /etc/nginx/nginx.conf RUN rm /etc/nginx/sites-enabled/*
RUN rm -f /etc/nginx/conf.d/*
RUN rm -f /etc/nginx/sites-enabled/*
COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-prod.conf COPY nginx/aio-builds.conf /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$AIO_BUILDS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$AIO_DOMAIN_NAME|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$AIO_UPLOAD_PORT|g" /etc/nginx/sites-available/aio-builds-prod.conf
RUN ln -s /etc/nginx/sites-available/aio-builds-prod.conf /etc/nginx/sites-enabled/aio-builds-prod.conf
COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-test.conf COPY nginx/aio-builds.conf /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$TEST_AIO_DOMAIN_NAME|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_DOMAIN_NAME}}|$TEST_AIO_DOMAIN_NAME|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$TEST_AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$TEST_AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$TEST_AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$TEST_AIO_UPLOAD_PORT|g" /etc/nginx/sites-available/aio-builds-test.conf
RUN ln -s /etc/nginx/sites-available/aio-builds-test.conf /etc/nginx/sites-enabled/aio-builds-test.conf
# Set up pm2 # Set up pm2

View File

@ -17,22 +17,16 @@ server {
server { server {
server_name "~^pr(?<pr>[1-9][0-9]*)-(?<sha>[0-9a-f]{40})\."; server_name "~^pr(?<pr>[1-9][0-9]*)-(?<sha>[0-9a-f]{40})\.";
listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2; listen {{$AIO_NGINX_PORT_HTTPS}} ssl;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2; listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt; ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key; ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
root {{$AIO_BUILDS_DIR}}/$pr/$sha; root {{$AIO_BUILDS_DIR}}/$pr/$sha;
disable_symlinks on from=$document_root; disable_symlinks on from=$document_root;
index index.html; index index.html;
gzip on;
gzip_comp_level 7;
gzip_types *;
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log; access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log; error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;
@ -49,13 +43,11 @@ server {
server { server {
server_name _; server_name _;
listen {{$AIO_NGINX_PORT_HTTPS}} ssl http2 default_server; listen {{$AIO_NGINX_PORT_HTTPS}} ssl default_server;
listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl http2; listen [::]:{{$AIO_NGINX_PORT_HTTPS}} ssl;
ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt; ssl_certificate {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.crt;
ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key; ssl_certificate_key {{$AIO_LOCALCERTS_DIR}}/{{$AIO_DOMAIN_NAME}}.key;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
access_log {{$AIO_NGINX_LOGS_DIR}}/access.log; access_log {{$AIO_NGINX_LOGS_DIR}}/access.log;
error_log {{$AIO_NGINX_LOGS_DIR}}/error.log; error_log {{$AIO_NGINX_LOGS_DIR}}/error.log;

View File

@ -18,8 +18,8 @@ function _main() {
// Exit codes: // Exit codes:
// - 0: The PR author is a member. // - 0: The PR author is a member.
// - 1: An error occurred. // - 1: The PR author is not a member.
// - 2: The PR author is not a member. // - 2: An error occurred.
buildVerifier.getPrAuthorTeamMembership(pr). buildVerifier.getPrAuthorTeamMembership(pr).
then(({author, isMember}) => { then(({author, isMember}) => {
if (isMember) { if (isMember) {
@ -27,10 +27,10 @@ function _main() {
} else { } else {
const errorMessage = `User '${author}' is not an active member of any of the following teams: ` + const errorMessage = `User '${author}' is not an active member of any of the following teams: ` +
`${allowedTeamSlugs.join(', ')}`; `${allowedTeamSlugs.join(', ')}`;
onError(errorMessage, 2); onError(errorMessage, 1);
} }
}). }).
catch(err => onError(err, 1)); catch(err => onError(err, 2));
} }
function onError(err: string, exitCode: number) { function onError(err: string, exitCode: number) {

View File

@ -1,3 +1,6 @@
// TODO(gkalpak): Find more suitable way to run as `www-data`.
process.setuid('www-data');
// Imports // Imports
import {getEnvVar} from '../common/utils'; import {getEnvVar} from '../common/utils';
import {uploadServerFactory} from './upload-server-factory'; import {uploadServerFactory} from './upload-server-factory';
@ -12,10 +15,8 @@ const AIO_PREVIEW_DEPLOYMENT_TOKEN = getEnvVar('AIO_PREVIEW_DEPLOYMENT_TOKEN');
const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG'); const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG');
const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME'); const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME');
const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT'); const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT');
const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');
// Run // Run
process.setuid(AIO_WWW_USER); // TODO(gkalpak): Find more suitable way to run as `www-data`.
_main(); _main();
// Functions // Functions

View File

@ -7,6 +7,7 @@ import * as shell from 'shelljs';
import {getEnvVar} from '../common/utils'; import {getEnvVar} from '../common/utils';
// Constans // Constans
const SERVER_USER = 'www-data';
const TEST_AIO_BUILDS_DIR = getEnvVar('TEST_AIO_BUILDS_DIR'); const TEST_AIO_BUILDS_DIR = getEnvVar('TEST_AIO_BUILDS_DIR');
const TEST_AIO_NGINX_HOSTNAME = getEnvVar('TEST_AIO_NGINX_HOSTNAME'); const TEST_AIO_NGINX_HOSTNAME = getEnvVar('TEST_AIO_NGINX_HOSTNAME');
const TEST_AIO_NGINX_PORT_HTTP = +getEnvVar('TEST_AIO_NGINX_PORT_HTTP'); const TEST_AIO_NGINX_PORT_HTTP = +getEnvVar('TEST_AIO_NGINX_PORT_HTTP');
@ -14,7 +15,6 @@ const TEST_AIO_NGINX_PORT_HTTPS = +getEnvVar('TEST_AIO_NGINX_PORT_HTTPS');
const TEST_AIO_UPLOAD_HOSTNAME = getEnvVar('TEST_AIO_UPLOAD_HOSTNAME'); const TEST_AIO_UPLOAD_HOSTNAME = getEnvVar('TEST_AIO_UPLOAD_HOSTNAME');
const TEST_AIO_UPLOAD_MAX_SIZE = +getEnvVar('TEST_AIO_UPLOAD_MAX_SIZE'); const TEST_AIO_UPLOAD_MAX_SIZE = +getEnvVar('TEST_AIO_UPLOAD_MAX_SIZE');
const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT'); const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT');
const WWW_USER = getEnvVar('AIO_WWW_USER');
// Interfaces - Types // Interfaces - Types
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; } export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
@ -31,7 +31,7 @@ class Helper {
public get nginxHostname() { return TEST_AIO_NGINX_HOSTNAME; } public get nginxHostname() { return TEST_AIO_NGINX_HOSTNAME; }
public get nginxPortHttp() { return TEST_AIO_NGINX_PORT_HTTP; } public get nginxPortHttp() { return TEST_AIO_NGINX_PORT_HTTP; }
public get nginxPortHttps() { return TEST_AIO_NGINX_PORT_HTTPS; } public get nginxPortHttps() { return TEST_AIO_NGINX_PORT_HTTPS; }
public get wwwUser() { return WWW_USER; } public get serverUser() { return SERVER_USER; }
public get uploadHostname() { return TEST_AIO_UPLOAD_HOSTNAME; } public get uploadHostname() { return TEST_AIO_UPLOAD_HOSTNAME; }
public get uploadPort() { return TEST_AIO_UPLOAD_PORT; } public get uploadPort() { return TEST_AIO_UPLOAD_PORT; }
public get uploadMaxSize() { return TEST_AIO_UPLOAD_MAX_SIZE; } public get uploadMaxSize() { return TEST_AIO_UPLOAD_MAX_SIZE; }
@ -46,7 +46,7 @@ class Helper {
// Constructor // Constructor
constructor() { constructor() {
shell.mkdir('-p', this.buildsDir); shell.mkdir('-p', this.buildsDir);
shell.exec(`chown -R ${this.wwwUser} ${this.buildsDir}`); shell.exec(`chown -R ${this.serverUser} ${this.buildsDir}`);
} }
// Methods - Public // Methods - Public
@ -64,7 +64,7 @@ class Helper {
public createDummyArchive(pr: string, sha: string, archivePath: string): CleanUpFn { public createDummyArchive(pr: string, sha: string, archivePath: string): CleanUpFn {
const inputDir = path.join(this.buildsDir, 'uploaded', pr, sha); const inputDir = path.join(this.buildsDir, 'uploaded', pr, sha);
const cmd1 = `tar --create --gzip --directory "${inputDir}" --file "${archivePath}" .`; const cmd1 = `tar --create --gzip --directory "${inputDir}" --file "${archivePath}" .`;
const cmd2 = `chown ${this.wwwUser} ${archivePath}`; const cmd2 = `chown ${this.serverUser} ${archivePath}`;
const cleanUpTemp = this.createDummyBuild(`uploaded/${pr}`, sha, true); const cleanUpTemp = this.createDummyBuild(`uploaded/${pr}`, sha, true);
shell.exec(cmd1); shell.exec(cmd1);
@ -82,7 +82,7 @@ class Helper {
this.writeFile(idxPath, {content: `PR: ${pr} | SHA: ${sha} | File: /index.html`}, force); this.writeFile(idxPath, {content: `PR: ${pr} | SHA: ${sha} | File: /index.html`}, force);
this.writeFile(barPath, {content: `PR: ${pr} | SHA: ${sha} | File: /foo/bar.js`}, force); this.writeFile(barPath, {content: `PR: ${pr} | SHA: ${sha} | File: /foo/bar.js`}, force);
shell.exec(`chown -R ${this.wwwUser} ${prDir}`); shell.exec(`chown -R ${this.serverUser} ${prDir}`);
return this.createCleanUpFn(() => shell.rm('-rf', prDir)); return this.createCleanUpFn(() => shell.rm('-rf', prDir));
} }
@ -166,7 +166,7 @@ class Helper {
// Create a file with the specified content. // Create a file with the specified content.
fs.writeFileSync(filePath, content || ''); fs.writeFileSync(filePath, content || '');
} }
shell.exec(`chown ${this.wwwUser} ${filePath}`); shell.exec(`chown ${this.serverUser} ${filePath}`);
return this.createCleanUpFn(() => shell.rm('-rf', cleanUpTarget)); return this.createCleanUpFn(() => shell.rm('-rf', cleanUpTarget));
} }

View File

@ -159,7 +159,7 @@ describe('upload-server (on HTTP)', () => {
}); });
it(`should create files/directories owned by '${h.wwwUser}'`, done => { it(`should create files/directories owned by '${h.serverUser}'`, done => {
const shaDir = path.join(h.buildsDir, pr, sha9); const shaDir = path.join(h.buildsDir, pr, sha9);
const idxPath = path.join(shaDir, 'index.html'); const idxPath = path.join(shaDir, 'index.html');
const barPath = path.join(shaDir, 'foo', 'bar.js'); const barPath = path.join(shaDir, 'foo', 'bar.js');
@ -167,7 +167,7 @@ describe('upload-server (on HTTP)', () => {
uploadPromise. uploadPromise.
then(() => Promise.all([ then(() => Promise.all([
h.runCmd(`find ${shaDir}`), h.runCmd(`find ${shaDir}`),
h.runCmd(`find ${shaDir} -user ${h.wwwUser}`), h.runCmd(`find ${shaDir} -user ${h.serverUser}`),
])). ])).
then(([{stdout: allFiles}, {stdout: userFiles}]) => { then(([{stdout: allFiles}, {stdout: userFiles}]) => {
expect(userFiles).toBe(allFiles); expect(userFiles).toBe(allFiles);

View File

@ -6,18 +6,18 @@
"author": "Angular", "author": "Angular",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"prebuild": "yarn clean-dist", "prebuild": "yarn run clean",
"build": "tsc", "build": "tsc",
"build-watch": "yarn tsc -- --watch", "build-watch": "yarn run tsc -- --watch",
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"", "clean": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"", "dev": "concurrently --kill-others --raw --success first \"yarn run build-watch\" \"yarn run test-watch\"",
"lint": "tslint --project tsconfig.json", "lint": "tslint --project tsconfig.json",
"pre~~test-only": "yarn lint", "pre~~test-only": "yarn run lint",
"~~test-only": "node dist/test", "~~test-only": "node dist/test",
"pretest": "yarn build", "pretest": "yarn run build",
"test": "yarn ~~test-only", "test": "yarn run ~~test-only",
"pretest-watch": "yarn build", "pretest-watch": "yarn run build",
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist" "test-watch": "nodemon --exec \"yarn run ~~test-only\" --watch dist"
}, },
"dependencies": { "dependencies": {
"express": "^4.14.1", "express": "^4.14.1",

View File

@ -20,11 +20,7 @@
## Starting the docker container ## Starting the docker container
- [Start docker container](vm-setup--start-docker-container.md) - [Create docker image](vm-setup--start-docker-container.md)
## Updating the docker container
- [Update docker container](vm-setup--update-docker-container.md)
## Miscellaneous ## Miscellaneous

View File

@ -8,7 +8,7 @@ This is an overview of the available scripts and commands.
The scripts are located inside `<aio-builds-setup-dir>/scripts/`. The following scripts are The scripts are located inside `<aio-builds-setup-dir>/scripts/`. The following scripts are
available: available:
- `create-image.sh`: - `build.sh`:
Can be used for creating a preconfigured docker image. Can be used for creating a preconfigured docker image.
See [here](vm-setup--create-docker-image.md) for more info. See [here](vm-setup--create-docker-image.md) for more info.
@ -18,13 +18,10 @@ available:
- `travis-preverify-pr.sh` - `travis-preverify-pr.sh`
Can be used for "preverifying" a PR before uploading the artifacts to the server. It checks that Can be used for "preverifying" a PR before uploading the artifacts to the server. It checks that
the author of the PR is a member of one of the specified GitHub teams and therefore allowed to the author of the PR a member of one of the specified GitHub teams and therefore allowed to upload
upload build artifacts. This is useful for CI integration. See [here](misc--integrate-with-ci.md) build artifacts. This is useful for CI integration. See [here](misc--integrate-with-ci.md) for
for more info. more info.
- `update-preview-server.sh`
Can be used for updating the docker container (and image) based on the latest changes checked out
from a git repository. See [here](vm-setup--update-docker-container.md) for more info.
## Commands ## Commands
The following commands are available globally from inside the docker container. They are either used The following commands are available globally from inside the docker container. They are either used

View File

@ -6,11 +6,11 @@
## Build docker image ## Build docker image
- `<aio-builds-setup-dir>/scripts/create-image.sh [<name>[:<tag>] [--build-arg <NAME>=<value> ...]]` - `<aio-builds-setup-dir>/scripts/build.sh [<name>[:<tag>] [--build-arg <NAME>=<value> ...]]`
- You can overwrite the default environment variables inside the image, by passing new values using - You can overwrite the default environment variables inside the image, by passing new values using
`--build-arg`. `--build-arg`.
**Note:** The script has to execute docker commands with `sudo`. **Note:** The build script has to execute docker commands with `sudo`.
## Example ## Example

View File

@ -7,16 +7,16 @@ command:
``` ```
sudo docker run \ sudo docker run \
--detach \ -d \
--dns 127.0.0.1 \ --dns 127.0.0.1 \
--name <instance-name> \ --name <instance-name> \
--publish 80:80 \ -p 80:80 \
--publish 443:443 \ -p 443:443 \
--restart unless-stopped \ --restart unless-stopped \
[--volume <host-cert-dir>:/etc/ssl/localcerts:ro] \ [-v <host-cert-dir>:/etc/ssl/localcerts:ro] \
--volume <host-secrets-dir>:/aio-secrets:ro \ -v <host-secrets-dir>:/aio-secrets:ro \
--volume <host-builds-dir>:/var/www/aio-builds \ -v <host-builds-dir>:/var/www/aio-builds \
[--volume <host-logs-dir>:/var/log/aio] \ [-v <host-logs-dir>:/var/log/aio] \
<name>[:<tag>] <name>[:<tag>]
``` ```
@ -27,7 +27,7 @@ can be found [here](https://docs.docker.com/engine/reference/run/).
sudo docker run \ sudo docker run \
# Start as a daemon. # Start as a daemon.
--detach \ -d \
# Use the local DNS server. # Use the local DNS server.
# (This is necessary for mapping internal URLs, e.g. for the Node.js upload-server.) # (This is necessary for mapping internal URLs, e.g. for the Node.js upload-server.)
@ -37,9 +37,9 @@ sudo docker run \
# Useful for running `docker` commands, e.g.: `docker stop <instance-name>` # Useful for running `docker` commands, e.g.: `docker stop <instance-name>`
--name <instance-name> \ --name <instance-name> \
# Map ports of the host VM (left) to ports of the docker container (right) # Map ports of the hosr VM (left) to ports of the docker container (right)
--publish 80:80 \ -p 80:80 \
--publish 443:443 \ -p 443:443 \
# Automatically restart the container (unless it was explicitly stopped by the user). # Automatically restart the container (unless it was explicitly stopped by the user).
# (This ensures that the container will be automatically started on boot.) # (This ensures that the container will be automatically started on boot.)
@ -48,22 +48,22 @@ sudo docker run \
# The directory the contains the SSL certificates. # The directory the contains the SSL certificates.
# (See [here](vm-setup--create-host-dirs-and-files.md) for more info.) # (See [here](vm-setup--create-host-dirs-and-files.md) for more info.)
# If not provided, the container will use self-signed certificates. # If not provided, the container will use self-signed certificates.
[--volume <host-cert-dir>:/etc/ssl/localcerts:ro] \ [-v <host-cert-dir>:/etc/ssl/localcerts:ro] \
# The directory the contains the secrets (e.g. GitHub token, JWT secret, etc). # The directory the contains the secrets (e.g. GitHub token, JWT secret, etc).
# (See [here](vm-setup--set-up-secrets.md) for more info.) # (See [here](vm-setup--set-up-secrets.md) for more info.)
--volume <host-secrets-dir>:/aio-secrets:ro \ -v <host-secrets-dir>:/aio-secrets:ro \
# The uploaded build artifacts will stored to and served from this directory. # The uploaded build artifacts will stored to and served from this directory.
# (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) - # (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) -
# this will be a directory inside the disk.) # this will be a directory inside the disk.)
--volume <host-builds-dir>:/var/www/aio-builds \ -v <host-builds-dir>:/var/www/aio-builds \
# The directory where the logs are being kept. # The directory where the logs are being kept.
# (See [here](vm-setup--create-host-dirs-and-files.md) for more info.) # (See [here](vm-setup--create-host-dirs-and-files.md) for more info.)
# If not provided, the logs will be kept inside the container, which means they will be lost # If not provided, the logs will be kept inside the container, which means they will be lost
# whenever a new container is created. # whenever a new container is created.
[--volume <host-logs-dir>:/var/log/aio] \ [-v <host-logs-dir>:/var/log/aio] \
# The name of the docker image to use (and an optional tag; defaults to `latest`). # The name of the docker image to use (and an optional tag; defaults to `latest`).
# (See [here](vm-setup--create-docker-image.md) for instructions on how to create the iamge.) # (See [here](vm-setup--create-docker-image.md) for instructions on how to create the iamge.)
@ -78,15 +78,15 @@ by the container for accesing secrets and SSL certificates and keeping the build
``` ```
sudo docker run \ sudo docker run \
--detach \ -d \
--dns 127.0.0.1 \ --dns 127.0.0.1 \
--name foobar-builds-1 \ --name foobar-builds-1 \
--publish 80:80 \ -p 80:80 \
--publish 443:443 \ -p 443:443 \
--restart unless-stopped \ --restart unless-stopped \
--volume /etc/ssl/localcerts:/etc/ssl/localcerts:ro \ -v /etc/ssl/localcerts:/etc/ssl/localcerts:ro \
--volume /foobar-secrets:/aio-secrets:ro \ -v /foobar-secrets:/aio-secrets:ro \
--volume /mnt/disks/foobar-builds:/var/www/aio-builds \ -v /mnt/disks/foobar-builds:/var/www/aio-builds \
--volume /foobar-logs:/var/log/aio \ -v /foobar-logs:/var/log/aio \
foobar-builds foobar-builds
``` ```

View File

@ -1,52 +0,0 @@
# VM setup - Update docker container
## Overview
Assuming you have cloned the repository containing the preview server code (as described
[here](vm-setup--create-docker-image.md)), you can use the `update-preview-server.sh` script on the
VM host to update the preview server based on changes in the source code.
The script will pull the latest changes from the origin's master branch and examine if there have
been any changes in files inside the preview server source code directory (see below). If there are,
it will create a new image and verify that is works as expected. Finally, it will stop and remove
the old docker container and image, create and new container based on the new image and start it.
The script assumes that the preview server source code is in the repository's
`aio/aio-builds-setup/` directory and expects the following inputs:
- **$1**: `HOST_REPO_DIR`
- **$2**: `HOST_LOCALCERTS_DIR`
- **$3**: `HOST_SECRETS_DIR`
- **$4**: `HOST_BUILDS_DIR`
- **$5**: `HOST_LOGS_DIR`
See [here](vm-setup--create-host-dirs-and-files.md) for more info on what each input directory is
used for.
**Note 1:** The script has to execute docker commands with `sudo`.
**Note 2:** Make sure the user that executes the script has access to update the repository
## Run the script manually
You may choose to manually run the script, when necessary. Example:
```
update-preview-server.sh \
/path/to/repo \
/path/to/localcerts \
/path/to/secrets \
/path/to/builds \
/path/to/logs
```
## Run the script automatically
You may choose to automatically trigger the script, e.g. using a cronjob. For example, the following
cronjob entry would run the script every hour and update the preview server (assuming the user has
the necessary permissions):
```
# Periodically check for changes and update the preview server (if necessary)
*/30 * * * * /path/to/update-preview-server.sh /path/to/repo /path/to/localcerts /path/to/secrets /path/to/builds /path/to/logs
```

View File

@ -2,9 +2,15 @@
set -eux -o pipefail set -eux -o pipefail
# Set up env # Set up env
source "`dirname $0`/_env.sh" source "`dirname $0`/env.sh"
readonly defaultImageNameAndTag="aio-builds:latest" readonly defaultImageNameAndTag="aio-builds:latest"
# Build `scripts-js/`
cd "$SCRIPTS_JS_DIR"
yarn install
yarn run build
cd -
# Create docker image # Create docker image
readonly nameAndOptionalTag=${1:-$defaultImageNameAndTag} readonly nameAndOptionalTag=${1:-$defaultImageNameAndTag}
sudo docker build --tag $nameAndOptionalTag ${@:2} $DOCKERBUILD_DIR sudo docker build --tag $nameAndOptionalTag ${@:2} $DOCKERBUILD_DIR

View File

@ -2,11 +2,10 @@
set -eux -o pipefail set -eux -o pipefail
# Set up env # Set up env
source "`dirname $0`/_env.sh" source "`dirname $0`/env.sh"
# Test `scripts-js/` # Test `scripts-js/`
( cd "$SCRIPTS_JS_DIR"
cd "$SCRIPTS_JS_DIR" yarn install
yarn install yarn test
yarn test cd -
)

View File

@ -2,14 +2,7 @@
set -eux -o pipefail set -eux -o pipefail
# Set up env # Set up env
source "`dirname $0`/_env.sh" source "`dirname $0`/env.sh"
# Build `scripts-js/`
(
cd "$SCRIPTS_JS_DIR"
yarn install
yarn build
)
# Preverify PR # Preverify PR
AIO_GITHUB_ORGANIZATION="angular" \ AIO_GITHUB_ORGANIZATION="angular" \

View File

@ -1,70 +0,0 @@
#!/usr/bin/env bash
set -eux -o pipefail
exec 3>&1
echo "[`date`] - Updating the preview server..."
# Input
readonly HOST_REPO_DIR=$1
readonly HOST_LOCALCERTS_DIR=$2
readonly HOST_SECRETS_DIR=$3
readonly HOST_BUILDS_DIR=$4
readonly HOST_LOGS_DIR=$5
# Constants
readonly PROVISIONAL_IMAGE_NAME=aio-builds:provisional
readonly LATEST_IMAGE_NAME=aio-builds:latest
readonly CONTAINER_NAME=aio
# Run
(
cd "$HOST_REPO_DIR"
readonly lastDeployedCommit=$(git rev-parse HEAD)
echo "Currently at commit $lastDeployedCommit."
# Pull latest master from origin.
git pull origin master
# Do not update the server unless files inside `aio-builds-setup/` have changed
# or the last attempt failed (identified by the provisional image still being around).
readonly relevantChangedFilesCount=$(git diff --name-only $lastDeployedCommit...HEAD | grep -P "^aio/aio-builds-setup/" | wc -l)
readonly lastAttemptFailed=$(sudo docker rmi "$PROVISIONAL_IMAGE_NAME" >> /dev/fd/3 && echo "true" || echo "false")
if [[ $relevantChangedFilesCount -eq 0 ]] && [[ "$lastAttemptFailed" != "true" ]]; then
echo "Skipping update because no relevant files have been touched."
exit 0
fi
# Create and verify a new docker image.
aio/aio-builds-setup/scripts/create-image.sh "$PROVISIONAL_IMAGE_NAME"
readonly imageVerified=$(sudo docker run --dns 127.0.0.1 --rm --volume $HOST_SECRETS_DIR:/aio-secrets:ro "$PROVISIONAL_IMAGE_NAME" /bin/bash -c "aio-init && aio-health-check && aio-verify-setup" >> /dev/fd/3 && echo "true" || echo "false")
if [[ "$imageVerified" != "true" ]]; then
echo "Failed to verify new docker image. Aborting update!"
exit 1
fi
# Remove the old container and replace the docker image.
sudo docker stop "$CONTAINER_NAME" || true
sudo docker rm "$CONTAINER_NAME" || true
sudo docker rmi "$LATEST_IMAGE_NAME" || true
sudo docker tag "$PROVISIONAL_IMAGE_NAME" "$LATEST_IMAGE_NAME"
sudo docker rmi "$PROVISIONAL_IMAGE_NAME"
# Create and start a docker container based on the new image.
sudo docker run \
--detach \
--dns 127.0.0.1 \
--name "$CONTAINER_NAME" \
--publish 80:80 \
--publish 443:443 \
--restart unless-stopped \
--volume $HOST_LOCALCERTS_DIR:/etc/ssl/localcerts:ro \
--volume $HOST_SECRETS_DIR:/aio-secrets:ro \
--volume $HOST_BUILDS_DIR:/var/www/aio-builds \
--volume $HOST_LOGS_DIR:/var/log/aio \
"$LATEST_IMAGE_NAME"
echo "The new docker image has been successfully deployed."
)

View File

@ -19,7 +19,7 @@ export class AppComponent {
movie: IMovie = null; movie: IMovie = null;
movies: IMovie[] = []; movies: IMovie[] = [];
showImage = true; showImage = true;
title = 'AngularJS to Angular Quick Ref Cookbook'; title: string = 'AngularJS to Angular Quick Ref Cookbook';
toggleImage(event: UIEvent) { toggleImage(event: UIEvent) {
this.showImage = !this.showImage; this.showImage = !this.showImage;
this.eventType = (event && event.type) || 'not provided'; this.eventType = (event && event.type) || 'not provided';

View File

@ -19,7 +19,7 @@ import { MovieService } from './movie.service';
export class MovieListComponent { export class MovieListComponent {
// #enddocregion class // #enddocregion class
favoriteHero: string; favoriteHero: string;
showImage = false; showImage: boolean = false;
movies: IMovie[]; movies: IMovie[];
// #docregion di // #docregion di

View File

@ -15,6 +15,6 @@ import { HEROES } from './hero';
}) })
export class HeroParentComponent { export class HeroParentComponent {
heroes = HEROES; heroes = HEROES;
master = 'Master'; master: string = 'Master';
} }
// #enddocregion // #enddocregion

View File

@ -11,8 +11,8 @@ import { Component } from '@angular/core';
` `
}) })
export class VersionParentComponent { export class VersionParentComponent {
major = 1; major: number = 1;
minor = 23; minor: number = 23;
newMinor() { newMinor() {
this.minor++; this.minor++;

View File

@ -16,7 +16,7 @@ import { UserService } from './user.service';
export class AppComponent { export class AppComponent {
// #enddocregion import-services // #enddocregion import-services
private userId = 1; private userId: number = 1;
// #docregion ctor // #docregion ctor
constructor(logger: LoggerService, public userContext: UserContextService) { constructor(logger: LoggerService, public userContext: UserContextService) {

View File

@ -1,4 +1,4 @@
/* tslint:disable:one-line*/ /* tslint:disable:one-line:check-open-brace*/
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';

View File

@ -1,4 +1,4 @@
/* tslint:disable:one-line*/ /* tslint:disable:one-line:check-open-brace*/
// #docplaster // #docplaster
// #docregion injection-token // #docregion injection-token
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Hero } from './hero'; import { Hero } from './hero';
export const HEROES: Hero[] = [ export var HEROES: Hero[] = [
{ id: 11, isSecret: false, name: 'Mr. Nice' }, { id: 11, isSecret: false, name: 'Mr. Nice' },
{ id: 12, isSecret: false, name: 'Narco' }, { id: 12, isSecret: false, name: 'Narco' },
{ id: 13, isSecret: false, name: 'Bombasto' }, { id: 13, isSecret: false, name: 'Bombasto' },

View File

@ -117,7 +117,7 @@ class OldLogger {
export class Provider6aComponent { export class Provider6aComponent {
log: string; log: string;
constructor(newLogger: NewLogger, oldLogger: OldLogger) { constructor(newLogger: NewLogger, oldLogger: OldLogger) {
if (newLogger === oldLogger) { if (newLogger === oldLogger){
throw new Error('expected the two loggers to be different instances'); throw new Error('expected the two loggers to be different instances');
} }
oldLogger.log('Hello OldLogger (but we want NewLogger)'); oldLogger.log('Hello OldLogger (but we want NewLogger)');
@ -140,7 +140,7 @@ export class Provider6aComponent {
export class Provider6bComponent { export class Provider6bComponent {
log: string; log: string;
constructor(newLogger: NewLogger, oldLogger: OldLogger) { constructor(newLogger: NewLogger, oldLogger: OldLogger) {
if (newLogger !== oldLogger) { if (newLogger !== oldLogger){
throw new Error('expected the two loggers to be the same instance'); throw new Error('expected the two loggers to be the same instance');
} }
oldLogger.log('Hello from NewLogger (via aliased OldLogger)'); oldLogger.log('Hello from NewLogger (via aliased OldLogger)');

View File

@ -8,7 +8,7 @@ import 'rxjs/add/operator/delay';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
isLoggedIn = false; isLoggedIn: boolean = false;
// store the URL so we can redirect after logging in // store the URL so we can redirect after logging in
redirectUrl: string; redirectUrl: string;

View File

@ -15,7 +15,7 @@ export class ComposeMessageComponent {
@HostBinding('style.position') position = 'absolute'; @HostBinding('style.position') position = 'absolute';
details: string; details: string;
sending = false; sending: boolean = false;
constructor(private router: Router) {} constructor(private router: Router) {}

View File

@ -1,4 +1,4 @@
/* tslint:disable use-output-property-decorator directive-class-suffix */ /* tslint:disable use-output-property-decorator */
// #docplaster // #docplaster
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core'; import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';

View File

@ -3,7 +3,7 @@ import { DependentService, FancyService } from './bag';
///////// Fakes ///////// ///////// Fakes /////////
export class FakeFancyService extends FancyService { export class FakeFancyService extends FancyService {
value = 'faked value'; value: string = 'faked value';
} }
//////////////////////// ////////////////////////
// #docregion FancyService // #docregion FancyService

View File

@ -677,5 +677,5 @@ class FakeGrandchildComponent { }
@Injectable() @Injectable()
class FakeFancyService extends FancyService { class FakeFancyService extends FancyService {
value = 'faked value'; value: string = 'faked value';
} }

View File

@ -20,7 +20,7 @@ export class Hero {
// #docregion FancyService // #docregion FancyService
@Injectable() @Injectable()
export class FancyService { export class FancyService {
protected value = 'real value'; protected value: string = 'real value';
getValue() { return this.value; } getValue() { return this.value; }
setValue(value: string) { this.value = value; } setValue(value: string) { this.value = value; }

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Hero } from './hero'; import { Hero } from './hero';
export const HEROES: Hero[] = [ export var HEROES: Hero[] = [
new Hero(11, 'Mr. Nice'), new Hero(11, 'Mr. Nice'),
new Hero(12, 'Narco'), new Hero(12, 'Narco'),
new Hero(13, 'Bombasto'), new Hero(13, 'Bombasto'),

View File

@ -5,7 +5,7 @@ export { HeroService } from '../hero.service';
import { Hero } from '../hero'; import { Hero } from '../hero';
import { HeroService } from '../hero.service'; import { HeroService } from '../hero.service';
export const HEROES: Hero[] = [ export var HEROES: Hero[] = [
new Hero(41, 'Bob'), new Hero(41, 'Bob'),
new Hero(42, 'Carol'), new Hero(42, 'Carol'),
new Hero(43, 'Ted'), new Hero(43, 'Ted'),

View File

@ -0,0 +1,37 @@
// #docregion show-hero
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
// #enddocregion show-hero
// #docregion show-hero-2
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
// #enddocregion show-hero-2
// #docregion show-hero-properties
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
// #enddocregion show-hero-properties
// #docregion multi-line-strings
template: '''
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div><label>name: </label>{{hero.name}}</div>'''
// #enddocregion multi-line-strings
// #docregion editing-Hero
template: '''
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input value="{{hero.name}}" placeholder="name">
</div>'''
// #enddocregion editing-Hero
// #docregion app-component-1
class AppComponent {
String title = 'Tour of Heroes';
var hero = 'Windstorm';
}
// #enddocregion app-component-1

View File

@ -0,0 +1,69 @@
// #docregion ng-for
<li *ngFor="let hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion ng-for
// #docregion heroes-styled
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
</ul>
// #enddocregion heroes-styled
// #docregion selectedHero-click
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion selectedHero-click
// #docregion selectedHero-details
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name">
</div>
// #enddocregion selectedHero-details
// #docregion ng-if
<div *ngIf="selectedHero != null">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name">
</div>
</div>
// #enddocregion ng-if
// #docregion hero-array-1
final List<Hero> heroes = mockHeroes;
// #enddocregion hero-array-1
// #docregion heroes-template-1
<h2>My Heroes</h2>
<ul class="heroes">
<li>
<!-- each hero goes here -->
</li>
</ul>
// #enddocregion heroes-template-1
// #docregion heroes-ngfor-1
<li *ngFor="let hero of heroes">
// #enddocregion heroes-ngfor-1
// #docregion class-selected-1
[class.selected]="hero == selectedHero"
// #enddocregion class-selected-1
// #docregion class-selected-2
<li *ngFor="let hero of heroes"
[class.selected]="hero == selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
</li>
// #enddocregion class-selected-2

View File

@ -2,14 +2,14 @@
import { Hero } from './hero'; import { Hero } from './hero';
export const HEROES: Hero[] = [ export const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' }, {id: 11, name: 'Mr. Nice'},
{ id: 12, name: 'Narco' }, {id: 12, name: 'Narco'},
{ id: 13, name: 'Bombasto' }, {id: 13, name: 'Bombasto'},
{ id: 14, name: 'Celeritas' }, {id: 14, name: 'Celeritas'},
{ id: 15, name: 'Magneta' }, {id: 15, name: 'Magneta'},
{ id: 16, name: 'RubberMan' }, {id: 16, name: 'RubberMan'},
{ id: 17, name: 'Dynama' }, {id: 17, name: 'Dynama'},
{ id: 18, name: 'Dr IQ' }, {id: 18, name: 'Dr IQ'},
{ id: 19, name: 'Magma' }, {id: 19, name: 'Magma'},
{ id: 20, name: 'Tornado' } {id: 20, name: 'Tornado'}
]; ];

View File

@ -1,15 +1,15 @@
// #docregion // #docregion
import { Hero } from './hero'; import { Hero } from './hero';
export const HEROES: Hero[] = [ export var HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' }, {id: 11, name: 'Mr. Nice'},
{ id: 12, name: 'Narco' }, {id: 12, name: 'Narco'},
{ id: 13, name: 'Bombasto' }, {id: 13, name: 'Bombasto'},
{ id: 14, name: 'Celeritas' }, {id: 14, name: 'Celeritas'},
{ id: 15, name: 'Magneta' }, {id: 15, name: 'Magneta'},
{ id: 16, name: 'RubberMan' }, {id: 16, name: 'RubberMan'},
{ id: 17, name: 'Dynama' }, {id: 17, name: 'Dynama'},
{ id: 18, name: 'Dr IQ' }, {id: 18, name: 'Dr IQ'},
{ id: 19, name: 'Magma' }, {id: 19, name: 'Magma'},
{ id: 20, name: 'Tornado' } {id: 20, name: 'Tornado'}
]; ];

View File

@ -5,7 +5,7 @@ import { promise } from 'selenium-webdriver';
const expectedH1 = 'Tour of Heroes'; const expectedH1 = 'Tour of Heroes';
const expectedTitle = `Angular ${expectedH1}`; const expectedTitle = `Angular ${expectedH1}`;
const targetHero = { id: 14, name: 'Celeritas' }; const targetHero = { id: 15, name: 'Magneta' };
const targetHeroDashboardIndex = 3; const targetHeroDashboardIndex = 3;
const nameSuffix = 'X'; const nameSuffix = 'X';
const newHeroName = targetHero.name + nameSuffix; const newHeroName = targetHero.name + nameSuffix;
@ -136,7 +136,7 @@ describe('Tutorial part 6', () => {
getPageElts().myHeroesHref.click(); getPageElts().myHeroesHref.click();
let page = getPageElts(); let page = getPageElts();
expect(page.myHeroes.isPresent()).toBeTruthy(); expect(page.myHeroes.isPresent()).toBeTruthy();
expect(page.allHeroes.count()).toEqual(11, 'number of heroes'); expect(page.allHeroes.count()).toEqual(10, 'number of heroes');
}); });
it(`selects and shows ${targetHero.name} as selected in list`, () => { it(`selects and shows ${targetHero.name} as selected in list`, () => {
@ -176,7 +176,7 @@ describe('Tutorial part 6', () => {
const page = getPageElts(); const page = getPageElts();
expect(page.myHeroes.isPresent()).toBeTruthy(); expect(page.myHeroes.isPresent()).toBeTruthy();
expect(page.allHeroes.count()).toEqual(10, 'number of heroes'); expect(page.allHeroes.count()).toEqual(9, 'number of heroes');
const heroesAfter = await toHeroArray(page.allHeroes); const heroesAfter = await toHeroArray(page.allHeroes);
const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName); const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName);
expect(heroesAfter).toEqual(expectedHeroes); expect(heroesAfter).toEqual(expectedHeroes);
@ -206,20 +206,20 @@ describe('Tutorial part 6', () => {
beforeAll(() => browser.get('')); beforeAll(() => browser.get(''));
it(`searches for 'Ce'`, async () => { it(`searches for 'Ma'`, async () => {
getPageElts().searchBox.sendKeys('Ce'); getPageElts().searchBox.sendKeys('Ma');
browser.sleep(1000);
expect(getPageElts().searchResults.count()).toBe(4);
});
it(`continues search with 'g'`, async () => {
getPageElts().searchBox.sendKeys('g');
browser.sleep(1000); browser.sleep(1000);
expect(getPageElts().searchResults.count()).toBe(2); expect(getPageElts().searchResults.count()).toBe(2);
}); });
it(`continues search with 'l'`, async () => { it(`continues search with 'n' and gets ${targetHero.name}`, async () => {
getPageElts().searchBox.sendKeys('l'); getPageElts().searchBox.sendKeys('n');
browser.sleep(1000);
expect(getPageElts().searchResults.count()).toBe(1);
});
it(`continues search with 'e' and gets ${targetHero.name}`, async () => {
getPageElts().searchBox.sendKeys('e');
browser.sleep(1000); browser.sleep(1000);
let page = getPageElts(); let page = getPageElts();
expect(page.searchResults.count()).toBe(1); expect(page.searchResults.count()).toBe(1);

View File

@ -2,18 +2,17 @@
import { InMemoryDbService } from 'angular-in-memory-web-api'; import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService { export class InMemoryDataService implements InMemoryDbService {
createDb() { createDb() {
const heroes = [ let heroes = [
{ id: 0, name: 'Zero' }, {id: 11, name: 'Mr. Nice'},
{ id: 11, name: 'Mr. Nice' }, {id: 12, name: 'Narco'},
{ id: 12, name: 'Narco' }, {id: 13, name: 'Bombasto'},
{ id: 13, name: 'Bombasto' }, {id: 14, name: 'Celeritas'},
{ id: 14, name: 'Celeritas' }, {id: 15, name: 'Magneta'},
{ id: 15, name: 'Magneta' }, {id: 16, name: 'RubberMan'},
{ id: 16, name: 'RubberMan' }, {id: 17, name: 'Dynama'},
{ id: 17, name: 'Dynama' }, {id: 18, name: 'Dr IQ'},
{ id: 18, name: 'Dr IQ' }, {id: 19, name: 'Magma'},
{ id: 19, name: 'Magma' }, {id: 20, name: 'Tornado'}
{ id: 20, name: 'Tornado' }
]; ];
return {heroes}; return {heroes};
} }

View File

@ -8,7 +8,7 @@ import { Attribute, Component, Inject, Optional } from '@angular/core';
}) })
// #enddocregion templateUrl // #enddocregion templateUrl
export class HeroTitleComponent { export class HeroTitleComponent {
msg = ''; msg: string = '';
constructor( constructor(
@Inject('titlePrefix') @Optional() private titlePrefix: string, @Inject('titlePrefix') @Optional() private titlePrefix: string,
@Attribute('title') private title: string @Attribute('title') private title: string

View File

@ -1,94 +0,0 @@
{
"rulesDirectory": [
"../../node_modules/codelyzer"
],
"rules": {
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": true,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true,
"import-destructuring-spacing": true
}
}

View File

@ -162,4 +162,22 @@ describe('Upgrade Tests', function () {
}); });
describe('Dividing routes', function() {
beforeAll(function () {
browser.get('/index-divide-routes.html');
});
it('allows ng1 routes', function () {
browser.get('/index-divide-routes.html#/villain');
expect(element(by.css('h2')).getText()).toBe('Mr. Nice - No More Mr. Nice Guy');
});
it('allows ng2 routes', function () {
browser.get('/index-divide-routes.html#/hero');
expect(element(by.css('h2')).getText()).toBe('Windstorm - Specific powers of controlling winds');
});
});
}); });

View File

@ -6,9 +6,8 @@ import { UpgradeModule } from '@angular/upgrade/static';
import { heroDetailComponent } from './hero-detail.component'; import { heroDetailComponent } from './hero-detail.component';
// #docregion ngmodule, register // #docregion ngmodule
import { Heroes } from './heroes'; import { Heroes } from './heroes';
// #enddocregion register
@NgModule({ @NgModule({
imports: [ imports: [
@ -18,10 +17,7 @@ import { Heroes } from './heroes';
providers: [ Heroes ] providers: [ Heroes ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion ngmodule // #enddocregion ngmodule
// #docregion register // #docregion register
@ -32,4 +28,7 @@ angular.module('heroApp', [])
.component('heroDetail', heroDetailComponent); .component('heroDetail', heroDetailComponent);
// #enddocregion register // #enddocregion register
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -22,10 +22,7 @@ import { ContainerComponent } from './container.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion heroupgrade // #enddocregion heroupgrade
@ -36,4 +33,7 @@ angular.module('heroApp', [])
downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory
); );
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -8,8 +8,7 @@ export const heroDetail = {
<div> <div>
<ng-transclude></ng-transclude> <ng-transclude></ng-transclude>
</div> </div>
`, `
transclude: true
}; };
// #enddocregion // #enddocregion

View File

@ -11,10 +11,7 @@ import { UpgradeModule } from '@angular/upgrade/static';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion ngmodule // #enddocregion ngmodule
angular.module('heroApp', []) angular.module('heroApp', [])
@ -25,5 +22,8 @@ angular.module('heroApp', [])
// #docregion bootstrap // #docregion bootstrap
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});
// #enddocregion bootstrap // #enddocregion bootstrap

View File

@ -1,12 +1,10 @@
// #docregion ng1module
angular.module('heroApp', []) angular.module('heroApp', [])
.controller('MainCtrl', function() { .controller('MainCtrl', function() {
this.message = 'Hello world'; this.message = 'Hello world';
}); });
// #enddocregion ng1module
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// #docregion bootstrap // #docregion bootstrap
angular.bootstrap(document.body, ['heroApp'], { strictDi: true }); angular.bootstrap(document.body, ['heroApp'], {strictDi: true});
// #enddocregion bootstrap // #enddocregion bootstrap
}); });

View File

@ -20,10 +20,7 @@ import { HeroDetailComponent } from './hero-detail.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
angular.module('heroApp', []) angular.module('heroApp', [])
@ -33,4 +30,7 @@ angular.module('heroApp', [])
inputs: ['hero'] inputs: ['hero']
}) as angular.IDirectiveFactory); }) as angular.IDirectiveFactory);
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -27,10 +27,7 @@ import { heroesServiceProvider } from './ajs-upgraded-providers';
// #docregion register // #docregion register
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion register // #enddocregion register
@ -41,4 +38,7 @@ angular.module('heroApp', [])
downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory
); );
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -0,0 +1,11 @@
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<router-outlet></router-outlet>
<div ng-view></div>
`,
})
export class AppComponent { }

View File

@ -0,0 +1,62 @@
// #docregion
declare var angular: angular.IAngularStatic;
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { HeroModule } from './hero.module';
// #docregion router-config
import { HashLocationStrategy, LocationStrategy } from '@angular/common';
import { RouterModule, UrlHandlingStrategy, UrlTree } from '@angular/router';
import { AppComponent } from './app.component';
class HybridUrlHandlingStrategy implements UrlHandlingStrategy {
// use only process the `/hero` url
shouldProcessUrl(url: UrlTree) { return url.toString().startsWith('/hero'); }
extract(url: UrlTree) { return url; }
merge(url: UrlTree, whole: UrlTree) { return url; }
}
@NgModule({
imports: [
BrowserModule,
UpgradeModule,
HeroModule,
RouterModule.forRoot([])
],
providers: [
// use hash location strategy
{ provide: LocationStrategy, useClass: HashLocationStrategy },
// use custom url handling strategy
{ provide: UrlHandlingStrategy, useClass: HybridUrlHandlingStrategy }
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
// #enddocregion router-config
import { Villain } from '../villain';
export const villainDetail = {
template: `
<h1>Villain detail</h1>
<h2>{{$ctrl.villain.name}} - {{$ctrl.villain.description}}</h2>
`,
controller: function() {
this.villain = new Villain(1, 'Mr. Nice', 'No More Mr. Nice Guy');
}
};
angular.module('heroApp', ['ngRoute'])
.component('villainDetail', villainDetail)
.config(['$locationProvider', '$routeProvider',
function config($locationProvider: angular.ILocationProvider,
$routeProvider: angular.route.IRouteProvider) {
// #docregion ajs-route
$routeProvider
.when('/villain', { template: '<villain-detail></villain-detail>' });
// #enddocregion ajs-route
}
]);

View File

@ -0,0 +1,32 @@
// #docregion
import { Component } from '@angular/core';
import { Hero } from '../hero';
@Component({
template: `
<h1>Hero detail</h1>
<h2>{{hero.name}} - {{hero.description}}</h2>
`
})
export class HeroDetailComponent {
hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds');
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@NgModule({
imports: [
CommonModule,
// #docregion a-route
RouterModule.forChild([
{ path: 'hero', children: [
{ path: '', component: HeroDetailComponent },
] },
])
// #enddocregion a-route
],
declarations: [ HeroDetailComponent ]
})
export class HeroModule {}

View File

@ -0,0 +1,10 @@
// #docregion
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -23,17 +23,21 @@ import { HeroDetailComponent } from './hero-detail.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #docregion downgradecomponent // #docregion downgradecomponent
angular.module('heroApp', []) angular.module('heroApp', [])
.controller('MainController', MainController) .controller('MainController', MainController)
.directive('heroDetail', downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory); .directive('heroDetail', downgradeComponent({
component: HeroDetailComponent,
inputs: ['hero'],
outputs: ['deleted']
}) as angular.IDirectiveFactory);
// #enddocregion downgradecomponent // #enddocregion downgradecomponent
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -21,10 +21,7 @@ import { HeroDetailComponent } from './hero-detail.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion ngmodule // #enddocregion ngmodule
// #docregion downgradecomponent // #docregion downgradecomponent
@ -34,9 +31,12 @@ import { downgradeComponent } from '@angular/upgrade/static';
angular.module('heroApp', []) angular.module('heroApp', [])
.directive( .directive(
'heroDetail', 'heroDetail',
downgradeComponent({ component: HeroDetailComponent }) as angular.IDirectiveFactory downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory
); );
// #enddocregion downgradecomponent // #enddocregion downgradecomponent
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -8,15 +8,15 @@ export function heroDetailDirective() {
deleted: '&' deleted: '&'
}, },
template: ` template: `
<h2>{{$ctrl.hero.name}} details!</h2> <h2>{{ctrl.hero.name}} details!</h2>
<div><label>id: </label>{{$ctrl.hero.id}}</div> <div><label>id: </label>{{ctrl.hero.id}}</div>
<button ng-click="$ctrl.onDelete()">Delete</button> <button ng-click="ctrl.onDelete()">Delete</button>
`, `,
controller: function() { controller: function() {
this.onDelete = () => { this.onDelete = () => {
this.deleted({hero: this.hero}); this.deleted({hero: this.hero});
}; };
}, },
controllerAs: '$ctrl' controllerAs: 'ctrl'
}; };
} }

View File

@ -22,10 +22,7 @@ import { ContainerComponent } from './container.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion heroupgrade // #enddocregion heroupgrade
@ -36,4 +33,7 @@ angular.module('heroApp', [])
downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory
); );
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -24,10 +24,7 @@ import { ContainerComponent } from './container.component';
] ]
}) })
export class AppModule { export class AppModule {
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, ['heroApp'], { strictDi: true });
}
} }
// #enddocregion hero-detail-upgrade // #enddocregion hero-detail-upgrade
@ -38,4 +35,7 @@ angular.module('heroApp', [])
downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory
); );
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true});
});

View File

@ -11,7 +11,7 @@ export const heroDetail = {
// #enddocregion hero-detail // #enddocregion hero-detail
// #docregion hero-detail-upgrade // #docregion hero-detail-upgrade
import { Directive, ElementRef, Injector, SimpleChanges } from '@angular/core'; import { Directive, ElementRef, Injector } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static'; import { UpgradeComponent } from '@angular/upgrade/static';
@Directive({ @Directive({

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Angular 2 Upgrade</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<script src="https://code.angularjs.org/1.5.5/angular.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular-route.js"></script>
<!-- Polyfills for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.1.js"></script>
<script>
System.import('app/divide-routes/main')
.then(null, console.error.bind(console));
</script>
</head>
<!--#docregion body-->
<body>
<my-app>Loading...</my-app>
</body>
<!--#enddocregion body-->
</html>

View File

@ -67,10 +67,7 @@ import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
}) })
export class AppModule { export class AppModule {
// #enddocregion bare // #enddocregion bare
constructor(private upgrade: UpgradeModule) { } ngDoBootstrap() {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.documentElement, ['phonecatApp']);
}
// #docregion bare // #docregion bare
} }
// #enddocregion bare, upgrademodule, httpmodule, phone, phonelist, phonedetail, checkmarkpipe // #enddocregion bare, upgrademodule, httpmodule, phone, phonelist, phonedetail, checkmarkpipe

View File

@ -1,6 +1,10 @@
// #docregion // #docregion
import { platformBrowser } from '@angular/platform-browser'; import { platformBrowser } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';
import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory'; import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory';
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then(platformRef => {
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.documentElement, ['phonecatApp']);
});

View File

@ -0,0 +1,7 @@
**/*.js
aot/**/*
!aot/bs-config.json
!aot/index.html
!copy-dist-files.js
!rollup-config.js
!systemjs.config.1.js

View File

@ -0,0 +1,34 @@
This is the Angular Phonecat application adjusted to fit our boilerplate project
structure.
The following changes from vanilla Phonecat are applied:
* Karma config for unit tests is in karma.conf.ng1.js because the boilerplate
Karma config is not compatible with the way Angular 1 tests need to be run.
The shell script run-unit-tests.sh can be used to run the unit tests.
* There's a `package.ng1.json`, which is not used to run anything but only to
show an example of changing the PhoneCat http-server root path.
* Also for the Karma shim, there is a `karma-test-shim.1.js` file which isn't
used but is shown in the test appendix.
* Instead of using Bower, Angular 1 and its dependencies are fetched from a CDN
in index.html and karma.conf.ng1.js.
* E2E tests have been moved to the parent directory, where `run-e2e-tests` can
discover and run them along with all the other examples.
* Most of the phone JSON and image data removed in the interest of keeping
repo weight down. Keeping enough to retain testability of the app.
## Running the app
Start like any example
npm run start
## Running unit tests
./run-unit-tests.sh
## Running E2E tests
Like for any example (at the project root):
gulp run-e2e-tests --filter=phonecat-2

View File

@ -0,0 +1,38 @@
<!-- #docregion -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="/app/">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="app.animations.css" />
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular-animate.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular-resource.js"></script>
<script src="https://code.angularjs.org/1.5.5/angular-route.js"></script>
<script src="app.module.ajs.js"></script>
<script src="app.config.js"></script>
<script src="app.animations.js"></script>
<script src="core/core.module.js"></script>
<script src="core/phone/phone.module.js"></script>
<script src="phone-list/phone-list.module.js"></script>
<script src="phone-detail/phone-detail.module.js"></script>
<script src="/node_modules/core-js/client/shim.min.js"></script>
<script src="/node_modules/zone.js/dist/zone.min.js"></script>
<script>window.module = 'aot';</script>
</head>
<body>
<phonecat-app></phonecat-app>
</body>
<script src="/dist/build.js"></script>
</html>

View File

@ -0,0 +1,14 @@
// #docregion
export abstract class RouteParams {
[key: string]: string;
}
export function routeParamsFactory(i: any) {
return i.get('$routeParams');
}
export const routeParamsProvider = {
provide: RouteParams,
useFactory: routeParamsFactory,
deps: ['$injector']
};

View File

@ -0,0 +1,30 @@
// #docregion
import { NgModule } from '@angular/core';
import { Routes, RouterModule, UrlHandlingStrategy, UrlTree } from '@angular/router';
import { APP_BASE_HREF, HashLocationStrategy, LocationStrategy } from '@angular/common';
import { PhoneListComponent } from './phone-list/phone-list.component';
export class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy {
shouldProcessUrl(url: UrlTree) {
return url.toString() === '/' || url.toString() === '/phones';
}
extract(url: UrlTree) { return url; }
merge(url: UrlTree, whole: UrlTree) { return url; }
}
const routes: Routes = [
{ path: '', redirectTo: 'phones', pathMatch: 'full' },
{ path: 'phones', component: PhoneListComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '!' },
{ provide: LocationStrategy, useClass: HashLocationStrategy },
{ provide: UrlHandlingStrategy, useClass: Ng1Ng2UrlHandlingStrategy }
]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,67 @@
/* Animate `ngRepeat` in `phoneList` component */
.phone-list-item.ng-enter,
.phone-list-item.ng-leave,
.phone-list-item.ng-move {
overflow: hidden;
transition: 0.5s linear all;
}
.phone-list-item.ng-enter,
.phone-list-item.ng-leave.ng-leave-active,
.phone-list-item.ng-move {
height: 0;
margin-bottom: 0;
opacity: 0;
padding-bottom: 0;
padding-top: 0;
}
.phone-list-item.ng-enter.ng-enter-active,
.phone-list-item.ng-leave,
.phone-list-item.ng-move.ng-move-active {
height: 120px;
margin-bottom: 20px;
opacity: 1;
padding-bottom: 4px;
padding-top: 15px;
}
/* Animate view transitions with `ngView` */
.view-container {
position: relative;
}
.view-frame {
margin-top: 20px;
}
.view-frame.ng-enter,
.view-frame.ng-leave {
background: white;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.view-frame.ng-enter {
animation: 1s fade-in;
z-index: 100;
}
.view-frame.ng-leave {
animation: 1s fade-out;
z-index: 99;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* Older browsers might need vendor-prefixes for keyframes and animation! */

View File

@ -0,0 +1,43 @@
'use strict';
angular.
module('phonecatApp').
animation('.phone', function phoneAnimationFactory() {
return {
addClass: animateIn,
removeClass: animateOut
};
function animateIn(element: JQuery, className: string, done: () => void) {
if (className !== 'selected') { return; }
element.css({
display: 'block',
position: 'absolute',
top: 500,
left: 0
}).animate({
top: 0
}, done);
return function animateInEnd(wasCanceled: boolean) {
if (wasCanceled) { element.stop(); }
};
}
function animateOut(element: JQuery, className: string, done: () => void) {
if (className !== 'selected') { return; }
element.css({
position: 'absolute',
top: 0,
left: 0
}).animate({
top: -500
}, done);
return function animateOutEnd(wasCanceled: boolean) {
if (wasCanceled) { element.stop(); }
};
}
});

View File

@ -0,0 +1,13 @@
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'phonecat-app',
template: `
<router-outlet></router-outlet>
<div class="view-container">
<div ng-view class="view-frame"></div>
</div>
`
})
export class AppComponent { }

View File

@ -0,0 +1,16 @@
'use strict';
angular.
module('phonecatApp').
config(['$locationProvider', '$routeProvider',
function config($locationProvider: angular.ILocationProvider,
$routeProvider: angular.route.IRouteProvider) {
$locationProvider.hashPrefix('!');
// #docregion ajs-routes
$routeProvider
.when('/phones/:phoneId', {
template: '<phone-detail></phone-detail>'
});
// #enddocregion ajs-routes
}
]);

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