Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
24db1ed938 | |||
82798e9d04 | |||
da8bb1b45b | |||
f5cbc2ee25 | |||
cbc1986c6f | |||
0982f993cb | |||
a5a29b0591 | |||
a8f3197f24 | |||
e6f37120fe | |||
6840b7bda9 | |||
68f458909a | |||
12acecf756 | |||
cfbed40ab6 | |||
fe1a6b8e42 | |||
13e29c4e89 | |||
fd52b178ed | |||
ca1f071b2e | |||
296adbbb72 | |||
c795ee1176 | |||
b550618afd | |||
d08d6eebff | |||
e9789abd05 | |||
f2ec2cbb99 | |||
8de2ace80a | |||
c977994864 | |||
12b8e1af55 | |||
9a188485f5 | |||
45a10419bc | |||
2245748c14 | |||
bcea196530 | |||
b9e32c833a | |||
be49e0ee93 | |||
bf95655a1a | |||
6bf5b84fa4 | |||
4836565ca7 | |||
750e4e8156 | |||
a0846194b7 | |||
bcf6b90c95 | |||
3ca2a0aa37 | |||
b4be96c65d | |||
434ff5fecb | |||
a1bb9c2d42 | |||
7e626bef0a | |||
a1e83a8ed2 | |||
cbeb197aa5 | |||
0330fa6b82 | |||
97135e8fd5 | |||
35bd07fc7b | |||
a8ac77b645 | |||
9ecd377a51 | |||
76171bd8b4 | |||
1f106d75bc | |||
a4fae8c405 | |||
33c07b3394 | |||
c9d06e676f | |||
c7c65d9fda | |||
257a9e3e6f | |||
c7c0a1688e | |||
7e95e2b0ba | |||
ddc286f4b5 | |||
3d17a3672e | |||
61d253f5fd |
@ -41,7 +41,7 @@ jobs:
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
||||
- run: bazel run @io_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel run @build_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
|
@ -1,9 +1,12 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
# force trusty as Google Chrome addon is not supported on Precise
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
# firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
@ -52,14 +55,15 @@ env:
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=docs_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_e2e
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
- env: "CI_MODE=aio_e2e"
|
||||
|
||||
before_install:
|
||||
# source the env.sh script so that the exported variables are available to other scripts later on
|
||||
|
@ -11,8 +11,15 @@ filegroup(
|
||||
# This won't scale in the general case.
|
||||
# TODO(alexeagle): figure out what to do
|
||||
"node_modules/typescript/**",
|
||||
"node_modules/zone.js/**/*.d.ts",
|
||||
"node_modules/zone.js/**",
|
||||
"node_modules/rxjs/**/*.d.ts",
|
||||
"node_modules/rxjs/**/*.js",
|
||||
"node_modules/@types/**/*.d.ts",
|
||||
"node_modules/tsickle/**",
|
||||
"node_modules/hammerjs/**/*.d.ts",
|
||||
"node_modules/protobufjs/**",
|
||||
"node_modules/bytebuffer/**",
|
||||
"node_modules/reflect-metadata/**",
|
||||
"node_modules/minimist/**/*.js",
|
||||
]),
|
||||
)
|
29
CHANGELOG.md
@ -1,3 +1,30 @@
|
||||
<a name="4.3.3"></a>
|
||||
## [4.3.3](https://github.com/angular/angular/compare/4.3.2...4.3.3) (2017-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** fix for element needing implicit parent placed in top-level ng-container ([f5cbc2e](https://github.com/angular/angular/commit/f5cbc2e)), closes [#18314](https://github.com/angular/angular/issues/18314)
|
||||
|
||||
|
||||
|
||||
<a name="4.3.2"></a>
|
||||
## [4.3.2](https://github.com/angular/angular/compare/4.3.1...4.3.2) (2017-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** export BrowserModule as apart of BrowserAnimationsModule ([#18263](https://github.com/angular/angular/issues/18263)) ([cbeb197](https://github.com/angular/angular/commit/cbeb197))
|
||||
* **compiler:** add equiv & disp attributes to Xliff2 ICU placeholders ([#18283](https://github.com/angular/angular/issues/18283)) ([a084619](https://github.com/angular/angular/commit/a084619)), closes [#17344](https://github.com/angular/angular/issues/17344)
|
||||
* **compiler:** allow numbers for ICU message cases in lexer ([#18095](https://github.com/angular/angular/issues/18095)) ([a8ac77b](https://github.com/angular/angular/commit/a8ac77b)), closes [#17799](https://github.com/angular/angular/issues/17799)
|
||||
* **core:** invoke error handler outside of the Angular Zone ([#18269](https://github.com/angular/angular/issues/18269)) ([a1bb9c2](https://github.com/angular/angular/commit/a1bb9c2)), closes [#17073](https://github.com/angular/angular/issues/17073) [#7774](https://github.com/angular/angular/issues/7774)
|
||||
* **platform-server:** don't clobber parse5 properties when setting ([#18237](https://github.com/angular/angular/issues/18237)) ([97135e8](https://github.com/angular/angular/commit/97135e8)), closes [#17050](https://github.com/angular/angular/issues/17050)
|
||||
* **router:** child CanActivate guard should wait for parent to complete ([#18110](https://github.com/angular/angular/issues/18110)) ([b9e32c8](https://github.com/angular/angular/commit/b9e32c8)), closes [#15670](https://github.com/angular/angular/issues/15670)
|
||||
* **router:** should throw when lazy loaded module doesn't define any routes ([#15001](https://github.com/angular/angular/issues/15001)) ([be49e0e](https://github.com/angular/angular/commit/be49e0e)), closes [#14596](https://github.com/angular/angular/issues/14596)
|
||||
* **upgrade:** throw error if trying to get injector before setting ([#18209](https://github.com/angular/angular/issues/18209)) ([1f106d7](https://github.com/angular/angular/commit/1f106d7))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.1"></a>
|
||||
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
|
||||
|
||||
@ -10,7 +37,7 @@
|
||||
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
|
||||
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
|
||||
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
|
||||
* **compiler-cli:** don't generate empty <target/> when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **compiler-cli:** don't generate empty `<target/>` when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
|
||||
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
|
||||
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
|
||||
|
12
WORKSPACE
@ -1,11 +1,17 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
git_repository(
|
||||
name = "io_bazel_rules_typescript",
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
commit = "3a8404d",
|
||||
tag = "0.0.5",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
|
||||
|
||||
node_repositories(package_json = "//:package.json")
|
||||
|
||||
git_repository(
|
||||
name = "build_bazel_rules_angular",
|
||||
remote = "https://github.com/bazelbuild/rules_angular.git",
|
||||
tag = "0.0.1",
|
||||
)
|
10
aio/content/examples/.gitignore
vendored
@ -43,13 +43,9 @@ dist/
|
||||
**/app/**/*.ajs.js
|
||||
|
||||
# aot
|
||||
**/*.ngfactory.ts
|
||||
**/*.ngsummary.json
|
||||
**/*.ngsummary.ts
|
||||
**/*.shim.ngstyle.ts
|
||||
**/*.metadata.json
|
||||
!aot/bs-config.json
|
||||
!aot/index.html
|
||||
*/aot/**/*
|
||||
!*/aot/bs-config.json
|
||||
!*/aot/index.html
|
||||
!rollup-config.js
|
||||
|
||||
# i18n
|
||||
|
@ -9,7 +9,7 @@ export class AppComponent {
|
||||
wolves = 0;
|
||||
gender = 'f';
|
||||
fly = true;
|
||||
logo = 'https://angular.io/resources/images/logos/angular/angular.png';
|
||||
logo = 'https://angular.io/assets/images/logos/angular/angular.png';
|
||||
count = 3;
|
||||
heroes: string[] = ['Magneta', 'Celeritas', 'Dynama'];
|
||||
inc(i: number) {
|
||||
|
@ -1,116 +0,0 @@
|
||||
/* #docregion , quickstart, toh */
|
||||
/* Master Styles */
|
||||
h1 {
|
||||
color: #369;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 250%;
|
||||
}
|
||||
h2, h3 {
|
||||
color: #444;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: lighter;
|
||||
}
|
||||
body {
|
||||
margin: 2em;
|
||||
}
|
||||
/* #enddocregion quickstart */
|
||||
body, input[text], button {
|
||||
color: #888;
|
||||
font-family: Cambria, Georgia;
|
||||
}
|
||||
/* #enddocregion toh */
|
||||
a {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button {
|
||||
font-family: Arial;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #eee;
|
||||
color: #aaa;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
/* Navigation link styles */
|
||||
nav a {
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
background-color: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a:visited, a:link {
|
||||
color: #607D8B;
|
||||
}
|
||||
nav a:hover {
|
||||
color: #039be5;
|
||||
background-color: #CFD8DC;
|
||||
}
|
||||
nav a.active {
|
||||
color: #039be5;
|
||||
}
|
||||
|
||||
/* items class */
|
||||
.items {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 24em;
|
||||
}
|
||||
.items li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.items li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.items li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.items li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
}
|
||||
.items .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.items .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
/* #docregion toh */
|
||||
/* everywhere else */
|
||||
* {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
@ -9,7 +9,8 @@ describe('PhoneCat Application', function() {
|
||||
|
||||
it('should redirect `index.html` to `index.html#!/phones', function() {
|
||||
browser.get('index.html');
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones');
|
||||
browser.sleep(1000); // Not sure why this is needed but it is. The route change works fine.
|
||||
expect(browser.getCurrentUrl()).toMatch(/\/phones$/);
|
||||
});
|
||||
|
||||
describe('View: Phone list', function() {
|
||||
@ -65,7 +66,7 @@ describe('PhoneCat Application', function() {
|
||||
|
||||
element.all(by.css('.phones li a')).first().click();
|
||||
browser.sleep(1000); // Not sure why this is needed but it is. The route change works fine.
|
||||
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
|
||||
expect(browser.getCurrentUrl()).toMatch(/\/phones\/nexus-s$/);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -124,7 +124,7 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||
|
||||
You also import NgModules_ from Angular _libraries_ using JavaScript import statements:
|
||||
You also import NgModules from Angular _libraries_ using JavaScript import statements:
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||
|
||||
|
@ -193,8 +193,8 @@ Angular supports most recent browsers. This includes the following specific vers
|
||||
|
||||
|
||||
|
||||
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
|
||||
using <a href="https://saucelabs.com/">SauceLabs</a> and
|
||||
Angular's continuous integration process runs unit tests of the framework on all of these browsers for every pull request,
|
||||
using <a href="https://saucelabs.com/">SauceLabs</a> and
|
||||
<a href="https://www.browserstack.com">Browserstack</a>.
|
||||
|
||||
|
||||
@ -215,7 +215,7 @@ that implement missing features in JavaScript.
|
||||
|
||||
|
||||
|
||||
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||
A particular browser may require at least one polyfill to run _any_ Angular application.
|
||||
You may need additional polyfills for specific features.
|
||||
|
||||
The tables below can help you determine which polyfills to load, depending on the browsers you target and the features you use.
|
||||
@ -241,7 +241,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<th>
|
||||
Browsers (Desktop & Mobile)
|
||||
</th>
|
||||
@ -253,7 +253,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
Chrome, Firefox, Edge, Safari 9+
|
||||
</td>
|
||||
@ -265,7 +265,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
Safari 7 & 8, IE10 & 11, Android 4.1+
|
||||
</td>
|
||||
@ -279,7 +279,7 @@ These are the polyfills required to run an Angular application on each supported
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
IE9
|
||||
</td>
|
||||
@ -309,7 +309,7 @@ Here are the features which may require additional polyfills:
|
||||
<table>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<th>
|
||||
Feature
|
||||
</th>
|
||||
@ -325,7 +325,7 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
[Animations](guide/animations)
|
||||
@ -363,14 +363,14 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
[NgClass](api/common/NgClass) on SVG elements
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
|
||||
[classList](guide/browser-support#classlist)
|
||||
</td>
|
||||
@ -382,16 +382,17 @@ Here are the features which may require additional polyfills:
|
||||
</tr>
|
||||
|
||||
<tr style="vertical-align: top">
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Http](guide/http) when sending and receiving binary data
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
|
||||
[Typed Array](guide/browser-support#typedarray) <br>[Blob](guide/browser-support#blob)<br>[FormData](guide/browser-support#formdata)
|
||||
[Typed Array](guide/browser-support#typedarray)<br>
|
||||
[Blob](guide/browser-support#blob)<br>
|
||||
[FormData](guide/browser-support#formdata)
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@ -405,7 +406,7 @@ Here are the features which may require additional polyfills:
|
||||
|
||||
|
||||
### Suggested polyfills ##
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
Below are the polyfills which are used to test the framework itself. They are a good starting point for an application.
|
||||
|
||||
|
||||
<table>
|
||||
@ -542,5 +543,5 @@ Below are the polyfills which are used to test the framework itself. They are a
|
||||
|
||||
|
||||
|
||||
\* Figures are for minified and gzipped code,
|
||||
\* Figures are for minified and gzipped code,
|
||||
computed with the <a href="http://closure-compiler.appspot.com/home">closure compiler</a>.
|
||||
|
@ -29,7 +29,7 @@ import {HttpClientModule} from '@angular/common/http';
|
||||
export class MyAppModule {}
|
||||
```
|
||||
|
||||
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
|
||||
Once you import `HttpClientModule` into your app module, you can inject `HttpClient`
|
||||
into your components and services.
|
||||
|
||||
## Making a request for JSON data
|
||||
@ -56,7 +56,7 @@ export class MyComponent implements OnInit {
|
||||
|
||||
// Inject HttpClient into your component or service.
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
// Make the HTTP request:
|
||||
this.http.get('/api/items').subscribe(data => {
|
||||
@ -72,7 +72,7 @@ export class MyComponent implements OnInit {
|
||||
|
||||
In the above example, the `data['results']` field access stands out because you use bracket notation to access the results field. If you tried to write `data.results`, TypeScript would correctly complain that the `Object` coming back from HTTP does not have a `results` property. That's because while `HttpClient` parsed the JSON response into an `Object`, it doesn't know what shape that object is.
|
||||
|
||||
You can, however, tell `HttpClient` what type the response will be, which is recommended.
|
||||
You can, however, tell `HttpClient` what type the response will be, which is recommended.
|
||||
To do so, first you define an interface with the correct shape:
|
||||
|
||||
```javascript
|
||||
@ -163,7 +163,7 @@ RxJS has a useful operator called `.retry()`, which automatically resubscribes t
|
||||
|
||||
First, import it:
|
||||
|
||||
```js
|
||||
```js
|
||||
import 'rxjs/add/operator/retry';
|
||||
```
|
||||
|
||||
@ -197,7 +197,7 @@ In addition to fetching data from the server, `HttpClient` supports mutating req
|
||||
|
||||
### Making a POST request
|
||||
|
||||
One common operation is to POST data to a server; for example when submitting a form. The code for
|
||||
One common operation is to POST data to a server; for example when submitting a form. The code for
|
||||
sending a POST request is very similar to the code for GET:
|
||||
|
||||
```javascript
|
||||
@ -261,12 +261,12 @@ The above sections detail how to use the basic HTTP functionality in `@angular/c
|
||||
|
||||
### Intercepting all requests or responses
|
||||
|
||||
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
|
||||
A major feature of `@angular/common/http` is _interception_, the ability to declare interceptors which sit in between your application and the backend. When your application makes a request, interceptors transform it
|
||||
before sending it to the server, and the interceptors can transform the response on its way back before your application sees it. This is useful for everything from authentication to logging.
|
||||
|
||||
#### Writing an interceptor
|
||||
|
||||
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
|
||||
To implement an interceptor, you declare a class that implements `HttpInterceptor`, which
|
||||
has a single `intercept()` method. Here is a simple interceptor which does nothing but forward the request through without altering it:
|
||||
|
||||
```javascript
|
||||
@ -319,7 +319,7 @@ An interceptor must pass through all events that it does not understand or inten
|
||||
|
||||
##### Ordering
|
||||
|
||||
When you provide multiple interceptors in an application, Angular applies them in the order that you
|
||||
When you provide multiple interceptors in an application, Angular applies them in the order that you
|
||||
provided them.
|
||||
|
||||
##### Immutability
|
||||
@ -335,10 +335,10 @@ If you have a need to mutate the request body, you need to copy the request body
|
||||
Since requests are immutable, they cannot be modified directly. To mutate them, use `clone()`:
|
||||
|
||||
```javascript
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpError<any>> {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// This is a duplicate. It is exactly the same as the original.
|
||||
const dupReq = req.clone();
|
||||
|
||||
|
||||
// Change the URL and replace 'http://' with 'https://'
|
||||
const secureReq = req.clone({url: req.url.replace('http://', 'https://')});
|
||||
}
|
||||
@ -357,7 +357,7 @@ import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/com
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Get the auth header from the service.
|
||||
const authHeader = this.auth.getAuthorizationHeader();
|
||||
@ -390,7 +390,7 @@ import 'rxjs/add/operator/do';
|
||||
|
||||
export class TimingInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const started = Date.now();
|
||||
return next
|
||||
@ -398,7 +398,7 @@ export class TimingInterceptor implements HttpInterceptor {
|
||||
.do(event => {
|
||||
if (event instanceof HttpResponse) {
|
||||
const elapsed = Date.now() - started;
|
||||
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
|
||||
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -416,7 +416,7 @@ abstract class HttpCache {
|
||||
* Returns a cached response, if any, or null if not present.
|
||||
*/
|
||||
abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
|
||||
|
||||
|
||||
/**
|
||||
* Adds or updates the response in the cache.
|
||||
*/
|
||||
@ -430,14 +430,14 @@ An interceptor can apply this cache to outgoing requests.
|
||||
@Injectable()
|
||||
export class CachingInterceptor implements HttpInterceptor {
|
||||
constructor(private cache: HttpCache) {}
|
||||
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Before doing anything, it's important to only cache GET requests.
|
||||
// Skip this interceptor if the request method isn't GET.
|
||||
if (req.method !== 'GET') {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
|
||||
// First, check the cache to see if this request exists.
|
||||
const cachedResponse = this.cache.get(req);
|
||||
if (cachedResponse) {
|
||||
@ -445,7 +445,7 @@ export class CachingInterceptor implements HttpInterceptor {
|
||||
// the request to the next handler.
|
||||
return Observable.of(cachedResponse);
|
||||
}
|
||||
|
||||
|
||||
// No cached response exists. Go to the network, and cache
|
||||
// the response when it arrives.
|
||||
return next.handle(req).do(event => {
|
||||
@ -469,17 +469,17 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
||||
if (req.method !== 'GET') {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
|
||||
// This will be an Observable of the cached value if there is one,
|
||||
// or an empty Observable otherwise. It starts out empty.
|
||||
let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();
|
||||
|
||||
|
||||
// Check the cache.
|
||||
const cachedResponse = this.cache.get(req);
|
||||
if (cachedResponse) {
|
||||
maybeCachedResponse = Observable.of(cachedResponse);
|
||||
}
|
||||
|
||||
|
||||
// Create an Observable (but don't subscribe) that represents making
|
||||
// the network request and caching the value.
|
||||
const networkResponse = next.handle(req).do(event => {
|
||||
@ -488,7 +488,7 @@ intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
|
||||
this.cache.put(req, event);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Now, combine the two and send the cached response first (if there is
|
||||
// one), and the network response second.
|
||||
return Observable.concat(maybeCachedResponse, networkResponse);
|
||||
@ -509,7 +509,7 @@ const req = new HttpRequest('POST', '/upload/file', file, {
|
||||
});
|
||||
```
|
||||
|
||||
This option enables tracking of progress events. Remember, every progress event triggers
|
||||
This option enables tracking of progress events. Remember, every progress event triggers
|
||||
change detection, so only turn them on if you intend to actually update the UI on each event.
|
||||
|
||||
Next, make the request through the `request()` method of `HttpClient`. The result will be an Observable of events, just like with interceptors:
|
||||
@ -532,11 +532,11 @@ http.request(req).subscribe(event => {
|
||||
|
||||
[Cross-Site Request Forgery (XSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website. `HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
|
||||
|
||||
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
|
||||
to relative URLs but not on GET/HEAD requests or
|
||||
By default, an interceptor sends this cookie on all mutating requests (POST, etc.)
|
||||
to relative URLs but not on GET/HEAD requests or
|
||||
on requests with an absolute URL.
|
||||
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
|
||||
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication
|
||||
cookie with a salt for added security.
|
||||
|
||||
In order to prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.
|
||||
@ -598,21 +598,21 @@ it('expects a GET request', inject([HttpClient, HttpTestingController], (http: H
|
||||
http
|
||||
.get('/data')
|
||||
.subscribe(data => expect(data['name']).toEqual('Test Data'));
|
||||
|
||||
|
||||
// At this point, the request is pending, and no response has been
|
||||
// sent. The next step is to expect that the request happened.
|
||||
const req = httpMock.expectOne('/data');
|
||||
|
||||
|
||||
// If no request with that URL was made, or if multiple requests match,
|
||||
// expectOne() would throw. However this test makes only one request to
|
||||
// this URL, so it will match and return a mock request. The mock request
|
||||
// can be used to deliver a response or make assertions against the
|
||||
// request. In this case, the test asserts that the request is a GET.
|
||||
expect(req.request.method).toEqual('GET');
|
||||
|
||||
|
||||
// Next, fulfill the request by transmitting a response.
|
||||
req.flush({name: 'Test Data'});
|
||||
|
||||
|
||||
// Finally, assert that there are no outstanding requests.
|
||||
httpMock.verify();
|
||||
}));
|
||||
|
@ -615,7 +615,7 @@ Once the application begins, the app root injector is closed to new providers.
|
||||
|
||||
Time passes and application logic triggers lazy loading of a module.
|
||||
Angular must add the lazy-loaded module's providers to an injector somewhere.
|
||||
It can't added them to the app root injector because that injector is closed to new providers.
|
||||
It can't add them to the app root injector because that injector is closed to new providers.
|
||||
So Angular creates a new child injector for the lazy-loaded module context.
|
||||
|
||||
|
||||
|
@ -1080,8 +1080,11 @@ To get access to the `FormArray` class, import it into `hero-detail.component.ts
|
||||
|
||||
|
||||
To _work_ with a `FormArray` you do the following:
|
||||
|
||||
1. Define the items (`FormControls` or `FormGroups`) in the array.
|
||||
|
||||
1. Initialize the array with items created from data in the _data model_.
|
||||
|
||||
1. Add and remove items as the user requires.
|
||||
|
||||
In this guide, you define a `FormArray` for `Hero.addresses` and
|
||||
|
@ -1830,7 +1830,7 @@ Finally, you activate the observable with `subscribe` method and (re)set the com
|
||||
|
||||
#### _ParamMap_ API
|
||||
|
||||
The `ParamMap` API is inspired by the [URLSearchParams interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParamsOPut). It provides methods
|
||||
The `ParamMap` API is inspired by the [URLSearchParams interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams). It provides methods
|
||||
to handle parameter access for both route parameters (`paramMap`) and query parameters (`queryParamMap`).
|
||||
|
||||
<table>
|
||||
|
BIN
aio/content/images/bios/gerardsans.jpg
Normal file
After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
aio/content/images/marketing/home/angular-mix.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 257 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 4.1 KiB |
@ -573,5 +573,14 @@
|
||||
"website": "http://www.methotic.com",
|
||||
"bio": "Thierry is a senior consultant and trainer, specialized on Angular, and a Google Developer Expert.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"gerardsans": {
|
||||
"name": "Gerard Sans",
|
||||
"picture": "gerardsans.jpg",
|
||||
"twitter": "gerardsans",
|
||||
"website": "https://medium.com/@gerard.sans",
|
||||
"bio": "Gerard is very excited about the future of the Web and JavaScript. Always happy Computer Science Engineer and humble Google Developer Expert. He loves to share his learnings by giving talks, trainings and writing about cool technologies. He loves running AngularZone and GraphQL London, mentoring students and giving back to the community.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Cross Platform</div>
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Speed and Performance</div>
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Productivity</div>
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Full Development Story</div>
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="generated/images/marketing/features/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
<!--Announcement Bar-->
|
||||
<div class="homepage-container">
|
||||
<div class="announcement-bar">
|
||||
<img src="generated/images/marketing/angular-mix.png" height="40" width="151">
|
||||
<img src="generated/images/marketing/home/angular-mix.png" height="40" width="151">
|
||||
<p>Join us at our newest event, October 2017</p>
|
||||
<a class="button" href="https://angularmix.com/">Learn More</a>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@
|
||||
<div layout="row" layout-xs="column" class="home-row homepage-container">
|
||||
<div class="promo-img-container promo-1">
|
||||
<div>
|
||||
<img height="222" width="340" src="assets/images/home/responsive-framework.svg" alt="responsive framework">
|
||||
<img height="222" width="340" src="generated/images/marketing/home/responsive-framework.svg" alt="responsive framework">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
|
||||
<div class="promo-img-container promo-2">
|
||||
<div>
|
||||
<img height="222" width="323" src="assets/images/home/speed-performance.svg" alt="speed and performance">
|
||||
<img height="222" width="323" src="generated/images/marketing/home/speed-performance.svg" alt="speed and performance">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,7 +74,7 @@
|
||||
<!-- Group 3-->
|
||||
<div layout="row" layout-xs="column" class="home-row">
|
||||
<div class="promo-img-container promo-3">
|
||||
<div><img src="assets/images/home/joyful-development.png" alt="IDE example"></div>
|
||||
<div><img src="generated/images/marketing/home/joyful-development.svg" alt="IDE example"></div>
|
||||
</div>
|
||||
|
||||
<div class="text-container">
|
||||
@ -100,7 +100,7 @@
|
||||
|
||||
<div class="promo-img-container promo-4">
|
||||
<div>
|
||||
<img src="assets/images/home/loved-by-millions.png" alt="angular on the map" width="455" height="228">
|
||||
<img src="generated/images/marketing/home/loved-by-millions.svg" alt="angular on the map" width="455" height="228">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -110,7 +110,7 @@
|
||||
|
||||
<a href="guide/quickstart">
|
||||
<div class="card">
|
||||
<img src="assets/images/icons/code-icon.svg" height="70px">
|
||||
<img src="generated/images/marketing/home/code-icon.svg" height="70px">
|
||||
<div class="card-text-container">
|
||||
<div class="text-headline">Get Started</div>
|
||||
<p>Start building your Angular application.</p>
|
||||
|
@ -17,7 +17,7 @@
|
||||
"title": "Events"
|
||||
},
|
||||
{
|
||||
"url": "https://blog.angularjs.org/",
|
||||
"url": "https://blog.angular.io/",
|
||||
"title": "Blog"
|
||||
}
|
||||
],
|
||||
@ -39,7 +39,7 @@
|
||||
"title": "Events"
|
||||
},
|
||||
{
|
||||
"url": "https://blog.angularjs.org/",
|
||||
"url": "https://blog.angular.io/",
|
||||
"title": "Blog"
|
||||
}
|
||||
]
|
||||
|
@ -137,7 +137,7 @@ Create a file in the `app` folder called `hero.service.ts`.
|
||||
|
||||
|
||||
The naming convention for service files is the service name in lowercase followed by `.service`.
|
||||
For a multi-word service name, use lower [dash-case](guide/glossary).
|
||||
For a multi-word service name, use lower [dash-case](guide/glossary#dash-case).
|
||||
For example, the filename for `SpecialSuperHeroService` is `special-super-hero.service.ts`.
|
||||
|
||||
</div>
|
||||
|
@ -83,7 +83,7 @@ Added hero "Zero" to confirm that the data service can handle a hero with `id==0
|
||||
Don't worry about the details of this backend substitution; you can
|
||||
skip it when you have a real web API server.
|
||||
|
||||
div>
|
||||
</div>
|
||||
|
||||
## Heroes and HTTP
|
||||
|
||||
|
@ -10,8 +10,8 @@
|
||||
},
|
||||
"static.ignore": [
|
||||
"\\.js\\.map$",
|
||||
"^/assets/images/.*/unused/",
|
||||
"^/generated/(?:docs/(?!api/api-list\\.json).*|images|live-examples|zips)/"
|
||||
"^(?:/|\\\\)assets(?:/|\\\\)images(?:/|\\\\).*(?:/|\\\\)_unused(?:/|\\\\)",
|
||||
"^(?:/|\\\\)generated(?:/|\\\\)(?:docs(?:/|\\\\)(?!api(?:/|\\\\)api-list\\.json).*|images(?:/|\\\\)(?!marketing(?:/|\\\\)).*|live-examples|zips)(?:/|\\\\)"
|
||||
],
|
||||
"static.versioned": [
|
||||
"\\.[0-9a-z]{20}\\."
|
||||
|
@ -33,8 +33,9 @@
|
||||
"docs-test": "node tools/transforms/test.js",
|
||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
||||
"boilerplate:add": "node ./tools/examples/add-example-boilerplate add",
|
||||
"boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||
"boilerplate:test": "node tools/examples/test.js",
|
||||
"generate-plunkers": "node ./tools/plunker-builder/generatePlunkers",
|
||||
"generate-zips": "node ./tools/example-zipper/generateZips",
|
||||
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
||||
@ -48,17 +49,18 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^4.2.4",
|
||||
"@angular/common": "^4.2.4",
|
||||
"@angular/compiler": "^4.2.4",
|
||||
"@angular/core": "^4.2.4",
|
||||
"@angular/forms": "^4.2.4",
|
||||
"@angular/http": "^4.2.4",
|
||||
"@angular/material": "^2.0.0-beta.7",
|
||||
"@angular/platform-browser": "^4.2.4",
|
||||
"@angular/platform-browser-dynamic": "^4.2.4",
|
||||
"@angular/platform-server": "^4.2.4",
|
||||
"@angular/router": "^4.2.4",
|
||||
"@angular/animations": "^4.3.1",
|
||||
"@angular/cdk": "^2.0.0-beta.8",
|
||||
"@angular/common": "^4.3.1",
|
||||
"@angular/compiler": "^4.3.1",
|
||||
"@angular/core": "^4.3.1",
|
||||
"@angular/forms": "^4.3.1",
|
||||
"@angular/http": "^4.3.1",
|
||||
"@angular/material": "^2.0.0-beta.8",
|
||||
"@angular/platform-browser": "^4.3.1",
|
||||
"@angular/platform-browser-dynamic": "^4.3.1",
|
||||
"@angular/platform-server": "^4.3.1",
|
||||
"@angular/router": "^4.3.1",
|
||||
"@angular/service-worker": "^1.0.0-beta.16",
|
||||
"classlist.js": "^1.1.20150312",
|
||||
"core-js": "^2.4.1",
|
||||
@ -72,7 +74,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "angular/cli-builds#webpack-next",
|
||||
"@angular/compiler-cli": "^4.2.4",
|
||||
"@angular/compiler-cli": "^4.3.1",
|
||||
"@types/jasmine": "^2.5.52",
|
||||
"@types/node": "~6.0.60",
|
||||
"archiver": "^1.3.0",
|
||||
@ -81,7 +83,7 @@
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.20.0-rc.6",
|
||||
"dgeni-packages": "^0.20.0",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -4,12 +4,8 @@ set -eu -o pipefail
|
||||
|
||||
readonly thisDir=$(cd $(dirname $0); pwd)
|
||||
readonly parentDir=$(dirname $thisDir)
|
||||
readonly TOKEN=${ANGULAR_PAYLOAD_FIREBASE_TOKEN:-}
|
||||
readonly PROJECT_NAME="angular-payload-size"
|
||||
|
||||
# temporarily turn on debugging - we disable it later in the script to prevent token leak
|
||||
set -x
|
||||
|
||||
source ${thisDir}/_payload-limits.sh
|
||||
|
||||
failed=false
|
||||
@ -45,20 +41,21 @@ timestamp=$(date +%s)
|
||||
payloadData="$payloadData\"timestamp\": $timestamp, "
|
||||
|
||||
# Add change source: application, dependencies, or 'application+dependencies'
|
||||
yarnChanged=false
|
||||
allChangedFiles=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | wc -l)
|
||||
allChangedFileNames=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir)
|
||||
|
||||
if [[ $allChangedFileNames == *"yarn.lock"* ]]; then
|
||||
yarnChanged=true
|
||||
applicationChanged=false
|
||||
dependenciesChanged=false
|
||||
if [[ $(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | grep -v aio/yarn.lock | grep -v content) ]]; then
|
||||
applicationChanged=true
|
||||
fi
|
||||
if [[ $(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir/yarn.lock) ]]; then
|
||||
dependenciesChanged=true
|
||||
fi
|
||||
|
||||
if [[ $allChangedFiles -eq 1 ]] && [[ "$yarnChanged" = true ]]; then
|
||||
if $dependenciesChanged && $applicationChanged; then
|
||||
change='application+dependencies'
|
||||
elif $dependenciesChanged; then
|
||||
# only yarn.lock changed
|
||||
change='dependencies'
|
||||
elif [[ $allChangedFiles -gt 1 ]] && [[ "$yarnChanged" = true ]]; then
|
||||
change='application+dependencies'
|
||||
elif [[ $allChangedFiles -gt 0 ]]; then
|
||||
elif $applicationChanged; then
|
||||
change='application'
|
||||
else
|
||||
# Nothing changed in aio/
|
||||
@ -76,7 +73,7 @@ if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then
|
||||
|
||||
# WARNING: FIREBASE_TOKEN should NOT be printed.
|
||||
set +x
|
||||
firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$TOKEN" $dbPath
|
||||
firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$ANGULAR_PAYLOAD_FIREBASE_TOKEN" $dbPath
|
||||
fi
|
||||
|
||||
if [[ $failed = true ]]; then
|
||||
|
@ -19,11 +19,6 @@ const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
// Constants
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
// Specify the path to Chrome on Travis
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
|
@ -632,7 +632,6 @@ describe('AppComponent', () => {
|
||||
it('should initialize the search worker', inject([SearchService], (searchService: SearchService) => {
|
||||
fixture.detectChanges(); // triggers ngOnInit
|
||||
expect(searchService.initWorker).toHaveBeenCalled();
|
||||
expect(searchService.loadIndex).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -76,8 +76,8 @@ export class AppComponent implements OnInit {
|
||||
|
||||
get homeImageUrl() {
|
||||
return this.isSideBySide ?
|
||||
'assets/images/logos/standard/logo-nav@2x.png' :
|
||||
'assets/images/logos/standard/shield-large.svg';
|
||||
'assets/images/logos/angular/logo-nav@2x.png' :
|
||||
'assets/images/logos/angular/shield-large.svg';
|
||||
}
|
||||
get isOpened() { return this.isSideBySide && this.isSideNavDoc; }
|
||||
get mode() { return this.isSideBySide ? 'side' : 'over'; }
|
||||
@ -111,8 +111,8 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
// Do not initialize the search on browsers that lack web worker support
|
||||
if ('Worker' in window) {
|
||||
this.searchService.initWorker('app/search/search-worker.js');
|
||||
this.searchService.loadIndex();
|
||||
// Delay initialization by up to 2 seconds
|
||||
this.searchService.initWorker('app/search/search-worker.js', 2000);
|
||||
}
|
||||
|
||||
this.onResize(window.innerWidth);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchBoxComponent } from './search-box.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
@ -36,30 +36,67 @@ describe('SearchBoxComponent', () => {
|
||||
});
|
||||
|
||||
describe('initialisation', () => {
|
||||
it('should get the current search query from the location service', inject([LocationService], (location: MockLocationService) => {
|
||||
it('should get the current search query from the location service',
|
||||
inject([LocationService], (location: MockLocationService) => fakeAsync(() => {
|
||||
location.search.and.returnValue({ search: 'initial search' });
|
||||
component.ngOnInit();
|
||||
expect(location.search).toHaveBeenCalled();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('initial search');
|
||||
expect(component.searchBox.nativeElement.value).toEqual('initial search');
|
||||
})));
|
||||
});
|
||||
|
||||
describe('onSearch', () => {
|
||||
it('should debounce by 300ms', fakeAsync(() => {
|
||||
component.doSearch();
|
||||
expect(host.searchHandler).not.toHaveBeenCalled();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should pass through the value of the input box', fakeAsync(() => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (input)';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (input)');
|
||||
}));
|
||||
|
||||
it('should only send events if the search value has changed', fakeAsync(() => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = 'some query';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.nativeElement.value = 'some other query';
|
||||
component.doSearch();
|
||||
tick(300);
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(2);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('on input', () => {
|
||||
it('should trigger the onSearch event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (input)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (input)');
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('on keyup', () => {
|
||||
it('should trigger the onSearch event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (keyup)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('keyup', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (keyup)');
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -73,28 +110,11 @@ describe('SearchBoxComponent', () => {
|
||||
});
|
||||
|
||||
describe('on click', () => {
|
||||
it('should trigger the search event', () => {
|
||||
it('should trigger a search', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.nativeElement.value = 'some query (click)';
|
||||
spyOn(component, 'doSearch');
|
||||
input.triggerEventHandler('click', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledWith('some query (click)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('event filtering', () => {
|
||||
it('should only send events if the search value has changed', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = 'some query';
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(1);
|
||||
|
||||
input.nativeElement.value = 'some other query';
|
||||
input.triggerEventHandler('input', { });
|
||||
expect(host.searchHandler).toHaveBeenCalledTimes(2);
|
||||
expect(component.doSearch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -26,10 +26,11 @@ import 'rxjs/add/operator/distinctUntilChanged';
|
||||
})
|
||||
export class SearchBoxComponent implements OnInit {
|
||||
|
||||
private searchDebounce = 300;
|
||||
private searchSubject = new Subject<string>();
|
||||
|
||||
@ViewChild('searchBox') searchBox: ElementRef;
|
||||
@Output() onSearch = this.searchSubject.distinctUntilChanged();
|
||||
@Output() onSearch = this.searchSubject.distinctUntilChanged().debounceTime(this.searchDebounce);
|
||||
@Output() onFocus = new EventEmitter<string>();
|
||||
|
||||
constructor(private locationService: LocationService) { }
|
||||
|
@ -1,24 +1,62 @@
|
||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SearchService } from './search.service';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
describe('SearchService', () => {
|
||||
|
||||
let injector: ReflectiveInjector;
|
||||
let service: SearchService;
|
||||
let sendMessageSpy: jasmine.Spy;
|
||||
let mockWorker: WebWorkerClient;
|
||||
|
||||
beforeEach(() => {
|
||||
sendMessageSpy = jasmine.createSpy('sendMessage').and.returnValue(Observable.of({}));
|
||||
mockWorker = { sendMessage: sendMessageSpy } as any;
|
||||
spyOn(WebWorkerClient, 'create').and.returnValue(mockWorker);
|
||||
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
SearchService,
|
||||
{ provide: NgZone, useFactory: () => new NgZone({ enableLongStackTrace: false }) }
|
||||
]);
|
||||
service = injector.get(SearchService);
|
||||
});
|
||||
|
||||
describe('loadIndex', () => {
|
||||
it('should send a "load-index" message to the worker');
|
||||
it('should connect the `ready` property to the response to the "load-index" message');
|
||||
describe('initWorker', () => {
|
||||
it('should create the worker and load the index after the specified delay', fakeAsync(() => {
|
||||
service.initWorker('some/url', 100);
|
||||
expect(WebWorkerClient.create).not.toHaveBeenCalled();
|
||||
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
|
||||
tick(100);
|
||||
expect(WebWorkerClient.create).toHaveBeenCalledWith('some/url', jasmine.any(NgZone));
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should send a "query-index" message to the worker');
|
||||
it('should push the response to the `searchResults` observable');
|
||||
beforeEach(() => {
|
||||
// We must initialize the service before calling search
|
||||
service.initWorker('some/url', 100);
|
||||
});
|
||||
|
||||
it('should trigger a `loadIndex` synchronously', () => {
|
||||
service.search('some query');
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
});
|
||||
|
||||
it('should send a "query-index" message to the worker', () => {
|
||||
service.search('some query');
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('query-index', 'some query');
|
||||
});
|
||||
|
||||
it('should push the response to the `searchResults` observable', () => {
|
||||
const mockSearchResults = { results: ['a', 'b'] };
|
||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
||||
let searchResults: any;
|
||||
service.searchResults.subscribe(results => searchResults = results);
|
||||
service.search('some query');
|
||||
expect(searchResults).toEqual(mockSearchResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,8 +7,10 @@ can be found in the LICENSE file at http://angular.io/license
|
||||
import { NgZone, Injectable, Type } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/operator/publishLast';
|
||||
import 'rxjs/add/observable/race';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
export interface SearchResults {
|
||||
@ -27,26 +29,50 @@ export interface SearchResult {
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
private worker: WebWorkerClient;
|
||||
private ready: Observable<boolean>;
|
||||
private resultsSubject = new ReplaySubject<SearchResults>(1);
|
||||
readonly searchResults = this.resultsSubject.asObservable();
|
||||
private searchesSubject = new ReplaySubject<string>(1);
|
||||
searchResults: Observable<SearchResults>;
|
||||
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
initWorker(workerUrl) {
|
||||
this.worker = new WebWorkerClient(new Worker(workerUrl), this.zone);
|
||||
}
|
||||
|
||||
loadIndex() {
|
||||
const ready = this.ready = this.worker.sendMessage<boolean>('load-index').publishLast();
|
||||
// trigger the index to be loaded immediately
|
||||
ready.connect();
|
||||
/**
|
||||
* Initialize the search engine. We offer an `initDelay` to prevent the search initialisation from delaying the
|
||||
* initial rendering of the web page. Triggering a search will override this delay and cause the index to be
|
||||
* loaded immediately.
|
||||
*
|
||||
* @param workerUrl the url of the WebWorker script that runs the searches
|
||||
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
|
||||
*/
|
||||
initWorker(workerUrl: string, initDelay: number) {
|
||||
const searchResults = Observable
|
||||
// Wait for the initDelay or the first search
|
||||
.race(
|
||||
Observable.timer(initDelay),
|
||||
this.searchesSubject.first()
|
||||
)
|
||||
.concatMap(() => {
|
||||
// Create the worker and load the index
|
||||
const worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return worker.sendMessage('load-index').concatMap(() =>
|
||||
// Once the index has loaded, switch to listening to the searches coming in
|
||||
this.searchesSubject.switchMap((query) =>
|
||||
// Each search gets switched to a web worker message, whose results are returned via an observable
|
||||
worker.sendMessage<SearchResults>('query-index', query)
|
||||
)
|
||||
);
|
||||
}).publish();
|
||||
|
||||
// Connect to the observable to kick off the timer
|
||||
searchResults.connect();
|
||||
|
||||
// Expose the connected observable to the rest of the world
|
||||
this.searchResults = searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search query to the index.
|
||||
* The results will appear on the `searchResults` observable.
|
||||
*/
|
||||
search(query: string) {
|
||||
this.ready.concatMap(ready => {
|
||||
return this.worker.sendMessage('query-index', query) as Observable<SearchResults>;
|
||||
}).subscribe(results => this.resultsSubject.next(results));
|
||||
this.searchesSubject.next(query);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,11 @@ export interface WebWorkerMessage {
|
||||
export class WebWorkerClient {
|
||||
private nextId = 0;
|
||||
|
||||
constructor(private worker: Worker, private zone: NgZone) {
|
||||
static create(workerUrl: string, zone: NgZone) {
|
||||
return new WebWorkerClient(new Worker(workerUrl), zone);
|
||||
}
|
||||
|
||||
private constructor(private worker: Worker, private zone: NgZone) {
|
||||
}
|
||||
|
||||
sendMessage<T>(type: string, payload?: any): Observable<T> {
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
BIN
aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.png
Normal file
After Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
BIN
aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.png
Normal file
After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |