Compare commits

..

130 Commits
9.0.1 ... 9.0.4

Author SHA1 Message Date
d06b6de409 release: cut the v9.0.4 release 2020-02-27 13:42:19 -08:00
9b53054ea8 refactor(ngcc): guard against a crash if source-map flattening fails (#35718)
Source-maps in the wild could be badly formatted,
causing the source-map flattening processing to fail
unexpectedly. Rather than causing the whole of ngcc
to crash, we gracefully fallback to just returning the
generated source-map instead.

PR Close #35718
2020-02-27 16:09:37 -05:00
bfe7657006 fix(ngcc): handle mappings outside the content when flattening source-maps (#35718)
Previously when rendering flattened source-maps, it was assumed that no
mapping would come from a line that is outside the lines of the actual
source content. It turns out this is not a valid assumption.

Now the code that renders flattened source-maps will handle such
mappings, with the additional benefit that the rendered source-map
will only contain mapping lines up to the last mapping, rather than a
mapping line for every content line.

Fixes #35709

PR Close #35718
2020-02-27 16:09:37 -05:00
7ff845b72f fix(ngcc): handle missing sources when flattening source-maps (#35718)
If a package has a source-map but it does not provide
the actual content of the sources, then the source-map
flattening was crashing.

Now we ignore such mappings that have no source
since we are not able to compute the merged
mapping if there is no source file.

Fixes #35709

PR Close #35718
2020-02-27 16:09:37 -05:00
bf6fbf5a74 feat(docs-infra): add useful links if landed on 404 page and no search results found (#34978)
Added additional links which can help user find the things they are
looking for when there are no search results (when searching or on a 404
page).

Note:
This commit increases the main bundle's payload size due to the extra
content of the `aio-search-results` component.

Fixes #31532

PR Close #34978
2020-02-27 11:02:00 -08:00
e4f05d1952 ci(docs-infra): increase AIO ViewEngine payload size limit (#34978)
In #35702, the payload size limit for Ivy builds was bumped to account
for small incremental increases in recent PRs. The ViewEngine size has
also increased similarly (~500B), but it was not updated in #35702,
because its total increase was just below the 500B error threshold (by
6B).

This commit bumps the ViewEngine size limit too.

Note: Any investigation for the Ivy size increase (as a follow-up
to #35702) will most likely also apply to ViewEngine, since the size was
increased by the same amount.

PR Close #34978
2020-02-27 11:01:59 -08:00
f11fc1e3bd docs: correct spelling of 'detection' (#35723)
PR Close #35723
2020-02-27 10:49:16 -08:00
9064f4ecdb fix(ngcc): allow deep-import warnings to be ignored (#35683)
This commit adds a new ngcc configuration, `ignorableDeepImportMatchers`
for packages. This is a list of regular expressions matching deep imports
that can be safely ignored from that package. Deep imports that are not
ignored cause a warning to be logged.

// FW-1892

Fixes #35615

PR Close #35683
2020-02-27 10:48:49 -08:00
0eda98a28b test(docs-infra): fix tests with new topics property (#35539)
PR Close #35539
2020-02-27 10:47:52 -08:00
60921e8efd feat(docs-infra): add searchKeywords preprocessor (#35539)
This commit adds a new preprocessor to use `${@searchKeywords}`, allowing
the docs to use a list of custom search phrases that will be
prioritized over the keywords found in the content.

Closes #35449

PR Close #35539
2020-02-27 10:47:52 -08:00
c78df781f7 release: cut the v9.0.3 release 2020-02-26 20:37:08 -08:00
dc800b2f9e ci: increase AIO payload size limit (#35702)
This commit updates AIO payload size limit that is triggering a problem after merging 0bc35a71e2. That commit added some payload size, but all checks were "green" for the original PR (#34574), so it looks like it's an accumulated payload size increase from multiple changes. The goal of this commit is to bring the master branch back to "green" state.

PR Close #35702
2020-02-26 16:31:56 -08:00
79aaaa3254 fix(ivy): injecting incorrect provider when re-providing injectable with useClass (#34574)
If an injectable has a `useClass`, Ivy injects the token in `useClass`, rather than the original injectable, if the injectable is re-provided under a different token. The correct behavior is that it should inject the re-provided token, no matter whether it has `useClass`.

Fixes #34110.

PR Close #34574
2020-02-26 13:00:22 -08:00
c2dbcd36a6 fix(animations): Remove ɵAnimationDriver from private exports (#35690)
ɵAnimationDriver can be safely removed from private exports as AnimationDriver
is already a public export.  Since its already available, we can safely remove
its declaration and migrate its only usage in our repo to rely on the public
AnimationDriver symbol.

PR Close #35690
2020-02-26 12:59:31 -08:00
788d5d7046 test: run /integration/ng_elements_schematics test with bazel (#35669)
PR Close #35669
2020-02-26 12:58:36 -08:00
9ff778abe8 build: add npm package manifest to npm_integration_test (#35669)
PR Close #35669
2020-02-26 12:58:36 -08:00
d58b5ce486 build: polish up bazel karma saucelabs info tools/saucelabs/README.md (#35667)
PR Close #35667
2020-02-26 12:58:15 -08:00
f25d00a45d ci: add ayazhafiz to ownership of language-service code (#35651)
PR Close #35651
2020-02-26 12:57:29 -08:00
4c2bd64642 fix(ivy): better inference for circularly referenced directive types (#35622)
It's possible to pass a directive as an input to itself. Consider:

```html
<some-cmp #ref [value]="ref">
```

Since the template type-checker attempts to infer a type for `<some-cmp>`
using the values of its inputs, this creates a circular reference where the
type of the `value` input is used in its own inference:

```typescript
var _t0 = SomeCmp.ngTypeCtor({value: _t0});
```

Obviously, this doesn't work. To resolve this, the template type-checker
used to generate a `null!` expression when a reference would otherwise be
circular:

```typescript
var _t0 = SomeCmp.ngTypeCtor({value: null!});
```

This effectively asks TypeScript to infer a value for this context, and
works well to resolve this simple cycle. However, if the template
instead tries to use the circular value in a larger expression:

```html
<some-cmp #ref [value]="ref.prop">
```

The checker would generate:

```typescript
var _t0 = SomeCmp.ngTypeCtor({value: (null!).prop});
```

In this case, TypeScript can't figure out any way `null!` could have a
`prop` key, and so it infers `never` as the type. `(never).prop` is thus a
type error.

This commit implements a better fallback pattern for circular references to
directive types like this. Instead of generating a `null!` in place for the
reference, a type is inferred by calling the type constructor again with
`null!` as its input. This infers the widest possible type for the directive
which is then used to break the cycle:

```typescript
var _t0 = SomeCmp.ngTypeCtor(null!);
var _t1 = SomeCmp.ngTypeCtor({value: _t0.prop});
```

This has the desired effect of validating that `.prop` is legal for the
directive type (the type of `#ref`) while also avoiding a cycle.

Fixes #35372
Fixes #35603
Fixes #35522

PR Close #35622
2020-02-26 12:57:10 -08:00
e6c416fe74 fix(ivy): provide a more detailed error message for NG6002/NG6003 (#35620)
NG6002/NG6003 are errors produced when an NgModule being compiled has an
imported or exported type which does not have the proper metadata (that is,
it doesn't appear to be an @NgModule, or @Directive, etc. depending on
context).

Previously this error message was a bit sparse. However, Github issues show
that this is the most common error users receive when for whatever reason
ngcc wasn't able to handle one of their libraries, or they just didn't run
it. So this commit changes the error message to offer a bit more useful
context, instructing users differently depending on whether the class in
question is from their own project, from NPM, or from a monorepo-style local
dependency.

PR Close #35620
2020-02-26 12:56:48 -08:00
36dc1c7872 fix(core): support sanitizer value in the [style] bindings (#35564)
When binding to `[style]` we correctly sanitized/unwrapped properties but we did not do it for the object itself.

```
@HostBinding("style")
style: SafeStyle = this.sanitizer.bypassSecurityTrustStyle(
    "background: red; color: white; display: block;"
  );
```

Above code would fail since the `[style]` would not unwrap the `SafeValue` and would treat it as object resulting in incorrect behavior.

Fix #35476 (FW-1875)

PR Close #35564
2020-02-26 12:56:10 -08:00
c5ef307d95 fix(docs-infra): do not break when cookies are disabled in the browser (#35557)
Whenever cookies are disabled in the browser, `window.localStorage` is
not avaialable to the app (i.e. even trying to access
`window.localStorage` throws an error).

To avoid breaking the app, we use a no-op `Storage` implementation if
`window.localStorage` is not available.

(This is similar to #33829, but for `localStorage` instead of
`sessionStorage`.)

Fixes #35555

PR Close #35557
2020-02-26 12:54:55 -08:00
74afc2df82 feat(docs-infra): add entry-point label on api endpoint docs (#35427)
On API docs pages for Angular packages (e.g. https://angular.io/api/common), we show all primary and secondary entry-points. Following a link to one of the secondary entry-points (e.g. https://angular.io/api/common/http), navigates the docs page for the secondary entry-point, where it is incorrectly (and misleadingly) labelled as PACKAGE and not as an entry-point.

Implemented a new ENTRY-POINT label and add support for correctly differentiating between entry-points and packages.

Fixes #34081

PR Close #35427
2020-02-26 12:54:23 -08:00
0a8e8cd1f3 feat(ngcc): implement source-map flattening (#35132)
The library used by ngcc to update the source files (MagicString) is able
to generate a source-map but it is not able to account for any previous
source-map that the input text is already associated with.

There have been various attempts to fix this but none have been very
successful, since it is not a trivial problem to solve.

This commit contains a novel approach that is able to load up a tree of
source-files connected by source-maps and flatten them down into a single
source-map that maps directly from the final generated file to the original
sources referenced by the intermediate source-maps.

PR Close #35132
2020-02-26 12:51:36 -08:00
4f9798007d test(core): check dependency in extended child (#34767)
Related to https://github.com/angular/angular/issues/31337

PR Close #34767
2020-02-26 12:50:53 -08:00
0328e030b3 docs(common): update ngForTrackBy error URL (#34761)
Old URL is redirected to
`https://angular.io/api/common/NgForOf#!#change-propagation`
which is not exactly
`https://angular.io/api/common/NgForOf#change-propagation`

PR Close #34761
2020-02-26 12:47:49 -08:00
Zac
ae7b5f4dc3 docs(core): correct insertBefore's description (#34547)
Update the description of `refChild`
Based on https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
PR Close #34547
2020-02-26 12:46:11 -08:00
Zac
a13bf202f6 docs(core): describe your change... (#34513)
According to https://angular.io/guide/lifecycle-hooks#ondestroy
"Put cleanup logic in ngOnDestroy(), the logic that must run before Angular destroys the directive."
PR Close #34513
2020-02-26 12:45:42 -08:00
19003a42f3 docs: more precise docs for template statement syntax (#34348)
PR Close #34348
2020-02-26 12:44:59 -08:00
1a4456d432 docs: add documentation of NgZone with zone.js (#34295)
PR Close #34295
2020-02-26 12:43:00 -08:00
406ce8c884 refactor(platform-browser): Hoist functions to workaround optimization bug. (#32230)
Technically, function definitions can live anywhere because they are
hoisted. However, in this case Closure optimizations break when exported
function definitions are referred in another static object that is
exported.

The bad pattern is:
```
exports const obj = {f};
export function f() {...}
```

which turns to the following in Closure's module system:
```
goog.module('m');

exports.obj = {f};

function f() {...}
exports.f = f;
```

which badly optimizes to (note module objects are collapsed)
```
var b = a; var a = function() {...};  // now b is undefined.
```

This is an optimizer bug and should be fixed in Closure, but in the
meantime this change is a noop and will unblock other changes we want to
make.

PR Close #32230
2020-02-26 09:04:41 -08:00
d6904882d0 fix(ivy): error when accessing NgModuleRef.componentFactoryResolver in constructor (#35637)
Currently we resolve the `NgModuleRef.componentFactoryResolver` by going through the injector, but the problem is that `ComponentFactoryResolver` has a dependency on `NgModuleRef`, which means that if the module that's attached to the ref tries to inject  `ComponentFactoryResolver` in its constructor, we'll create a circular dependency which throws at runtime.

These changes resolve the issue by creating the `ComponentFactoryResolver` manually ahead of time without going through the injector. We can do this safely, because the only dependency for the resolver is the current module ref which is providing it.

Aside from fixing the issue, another advantage to this approach is that it should reduce the amount of generated JS, because it removes a getter and a provider definitio.

Fixes #35580.

PR Close #35637
2020-02-25 13:19:14 -08:00
c9cac92628 build(packaging): add repository.directory field to package.jsons (#27544)
PR Close #27544
2020-02-25 13:12:46 -08:00
7403ba13d5 fix(language-service): get the right 'ElementAst' in the nested HTML tag (#35317)
For example, '<div><p string-model~{cursor}></p></div>', when provide the hover info for 'string-model', the 'path.head' is root tag 'div'. Use the parent of 'path.tail' instead.

PR Close #35317
2020-02-25 13:12:09 -08:00
349539e551 perf(core): avoid recursive scope recalculation when TestBed.overrideModule is used (#35454)
Currently if TestBed detects that TestBed.overrideModule was used for module X, transitive scopes are recalculated recursively for all modules that X imports and previously calculated data (stored in cache) is ignored. This behavior was introduced in https://github.com/angular/angular/pull/33787 to fix stale transitive scopes issue (cache was not updated if module overrides are present).

The perf issue comes from a "diamond" problem, where module X is overridden which imports modules A and B, which both import module C. Under previous logic, module C gets its transitive deps recomputed multiple times, during the recompute for both A and B. For deep graphs and big common/shared modules this can be super costly.

This commit updates the logic to recalculate ransitive scopes for the overridden module, while keeping previously calculated scopes of other modules untouched.

PR Close #35454
2020-02-25 13:11:43 -08:00
315ad6370a docs: update getting started topics to avoid duplicate topic names (#35457)
PR Close #35457
2020-02-25 13:10:31 -08:00
64a415b91c fix(service-worker): treat 503 as offline (#35595)
Prior to this commit the service worker only treated 504 errors as "effectively offline".
This commit changes the behaviour to treat both 503 (Service Unavailable) and 504 as "offline".

Fixes #35571

PR Close #35595
2020-02-25 13:10:11 -08:00
47d6ab9d92 test(compiler): add correct use case of ngFor in r3 ast (#35671)
The only test case for `ngFor` exercises an incorrect usage which causes
two bound attributes to be generated . This commit adds a canonical and
correct usage to show the difference between the two.

PR Close #35671
2020-02-25 13:09:09 -08:00
8cac5fec20 ci: move saucelabs_ivy & saucelabs_view_engine to the daily monitoring CircleCI workflow (#35516)
PR Close #35516
2020-02-24 17:27:22 -08:00
2b0c2a44d0 test: disable broken saucelabs tests with “fixme-saucelabs-ivy” & “fixme-saucelabs-ve” tags (#35516)
PR Close #35516
2020-02-24 17:27:22 -08:00
11f65c0c6c test: saucelab targets for all karma tests (#35516)
PR Close #35516
2020-02-24 17:27:22 -08:00
1d9e00ec38 build: fix unbound variable in sauce-service.sh script (#35516)
PR Close #35516
2020-02-24 17:27:22 -08:00
2f140f5118 fix(router): removed unused ApplicationRef dependency (#35642)
As a part of the process of setting up Router providers, we use `ApplicationRef` as a dependency while providing `Router` token. The thing is that `ApplicationRef` is actually unused (all referenced were removed in 5a849829c4 (diff-c0baae5e1df628e1a217e8dc38557fcb)), but it's still listed as dependency. This is causing problems in case `Router` is used as a dependency for factory functions provided as `APP_INITIALIZERS` multi-token (causing cyclic dependency). This commit removes unused `ApplicationRef` dependency in `Router`, so it can be used without causing cyclic dependency issue.

PR Close #35642
2020-02-24 17:27:02 -08:00
8f48dc0653 docs: add more relevant statistic for page load (#35649)
PR Close #35649
2020-02-24 17:26:41 -08:00
89f6b341a3 docs: add amazon s3 builder package (#34783)
PR Close #34783
2020-02-24 09:14:04 -08:00
d83f62d30a fix(ngcc): capture path-mapped entry-points that start with same string (#35592)
Previously if there were two path-mapped libraries that are in
different directories but the path of one started with same string
as the path of the other, we would incorrectly return the shorter
path - e.g. `dist/my-lib` and `dist/my-lib-second`. This was because
the list of `basePaths` was searched in ascending alphabetic order and
we were using `startsWith()` to match the path.

Now the `basePaths` are searched in reverse alphabetic order so the
longer path will be matched correctly.

// FW-1873

Fixes #35536

PR Close #35592
2020-02-24 09:11:44 -08:00
1112875981 fix(localize): improve placeholder mismatch error message (#35593)
The original error message was confusing since often it is the
translation that is at fault not the message.

PR Close #35593
2020-02-24 09:11:21 -08:00
a4572c3b12 docs: fix routing code to use navigate (#35176)
Previously, the example in the `router` guide was not
preserving the query params after logging in.

This commit fixes this by using `navigate` instead of
using `navigateByUrl`, which ignores any properties in
the `NavigationExtras` that would change the provided URL.

Fixes 34917

PR Close #35176
2020-02-24 09:00:03 -08:00
45bd6d6e40 build: add instructions for adding new integration tests to integration/README.md (#33927)
PR Close #33927
2020-02-24 08:59:21 -08:00
4caacf2aff build: optimize integration tests on CI (#33927)
PR Close #33927
2020-02-24 08:59:20 -08:00
9f53df8168 build: add new integration tests & bazel-in-bazel tests to … glob (#33927)
Move bazel-in-bazel them to test job & increase it is 2xlarge+. test_integration_bazel is removed. Overall CI credit usage is reduced.

test: include ng_elements_schematics in legacy integration tests temporarily

This test was recently added and use a new pattern that doesn't work with npm_integration_test out of the box. It needs some refactoring to work. Left a TODO for this

PR Close #33927
2020-02-24 08:59:20 -08:00
3ef4b079e4 build: use puppeteer in integration/bazel-schematics (#33927)
PR Close #33927
2020-02-24 08:59:20 -08:00
49db9eeb33 build: remove dep on tsickle from platform-server (#33927)
PR Close #33927
2020-02-24 08:59:20 -08:00
9efd7afd8a build: remove CI_CHROMEDIRVER_VERSION_ARG from integration/bazel-schematics (#33927)
This means we don't need the action_env. Borrowed from @gkalpak's changes in https://github.com/angular/angular/pull/35381.

PR Close #33927
2020-02-24 08:59:20 -08:00
0b33828970 ci: bump CircleCi test job to xlarge2 CI job (#33927)
Now that large integration tests are running locally in parallel (they can't run on RBE yet as they require network access for yarn install), this test is running out of memory consistently with the xlarge machine

PR Close #33927
2020-02-24 08:59:20 -08:00
97556fd2ca ci: install java runtime in test job (#33927)
Install java runtime which is required by some integration tests such as `//integration:hello_world__closure_test`, `//integration:i18n_test` and `//integration:ng_elements_test` to run the closure compiler.

PR Close #33927
2020-02-24 08:59:20 -08:00
d95b2f0100 build: no-remote-exec for integration tests (#33927)
For now until RBE actions can access network for `yarn install`

PR Close #33927
2020-02-24 08:59:20 -08:00
2f572772b0 build: add npm_integration_test && angular_integration_test (#33927)
* it's tricky to get out of the runfiles tree with `bazel test` as `BUILD_WORKSPACE_DIRECTORY` is not set but I employed a trick to read the `DO_NOT_BUILD_HERE` file that is one level up from `execroot` and that contains the workspace directory. This is experimental and if `bazel test //:test.debug` fails than `bazel run` is still guaranteed to work as  `BUILD_WORKSPACE_DIRECTORY` will be set in that context

* test //integration:bazel_test and //integration:bazel-schematics_test exclusively

* run "exclusive" and "manual" bazel-in-bazel integration tests in their own CI job as they take 8m+ to execute

```
//integration:bazel-schematics_test                                      PASSED in 317.2s
//integration:bazel_test                                                 PASSED in 167.8s
```

* Skip all integration tests that are now handled by angular_integration_test except the tests that are tracked for payload size; these are:
- cli-hello-world*
- hello_world__closure

* add & pin @babel deps as newer versions of babel break //packages/localize/src/tools/test:test

@babel/core dep had to be pinned to 7.6.4 or else //packages/localize/src/tools/test:test failed. Also //packages/localize uses @babel/generator, @babel/template, @babel/traverse & @babel/types so these deps were added to package.json as they were not being hoisted anymore from @babel/core transitive.

NB: integration/hello_world__systemjs_umd test must run with systemjs 0.20.0
NB: systemjs must be at 0.18.10 for legacy saucelabs job to pass
NB: With Bazel 2.0, the glob for the files to test `"integration/bazel/**"` is empty if integation/bazel is in .bazelignore. This glob worked under these conditions with 1.1.0. I did not bother testing with 1.2.x as not having integration/bazel in .bazelignore is correct.

PR Close #33927
2020-02-24 08:59:20 -08:00
bb09cd0e41 fix(ivy): error in AOT when pipe inherits constructor from injectable that uses DI (#35468)
When a pipe inherits its constructor, and as a result its factory, from an injectable in AOT mode, it can end up throwing an error, because the inject implementation hasn't been set yet. These changes ensure that the implementation is set before the pipe's factory is invoked.

Note that this isn't a problem in JIT mode, because the factory inheritance works slightly differently, hence why this test isn't going through `TestBed`.

Fixes #35277.

PR Close #35468
2020-02-21 12:37:09 -08:00
628f957c4a fix(ivy): add strictLiteralTypes to align Ivy + VE checking of literals (#35462)
Under View Engine's default (non-fullTemplateTypeCheck) checking, object and
array literals which appear in templates are treated as having type `any`.
This allows a number of patterns which would not otherwise compile, such as
indexing an object literal by a string:

```html
{{ {'a': 1, 'b': 2}[value] }}
```

(where `value` is `string`)

Ivy, meanwhile, has always inferred strong types for object literals, even
in its compatibility mode. This commit fixes the bug, and adds the
`strictLiteralTypes` flag to specifically control this inference. When the
flag is `false` (in compatibility mode), object and array literals receive
the `any` type.

PR Close #35462
2020-02-21 12:36:12 -08:00
02599e452a fix(ivy): emulate a View Engine type-checking bug with safe navigation (#35462)
In its default compatibility mode, the Ivy template type-checker attempts to
emulate the View Engine default mode as accurately as is possible. This
commit addresses a gap in this compatibility that stems from a View Engine
type-checking bug.

Consider two template expressions:

```html
{{ obj?.field }}
{{ fn()?.field }}
```

and suppose that the type of `obj` and `fn()` are the same - both return
either `null` or an object with a `field` property.

Under View Engine, these type-check differently. The `obj` case will catch
if the object type (when not null) does not have a `field` property, while
the `fn()` case will not. This is due to how View Engine represents safe
navigations:

```typescript
// for the 'obj' case
(obj == null ? null as any : obj.field)

// for the 'fn()' case
let tmp: any;
((tmp = fn()) == null ? null as any : tmp.field)
```

Because View Engine uses the same code generation backend as it does to
produce the runtime code for this expression, it uses a ternary for safe
navigation, with a temporary variable to avoid invoking 'fn()' twice. The
type of this temporary variable is 'any', however, which causes the
`tmp.field` check to be meaningless.

Previously, the Ivy template type-checker in compatibility mode assumed that
`fn()?.field` would always check for the presence of 'field' on the non-null
result of `fn()`. This commit emulates the View Engine bug in Ivy's
compatibility mode, so an 'any' type will be inferred under the same
conditions.

As part of this fix, a new format for safe navigation operations in template
type-checking code is introduced. This is based on the realization that
ternary based narrowing is unnecessary.

For the `fn()` case in strict mode, Ivy now generates:

```typescript
(null as any ? fn()!.field : undefined)
```

This effectively uses the ternary operator as a type "or" operation. The
resulting type will be a union of the type of `fn()!.field` with
`undefined`.

For the `fn()` case in compatibility mode, Ivy now emulates the bug with:

```typescript
(fn() as any).field
```

The cast expression includes the call to `fn()` and allows it to be checked
while still returning a type of `any` from the expression.

For the `obj` case in compatibility mode, Ivy now generates:

```typescript
(obj!.field as any)
```

This cast expression still returns `any` for its type, but will check for
the existence of `field` on the type of `obj!`.

PR Close #35462
2020-02-21 12:36:12 -08:00
0700279fb6 fix(language-service): provide hover for interpolation in attribute value (#35494)
I think the bug is introduced in my PR#34847. For example, 'model="{{title}}"', the attribute value cannot be parsed by 'parseTemplateBindings'.

PR Close #35494
2020-02-21 12:35:52 -08:00
a491f7e2af fix(language-service): infer context type of structural directives (#35537) (#35561)
PR Close #35561
2020-02-21 09:11:55 -08:00
af4fe3aa4e fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)
In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).

Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.

In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.

In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).

As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.

---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).

Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.

The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.

Jira issue: https://angular-team.atlassian.net/browse/FW-1689

PR Close #35191
2020-02-21 09:06:47 -08:00
6faaec60b1 refactor(compiler-cli): rename the BuiltinFn type to the more generic KnownFn (#35191)
This is in preparation of using the `KnownFn` type for known TypeScript
helpers (in addition to built-in functions/methods). This will in turn
allow simplifying the detection of both imported and emitted TypeScript
helpers.

PR Close #35191
2020-02-21 09:06:47 -08:00
12e3db8d6f fix(ngcc): handle imports in dts files when processing CommonJS (#35191)
When statically evaluating CommonJS code it is possible to find that we
are looking for the declaration of an identifier that actually came from
a typings file (rather than a CommonJS file).

Previously, the CommonJS reflection host would always try to use a
CommonJS specific algorithm for finding identifier declarations, but
when the id is actually in a typings file this resulted in the returned
declaration being the containing file of the declaration rather than the
declaration itself.

Now the CommonJS reflection host will check to see if the file
containing the identifier is a typings file and use the appropriate
stategy.

(Note: This is the equivalent of #34356 but for CommonJS.)

PR Close #35191
2020-02-21 09:06:47 -08:00
824d9a8cbf build: update cli packages to latest versions (#35608)
PR Close #35608
2020-02-21 08:31:19 -08:00
8a531e2917 fix(ivy): incorrectly generating shared pure function between null and object literal (#35481)
In #33705 we made it so that we generate pure functions for object/array literals in order to avoid having them be shared across elements/views. The problem this introduced is that further down the line the `ContantPool` uses the generated literal in order to figure out whether to share an existing factory or to create a new one. `ConstantPool` determines whether to share a factory by creating a key from the AST node and using it to look it up in the factory cache, however the key generation function didn't handle function invocations and replaced them with `null`. This means that the key for `{foo: pureFunction0(...)}` and `{foo: null}` are the same.

These changes rework the logic so that instead of generating a `null` key
for function invocations, we generate a variable called `<unknown>` which
shouldn't be able to collide with anything.

Fixes #35298.

PR Close #35481
2020-02-20 15:23:59 -08:00
afc5b3eede perf(ivy): remove unused event argument in listener instructions (#35097)
Currently Ivy always generates the `$event` function argument, even if it isn't being used by the listener expressions. This can lead to unnecessary bytes being generated, because optimizers won't remove unused arguments by default. These changes add some logic to avoid adding the argument when it isn't required.

PR Close #35097
2020-02-20 15:22:14 -08:00
7d2ea938ee feat: add an tickOptions parameter with property processNewMacroTasksSynchronously. (#33838)
This option will control whether to invoke the new macro tasks when ticking.

Close #33799

PR Close #33838
2020-02-20 15:15:00 -08:00
d63ba9cfd3 fix(ivy): Add style="{{exp}}" based interpolation (#34202)
Fixes #33575

Add support for interpolation in styles as shown:
```
<div style="color: {{exp1}}; width: {{exp2}};">
```

PR Close #34202
2020-02-20 15:13:11 -08:00
a4b388d4ec docs: clarify getting started deployment guide (#35510)
PR Close #35510
2020-02-20 15:12:30 -08:00
aebd6620d7 fix(ngcc): add default config for angular2-highcharts (#35527)
The package is deprecated (and thus not going to have a new release),
but still has ~7000 weekly downloads.

Fixes #35399

PR Close #35527
2020-02-20 15:12:09 -08:00
39bd9a7c94 fix(ngcc): correctly detect outer aliased class identifiers in ES5 (#35527)
In ES5 and ES2015, class identifiers may have aliases. Previously, the
`NgccReflectionHost`s recognized the following formats:
- ES5:
    ```js
    var MyClass = (function () {
      function InnerClass() {}
      InnerClass_1 = InnerClass;
      ...
    }());
    ```
- ES2015:
    ```js
    let MyClass = MyClass_1 = class MyClass { ... };
    ```

In addition to the above, this commit adds support for recognizing an
alias outside the IIFE in ES5 classes (which was previously not
supported):
```js
var MyClass = MyClass_1 = (function () { ... }());
```

Jira issue: [FW-1869](https://angular-team.atlassian.net/browse/FW-1869)

Partially addresses #35399.

PR Close #35527
2020-02-20 15:12:09 -08:00
4b93df06e0 refactor(ngcc): tighten method parameter type to avoid redundant check (#35527)
`Esm5ReflectionHost#getInnerFunctionDeclarationFromClassDeclaration()`
was already called with `ts.Declaration`, not `ts.Node`, so we can
tighten its parameter type and get rid of a redundant check.
`getIifeBody()` (called inside
`getInnerFunctionDeclarationFromClassDeclaration()`) will check whether
the given `ts.Declaration` is a `ts.VariableDeclaration`.

PR Close #35527
2020-02-20 15:12:08 -08:00
72664cac19 fix(compiler): use FatalDiagnosticError to generate better error messages (#35244)
Prior to this commit, decorator handling logic in Ngtsc used `Error` to throw errors. This commit replaces most of these instances with `FatalDiagnosticError` class, which provider a better diagnostics error (including location of the problematic code).

PR Close #35244
2020-02-20 11:25:24 -08:00
bc7a8a85f2 fix(localize): support minified ES5 $localize calls (#35562)
Some minification tooling modifies `$localize` calls
to contain a comma sequence of items, where the
cooked and raw values are assigned to variables.

This change improves our ability to recognize these
structures.

Fixes #35376

PR Close #35562
2020-02-20 10:55:54 -08:00
375aa7399d docs: add downgraded output kebab-case example (#35581)
PR Close #35581
2020-02-20 10:55:35 -08:00
7dbbe24ccf fix(docs-infra): highlight the currently active node in top-bar (#33351)
Related to #33239 and #33255.

PR Close #33351
2020-02-20 10:52:55 -08:00
677d277ccc fix(docs-infra): use .tooltip in aio-top-menu items (#33351)
The top-menu items have both a `title` and a `tooltip` property. The
`title` is used as text content, so there is little point in using it as
"tooltip" (via the HTMLElement's `title` property) too.

This commit switches to using the `tooltip` property to populate the
`title`. Note that in many cases, the `tooltip` property is derived from
`title` anyway (so there is no practical change in behavior in these
cases).

PR Close #33351
2020-02-20 10:52:54 -08:00
224aaae352 fix(animations): false positive when detecting Node in Webpack builds (#35134)
We have to do some extra work in the animations module when we identify a Node environment which we determine based on the `process` global. The problem is that by default Webpack will polyfill the `process`, causing us to incorrectly identify it. These changes make it so that the check isn't thrown off by Webpack.

Fixes #35117.

PR Close #35134
2020-02-20 10:51:16 -08:00
5cdf806126 docs: edit displaying-data page to introduce section (#35507)
PR Close #35507
2020-02-20 10:50:06 -08:00
3a97972e58 fix(docs-infra): improve focus styles in topnav and footer (#33255)
Fixes #33239

PR Close #33255
2020-02-19 12:51:28 -08:00
f5e1faa75e fix(core): make subclass inherit developer-defined data (#35105)
PR Close #35105
2020-02-19 12:50:49 -08:00
5bec534bfc build: remove dependency on @types/chokidar (#35371)
We recently updated chokidar to `3.0.0`. The latest version of
chokidar provides TypeScript types on its own and makes the extra
dependency on the `@types` unnecessary.

This seems to have caused the `build-packages-dist` script to fail with
an error like:

```
[strictDeps] transitive dependency on external/npm/node_modules/chokidar/types/index.d.ts
   not allowed. Please add the BUILD target to your rule's deps.
```

It's unclear why that happens, but a reasonable theory would be that
the TS compilation accidentally picked up the types from `chokidar`
instead of `@types/chokidar`, and the strict deps `@bazel/typescript`
check reported this as issue because it's not an explicit target dependency.

PR Close #35371
2020-02-19 12:49:53 -08:00
5edeee69dd release: cut the v9.0.2 release 2020-02-19 11:18:40 -08:00
ce85cbf2d3 Revert "feat(ngcc): pause async ngcc processing if another process has the lockfile (#35131)"
This reverts commit b970028057.

This is a feature commit and was improperly merged to the patch branch.
2020-02-19 11:14:31 -08:00
c305b5ca31 ci: increase AIO payload size limit (#35538)
This commit updates AIO payload size limit that is triggering a problem after merging f95b8ce07e. That commit added some payload size, but all checks were "green" for the PR (https://github.com/angular/angular/pull/34481) after rebase that happened a couple hours before the merge, so this is an accumulated payload size increase from multiple changes. The goal of this commit is to bring the master and patch branches back to "green" state.

PR Close #35538
2020-02-19 09:07:21 -08:00
b970028057 feat(ngcc): pause async ngcc processing if another process has the lockfile (#35131)
ngcc uses a lockfile to prevent two ngcc instances from executing at the
same time. Previously, if a lockfile was found the current process would
error and exit.

Now, when in async mode, the current process is able to wait for the previous
process to release the lockfile before continuing itself.

PR Close #35131
2020-02-18 17:20:42 -08:00
e67c69a782 refactor(compiler-cli): add invalidateCaches to CachedFileSystem (#35131)
This is needed by ngcc when reading volatile files that may
be changed by an external process (e.g. the lockfile).

PR Close #35131
2020-02-18 17:20:42 -08:00
03a8b16ec9 fix(ivy): add attributes and classes to host elements based on selector (#34481)
In View Engine, host element of dynamically created component received attributes and classes extracted from component's selector. For example, if component selector is `[attr] .class`, the `attr` attribute and `.class` class will be add to host element. This commit adds similar logic to Ivy, to make sure this behavior is aligned with View Engine.

PR Close #34481
2020-02-18 17:18:13 -08:00
fd4ce84584 fix(ivy): queries should match elements inside ng-container with the descendants: false option (#35384)
Before this change content queries with the `descendants: false` option, as implemented in ivy,
would not descendinto `<ng-container>` elements. This behaviour was different from the way the
View Engine worked. This change alligns ngIvy and VE behaviours when it comes to queries and the
`<ng-container>` elements and fixes a common bugs where a query target was placed inside the
`<ng-container>` element with a * directive on it.

Before:

```html
<needs-target>
  <ng-container *ngIf="condition">
    <div #target>...</div>  <!-- this node would NOT match -->
  </ng-container>
</needs-target>
```

After:

```html
<needs-target>
  <ng-container *ngIf="condition">
    <div #target>...</div>  <!-- this node WILL match -->
  </ng-container>
</needs-target>
```

Fixes #34768

PR Close #35384
2020-02-18 17:17:46 -08:00
0671e540c2 fix(ivy): wrong context passed to ngOnDestroy when resolved multiple times (#35249)
When the same provider is resolved multiple times on the same node, the first invocation had the correct context, but all subsequent ones were incorrect because we were registering the hook multiple times under different indexes in `destroyHooks`.

Fixes #35167.

PR Close #35249
2020-02-18 17:17:06 -08:00
45c7b23cc8 fix(docs-infra): set th/td to proper width (#35437)
Previously, the tables on the event page were misaligned. This commit
fixes this by setting the width of all `td`'s and `th`'s.

PR Close #35437
2020-02-18 12:48:31 -08:00
9b31f77c19 style(docs-infra): properly format files (#35318)
PR Close #35318
2020-02-18 12:45:07 -08:00
927d691d56 fix(docs-infra): preserves query and hash when switching angular versions (#35318)
Previously, when switching angular versions through the
version selector in the sidenav, the query and hash is lost.
The user has to manually navigate to the original location again.

This commit fixes this issue and preserves the query and hash
when navigating between different versions.

Closes #24495

PR Close #35318
2020-02-18 12:45:07 -08:00
4fb5e21426 fix(core): better handing of ICUs outside of i18n blocks (#35347)
Currently the logic that handles ICUs located outside of i18n blocks may throw exceptions at runtime. The problem is caused by the fact that we store incorrect TNode index for previous TNode (index that includes HEADER_OFFSET) and do not store a flag whether this TNode is a parent or a sibling node. As a result, the logic that assembles the final output uses incorrect TNodes and in some cases throws exceptions (when incompatible structure is extracted from tView.data due to the incorrect index). This commit adjusts the index and captures whether TNode is a parent to make sure underlying logic manipulates correct TNode.

PR Close #35347
2020-02-18 12:43:45 -08:00
6518dae45e fix(docs-infra): add fallback to web fonts so downloading does not block rendering (#35352)
Light house was reporting that 'Ensure text remains visible during webfont load' solution to this problem was adding &swap to the end of web fonts this leads to our first text showing before web-font download and improves the performance of site link to article: https://web.dev/font-display/\?utm_source\=lighthouse\&utm_medium\=lr

PR Close #35352
2020-02-18 12:43:17 -08:00
83d68b3521 ci: get rid of the CI_CHROMEDRIVER_VERSION_ARG env var (#35381)
Previously, we needed to manually specify a ChromeDriver version to
download on CI that would be compatible with the browser version
provided by the docker image used to run the tests. This was kept in the
`CI_CHROMEDRIVER_VERSION_ARG` environment variable.

With recent commits, we use the browser provided by `puppeteer` and can
determine the correct ChromeDriver version programmatically. Therefore,
we no longer need the `CI_CHROMEDRIVER_VERSION_ARG` environment
variable.

NOTE:
There is still one place (the `bazel-schematics` integration project)
where a hard-coded ChromeDriver version is necessary. Since I am not
sure what is the best way to refactor the tests to not rely on a
hard-coded version, I left it as a TODO for a follow-up PR.

PR Close #35381
2020-02-18 12:42:49 -08:00
cf28373629 build(docs-infra): use puppeteer to get a browser for docs examples tests (#35381)
In #35049, integration and AIO tests were changed to use the browser
provided by `puppeteer` in tests. This commit switches the docs examples
tests to use the same setup.

IMPLEMENTATION NOTE:
The examples are used to create ZIP archives that docs users can
download to experiment with. Since we want the downloaded projects to
resemble an `@angular/cli` generated project, we do not want to affect
the project's Protractor configuration in order to use `puppeteer`.

To achieve this, a second Protractor configuration is created (which is
ignored when creating the ZIP archives) that extends the original one
and passes the approperiate arguments to use the browser provided by
`puppeteer`. This new configuration (`protractor-puppeteer.conf.js`) is
used when running the docs examples tests (on CI or locally during
development).

PR Close #35381
2020-02-18 12:42:48 -08:00
598b3ff5d7 build: several minor fixes related to using puppeteer (#35381)
This is a follow-up to #35049 with a few minor fixes related to using
the browser provided by `puppeteer` to run tests. Included fixes:

- Make the `webdriver-manager-update.js` really portable. (Previously,
  it needed to be run from the directory that contained the
  `node_modules/` directory. Now, it can be executed from a subdirectory
  and will correctly resolve dependencies.)

- Use the `puppeteer`-based setup in AIO unit and e2e tests to ensure
  that the downloaded ChromeDriver version matches the browser version
  used in tests.

- Use the `puppeteer`-based setup in the `aio_monitoring_stable` CI job
  (as happens with `aio_monitoring_next`).

- Use the [recommended way][1] of getting the browser port when using
  `puppeteer` with `lighthouse` and avoid hard-coding the remote
  debugging port (to be able to handle multiple instances running
  concurrently).

[1]: https://github.com/GoogleChrome/lighthouse/blame/51df179a0/docs/puppeteer.md#L49

PR Close #35381
2020-02-18 12:42:48 -08:00
011937555f docs(upgrade): separate AngularJS Material typings to its own block (#35514)
PR Close #35514
2020-02-18 12:42:20 -08:00
f33d8fe11d docs(upgrade): add instructions for more AngularJS related typings (#35514)
angular-aria is a core AngularJS module packaged separately, and
angular-material is its own thing entirely.

PR Close #35514
2020-02-18 12:42:20 -08:00
c9d80b2fc8 docs(forms): remove outdated ngForm selector deprecation notice (#35435)
In https://github.com/angular/angular/pull/33058, we removed support
for the `ngForm` selector in the NgForm directive. We deleted most
of the deprecation notices in that PR, but we missed a paragraph
of documentation in the API docs for NgForm.

This commit removes the outdated paragraph that makes it seem like
the selector is still around and deprecated (as opposed to removed),
as it might confuse users.

PR Close #35435
2020-02-14 15:34:01 -08:00
81c40cb5e8 build: enable network for docker on remote executors (#35432)
This is being done as a pre-factor for running integration tests
with bazel on RBE.

PR Close #35432
2020-02-14 15:33:38 -08:00
822036362b fix(core): correctly concatenate static and dynamic binding to class when shadowed (#35350)
Given:
```
<div class="s1" [class]="null" [ngClass]="exp">
```
Notice that `[class]` binding is not a `string`. As a result the existing logic would not concatenate `[class]` with `class="s1"`. The resulting falsy value would than be sent to `ngClass` which would promptly clear all styles on the `<div>`

The new logic correctly handles falsy values for `[class]` bindings.

Fix #35335

PR Close #35350
2020-02-14 15:33:15 -08:00
eee8c7f718 docs: change item to items to match the code (#35433)
Closes #35368

PR Close #35433
2020-02-14 11:15:07 -08:00
1797390c8b fix(core): remove support for Map/Set in [class]/[style] bindings (#35392)
Close FW-1863

PR Close #35392
2020-02-14 11:14:44 -08:00
4a4b6be731 ci: update the browser test matrix to match supported browsers (#35202)
Updated to match https://angular.io/guide/browser-support#browser-support

PR Close #35202
2020-02-14 11:14:06 -08:00
4b1dcaf0f5 fix(ivy): LFrame needs to release memory on leaveView() (#35156)
Root cause is that for perf reasons we cache `LFrame` so that we don't have to allocate it all the time. To be extra fast we clear the `LFrame` on `enterView()` rather that on `leaveView()`. The implication of this strategy is that the deepest `LFrame` will retain objects until the `LFrame` allocation depth matches the deepest object.

The fix is to simply clear the `LFrame` on `leaveView()` rather then on `enterView()`

Fix #35148

PR Close #35156
2020-02-14 11:13:37 -08:00
4f8d30361a build: update to bazel_toolchains 2.1.0 (#35430)
Needed with @bazel/bazel 2.1.0 update

PR Close #35430
2020-02-13 16:29:34 -08:00
098ba19560 build: update to @bazel/bazel 2.1.0 (#35430)
Includes new feature to honor .bazelignore in external repositories. rules_nodejs 1.3.0 now generates a .bazelignore for the @npm repository so that Bazel ignores the @npm//:node_modules folder.

PR Close #35430
2020-02-13 16:29:33 -08:00
5149d98acc build: update to rules_nodejs 1.3.0 (#35430)
Brings in feat: builtin: expose @npm//foo__all_files filegroup that includes all files in the npm package (https://github.com/bazelbuild/rules_nodejs/commit/8d77827) that is needed for npm_integration_test @npm//puppeteer pkg_tar on OSX (as the OSX Chrrome libs are extracted to paths that contain spaces)

PR Close #35430
2020-02-13 16:29:33 -08:00
db693f482f docs: Fix minor typos and coding styles (#35325)
- Fix minor typos in the Getting Started, Forms and AOT Compiler guide.
- Fix minor typo in the Tour of Heroes app.
- Fix coding styles in the Getting Started guide and the Tour of Heroes app

PR Close #35325
2020-02-13 13:31:31 -08:00
84c9c6ecc2 docs: minor typo fix (#35423)
PR Close #35423
2020-02-13 10:08:24 -08:00
a52d103341 ci(docs-infra): update payload limits (#35379)
The update to Angular 9.0.0 appears to have lowered the main.js
file slightly, while the current master build of Angular appears
to have gone up slightly.

PR Close #35379
2020-02-13 10:07:58 -08:00
ec77bc4fc5 build(docs-infra): update to latest Angular Material (#35379)
PR Close #35379
2020-02-13 10:07:58 -08:00
1129f10f26 build(docs-infra): pin @webcomponents/custom-elements to 1.2.1 (#35379)
The previous range (^1.2.0) allowed the version 1.3.2 to be
installed which caused the ES2015 polyfills.js file to increase
in size unwantedly.

PR Close #35379
2020-02-13 10:07:57 -08:00
3006a560fd build(docs-infra): update AIO to Angular 9.0.0 (#35379)
PR Close #35379
2020-02-13 10:07:57 -08:00
8e9be6ce0d build(docs-infra): update to latest dgeni-packages (#35379)
After the previous update to yarn.lock a problem surfaced
with the `mkdir-promise` package. The latest version of
dgeni-packages (0.28.3) fixes this problem.

PR Close #35379
2020-02-13 10:07:57 -08:00
99e4daec94 build(docs-infra): refresh yarn.lock (#35379)
This file had not been updated for some time and lots of
the dependencies have new versions.

This is actually necessary because (at least on OS/X) there
is a problem with `chokidar` and `fsevent` that is solved by
bumping the versions here.

PR Close #35379
2020-02-13 10:07:57 -08:00
90c249bc78 docs: Update doc to use ng firebase schematics for deployment (#35355)
Previously, the `deployment` section, was using the `firebase`
CLI to deploy the angular project into firebase. With the better
integration through the `fire` schematics, it is now easier to
deploy angular applications into firebase. This commit takes
care of this, by outlined the required steps for deployment.

Closes #35274

PR Close #35355
2020-02-13 10:07:27 -08:00
83941d68df docs: update release.md and rename 'beta' releases to 'next' (#35276)
also clarify what 'next' releases are.

PR Close #35276
2020-02-13 10:00:52 -08:00
a30fd2993b fix(ivy): error if directive with synthetic property binding is on same node as directive that injects ViewContainerRef (#35343)
In the `loadRenderer` we make an assumption that the value will always be an `LView`, but if there's a directive on the same node which injects `ViewContainerRef` the `LView` will be wrapped in an `LContainer`. These changes add a call to unwrap the value before we try to read the value off of it.

Fixes #35342.

PR Close #35343
2020-02-12 17:14:25 -08:00
a84093a971 docs: remove service from region where it was added before it was created (#35354)
The message service was added in a section create message service but was impoted much before it removed those imports because they can be confusing

Fixes #35259

PR Close #35354
2020-02-12 16:46:23 -08:00
37e1c04e6a ci: use pipeline values to define the CI_COMMIT_RANGE (#35348)
PR Close #35348
2020-02-12 16:40:48 -08:00
985762b5bc docs: Rename FAQ to Useful Tips (#35316)
Previously, a section in the FAQ was not clear when discussing a
simple unit test. We also want to move away from question-based
sections. This commit clarified the confusing section and
changed all question-based sections.

Closes #35056

PR Close #35316
2020-02-12 16:40:18 -08:00
be0f994657 ci: update pullapprove config to ensure complete coverage of files (#35060)
PR Close #35060
2020-02-12 16:39:14 -08:00
6aa259246e ci: add verification of the pullapprove config (#35060)
Verify that all files in the repo are covered by the pullapprove config
and that all rules in the pullapprove config match at least one file
in the repo.

PR Close #35060
2020-02-12 16:39:14 -08:00
ad7850e4b8 ci: reenable draft mode conditioning for pullapprove (#35396)
Previously there was a regression in PullApprove which preventing draft mode
from being respected correctly for PullApprove processing.  This regression
has been remedied and we should be able to once again respect draft mode for
PRs.

PR Close #35396
2020-02-12 16:36:35 -08:00
350 changed files with 23654 additions and 8931 deletions

View File

@ -1,10 +1,97 @@
# Bazel does not yet support wildcards or other .gitignore semantics for
# .bazelignore. Two issues for this feature request are outstanding:
# https://github.com/bazelbuild/bazel/issues/7093
# https://github.com/bazelbuild/bazel/issues/8106
.git
node_modules
dist
aio/content
aio/node_modules
aio/tools/examples/shared/node_modules
integration/bazel
integration/bazel-schematics/demo
integration/platform-server/node_modules
packages/bazel/node_modules
integration/bazel/bazel-bazel
integration/bazel/bazel-bin
integration/bazel/bazel-out
integration/bazel/bazel-testlogs
integration/bazel-schematics/demo
# All integration test node_modules folders
integration/bazel/node_modules
integration/bazel-schematics/node_modules
integration/cli-hello-world/node_modules
integration/cli-hello-world-ivy-compat/node_modules
integration/cli-hello-world-ivy-i18n/node_modules
integration/cli-hello-world-ivy-minimal/node_modules
integration/cli-hello-world-lazy/node_modules
integration/cli-hello-world-lazy-rollup/node_modules
integration/dynamic-compiler/node_modules
integration/hello_world__closure/node_modules
integration/hello_world__systemjs_umd/node_modules
integration/i18n/node_modules
integration/injectable-def/node_modules
integration/ivy-i18n/node_modules
integration/language_service_plugin/node_modules
integration/ng_elements/node_modules
integration/ng_elements_schematics/node_modules
integration/ng_update/node_modules
integration/ng_update_migrations/node_modules
integration/ngcc/node_modules
integration/platform-server/node_modules
integration/service-worker-schema/node_modules
integration/side-effects/node_modules
integration/terser/node_modules
integration/typings_test_ts36/node_modules
integration/typings_test_ts37/node_modules
# All integration test .yarn_local_cache folders
integration/bazel/.yarn_local_cache
integration/bazel-schematics/.yarn_local_cache
integration/cli-hello-world/.yarn_local_cache
integration/cli-hello-world-ivy-compat/.yarn_local_cache
integration/cli-hello-world-ivy-i18n/.yarn_local_cache
integration/cli-hello-world-ivy-minimal/.yarn_local_cache
integration/cli-hello-world-lazy/.yarn_local_cache
integration/cli-hello-world-lazy-rollup/.yarn_local_cache
integration/dynamic-compiler/.yarn_local_cache
integration/hello_world__closure/.yarn_local_cache
integration/hello_world__systemjs_umd/.yarn_local_cache
integration/i18n/.yarn_local_cache
integration/injectable-def/.yarn_local_cache
integration/ivy-i18n/.yarn_local_cache
integration/language_service_plugin/.yarn_local_cache
integration/ng_elements/.yarn_local_cache
integration/ng_elements_schematics/.yarn_local_cache
integration/ng_update/.yarn_local_cache
integration/ng_update_migrations/.yarn_local_cache
integration/ngcc/.yarn_local_cache
integration/platform-server/.yarn_local_cache
integration/service-worker-schema/.yarn_local_cache
integration/side-effects/.yarn_local_cache
integration/terser/.yarn_local_cache
integration/typings_test_ts36/.yarn_local_cache
integration/typings_test_ts37/.yarn_local_cache
# All integration test NPM_PACKAGE_MANIFEST.json folders
integration/bazel/NPM_PACKAGE_MANIFEST.json
integration/bazel-schematics/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-compat/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-i18n/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-ivy-minimal/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-lazy/NPM_PACKAGE_MANIFEST.json
integration/cli-hello-world-lazy-rollup/NPM_PACKAGE_MANIFEST.json
integration/dynamic-compiler/NPM_PACKAGE_MANIFEST.json
integration/hello_world__closure/NPM_PACKAGE_MANIFEST.json
integration/hello_world__systemjs_umd/NPM_PACKAGE_MANIFEST.json
integration/i18n/NPM_PACKAGE_MANIFEST.json
integration/injectable-def/NPM_PACKAGE_MANIFEST.json
integration/ivy-i18n/NPM_PACKAGE_MANIFEST.json
integration/language_service_plugin/NPM_PACKAGE_MANIFEST.json
integration/ng_elements/NPM_PACKAGE_MANIFEST.json
integration/ng_elements_schematics/NPM_PACKAGE_MANIFEST.json
integration/ng_update/NPM_PACKAGE_MANIFEST.json
integration/ng_update_migrations/NPM_PACKAGE_MANIFEST.json
integration/ngcc/NPM_PACKAGE_MANIFEST.json
integration/platform-server/NPM_PACKAGE_MANIFEST.json
integration/service-worker-schema/NPM_PACKAGE_MANIFEST.json
integration/side-effects/NPM_PACKAGE_MANIFEST.json
integration/terser/NPM_PACKAGE_MANIFEST.json
integration/typings_test_ts36/NPM_PACKAGE_MANIFEST.json
integration/typings_test_ts37/NPM_PACKAGE_MANIFEST.json

View File

@ -62,6 +62,16 @@ test --test_output=errors
# Bazel flags for CircleCI are in /.circleci/bazel.linux.rc and /.circleci/bazel.windows.rc
##################################
# Settings for integration tests #
##################################
# Trick bazel into treating BUILD files under integration/bazel as being regular files
# This lets us glob() up all the files inside this integration test to make them inputs to tests
# (Note, we cannot use common --deleted_packages because the bazel version command doesn't support it)
build --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
query --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
################################
# Temporary Settings for Ivy #
################################
@ -100,7 +110,6 @@ build:remote --javabase=@rbe_ubuntu1604_angular//java:jdk
build:remote --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
build:remote --java_toolchain=@bazel_tools//tools/jdk:toolchain_hostjdk8
build:remote --crosstool_top=@rbe_ubuntu1604_angular//cc:toolchain
build:remote --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1
build:remote --extra_toolchains=@rbe_ubuntu1604_angular//config:cc-toolchain
build:remote --extra_execution_platforms=//tools:rbe_ubuntu1604-angular
build:remote --host_platform=//tools:rbe_ubuntu1604-angular

View File

@ -65,8 +65,8 @@ var_10: &only_on_master
# (Using the tag in not necessary when pinning by ID, but include it anyway for documentation purposes.)
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
# **NOTE 3**: If you change the version of the `*-browsers` docker image, make sure the
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
# version that is compatible with the Chrome version in the image.
# `--versions.chrome` arg in `integration/bazel-schematics/test.sh` specifies a
# ChromeDriver version that is compatible with the Chrome version in the image.
executors:
default-executor:
parameters:
@ -78,20 +78,6 @@ executors:
resource_class: << parameters.resource_class >>
working_directory: ~/ng
browsers-executor:
parameters:
resource_class:
type: string
default: medium
docker:
# The browser docker image comes with Chrome and Firefox preinstalled. This is just
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
# docker image with browsers pre-installed.
- image: circleci/node:12.14.1-browsers@sha256:792797ab9be3179be7c9fc38a0931a3349288e699467c8d646d7c54e148ae46c
resource_class: << parameters.resource_class >>
working_directory: ~/ng
windows-executor:
working_directory: ~/ng
resource_class: windows.medium
@ -136,11 +122,29 @@ commands:
# circleci/node:x.x.x-browsers image.
sudo apt-get -y install libgtk-3-0 libasound2 libnss3 libxss1
# Install java runtime which is required by some integration tests such as
# //integration:hello_world__closure_test, //integration:i18n_test and
# //integration:ng_elements_test to run the closure compiler
install_java:
description: Install java
steps:
- run:
name: Install java
command: |
sudo apt-get update
# Install java runtime
sudo apt-get install default-jre
# Initializes the CI environment by setting up common environment variables.
init_environment:
description: Initializing environment (setting up variables)
steps:
- run: ./.circleci/env.sh
- run:
name: Set up environment
environment:
CIRCLE_GIT_BASE_REVISION: << pipeline.git.base_revision >>
CIRCLE_GIT_REVISION: << pipeline.git.revision >>
command: ./.circleci/env.sh
- run:
# Configure git as the CircleCI `checkout` command does.
# This is needed because we only checkout on the setup job.
@ -271,14 +275,21 @@ jobs:
(echo -e "\n.bzl files have lint errors. Please run ''yarn bazel:lint-fix''"; exit 1)'
- run: yarn gulp lint
- run: node tools/pullapprove/verify.js
test:
executor:
name: default-executor
resource_class: xlarge
# Now that large integration tests are running locally in parallel (they can't run on RBE yet
# as they require network access for yarn install), this test is running out of memory
# consistently with the xlarge machine.
# TODO: switch back to xlarge once integration tests are running on remote-exec
resource_class: 2xlarge+
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- install_java
- run:
command: yarn bazel test //... --build_tag_filters=-ivy-only --test_tag_filters=-ivy-only
no_output_timeout: 20m
@ -291,6 +302,7 @@ jobs:
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# We need to explicitly specify the --symlink_prefix option because otherwise we would
# not be able to easily find the output bin directory when uploading artifacts for size
# measurements.
@ -317,11 +329,7 @@ jobs:
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
destination: core/todo/bundle.br
# This job is currently a PoC for running tests on SauceLabs via bazel. It runs a subset of the
# tests in `legacy-unit-tests-saucelabs` (see
# [BUILD.bazel](https://github.com/angular/angular/blob/ef44f51d5/BUILD.bazel#L66-L92)).
#
# NOTE: This is currently limited to master builds only. See the `default_workflow` configuration.
# NOTE: This is currently limited to master builds only. See the `monitoring` configuration.
saucelabs_view_engine:
executor:
name: default-executor
@ -334,16 +342,18 @@ jobs:
- init_environment
- init_saucelabs_environment
- run:
name: Run Bazel tests on Saucelabs
name: Run Bazel tests on Saucelabs with ViewEngine
# See /tools/saucelabs/README.md for more info
command: |
yarn bazel run //tools/saucelabs:sauce_service_setup
yarn bazel test //:saucelabs_unit_tests_poc_suite --config=saucelabs
TESTS=$(./node_modules/.bin/bazel query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "ivy-only", ...) except attr("tags", "fixme-saucelabs-ve", ...)')
yarn bazel test --config=saucelabs ${TESTS}
yarn bazel run //tools/saucelabs:sauce_service_stop
no_output_timeout: 20m
no_output_timeout: 40m
- notify_webhook_on_fail:
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
# NOTE: This is currently limited to master builds only. See the `monitoring` configuration.
saucelabs_ivy:
executor:
name: default-executor
@ -356,13 +366,16 @@ jobs:
- init_environment
- init_saucelabs_environment
- run:
name: Run Bazel tests on Saucelabs
name: Run Bazel tests on Saucelabs with Ivy
# See /tools/saucelabs/README.md for more info
command: |
yarn bazel run //tools/saucelabs:sauce_service_setup
yarn bazel test //:saucelabs_unit_tests --config=saucelabs --config=ivy
TESTS=$(./node_modules/.bin/bazel query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "no-ivy-aot", ...) except attr("tags", "fixme-saucelabs-ivy", ...)')
yarn bazel test --config=saucelabs --config=ivy ${TESTS}
yarn bazel run //tools/saucelabs:sauce_service_stop
no_output_timeout: 20m
no_output_timeout: 40m
- notify_webhook_on_fail:
webhook_url_env_var: SLACK_DEV_INFRA_CI_FAILURES_WEBHOOK_URL
test_aio:
executor: default-executor
@ -440,13 +453,13 @@ jobs:
type: boolean
default: false
executor:
# Needed because the example e2e tests depend on Chrome.
name: browsers-executor
name: default-executor
resource_class: xlarge
parallelism: 5
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
# Install aio
- run: yarn --cwd aio install --frozen-lockfile --non-interactive
- when:
@ -540,25 +553,18 @@ jobs:
paths:
- ng/dist/packages-dist-ivy-aot
# We run the integration tests outside of Bazel for now.
# They are a separate workflow job so that they can be easily re-run.
# When the tests are ported to bazel test targets, they should move to the "test"
# job above, as part of the bazel test command. That has flaky_test_attempts so the
# need to re-run manually should be alleviated.
# We run a subset of the integration tests outside of Bazel that track
# payload size.
# See comments inside the integration/run_tests.sh script.
# TODO(gregmagolan): move payload size tracking to Bazel and remove this job.
integration_test:
executor:
# Needed because the integration/bazel-schematics test expects Chrome to be installed
# TODO(gregmagolan): remove the dependency on local chrome from that test
name: browsers-executor
# Note: we run Bazel in one of the integration tests, and it can consume >2G
# of memory. Together with the system under test, this can exhaust the RAM
# on a 4G worker so we use a larger machine here too.
resource_class: xlarge
parallelism: 4
executor: default-executor
parallelism: 3
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- install_java
# Runs the integration tests in parallel across multiple CircleCI container instances. The
# amount of container nodes for this job is controlled by the "parallelism" option.
- run: ./integration/run_tests.sh ${CIRCLE_NODE_INDEX} ${CIRCLE_NODE_TOTAL}
@ -625,12 +631,11 @@ jobs:
- run: ./scripts/ci/publish-build-artifacts.sh
aio_monitoring_stable:
# This job needs Chrome to be globally installed because the tests run with Protractor
# which does not load the browser through the Bazel webtesting rules.
executor: browsers-executor
executor: default-executor
steps:
- custom_attach_workspace
- init_environment
- install_chrome_libs
- run: setPublicVar_CI_STABLE_BRANCH
- run:
name: Check out `aio/` and yarn from the stable branch
@ -813,17 +818,6 @@ workflows:
- legacy-unit-tests-saucelabs:
requires:
- setup
- saucelabs_ivy:
requires:
- test_ivy_aot
- saucelabs_view_engine:
# This job is currently a PoC and a subset of `legacy-unit-tests-saucelabs`. Running on
# master only to avoid wasting resources.
# TODO: Run this job on all branches (including PRs) as soon as it is not a PoC and
# we can remove the legacy saucelabs job.
<<: *only_on_master
requires:
- setup
- test_aio:
requires:
- setup
@ -873,7 +867,6 @@ workflows:
- test
- test_ivy_aot
- integration_test
- saucelabs_ivy
# Only publish if `aio`/`docs` tests using the locally built Angular packages pass
- test_aio_local
- test_aio_local_viewengine
@ -905,7 +898,7 @@ workflows:
requires:
- test_ivy_aot
aio_monitoring:
monitoring:
jobs:
- setup
- aio_monitoring_stable:
@ -914,8 +907,26 @@ workflows:
- aio_monitoring_next:
requires:
- setup
- saucelabs_ivy:
# Testing saucelabs via Bazel currently taking longer than the legacy saucelabs job as it
# each karma_web_test target is provisioning and tearing down browsers which is adding
# a lot of overhead. Running once daily on master only to avoid wasting resources and
# slowing down CI for PRs.
# TODO: Run this job on all branches (including PRs) once karma_web_test targets can
# share provisioned browsers and we can remove the legacy saucelabs job.
requires:
- setup
- saucelabs_view_engine:
# Testing saucelabs via Bazel currently taking longer than the legacy saucelabs job as it
# each karma_web_test target is provisioning and tearing down browsers which is adding
# a lot of overhead. Running once daily on master only to avoid wasting resources and
# slowing down CI for PRs.
# TODO: Run this job on all branches (including PRs) once karma_web_test targets can
# share provisioned browsers and we can remove the legacy saucelabs job.
requires:
- setup
triggers:
- schedule:
<<: *only_on_master
# Runs AIO monitoring jobs at 10:00AM every day.
# Runs monitoring jobs at 10:00AM every day.
cron: "0 10 * * *"

View File

@ -19,19 +19,10 @@ setPublicVar CI_AIO_MIN_PWA_SCORE "95";
# This is the branch being built; e.g. `pull/12345` for PR builds.
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
setPublicVar CI_BUILD_URL "$CIRCLE_BUILD_URL";
# ChromeDriver version compatible with the Chrome version included in the docker image used in
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 79.0.3945.130";
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
# `CI_COMMIT_RANGE` is only used on push builds (a.k.a. non-PR, non-scheduled builds and rerun
# workflows of such builds).
# NOTE: With [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) enabled,
# `CIRCLE_COMPARE_URL` is no longer available and the commit range cannot be reliably
# detected. Fall back to only considering the last commit (which is accurate in the majority
# of cases for push builds).
setPublicVar CI_COMMIT_RANGE "`[[ ${CIRCLE_PR_NUMBER:-false} != false ]] && echo "" || echo "$CIRCLE_SHA1~1...$CIRCLE_SHA1"`";
setPublicVar CI_COMMIT_RANGE "$CIRCLE_GIT_BASE_REVISION..$CIRCLE_GIT_REVISION";
setPublicVar CI_PULL_REQUEST "${CIRCLE_PR_NUMBER:-false}";
setPublicVar CI_REPO_NAME "$CIRCLE_PROJECT_REPONAME";
setPublicVar CI_REPO_OWNER "$CIRCLE_PROJECT_USERNAME";

View File

@ -1,166 +0,0 @@
#!/usr/bin/env node
/**
* **Usage:**
* ```
* node get-commit-range <build-number> [<compare-url> [<circle-token>]]
* ```
*
* Returns the commit range, either extracting it from `compare-url` (if defined), which is of the
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
*
* > !!! WARNING !!!
* > !!
* > !! When [CircleCI Pipelines](https://circleci.com/docs/2.0/build-processing) is enabled, the
* > !! `CIRCLE_COMPARE_URL` environment variable is not available at all and this script does not
* > !! work.
* > !!!!!!!!!!!!!!!
*
* **Context:**
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit
* range) on push builds (a.k.a. non-PR, non-scheduled builds). Yet, when a workflow is rerun
* (either from the beginning or from failed jobs) - e.g. when a job flakes - CircleCI does not set
* the `CIRCLE_COMPARE_URL`.
*
* **Implementation details:**
* This script relies on the fact that all rerun workflows share the same CircleCI workspace and the
* (undocumented) fact that the workspace ID happens to be the same as the workflow ID that first
* created it.
*
* For example, for a job on push build workflows, the CircleCI API will return data that look like:
* ```js
* {
* compare: 'THE_COMPARE_URL_WE_ARE_LOOKING_FOR',
* //...
* previous: {
* // ...
* build_num: 12345,
* },
* //...
* workflows: {
* //...
* workflow_id: 'SOME_ID_A',
* workspace_id: 'SOME_ID_A', // Same as `workflow_id`.
* }
* }
* ```
*
* If the workflow is rerun, the data for jobs on the new workflow will look like:
* ```js
* {
* compare: null, // ¯\_(ツ)_/¯
* //...
* previous: {
* // ...
* build_num: 23456,
* },
* //...
* workflows: {
* //...
* workflow_id: 'SOME_ID_B',
* workspace_id: 'SOME_ID_A', // Different from current `workflow_id`.
* // Same as original `workflow_id`. \o/
* }
* }
* ```
*
* This script uses the `previous.build_num` (which points to the previous build number on the same
* branch) to traverse the jobs backwards, until it finds a job from the original workflow. Such a
* job (if found) should also contain the compare URL.
*
* **NOTE 1:**
* This is only useful on workflows which are created by rerunning a workflow for which
* `CIRCLE_COMPARE_URL` was defined.
*
* **NOTE 2:**
* The `circleToken` will be used for CircleCI API requests if provided, but it is not needed for
* accessing the read-only endpoints that we need (as long as the current project is FOSS and the
* corresponding setting is turned on in "Advanced Settings" in the project dashboard).
*
* ---
* Inspired by https://circleci.com/orbs/registry/orb/iynere/compare-url
* (source code: https://github.com/iynere/compare-url-orb).
*
* We are not using the `compare-url` orb for the following reasons:
* 1. (By looking at the code) it would only work if the rerun workflow is the latest workflow on
* the branch (which is not guaranteed to be true).
* 2. It is less efficient (e.g. makes unnecessary CircleCI API requests for builds on different
* branches, installs extra dependencies, persists files to the workspace (as a means of passing
* the result to the calling job), etc.).
* 3. It is slightly more complicated to setup and consume than our own script.
* 4. Its implementation is more complicated than needed for our usecase (e.g. handles different git
* providers, handles newly created branches, etc.).
*/
// Imports
const {get: httpsGet} = require('https');
// Constants
const API_URL_BASE = 'https://circleci.com/api/v1.1/project/github/angular/angular';
const COMPARE_URL_RE = /^.*\/([0-9a-f]+\.\.\.[0-9a-f]+)$/i;
// Run
_main(process.argv.slice(2));
// Helpers
async function _main([buildNumber, compareUrl = '', circleToken = '']) {
try {
if (!buildNumber || isNaN(buildNumber)) {
throw new Error(
'Missing or invalid arguments.\n' +
'Expected: buildNumber (number), compareUrl? (string), circleToken? (string)');
}
if (!compareUrl) {
compareUrl = await getCompareUrl(buildNumber, circleToken);
}
const commitRangeMatch = COMPARE_URL_RE.exec(compareUrl)
const commitRange = commitRangeMatch ? commitRangeMatch[1] : '';
console.log(commitRange);
} catch (err) {
console.error(err);
process.exit(1);
}
}
function getBuildInfo(buildNumber, circleToken) {
console.error(`BUILD ${buildNumber}`);
const url = `${API_URL_BASE}/${buildNumber}?circle-token=${circleToken}`;
return getJson(url);
}
async function getCompareUrl(buildNumber, circleToken) {
let info = await getBuildInfo(buildNumber, circleToken);
const targetWorkflowId = info.workflows.workspace_id;
while (info.workflows.workflow_id !== targetWorkflowId) {
info = await getBuildInfo(info.previous.build_num, circleToken);
}
return info.compare || '';
}
function getJson(url) {
return new Promise((resolve, reject) => {
const opts = {headers: {Accept: 'application/json'}};
const onResponse = res => {
const statusCode = res.statusCode || -1;
const isSuccess = (200 <= statusCode) && (statusCode < 400);
let responseText = '';
res.
on('error', reject).
on('data', d => responseText += d).
on('end', () => isSuccess ?
resolve(JSON.parse(responseText)) :
reject(`Error getting '${url}' (status ${statusCode}):\n${responseText}`));
};
httpsGet(url, opts, onResponse).
on('error', reject).
end();
});
}

View File

@ -40,6 +40,7 @@
# AndrewKushnir - Andrew Kushnir
# andrewseguin - Andrew Seguin
# atscott - Andrew Scott
# ayazhafiz - Ayaz Hafiz
# clydin - Charles Lyding
# crisbeto - Kristiyan Kostadinov
# dennispbrown - Denny Brown
@ -108,6 +109,9 @@ pullapprove_conditions:
- condition: "'PR state: WIP' not in labels"
unmet_status: pending
explanation: "Waiting to send reviews as PR is WIP"
- condition: "not draft"
unmet_status: pending
explanation: "Waiting to send reviews as PR is in draft"
groups:
@ -627,6 +631,7 @@ groups:
])
reviewers:
users:
- ayazhafiz
- kyliau
teams:
- ~framework-global-approvers
@ -640,7 +645,8 @@ groups:
conditions:
- >
contains_any_globs(files, [
'packages/zone.js/**'
'packages/zone.js/**',
'aio/content/guide/zone.md'
])
reviewers:
users:
@ -950,6 +956,7 @@ groups:
'tools/ng_rollup_bundle/**',
'tools/ngcontainer/**',
'tools/npm/**',
'tools/npm_integration_test/**',
'tools/public_api_guard/BUILD.bazel',
'tools/public_api_guard/public_api_guard.bzl',
'tools/pullapprove/**',
@ -961,6 +968,7 @@ groups:
'tools/testing/**',
'tools/ts-api-guardian/**',
'tools/tslint/**',
'tools/utils/**',
'tools/validate-commit-message/**',
'tools/yarn/**',
'tools/*',

View File

@ -1,7 +1,5 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "karma_web_test")
exports_files([
"LICENSE",
"protractor-perf.conf.js",
@ -46,76 +44,3 @@ filegroup(
"@npm//:node_modules/angular-mocks-1.6/angular-mocks.js",
],
)
# To run manually:
# Setup your SAUCE_USERNAME, SAUCE_ACCESS_KEY & SAUCE_TUNNEL_IDENTIFIER.
# If on OSX, also set SAUCE_CONNECT to the path of your `sc` binary.
# environment variables and run:
# ```
# yarn bazel run //tools/saucelabs:sauce_service_setup
# yarn bazel test //:saucelabs_unit_tests --config=saucelabs --config=ivy
# ```
# See /tools/saucelabs/README.md for more info on karma Saucelabs tests under Bazel.
karma_web_test(
name = "saucelabs_unit_tests",
# Default timeout is moderate (5min). This causes the test to be terminated while
# Saucelabs browsers keep running. Ultimately resulting in failing tests and browsers
# unnecessarily being acquired. Our specified Saucelabs idle timeout is 10min, so we use
# Bazel's long timeout (15min). This ensures that Karma can shut down properly.
timeout = "long",
karma = "//tools/saucelabs:karma-saucelabs",
tags = [
"manual",
"no-remote-exec",
"saucelabs",
],
deps = [
"//packages/core/test/acceptance:acceptance_lib",
],
)
SAUCE_TEST_SUITE_TARGETS = [
"packages/common/http/test:test_lib",
"packages/common/http/testing/test:test_lib",
"packages/common/test:test_lib",
"packages/core/test:test_lib",
"packages/forms/test:test_lib",
"packages/http/test:test_lib",
]
[
# These target runs in CI with View Engine as a Saucelabs and Bazel proof-of-concept. It's a
# subset of the legacy saucelabs tests.
karma_web_test(
name = "saucelabs_unit_tests_poc_%s" % test.replace("/", "_").replace(":", "_").replace(".", "_"),
# Default timeout is moderate (5min). This causes the test to be terminated while
# Saucelabs browsers keep running. Ultimately resulting in failing tests and browsers
# unnecessarily being acquired. Our specified Saucelabs idle timeout is 10min, so we use
# Bazel's long timeout (15min). This ensures that Karma can shut down properly.
timeout = "long",
karma = "//tools/saucelabs:karma-saucelabs",
tags = [
"exclusive",
"manual",
"no-remote-exec",
"saucelabs",
],
deps = ["//%s" % test],
)
for test in SAUCE_TEST_SUITE_TARGETS
]
# To run manually:
# Setup your SAUCE_USERNAME, SAUCE_ACCESS_KEY & SAUCE_TUNNEL_IDENTIFIER.
# If on OSX, also set SAUCE_CONNECT to the path of your `sc` binary.
# environment variables and run:
# ```
# yarn bazel run //tools/saucelabs:sauce_service_setup
# yarn bazel test //:saucelabs_unit_tests_poc_suite --config=saucelabs
# ```
# See /tools/saucelabs/README.md for more info on karma Saucelabs tests under Bazel.
test_suite(
name = "saucelabs_unit_tests_poc_suite",
tags = ["manual"],
tests = ["//:saucelabs_unit_tests_poc_%s" % test.replace("/", "_").replace(":", "_").replace(".", "_") for test in SAUCE_TEST_SUITE_TARGETS],
)

View File

@ -1,3 +1,79 @@
<a name="9.0.4"></a>
## [9.0.4](https://github.com/angular/angular/compare/9.0.3...9.0.4) (2020-02-27)
### Bug Fixes
* **ngcc:** allow deep-import warnings to be ignored ([#35683](https://github.com/angular/angular/issues/35683)) ([9064f4e](https://github.com/angular/angular/commit/9064f4e)), closes [#35615](https://github.com/angular/angular/issues/35615)
* **ngcc:** handle mappings outside the content when flattening source-maps ([#35718](https://github.com/angular/angular/issues/35718)) ([bfe7657](https://github.com/angular/angular/commit/bfe7657)), closes [#35709](https://github.com/angular/angular/issues/35709)
* **ngcc:** handle missing sources when flattening source-maps ([#35718](https://github.com/angular/angular/issues/35718)) ([7ff845b](https://github.com/angular/angular/commit/7ff845b)), closes [#35709](https://github.com/angular/angular/issues/35709)
<a name="9.0.3"></a>
## [9.0.3](https://github.com/angular/angular/compare/9.0.2...9.0.3) (2020-02-27)
### Bug Fixes
* **animations:** false positive when detecting Node in Webpack builds ([#35134](https://github.com/angular/angular/issues/35134)) ([224aaae](https://github.com/angular/angular/commit/224aaae)), closes [#35117](https://github.com/angular/angular/issues/35117)
* **animations:** Remove ɵAnimationDriver from private exports ([#35690](https://github.com/angular/angular/issues/35690)) ([c2dbcd3](https://github.com/angular/angular/commit/c2dbcd3))
* **compiler:** use FatalDiagnosticError to generate better error messages ([#35244](https://github.com/angular/angular/issues/35244)) ([72664ca](https://github.com/angular/angular/commit/72664ca))
* **core:** make subclass inherit developer-defined data ([#35105](https://github.com/angular/angular/issues/35105)) ([f5e1faa](https://github.com/angular/angular/commit/f5e1faa))
* **core:** support sanitizer value in the [style] bindings ([#35564](https://github.com/angular/angular/issues/35564)) ([36dc1c7](https://github.com/angular/angular/commit/36dc1c7)), closes [#35476](https://github.com/angular/angular/issues/35476)
* **core:** Add `style="{{exp}}"` based interpolation ([#34202](https://github.com/angular/angular/issues/34202)) ([d63ba9c](https://github.com/angular/angular/commit/d63ba9c)), closes [#33575](https://github.com/angular/angular/issues/33575)
* **core:** add strictLiteralTypes to align core + VE checking of literals ([#35462](https://github.com/angular/angular/issues/35462)) ([628f957](https://github.com/angular/angular/commit/628f957))
* **core:** better inference for circularly referenced directive types ([#35622](https://github.com/angular/angular/issues/35622)) ([4c2bd64](https://github.com/angular/angular/commit/4c2bd64)), closes [#35372](https://github.com/angular/angular/issues/35372) [#35603](https://github.com/angular/angular/issues/35603) [#35522](https://github.com/angular/angular/issues/35522)
* **core:** emulate a View Engine type-checking bug with safe navigation ([#35462](https://github.com/angular/angular/issues/35462)) ([02599e4](https://github.com/angular/angular/commit/02599e4))
* **core:** error in AOT when pipe inherits constructor from injectable that uses DI ([#35468](https://github.com/angular/angular/issues/35468)) ([bb09cd0](https://github.com/angular/angular/commit/bb09cd0)), closes [#35277](https://github.com/angular/angular/issues/35277)
* **core:** error when accessing NgModuleRef.componentFactoryResolver in constructor ([#35637](https://github.com/angular/angular/issues/35637)) ([d690488](https://github.com/angular/angular/commit/d690488)), closes [#35580](https://github.com/angular/angular/issues/35580)
* **core:** incorrectly generating shared pure function between null and object literal ([#35481](https://github.com/angular/angular/issues/35481)) ([8a531e2](https://github.com/angular/angular/commit/8a531e2)), closes [#33705](https://github.com/angular/angular/issues/33705) [#35298](https://github.com/angular/angular/issues/35298)
* **core:** injecting incorrect provider when re-providing injectable with useClass ([#34574](https://github.com/angular/angular/issues/34574)) ([79aaaa3](https://github.com/angular/angular/commit/79aaaa3)), closes [#34110](https://github.com/angular/angular/issues/34110)
* **core:** provide a more detailed error message for NG6002/NG6003 ([#35620](https://github.com/angular/angular/issues/35620)) ([e6c416f](https://github.com/angular/angular/commit/e6c416f))
* **language-service:** get the right 'ElementAst' in the nested HTML tag ([#35317](https://github.com/angular/angular/issues/35317)) ([7403ba1](https://github.com/angular/angular/commit/7403ba1))
* **language-service:** infer context type of structural directives ([#35537](https://github.com/angular/angular/issues/35537)) ([#35561](https://github.com/angular/angular/issues/35561)) ([a491f7e](https://github.com/angular/angular/commit/a491f7e))
* **language-service:** provide hover for interpolation in attribute value ([#35494](https://github.com/angular/angular/issues/35494)) ([0700279](https://github.com/angular/angular/commit/0700279)), closes [PR#34847](https://github.com/PR/issues/34847)
* **localize:** improve placeholder mismatch error message ([#35593](https://github.com/angular/angular/issues/35593)) ([1112875](https://github.com/angular/angular/commit/1112875))
* **localize:** support minified ES5 `$localize` calls ([#35562](https://github.com/angular/angular/issues/35562)) ([bc7a8a8](https://github.com/angular/angular/commit/bc7a8a8)), closes [#35376](https://github.com/angular/angular/issues/35376)
* **ngcc:** add default config for `angular2-highcharts` ([#35527](https://github.com/angular/angular/issues/35527)) ([aebd662](https://github.com/angular/angular/commit/aebd662)), closes [#35399](https://github.com/angular/angular/issues/35399)
* **ngcc:** capture path-mapped entry-points that start with same string ([#35592](https://github.com/angular/angular/issues/35592)) ([d83f62d](https://github.com/angular/angular/commit/d83f62d)), closes [#35536](https://github.com/angular/angular/issues/35536)
* **ngcc:** correctly detect emitted TS helpers in ES5 ([#35191](https://github.com/angular/angular/issues/35191)) ([af4fe3a](https://github.com/angular/angular/commit/af4fe3a))
* **ngcc:** correctly detect outer aliased class identifiers in ES5 ([#35527](https://github.com/angular/angular/issues/35527)) ([39bd9a7](https://github.com/angular/angular/commit/39bd9a7)), closes [#35399](https://github.com/angular/angular/issues/35399)
* **ngcc:** handle imports in dts files when processing CommonJS ([#35191](https://github.com/angular/angular/issues/35191)) ([12e3db8](https://github.com/angular/angular/commit/12e3db8)), closes [#34356](https://github.com/angular/angular/issues/34356)
* **router:** removed unused ApplicationRef dependency ([#35642](https://github.com/angular/angular/issues/35642)) ([2f140f5](https://github.com/angular/angular/commit/2f140f5)), closes [/github.com/angular/angular/commit/5a849829c42330d7e88e83e916e6e36380c97a97#diff-c0baae5e1df628e1a217e8dc38557](https://github.com//github.com/angular/angular/commit/5a849829c42330d7e88e83e916e6e36380c97a97/issues/diff-c0baae5e1df628e1a217e8dc38557)
* **service-worker:** treat 503 as offline ([#35595](https://github.com/angular/angular/issues/35595)) ([64a415b](https://github.com/angular/angular/commit/64a415b)), closes [#35571](https://github.com/angular/angular/issues/35571)
### Features
* **ngcc:** implement source-map flattening ([#35132](https://github.com/angular/angular/issues/35132)) ([0a8e8cd](https://github.com/angular/angular/commit/0a8e8cd))
* **zone.js** add an tickOptions parameter with property processNewMacroTasksSynchronously. ([#33838](https://github.com/angular/angular/issues/33838)) ([7d2ea93](https://github.com/angular/angular/commit/7d2ea93)), closes [#33799](https://github.com/angular/angular/issues/33799)
### Performance Improvements
* **core:** avoid recursive scope recalculation when TestBed.overrideModule is used ([#35454](https://github.com/angular/angular/issues/35454)) ([349539e](https://github.com/angular/angular/commit/349539e))
* **core:** remove unused event argument in listener instructions ([#35097](https://github.com/angular/angular/issues/35097)) ([afc5b3e](https://github.com/angular/angular/commit/afc5b3e))
<a name="9.0.2"></a>
## [9.0.2](https://github.com/angular/angular/compare/9.0.1...9.0.2) (2020-02-19)
### Bug Fixes
* **core:** better handing of ICUs outside of i18n blocks ([#35347](https://github.com/angular/angular/issues/35347)) ([4fb5e21](https://github.com/angular/angular/commit/4fb5e21))
* **core:** correctly concatenate static and dynamic binding to `class` when shadowed ([#35350](https://github.com/angular/angular/issues/35350)) ([8220363](https://github.com/angular/angular/commit/8220363)), closes [#35335](https://github.com/angular/angular/issues/35335)
* **core:** remove support for `Map`/`Set` in `[class]`/`[style]` bindings ([#35392](https://github.com/angular/angular/issues/35392)) ([1797390](https://github.com/angular/angular/commit/1797390))
* **ivy:** `LFrame` needs to release memory on `leaveView()` ([#35156](https://github.com/angular/angular/issues/35156)) ([4b1dcaf](https://github.com/angular/angular/commit/4b1dcaf)), closes [#35148](https://github.com/angular/angular/issues/35148)
* **ivy:** add attributes and classes to host elements based on selector ([#34481](https://github.com/angular/angular/issues/34481)) ([03a8b16](https://github.com/angular/angular/commit/03a8b16))
* **ivy:** error if directive with synthetic property binding is on same node as directive that injects ViewContainerRef ([#35343](https://github.com/angular/angular/issues/35343)) ([a30fd29](https://github.com/angular/angular/commit/a30fd29)), closes [#35342](https://github.com/angular/angular/issues/35342)
* **ivy:** queries should match elements inside ng-container with the descendants: false option ([#35384](https://github.com/angular/angular/issues/35384)) ([fd4ce84](https://github.com/angular/angular/commit/fd4ce84)), closes [#34768](https://github.com/angular/angular/issues/34768)
* **ivy:** wrong context passed to ngOnDestroy when resolved multiple times ([#35249](https://github.com/angular/angular/issues/35249)) ([0671e54](https://github.com/angular/angular/commit/0671e54)), closes [#35167](https://github.com/angular/angular/issues/35167)
<a name="9.0.1"></a>
## [9.0.1](https://github.com/angular/angular/compare/9.0.0...9.0.1) (2020-02-12)

View File

@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Fetch rules_nodejs so we can install our npm dependencies
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "6bcef105e75cac3c5f8212e0d0431b6ec1aaa1963e093b0091474ab98ecf29d2",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.2.2/rules_nodejs-1.2.2.tar.gz"],
sha256 = "b6670f9f43faa66e3009488bbd909bc7bc46a5a9661a33f6bc578068d1837f37",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.3.0/rules_nodejs-1.3.0.tar.gz"],
)
# Check the bazel version and download npm dependencies
@ -18,6 +18,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "check_bazel_version", "check_rule
# Bazel version must be at least the following version because:
# - 0.26.0 managed_directories feature added which is required for nodejs rules 0.30.0
# - 0.27.0 has a fix for managed_directories after `rm -rf node_modules`
# - 2.1.0 feature added to honor .bazelignore in external repositories
check_bazel_version(
message = """
You no longer need to install Bazel on your machine.
@ -26,10 +27,10 @@ Try running `yarn bazel` instead.
(If you did run that, check that you've got a fresh `yarn install`)
""",
minimum_bazel_version = "2.0.0",
minimum_bazel_version = "2.1.0",
)
check_rules_nodejs_version(minimum_version_string = "1.2.2")
check_rules_nodejs_version(minimum_version_string = "1.3.0")
# Setup the Node.js toolchain
node_repositories(
@ -42,8 +43,11 @@ node_repositories(
package_json = ["//:package.json"],
)
load("//integration:angular_integration_test.bzl", "npm_package_archives")
yarn_install(
name = "npm",
manual_build_file_contents = npm_package_archives(),
package_json = "//:package.json",
yarn_lock = "//:yarn.lock",
)

View File

@ -4,9 +4,10 @@
"cmd": "yarn",
"args": [
"e2e",
"--protractor-config=e2e/protractor-puppeteer.conf.js",
"--no-webdriver-update",
"--port={PORT}"
]
}
]
}
}

View File

@ -4,9 +4,10 @@
"cmd": "yarn",
"args": [
"e2e",
"--protractor-config=e2e/protractor-puppeteer.conf.js",
"--no-webdriver-update",
"--port={PORT}"
]
}
]
}
}

View File

@ -39,10 +39,10 @@ export class CartComponent implements OnInit {
// #enddocregion props-services
onSubmit(customerData) {
// Process checkout data here
console.warn('Your order has been submitted', customerData);
this.items = this.cartService.clearCart();
this.checkoutForm.reset();
console.warn('Your order has been submitted', customerData);
}
// #docregion props-services, inject-form-builder, checkout-form, checkout-form-group
}

View File

@ -39,8 +39,8 @@ export class ProductDetailsComponent implements OnInit {
// #enddocregion props-methods, get-product
// #docregion add-to-cart
addToCart(product) {
window.alert('Your product has been added to the cart!');
this.cartService.addToCart(product);
window.alert('Your product has been added to the cart!');
}
// #docregion props-methods, get-product, inject-cart-service
}

View File

@ -5,9 +5,10 @@
"cmd": "yarn",
"args": [
"e2e",
"--protractor-config=e2e/protractor-puppeteer.conf.js",
"--no-webdriver-update",
"--port={PORT}"
]
}
]
}
}

View File

@ -1,6 +1,6 @@
// #docregion
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
@ -25,12 +25,12 @@ export class LoginComponent {
this.authService.login().subscribe(() => {
this.setMessage();
if (this.authService.isLoggedIn) {
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
let redirect = this.authService.redirectUrl ? this.router.parseUrl(this.authService.redirectUrl) : '/admin';
// Usually you would use the redirect URL from the auth service.
// However to keep the example simple, we will always redirect to `/admin`.
const redirectUrl = '/admin';
// Redirect the user
this.router.navigateByUrl(redirect);
this.router.navigate([redirectUrl]);
}
});
}

View File

@ -1,8 +1,7 @@
// #docregion
import { Component } from '@angular/core';
import { Router,
NavigationExtras } from '@angular/router';
import { AuthService } from '../auth.service';
import { Component } from '@angular/core';
import { NavigationExtras, Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-login',
@ -26,9 +25,9 @@ export class LoginComponent {
this.authService.login().subscribe(() => {
this.setMessage();
if (this.authService.isLoggedIn) {
// Get the redirect URL from our auth service
// If no redirect has been set, use the default
let redirect = this.authService.redirectUrl ? this.router.parseUrl(this.authService.redirectUrl) : '/admin';
// Usually you would use the redirect URL from the auth service.
// However to keep the example simple, we will always redirect to `/admin`.
const redirectUrl = '/admin';
// #docregion preserve
// Set our navigation extras object
@ -39,7 +38,7 @@ export class LoginComponent {
};
// Redirect the user
this.router.navigateByUrl(redirect, navigationExtras);
this.router.navigate([redirectUrl], navigationExtras);
// #enddocregion preserve
}
});

View File

@ -1,7 +1,7 @@
{
"projectType": "service-worker",
"e2e": [
{"cmd": "yarn", "args": ["e2e", "--no-webdriver-update", "--port={PORT}"]},
{"cmd": "yarn", "args": ["e2e", "--protractor-config=e2e/protractor-puppeteer.conf.js", "--no-webdriver-update", "--port={PORT}"]},
{"cmd": "yarn", "args": ["build", "--prod"]},
{"cmd": "node", "args": ["--eval", "assert(fs.existsSync('./dist/ngsw.json'), 'ngsw.json is missing')"]},
{"cmd": "node", "args": ["--eval", "assert(fs.existsSync('./dist/ngsw-worker.js'), 'ngsw-worker.js is missing')"]},

View File

@ -25,21 +25,21 @@ describe('Angular async helper', () => {
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
it('should run async test with task', async(() => {
const id = setInterval(() => {
actuallyDone = true;
clearInterval(id);
}, 100);
}));
const id = setInterval(() => {
actuallyDone = true;
clearInterval(id);
}, 100);
}));
it('should run async test with successful promise', async(() => {
const p = new Promise(resolve => { setTimeout(resolve, 10); });
p.then(() => { actuallyDone = true; });
}));
const p = new Promise(resolve => { setTimeout(resolve, 10); });
p.then(() => { actuallyDone = true; });
}));
it('should run async test with failed promise', async(() => {
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
p.catch(() => { actuallyDone = true; });
}));
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
p.catch(() => { actuallyDone = true; });
}));
// Use done. Can also use async or fakeAsync.
it('should run async test with successful delayed Observable', (done: DoneFn) => {
@ -48,56 +48,84 @@ describe('Angular async helper', () => {
});
it('should run async test with successful delayed Observable', async(() => {
const source = of (true).pipe(delay(10));
source.subscribe(val => actuallyDone = true, err => fail(err));
}));
const source = of (true).pipe(delay(10));
source.subscribe(val => actuallyDone = true, err => fail(err));
}));
it('should run async test with successful delayed Observable', fakeAsync(() => {
const source = of (true).pipe(delay(10));
source.subscribe(val => actuallyDone = true, err => fail(err));
const source = of (true).pipe(delay(10));
source.subscribe(val => actuallyDone = true, err => fail(err));
tick(10);
}));
tick(10);
}));
});
describe('fakeAsync', () => {
// #docregion fake-async-test-tick
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
let called = false;
setTimeout(() => { called = true; }, 100);
tick(100);
expect(called).toBe(true);
}));
let called = false;
setTimeout(() => { called = true; }, 100);
tick(100);
expect(called).toBe(true);
}));
// #enddocregion fake-async-test-tick
// #docregion fake-async-test-tick-new-macro-task-sync
it('should run new macro task callback with delay after call tick with millis',
fakeAsync(() => {
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
const callback = jasmine.createSpy('callback');
nestedTimer(callback);
expect(callback).not.toHaveBeenCalled();
tick(0);
// the nested timeout will also be triggered
expect(callback).toHaveBeenCalled();
}));
// #enddocregion fake-async-test-tick-new-macro-task-sync
// #docregion fake-async-test-tick-new-macro-task-async
it('should not run new macro task callback with delay after call tick with millis',
fakeAsync(() => {
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
const callback = jasmine.createSpy('callback');
nestedTimer(callback);
expect(callback).not.toHaveBeenCalled();
tick(0, {processNewMacroTasksSynchronously: false});
// the nested timeout will not be triggered
expect(callback).not.toHaveBeenCalled();
tick(0);
expect(callback).toHaveBeenCalled();
}));
// #enddocregion fake-async-test-tick-new-macro-task-async
// #docregion fake-async-test-date
it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
const start = Date.now();
tick(100);
const end = Date.now();
expect(end - start).toBe(100);
}));
const start = Date.now();
tick(100);
const end = Date.now();
expect(end - start).toBe(100);
}));
// #enddocregion fake-async-test-date
// #docregion fake-async-test-rxjs
it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => {
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
// to patch rxjs scheduler
let result = null;
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
expect(result).toBeNull();
tick(1000);
expect(result).toBe('hello');
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
// to patch rxjs scheduler
let result = null;
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
expect(result).toBeNull();
tick(1000);
expect(result).toBe('hello');
const start = new Date().getTime();
let dateDiff = 0;
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
const start = new Date().getTime();
let dateDiff = 0;
interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start));
tick(1000);
expect(dateDiff).toBe(1000);
tick(1000);
expect(dateDiff).toBe(2000);
}));
tick(1000);
expect(dateDiff).toBe(1000);
tick(1000);
expect(dateDiff).toBe(2000);
}));
// #enddocregion fake-async-test-rxjs
});

View File

@ -5,9 +5,9 @@ import { Observable } from 'rxjs';
class DummyHeroesComponent {
heroes: Observable<Hero[]>;
// #docregion ctor
constructor(private heroService: HeroService) {}
// #enddocregion ctor
// #docregion getHeroes
getHeroes(): void {
// #docregion get-heroes

View File

@ -5,8 +5,8 @@ import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
// #docregion hero-service-import
import { HeroService } from '../hero.service';
import { MessageService } from '../message.service';
// #enddocregion hero-service-import
import { MessageService } from '../message.service';
@Component({
selector: 'app-heroes',

View File

@ -570,7 +570,7 @@ In the template type-checking phase, the Angular template compiler uses the Type
Enable this phase explicitly by adding the compiler option `"fullTemplateTypeCheck"` in the `"angularCompilerOptions"` of the project's `tsconfig.json`
(see [Angular Compiler Options](guide/angular-compiler-options)).
<div class="alert is-helpful>
<div class="alert is-helpful">
In [Angular Ivy](guide/ivy), the template type checker has been completely rewritten to be more capable as well as stricter, meaning it can catch a variety of new errors that the previous type checker would not detect.

View File

@ -11,6 +11,8 @@ about the features and tools that can help you develop and deliver Angular appli
## Application architecture
* The [Components and templates](guide/displaying-data) guide explains how to connect the application data in your [components](guide/glossary#component) to your page-display [templates](guide/glossary#template), to create a complete interactive application.
* The [NgModules](guide/ngmodules) guide provides in-depth information on the modular structure of an Angular application.
* The [Routing and navigation](guide/router) guide provides in-depth information on how to construct applications that allow a user to navigate to different [views](guide/glossary#view) within your single-page app.

View File

@ -80,6 +80,7 @@ In the table below, you can find a list of packages which implement deployment f
| [Netlify](https://www.netlify.com/) | [`@netlify-builder/deploy`](https://npmjs.org/package/@netlify-builder/deploy) |
| [GitHub pages](https://pages.github.com/) | [`angular-cli-ghpages`](https://npmjs.org/package/angular-cli-ghpages) |
| [NPM](https://npmjs.com/) | [`ngx-deploy-npm`](https://npmjs.org/package/ngx-deploy-npm) |
| [Amazon Cloud S3](https://aws.amazon.com/s3/?nc2=h_ql_prod_st_s3) | [`@jefiozie/ngx-aws-deploy`](https://www.npmjs.com/package/@jefiozie/ngx-aws-deploy) |
If you're deploying to a self-managed server or there's no builder for your favorite cloud platform, you can either create a builder that allows you to use the `ng deploy` command, or read through this guide to learn how to manually deploy your app.

View File

@ -1,11 +1,20 @@
# Displaying data
# Displaying data in views
You can display data by binding controls in an HTML template to properties of an Angular component.
Angular [components](guide/glossary#component) form the data structure of your application.
The HTML [template](guide/glossary#template) associated with a component provides the means to display that data in the context of a web page.
Together, a component's class and template form a [view](guide/glossary#view) of your application data.
In this page, you'll create a component with a list of heroes.
You'll display the list of hero names and
conditionally show a message below the list.
The process of combining data values with their representation on the page is called [data binding](guide/glossary#data-binding).
You display your data to a user (and collect data from the user) by *binding* controls in the HTML template to the data properties of the component class.
In addition, you can add logic to the template by including [directives](guide/glossary#directive), which tell Angular how to modify the page as it is rendered.
Angular defines a *template language* that expands HTML notation with syntax that allows you to define various kinds of data binding and logical directives.
When the page is rendered, Angular interprets the template syntax to update the HTML according to your logic and current data state.
Before you read the complete [template syntax guide](guide/template-syntax), the exercises on this page give you a quick demonstration of how template syntax works.
In this demo, you'll create a component with a list of heroes.
You'll display the list of hero names and conditionally show a message below the list.
The final UI looks like this:
<div class="lightbox">
@ -14,20 +23,14 @@ The final UI looks like this:
<div class="alert is-helpful">
The <live-example></live-example> demonstrates all of the syntax and code
snippets described in this page.
The <live-example></live-example> demonstrates all of the syntax and code snippets described in this page.
</div>
{@a interpolation}
## Showing component properties with interpolation
The easiest way to display a component property
is to bind the property name through interpolation.
The easiest way to display a component property is to bind the property name through interpolation.
With interpolation, you put the property name in the view template, enclosed in double curly braces: `{{myHero}}`.
Use the CLI command [`ng new displaying-data`](cli/new) to create a workspace and app named `displaying-data`.
@ -39,63 +42,43 @@ changing the template and the body of the component.
When you're done, it should look like this:
<code-example path="displaying-data/src/app/app.component.1.ts" header="src/app/app.component.ts"></code-example>
You added two properties to the formerly empty component: `title` and `myHero`.
The template displays the two component properties using double curly brace
interpolation:
<code-example path="displaying-data/src/app/app.component.1.ts" header="src/app/app.component.ts (template)" region="template"></code-example>
<div class="alert is-helpful">
The template is a multi-line string within ECMAScript 2015 backticks (<code>\`</code>).
The backtick (<code>\`</code>)&mdash;which is *not* the same character as a single
quote (`'`)&mdash;allows you to compose a string over several lines, which makes the
HTML more readable.
</div>
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
inserts those values into the browser. Angular updates the display
when these properties change.
<div class="alert is-helpful">
More precisely, the redisplay occurs after some kind of asynchronous event related to
the view, such as a keystroke, a timer completion, or a response to an HTTP request.
</div>
Notice that you don't call **new** to create an instance of the `AppComponent` class.
Angular is creating an instance for you. How?
The CSS `selector` in the `@Component` decorator specifies an element named `<app-root>`.
That element is a placeholder in the body of your `index.html` file:
<code-example path="displaying-data/src/index.html" header="src/index.html (body)" region="body"></code-example>
When you bootstrap with the `AppComponent` class (in <code>main.ts</code>), Angular looks for a `<app-root>`
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
inside the `<app-root>` tag.
@ -109,45 +92,44 @@ Now run the app. It should display the title and hero name:
The next few sections review some of the coding choices in the app.
## Template inline or template file?
## Choosing the template source
The `@Component` metadata tells Angular where to find the component's template.
You can store your component's template in one of two places.
You can define it *inline* using the `template` property, or you can define
the template in a separate HTML file and link to it in
the component metadata using the `@Component` decorator's `templateUrl` property.
The choice between inline and separate HTML is a matter of taste,
circumstances, and organization policy.
Here the app uses inline HTML because the template is small and the demo
is simpler without the additional HTML file.
* You can define the template *inline* using the `template` property of the `@Component` decorator. An inline template is useful for a small demo or test.
* Alternatively, you can define the template in a separate HTML file and link to that file in the `templateUrl` property of the `@Component` decorator. This configuration is typical for anything more complex than a small test or demo, and is the default when you generate a new component.
In either style, the template data bindings have the same access to the component's properties.
Here the app uses inline HTML because the template is small and the demo is simpler without the additional HTML file.
<div class="alert is-helpful">
By default, the Angular CLI command [`ng generate component`](cli/generate) generates components with a template file. You can override that with:
By default, the Angular CLI command [`ng generate component`](cli/generate) generates components with a template file.
You can override that by adding the "-t" (short for `inlineTemplate=true`) option:
<code-example hideCopy language="sh" class="code-shell">
ng generate component hero -it
ng generate component hero -t
</code-example>
</div>
## Constructor or variable initialization?
Although this example uses variable assignment to initialize the components, you could instead declare and initialize the properties using a constructor:
## Initialization
The following example uses variable assignment to initialize the components.
<code-example path="displaying-data/src/app/app-ctor.component.1.ts" region="class"></code-example>
You could instead declare and initialize the properties using a constructor.
This app uses more terse "variable assignment" style simply for brevity.
This app uses more terse "variable assignment" style simply for brevity.
{@a ngFor}
## Showing an array property with ***ngFor**
## Add logic to loop through data
The `*ngFor` directive (predefined by Angular) lets you loop through data. The following example uses the directive to show all of the values in an array property.
To display a list of heroes, begin by adding an array of hero names to the component and redefine `myHero` to be the first name in the array.
@ -155,15 +137,12 @@ To display a list of heroes, begin by adding an array of hero names to the compo
<code-example path="displaying-data/src/app/app.component.2.ts" header="src/app/app.component.ts (class)" region="class"></code-example>
Now use the Angular `ngFor` directive in the template to display
each item in the `heroes` list.
Now use the Angular `ngFor` directive in the template to display each item in the `heroes` list.
<code-example path="displaying-data/src/app/app.component.2.ts" header="src/app/app.component.ts (template)" region="template"></code-example>
This UI uses the HTML unordered list with `<ul>` and `<li>` tags. The `*ngFor`
in the `<li>` element is the Angular "repeater" directive.
It marks that `<li>` element (and its children) as the "repeater template":
@ -171,20 +150,13 @@ It marks that `<li>` element (and its children) as the "repeater template":
<code-example path="displaying-data/src/app/app.component.2.ts" header="src/app/app.component.ts (li)" region="li"></code-example>
<div class="alert is-important">
Don't forget the leading asterisk (\*) in `*ngFor`. It is an essential part of the syntax.
For more information, see the [Template Syntax](guide/template-syntax#ngFor) page.
</div>
Notice the `hero` in the `ngFor` double-quoted instruction;
it is an example of a template input variable. Read
more about template input variables in the [microsyntax](guide/template-syntax#microsyntax) section of
@ -194,18 +166,13 @@ Angular duplicates the `<li>` for each item in the list, setting the `hero` vari
to the item (the hero) in the current iteration. Angular uses that variable as the
context for the interpolation in the double curly braces.
<div class="alert is-helpful">
In this case, `ngFor` is displaying an array, but `ngFor` can
repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) object.
</div>
Now the heroes appear in an unordered list.
<div class="lightbox">
@ -228,13 +195,11 @@ of hero names into an array of `Hero` objects. For that you'll need a `Hero` cla
ng generate class hero
</code-example>
With the following code:
This command creates the following code.
<code-example path="displaying-data/src/app/hero.ts" header="src/app/hero.ts"></code-example>
You've defined a class with a constructor and two properties: `id` and `name`.
It might not look like the class has properties, but it does.
@ -245,8 +210,6 @@ Consider the first parameter:
<code-example path="displaying-data/src/app/hero.ts" header="src/app/hero.ts (id)" region="id"></code-example>
That brief syntax does a lot:
* Declares a constructor parameter and its type.
@ -254,7 +217,6 @@ That brief syntax does a lot:
* Initializes that property with the corresponding argument when creating an instance of the class.
### Using the Hero class
After importing the `Hero` class, the `AppComponent.heroes` property can return a _typed_ array
@ -273,7 +235,6 @@ Fix that to display only the hero's `name` property.
<code-example path="displaying-data/src/app/app.component.3.ts" header="src/app/app.component.ts (template)" region="template"></code-example>
The display looks the same, but the code is clearer.
{@a ngIf}
@ -291,46 +252,35 @@ To see it in action, add the following paragraph at the bottom of the template:
<code-example path="displaying-data/src/app/app.component.ts" header="src/app/app.component.ts (message)" region="message"></code-example>
<div class="alert is-important">
Don't forget the leading asterisk (\*) in `*ngIf`. It is an essential part of the syntax.
Read more about `ngIf` and `*` in the [ngIf section](guide/template-syntax#ngIf) of the [Template Syntax](guide/template-syntax) page.
</div>
The template expression inside the double quotes,
`*ngIf="heroes.length > 3"`, looks and behaves much like TypeScript.
When the component's list of heroes has more than three items, Angular adds the paragraph
to the DOM and the message appears. If there are three or fewer items, Angular omits the
paragraph, so no message appears. For more information,
see the [template expressions](guide/template-syntax#template-expressions) section of the
[Template Syntax](guide/template-syntax) page.
to the DOM and the message appears.
If there are three or fewer items, Angular omits the paragraph, so no message appears.
For more information, see [template expressions](guide/template-syntax#template-expressions).
<div class="alert is-helpful">
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM. That improves performance, especially in larger projects when conditionally including or excluding
big chunks of HTML with many data bindings.
</div>
Try it out. Because the array has four items, the message should appear.
Go back into <code>app.component.ts</code> and delete or comment out one of the elements from the heroes array.
The browser should refresh automatically and the message should disappear.
## Summary
Now you know how to use:
@ -341,7 +291,6 @@ Now you know how to use:
Here's the final code:
<code-tabs>
<code-pane header="src/app/app.component.ts" path="displaying-data/src/app/app.component.ts" region="final">

View File

@ -54,17 +54,13 @@ See [Keeping Up-to-Date](guide/updating "Updating your projects") for more infor
{@a previews}
### Preview releases
We let you preview what's coming by providing Beta releases and Release Candidates (`rc`) for each major and minor release:
We let you preview what's coming by providing "Next" and Release Candidates (`rc`) pre-releases for each major and minor release:
<!--
* **Next:** The release that is under active development. The next release is indicated by a release tag appended with the `next` identifier, such as `8.1.0-next.0`. For the next version of the documentation, see [next.angular.io](https://next.angular.io).
-->
* **Next:** The release that is under active development and testing. The next release is indicated by a release tag appended with the `-next` identifier, such as `8.1.0-next.0`.
* **Beta:** A release that is under active development and testing. A Beta release is indicated by a release tag appended with the `beta` identifier, such as `8.0.0-beta.0`.
* **Release candidate:** A release that is feature complete and in final testing. A release candidate is indicated by a release tag appended with the `-rc` identifier, such as version `8.1.0-rc.0`.
* **Release candidate:** A release that is feature complete and in final testing. A release candidate is indicated by a release tag appended with the `rc` identifier, such as version `8.1.0-rc`.
The next version of the documentation is available at [next.angular.io](https://next.angular.io). This includes any documentation for Beta or Release Candidate features and APIs.
The latest `next` or `rc` pre-release version of the documentation is available at [next.angular.io](https://next.angular.io).
{@a frequency}
@ -74,7 +70,7 @@ We work toward a regular schedule of releases, so that you can plan and coordina
<div class="alert is-helpful">
Disclaimer: Dates are offered as general guidance and will be adjusted by us when necessary to ensure delivery of a high-quality platform.
Disclaimer: Dates are offered as general guidance and will be adjusted by us when necessary to ensure delivery of a high-quality platform.
</div>
@ -84,9 +80,9 @@ In general, you can expect the following release cycle:
* 1-3 minor releases for each major release
* A patch release almost every week
* A patch release and pre-release (`next` or `rc`) build almost every week
This cadence of releases gives you access to new features as soon as they are ready, while maintaining the stability and reliability of the platform for production users.
This cadence of releases gives eager developers access to new features as soon as they are fully developed and pass through our code review and integration testing processes, while maintaining the stability and reliability of the platform for production users that prefer to receive features after they have been validated by Google and other developers that use the pre-release builds.

View File

@ -3302,7 +3302,13 @@ Although it doesn't actually log in, it has what you need for this discussion.
It has an `isLoggedIn` flag to tell you whether the user is authenticated.
Its `login` method simulates an API call to an external service by returning an
observable that resolves successfully after a short pause.
The `redirectUrl` property will store the attempted URL so you can navigate to it after authenticating.
The `redirectUrl` property stores the URL that the user wanted to access so you can navigate to it after authentication.
<div class="alert is-helpful">
To keep things simple, this example redirects unauthenticated users to `/admin`.
</div>
Revise the `AuthGuard` to call it.

View File

@ -117,7 +117,7 @@ You will see:
<div class="alert is-helpful">
Getting Started assumes the [StackBlitz](https://stackblitz.com/) online development environment.
To learn how to export an app from StackBlitz to your local environment, skip ahead to the [Deployment](start/deployment "Getting Started: Deployment") section.
To learn how to export an app from StackBlitz to your local environment, skip ahead to the [Deployment](start/start-deployment "Getting Started: Deployment") section.
</div>

View File

@ -237,16 +237,15 @@ You're free to change anything, anywhere, during this turn of the event loop.
Like template expressions, template *statements* use a language that looks like JavaScript.
The template statement parser differs from the template expression parser and
specifically supports both basic assignment (`=`) and chaining expressions
(with <code>;</code> or <code>,</code>).
specifically supports both basic assignment (`=`) and chaining expressions with <code>;</code>.
However, certain JavaScript syntax is not allowed:
However, certain JavaScript and template expression syntax is not allowed:
* <code>new</code>
* increment and decrement operators, `++` and `--`
* operator assignment, such as `+=` and `-=`
* the bitwise operators `|` and `&`
* the [template expression operators](guide/template-syntax#expression-operators)
* the bitwise operators, such as `|` and `&`
* the [pipe operator](guide/template-syntax#pipe)
### Statement context
@ -731,11 +730,11 @@ As you can see here, the `parentItem` in `AppComponent` is a string, which the `
The previous simple example showed passing in a string. To pass in an object,
the syntax and thinking are the same.
In this scenario, `ListItemComponent` is nested within `AppComponent` and the `item` property expects an object.
In this scenario, `ListItemComponent` is nested within `AppComponent` and the `items` property expects an array of objects.
<code-example path="property-binding/src/app/app.component.html" region="pass-object" header="src/app/app.component.html"></code-example>
The `item` property is declared in the `ListItemComponent` with a type of `Item` and decorated with `@Input()`:
The `items` property is declared in the `ListItemComponent` with a type of `Item` and decorated with `@Input()`:
<code-example path="property-binding/src/app/list-item/list-item.component.ts" region="item-input" header="src/app/list-item.component.ts"></code-example>
@ -748,7 +747,7 @@ specify a different item in `app.component.ts` so that the new item will render:
<code-example path="property-binding/src/app/app.component.ts" region="pass-object" header="src/app.component.ts"></code-example>
You just have to make sure, in this case, that you're supplying an object because that's the type of `item` and is what the nested component, `ListItemComponent`, expects.
You just have to make sure, in this case, that you're supplying an array of objects because that's the type of `items` and is what the nested component, `ListItemComponent`, expects.
In this example, `AppComponent` specifies a different `item` object
(`currentItem`) and passes it to the nested `ListItemComponent`. `ListItemComponent` was able to use `currentItem` because it matches what an `Item` object is according to `item.ts`. The `item.ts` file is where

View File

@ -121,6 +121,7 @@ In case of a false positive like these, there are a few options:
|`strictOutputEventTypes`|Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`.|
|`strictDomEventTypes`|Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`.|
|`strictContextGenerics`|Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be `any`.|
|`strictLiteralTypes`|Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be `any`.|
If you still have issues after troubleshooting with these flags, you can fall back to full mode by disabling `strictTemplates`.

View File

@ -1266,7 +1266,8 @@ You do have to call [tick()](api/core/testing/tick) to advance the (virtual) clo
Calling [tick()](api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish.
In this case, it waits for the error handler's `setTimeout()`.
The [tick()](api/core/testing/tick) function accepts milliseconds as a parameter (defaults to 0 if not provided). The parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback.
The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called processNewMacroTasksSynchronously (defaults is true) represents whether to invoke
new generated macro tasks when ticking.
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
@ -1276,6 +1277,22 @@ The [tick()](api/core/testing/tick) function accepts milliseconds as a parameter
The [tick()](api/core/testing/tick) function is one of the Angular testing utilities that you import with `TestBed`.
It's a companion to `fakeAsync()` and you can only call it within a `fakeAsync()` body.
#### tickOptions
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-tick-new-macro-task-sync">
</code-example>
In this example, we have a new macro task (nested setTimeout), by default, when we `tick`, the setTimeout `outside` and `nested` will both be triggered.
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-tick-new-macro-task-async">
</code-example>
And in some case, we don't want to trigger the new maco task when ticking, we can use `tick(milliseconds, {processNewMacroTasksSynchronously: false})` to not invoke new maco task.
#### Comparing dates inside fakeAsync()
`fakeAsync()` simulates passage of time, which allows you to calculate the difference between dates inside `fakeAsync()`.
@ -3582,13 +3599,13 @@ The Angular `By` class has three static methods for common predicates:
<hr>
{@a faq}
{@a useful-tips}
## Frequently Asked Questions
## Useful tips
{@a q-spec-file-location}
#### Why put spec file next to the file it tests?
#### Place your spec file next to the file it tests
It's a good idea to put unit test spec files in the same folder
as the application source code files that they test:
@ -3599,11 +3616,9 @@ as the application source code files that they test:
- When you move the source (inevitable), you remember to move the test.
- When you rename the source file (inevitable), you remember to rename the test file.
<hr>
{@a q-specs-in-test-folder}
#### When would I put specs in a test folder?
#### Place your spec files in a test folder
Application integration specs can test the interactions of multiple parts
spread across folders and modules.
@ -3615,15 +3630,17 @@ It's often better to create an appropriate folder for them in the `tests` direct
Of course specs that test the test helpers belong in the `test` folder,
next to their corresponding helper files.
{@a q-e2e}
{@a q-kiss}
#### Why not rely on E2E tests of DOM integration?
#### Keep it simple
The component DOM tests described in this guide often require extensive setup and
advanced techniques whereas the [unit tests](#component-class-testing)
are comparatively simple.
[Component class testing](#component-class-testing) should be kept very clean and simple.
It should test only a single unit. On a first glance, you should be able to understand
what the test is testing. If it's doing more, then it doesn't belong here.
#### Why not defer DOM integration tests to end-to-end (E2E) testing?
{@a q-end-to-end}
#### Use E2E (end-to-end) to test more than a single unit
E2E tests are great for high-level validation of the entire system.
But they can't give you the comprehensive test coverage that you'd expect from unit tests.

View File

@ -127,8 +127,7 @@ people who otherwise couldn't use the app at all.
### Show the first page quickly
Displaying the first page quickly can be critical for user engagement.
[53 percent of mobile site visits are abandoned](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/)
if pages take longer than 3 seconds to load.
Pages that load faster perform better, [even with changes as small as 100ms](https://web.dev/shopping-for-speed-on-ebay/).
Your app may have to launch faster to engage these users before they decide to do something else.
With Angular Universal, you can generate landing pages for the app that look like the complete app.

View File

@ -539,12 +539,14 @@ of multiple words. In Angular, you would bind these attributes using camelCase:
<code-example format="">
[myHero]="hero"
(heroDeleted)="handleHeroDeleted($event)"
</code-example>
But when using them from AngularJS templates, you must use kebab-case:
<code-example format="">
[my-hero]="hero"
(hero-deleted)="handleHeroDeleted($event)"
</code-example>
</div>
@ -1162,11 +1164,19 @@ Begin by installing TypeScript to the project.
</code-example>
Install type definitions for the existing libraries that
you're using but that don't come with prepackaged types: AngularJS and the
you're using but that don't come with prepackaged types: AngularJS, AngularJS Material, and the
Jasmine unit test framework.
For the PhoneCat app, we can install the necessary type definitions by running the following command:
<code-example format="">
npm install @types/jasmine @types/angular @types/angular-animate @types/angular-cookies @types/angular-mocks @types/angular-resource @types/angular-route @types/angular-sanitize --save-dev
npm install @types/jasmine @types/angular @types/angular-animate @types/angular-aria @types/angular-cookies @types/angular-mocks @types/angular-resource @types/angular-route @types/angular-sanitize --save-dev
</code-example>
If you are using AngularJS Material, you can install the type definitions via:
<code-example format="">
npm install @types/angular-material --save-dev
</code-example>
You should also configure the TypeScript compiler with a `tsconfig.json` in the project directory

418
aio/content/guide/zone.md Normal file
View File

@ -0,0 +1,418 @@
# NgZone
A zone is an execution context that persists across async tasks. You can think of it as [thread-local storage](http://en.wikipedia.org/wiki/Thread-local_storage) for JavaScript VMs.
This guide describes how to use Angular's NgZone to automatically detect changes in the component to update HTML.
## Fundamentals of change detection
To understand the benefits of `NgZone`, it is important to have a clear grasp of what change detection is and how it works.
### Displaying and updating data in Angular
In Angular, you can [display data](guide/displaying-data) by binding controls in an HTML template to the properties of an Angular component.
<code-example path="displaying-data/src/app/app.component.1.ts" header="src/app/app.component.ts"></code-example>
In addition, you can bind DOM events to a method of an Angular component. In such methods, you can also update a property of the Angular component, which updates the corresponding data displayed in the template.
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-component" header="src/app/click-me.component.ts"></code-example>
In both of the above examples, the component's code updates only the property of the component.
However, the HTML is also updated automatically.
This guide describes how and when Angular renders the HTML based on the data from the Angular component.
### Detecting changes with plain JavaScript
To clarify how changes are detected and values updated, consider the following code written in plain JavaScript.
```javascript
<html>
<div id="dataDiv"></div>
<button id="btn">updateData<btn>
<canvas id="canvas"><canvas>
<script>
let value = 'initialValue';
// initial rendering
detectChange();
function renderHTML() {
document.getElementById('dataDiv').innerText = value;
}
function detectChange() {
const currentValue = document.getElementById('dataDiv').innerText;
if (currentValue !== value) {
renderHTML();
}
}
// example 1: update data inside button click event handler
document.getElementById('btn').addEventListener('click', () => {
// update value
value = 'button update value';
// call detectChange manually
detectChange();
});
// example 2: Http Request
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
// get response from server
value = this.responseText;
// call detectChange manually
detectChange();
});
xhr.open('GET', serverUrl);
xhr.send();
// example 3: setTimeout
setTimeout(() => {
// update value inside setTimeout callback
value = 'timeout update value';
// call detectChange manually
detectChange();
}, 100);
// example 4: Promise.then
Promise.resolve('promise resolved a value').then((v) => {
// update value inside Promise thenCallback
value = v;
// call detectChange manually
detectChange();
}, 100);
// example 5: some other asynchronous APIs
document.getElementById('canvas').toBlob(blob => {
// update value when blob data is created from the canvas
value = `value updated by canvas, size is ${blog.size}`;
// call detectChange manually
detectChange();
});
</script>
</html>
```
After you update the data, you need to call `detectChange()` manually to check whether the data changed.
If the data changed, you render the HTML to reflect the updated data.
In Angular, this step is unnecessary. Whenever you update the data, your HTML is updated automatically.
### When apps update HTML
To understand how change detection works, first consider when the application needs to update the HTML. Typically, updates occur for one of the following reasons:
1. Component initialization. For example, when bootstrapping an Angular application, Angular loads the bootstrap component and triggers the [ApplicationRef.tick()](api/core/ApplicationRef#tick) to call change detection and View Rendering. Just as in the [displaying data](guide/displaying-data) sample, the `AppComponent` is the bootstrap component. This component has the properties `title` and `myHero`, which the application renders in the HTML.
2. Event listener. The DOM event listener can update the data in an Angular component and also trigger change detection, as in the following example.
<code-example path="user-input/src/app/click-me.component.ts" region="click-me-component" header="src/app/click-me.component.ts"></code-example>
3. Http Data Request. You can also get data from a server through an Http request. For example:
```typescript
@Component({
selector: 'app-root',
template: '<div>{{data}}</div>';
})
export class AppComponent implements OnInit {
data = 'initial value';
constructor(private httpClient: HttpClient) {}
ngOnInit() {
this.httpClient.get(serverUrl).subscribe(response => {
// user does not need to trigger change detection manually
data = response.data;
});
}
}
```
4. MacroTasks, such as `setTimeout()`/`setInterval()`. You can also update the data in the callback function of `macroTask` such as `setTimeout()`. For example:
```typescript
@Component({
selector: 'app-root',
template: '<div>{{data}}</div>';
})
export class AppComponent implements OnInit {
data = 'initial value';
ngOnInit() {
setTimeout(() => {
// user does not need to trigger change detection manually
data = 'value updated';
});
}
}
```
5. MicroTask, such as `Promise.then()`. Other asynchronous APIs return a Promise object (such as `fetch`), so the `then()` callback function can also update the data. For example:
```typescript
@Component({
selector: 'app-root',
template: '<div>{{data}}</div>';
})
export class AppComponent implements OnInit {
data = 'initial value';
ngOnInit() {
Promise.resolve(1).then(v => {
// user does not need to trigger change detection manually
data = v;
});
}
}
```
6. Other async operations. In addition to `addEventListener()`/`setTimeout()`/`Promise.then()`, there are other operations that can update the data asynchronously. Some examples include `WebSocket.onmessage()` and `Canvas.toBlob()`.
The preceding list contains most common scenarios in which the application might change the data. Angular runs change detection whenever it detects that data could have changed.
The result of change detection is that DOM is updated with new data. Angular detects the changes in different ways. For component initialization, Angular calls change detection explicitly. For [asynchronous operations](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous), Angular uses a Zone to detect changes in places where the data could have possibly mutated and it runs change detection automatically.
## Zones and execution contexts
A zone provides an execution context that persists across async tasks. [Execution Context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) is an abstract concept that holds information about the environment within the current code being executed. Consider the following example.
```javascript
const callback = function() {
console.log('setTimeout callback context is', this);
}
const ctx1 = {
name: 'ctx1'
};
const ctx2 = {
name: 'ctx2'
};
const func = function() {
console.log('caller context is', this);
setTimeout(callback);
}
func.apply(ctx1);
func.apply(ctx2);
```
The value of `this` in the callback of `setTimeout` might differ depending on when `setTimeout` is called.
Thus you can lose the context in asynchronous operations.
A zone provides a new zone context other than `this`, the zone context persists across asynchronous operations.
In the following example, the new zone context is called `zoneThis`.
```javascript
zone.run(() => {
// now you are in a zone
expect(zoneThis).toBe(zone);
setTimeout(function() {
// the zoneThis context will be the same zone
// when the setTimeout is scheduled
expect(zoneThis).toBe(zone);
});
});
```
This new context, `zoneThis`, can be retrieved from the `setTimeout()` callback function, and this context is the same when the `setTimeout()` is scheduled.
To get the context, you can call [`Zone.current`](https://github.com/angular/angular/blob/master/packages/zone.js/lib/zone.ts).
### Zones and async lifecycle hooks
Zone.js can create contexts that persist across asynchronous operations as well as provide lifecycle hooks for asynchronous operations.
```javascript
const zone = Zone.current.fork({
name: 'zone',
onScheduleTask: function(delegate, curr, target, task) {
console.log('new task is scheduled: ', task.type, task.source);
return delegate.scheduleTask(target, task);
},
onInvokeTask: function(delegate, curr, target, task, applyThis, applyArgs) {
console.log('task will be invoked', task.type, task.source);
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
onHasTask: function(delegate, curr, target, hasTaskState) {
console.log('task state changed in the zone', hasTaskState);
return delegate.hasTask(target, hasTaskState);
},
onInvoke: function(delegate, curr, target, callback, applyThis, applyArgs) {
console.log('the callback will be invoked', callback);
return delegate.invoke(target, callback, applyThis, applyArgs);
}
});
zone.run(() => {
setTimeout(() => {
console.log('timeout callback is invoked.');
});
});
```
The above example creates a zone with several hooks.
`onXXXTask` hooks trigger when the status of Task changes.
The Zone Task concept is very similar to the Javascript VM Task concept.
- `macroTask`: such as `setTimeout()`.
- `microTask`: such as `Promise.then()`.
- `eventTask`: such as `element.addEventListener()`.
The `onInvoke` hook triggers when a synchronize function is executed in a Zone.
These hooks trigger under the following circumstances:
- `onScheduleTask`: triggers when a new asynchronous task is scheduled, such as when you call `setTimeout()`.
- `onInvokeTask`: triggers when an asynchronous task is about to execute, such as when the callback of `setTimeout()` is about to execute.
- `onHasTask`: triggers when the status of one kind of task inside a zone changes from stable to unstable or from unstable to stable. A status of stable means there are no tasks inside the Zone, while unstable means a new task is scheduled in the zone.
- `onInvoke`: triggers when a synchronize function is going to execute in the zone.
With these hooks, `Zone` can monitor the status of all synchronize and asynchronous operations inside a zone.
The above example returns the following output.
```
the callback will be invoked () => {
setTimeout(() => {
console.log('timeout callback is invoked.');
});
}
new task is scheduled: macroTask setTimeout
task state changed in the zone { microTask: false,
macroTask: true,
eventTask: false,
change: 'macroTask' }
task will be invoked macroTask setTimeout
timeout callback is invoked.
task state changed in the zone { microTask: false,
macroTask: false,
eventTask: false,
change: 'macroTask' }
```
All of the functions of Zone are provided by a library called [zone.js](https://github.com/angular/angular/tree/master/packages/zone.js/README.md).
This library implements those features by intercepting asynchronous APIs through monkey patching.
Monkey patching is a technique to add or modify the default behavior of a function at runtime without changing the source code.
## NgZone
While Zone.js can monitor all the states of synchronous and asynchronous operations, Angular additionally provides a service called NgZone.
This service creates a zone named `angular` to automatically trigger change detection when the following conditions are satisfied:
1. When a sync or async function is executed.
1. When there is no `microTask` scheduled.
### NgZone `run()`/`runOutsideOfAngular()`
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then(),and `addEventListener()`.
For the full list, see the [Zone Module document](https://github.com/angular/angular/blob/master/packages/zone.js/MODULE.md).
Therefore in those asynchronous APIs, you don't need to trigger change detection manually.
There are still some third party APIs that Zone does not handle.
In those cases, the NgZone service provides a [`run()`](api/core/NgZone#run) method that allows you to execute a function inside the angular zone.
This function, and all asynchronous operations in that function, trigger change detection automatically at the correct time.
```typescript
export class AppComponent implements OnInit {
constructor(private ngZone: NgZone) {}
ngOnInit() {
// new async API is not handled by Zone, so you need to
// use ngZone.run to make the asynchronous operation in angular zone
// and trigger change detection automatically
this.ngZone.run(() => {
someNewAsyncAPI(() => {
// update data of component
});
});
}
}
```
By default, all asynchronous operations are inside the angular zone, which triggers change detection automatically.
Another common case is when you don't want to trigger change detection.
In that situation, you can use another NgZone method: [runOutsideAngular()](api/core/NgZone#runoutsideangular).
```typescript
export class AppComponent implements OnInit {
constructor(private ngZone: NgZone) {}
ngOnInit() {
// you know no data will be updated
// you don't want to do change detection in this
// specified operation, you can call runOutsideAngular
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
// do something will not update component data
});
});
}
}
```
### Seting up Zone.js
To make Zone.js available in Angular, you need to import the zone.js package.
If you are using the Angular CLI, this step is done automatically, and you will see the following line in the `src/polyfills.ts`:
```typescript
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
```
Before importing the `zone.js` package, you can set the following configurations:
- You can disable some asynchronous API monkey patching for better performance.
For example, you can disable the `requestAnimationFrame()` monkey patch, so the callback of `requestAnimationFrame()` will not trigger change detection.
This is useful if, in your application, the callback of the `requestAnimationFrame()` will not update any data.
- You can specify that certain DOM events not run inside the angular zone; for example, to prevent a `mousemove` or `scroll` event to trigger change detection.
There are several other settings you can change.
To make these changes, you need to create a `zone-flags.ts` file, such as the following.
```typescript
(window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
(window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
```
Next, import `zone-flags` before you import `zone` in the `polyfills.ts`.
```typescript
/***************************************************************************************************
* Zone JS is required by default for Angular.
*/
import `./zone-flags`;
import 'zone.js/dist/zone'; // Included with Angular CLI.
```
For more information of what you can configure, see the [zone.js](https://github.com/angular/angular/tree/master/packages/zone.js) documentation.
### NoopZone
`Zone` helps Angular know when to trigger change detection and let the developers focus on the application development.
By default, `Zone` is loaded and works without additional configuration. However, you don't have to use `Zone` to make Angular work, instead opting to trigger change detection on your own.
<div class="alert is-helpful">
<h4>Disabling <code>Zone</code></h4>
**If you disable `Zone`, you will need to trigger all change detection at the correct timing yourself, which requires comprehensive knowledge of change detection**.
</div>
To remove `zone.js`, make the following changes.
1. Remove the `zone.js` import from `polyfills.ts`.
```typescript
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
// import 'zone.js/dist/zone'; // Included with Angular CLI.
```
2. Bootstrap Angular with `noop zone` in `src/main.ts`.
```typescript
platformBrowserDynamic().bootstrapModule(AppModule, {ngZone: 'noop'})
.catch(err => console.error(err));
```

View File

@ -67,22 +67,22 @@
"tooltip": "Introduction to Angular's component model, template syntax, and component communication."
},
{
"url": "start/routing",
"url": "start/start-routing",
"title": "Routing",
"tooltip": "Introduction to routing between components using the browser's URL."
},
{
"url": "start/data",
"url": "start/start-data",
"title": "Managing Data",
"tooltip": "Introduction to services and accessing external data via HTTP."
},
{
"url": "start/forms",
"url": "start/start-forms",
"title": "Forms",
"tooltip": "Learn about fetching and managing data from users with forms."
},
{
"url": "start/deployment",
"url": "start/start-deployment",
"title": "Deployment",
"tooltip": "Move to local development, or deploy your application to Firebase or your own server."
}
@ -439,6 +439,11 @@
"tooltip": "Animate route transitions."
}
]
},
{
"url": "guide/zone",
"title": "NgZone",
"tooltip": "How NgZone works"
}
]
},

View File

@ -92,7 +92,7 @@ To help you get going, the following steps use predefined product data from the
<div class="alert is-helpful">
`*ngFor` is a "structural directive". Structural directives shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Any directive with an asterisk, `*`, is a structural directive.
`*ngFor` is a "structural directive". Structural directives shape or reshape the DOM's structure, typically by adding, removing, and manipulating the elements to which they are attached. Directives with an asterisk, `*`, are structural directives.
</div>
@ -358,5 +358,5 @@ You've learned about the foundation of Angular: components and template syntax.
You've also learned how the component class and template interact, and how components communicate with each other.
To continue exploring Angular, choose either of the following options:
* [Continue to the "Routing" section](start/routing "Getting Started: Routing") to create a product details page that can be accessed by clicking a product name and that has its own URL pattern.
* [Skip ahead to the "Deployment" section](start/deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.
* [Continue to the "Routing" section](start/start-routing "Getting Started: Routing") to create a product details page that can be accessed by clicking a product name and that has its own URL pattern.
* [Skip ahead to the "Deployment" section](start/start-deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.

View File

@ -1,6 +1,6 @@
# Managing Data
# Getting Started with Angular: Managing Data
At the end of [Routing](start/routing "Getting Started: Routing"), the online store application has a product catalog with two views: a product list and product details.
At the end of [Routing](start/start-routing "Getting Started: Routing"), the online store application has a product catalog with two views: a product list and product details.
Users can click on a product name from the list to see details in a new view, with a distinct URL, or route.
This page guides you through creating the shopping cart in three phases:
@ -29,7 +29,7 @@ about products in the cart.
<div class="alert is-helpful">
Later, the [Forms](start/forms "Getting Started: Forms") part of
Later, the [Forms](start/start-forms "Getting Started: Forms") part of
this tutorial guides you through accessing this cart service
from the page where the user checks out.
@ -234,7 +234,7 @@ This section shows you how to use the HTTP client to retrieve shipping prices fr
### Predefined shipping data
The app StackBlitz generates for this guide comes with predefined shipping data in `assets/shipping.json`.
The application that StackBlitz generates for this guide comes with predefined shipping data in `assets/shipping.json`.
Use this data to add shipping prices for items in the cart.
<code-example header="src/assets/shipping.json" path="getting-started/src/assets/shipping.json">
@ -314,7 +314,7 @@ Now that your app can retrieve shipping data, create a shipping component and t
There's no link to the new shipping component yet, but you can see its template in the preview pane by entering the URL its route specifies. The URL has the pattern: `https://getting-started.stackblitz.io/shipping` where the `getting-started.stackblitz.io` part may be different for your StackBlitz project.
1. Modify the shipping component so it uses the cart service to retrieve shipping data via HTTP from the `shipping.json` file.
1. Modify the shipping component so that it uses the cart service to retrieve shipping data via HTTP from the `shipping.json` file.
1. Import the cart service.
@ -362,5 +362,5 @@ Now that your app can retrieve shipping data, create a shipping component and t
Congratulations! You have an online store application with a product catalog and shopping cart. You can also look up and display shipping prices.
To continue exploring Angular, choose either of the following options:
* [Continue to the "Forms" section](start/forms "Getting Started: Forms") to finish the app by adding the shopping cart page and a checkout form.
* [Skip ahead to the "Deployment" section](start/deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.
* [Continue to the "Forms" section](start/start-forms "Getting Started: Forms") to finish the app by adding the shopping cart page and a checkout form.
* [Skip ahead to the "Deployment" section](start/start-deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.

View File

@ -1,4 +1,4 @@
# Deployment
# Getting Started with Angular: Deployment
To deploy your application, you have to compile it, and then host the JavaScript, CSS, and HTML on a web server. Built Angular applications are very portable and can live in any environment or served by any technology, such as Node, Java, .NET, PHP, and many others.
@ -6,13 +6,11 @@ To deploy your application, you have to compile it, and then host the JavaScript
<div class="alert is-helpful">
Whether you came here directly from [Your First App](start "Getting Started: Your First App"), or completed the entire online store application through the [Routing](start/routing "Getting Started: Routing"), [Managing Data](start/data "Getting Started: Managing Data"), and [Forms](start/forms "Getting Started: Forms") sections, you have an application that you can deploy by following the instructions in this section.
Whether you came here directly from [Your First App](start "Getting Started: Your First App"), or completed the entire online store application through the [Routing](start/start-routing "Getting Started: Routing"), [Managing Data](start/start-data "Getting Started: Managing Data"), and [Forms](start/start-forms "Getting Started: Forms") sections, you have an application that you can deploy by following the instructions in this section.
</div>
## Share your application
StackBlitz projects are public by default, allowing you to share your Angular app via the project URL. Keep in mind that this is a great way to share ideas and prototypes, but it is not intended for production hosting.
@ -24,9 +22,9 @@ StackBlitz projects are public by default, allowing you to share your Angular ap
## Building locally
To build your application locally or for production, you will need to download the source code from your StackBlitz project. Click the `Download Project` icon in the left menu across from `Project` to download your files.
To build your application locally or for production, download the source code from your StackBlitz project by clicking the `Download Project` icon in the left menu across from `Project` to download your files.
Once you have the source code downloaded and unzipped, use the [Angular Console](https://angularconsole.com "Angular Console web site") to serve the application, or you install `Node.js` and have the Angular CLI installed.
Once you have the source code downloaded and unzipped, use the [Angular Console](https://angularconsole.com "Angular Console web site") to serve the application, or install `Node.js` and serve your app with the Angular CLI.
From the terminal, install the Angular CLI globally with:
@ -34,7 +32,7 @@ From the terminal, install the Angular CLI globally with:
npm install -g @angular/cli
```
This will install the command `ng` into your system, which is the command you use to create new workspaces, new projects, serve your application during development, or produce builds that can be shared or distributed.
This installs the command `ng` on your system, which is the command you use to create new workspaces, new projects, serve your application during development, or produce builds to share or distribute.
Create a new Angular CLI workspace using the [`ng new`](cli/new "CLI ng new command reference") command:
@ -42,7 +40,7 @@ Create a new Angular CLI workspace using the [`ng new`](cli/new "CLI ng new comm
ng new my-project-name
```
From there you replace the `/src` folder with the one from your `StackBlitz` download, and then perform a build.
In your new CLI generated app, replace the `/src` folder with the one from your `StackBlitz` download, and then perform a build.
```sh
ng build --prod
@ -58,7 +56,7 @@ If the above `ng build` command throws an error about missing packages, append t
#### Hosting the built project
The files in the `dist/my-project-name` folder are static and can be hosted on any web server capable of serving files (`Node.js`, Java, .NET) or any backend (Firebase, Google Cloud, App Engine, others).
The files in the `dist/my-project-name` folder are static. This means you can host them on any web server capable of serving files (such as `Node.js`, Java, .NET), or any backend (such as Firebase, Google Cloud, or App Engine).
### Hosting an Angular app on Firebase
@ -66,33 +64,33 @@ One of the easiest ways to get your site live is to host it using Firebase.
1. Sign up for a firebase account on [Firebase](https://firebase.google.com/ "Firebase web site").
1. Create a new project, giving it any name you like.
1. Install the `firebase-tools` CLI that will handle your deployment using `npm install -g firebase-tools`.
1. Add the `@angular/fire` schematics that will handle your deployment using `ng add @angular/fire`.
1. Connect your CLI to your Firebase account and initialize the connection to your project using `firebase login` and `firebase init`.
1. Follow the prompts to select the `Firebase` project you are creating for hosting.
- Select the `Hosting` option on the first prompt.
- Select the project you previously created on Firebase.
- Select `dist/my-project-name` as the public directory.
1. Deploy your application with `firebase deploy`, because the command `firebase init` has created a `firebase.json` file that tells Firebase how to serve your app.
- Select the `Hosting` option on the first prompt.
- Select the project you previously created on Firebase.
- Select `dist/my-project-name` as the public directory.
1. Deploy your application with `ng deploy`.
1. Once deployed, visit https://your-firebase-project-name.firebaseapp.com to see it live!
### Hosting an Angular app anywhere else
To host an Angular app on another web host, you'll need to upload or send the files to the host.
Because you are building a Single Page Application, you'll also need to make sure you redirect any invalid URLs to your `index.html` file.
Learn more about development and distribution of your application in the [Building & Serving](guide/build "Building and Serving Angular Apps") and [Deployment](guide/deployment "Deployment guide") guides.
To host an Angular app on another web host, upload or send the files to the host.
Because you are building a single page application, you'll also need to make sure you redirect any invalid URLs to your `index.html` file.
Read more about development and distribution of your application in the [Building & Serving](guide/build "Building and Serving Angular Apps") and [Deployment](guide/deployment "Deployment guide") guides.
## Join our community
## Join the Angular community
You are now an Angular developer! [Share this moment](https://twitter.com/intent/tweet?url=https://angular.io/start&text=I%20just%20finished%20the%20Angular%20Getting%20Started%20Tutorial "Angular on Twitter"), tell us what you thought of this Getting Started, or submit [suggestions for future editions](https://github.com/angular/angular/issues/new/choose "Angular GitHub repository new issue form").
You are now an Angular developer! [Share this moment](https://twitter.com/intent/tweet?url=https://angular.io/start&text=I%20just%20finished%20the%20Angular%20Getting%20Started%20Tutorial "Angular on Twitter"), tell us what you thought of this Getting Started, or submit [suggestions for future editions](https://github.com/angular/angular/issues/new/choose "Angular GitHub repository new issue form").
Angular offers many more capabilities, and you now have a foundation that empowers you to build an application and explore those other capabilities:
* Angular provides advanced capabilities for mobile apps, animation, internationalization, server-side rendering, and more.
* [Angular Material](https://material.angular.io/ "Angular Material web site") offers an extensive library of Material Design components.
* [Angular Protractor](https://protractor.angular.io/ "Angular Protractor web site") offers an end-to-end testing framework for Angular apps.
* Angular also has an extensive [network of 3rd-party tools and libraries](https://angular.io/resources "Angular resources list").
* Angular provides advanced capabilities for mobile apps, animation, internationalization, server-side rendering, and more.
* [Angular Material](https://material.angular.io/ "Angular Material web site") offers an extensive library of Material Design components.
* [Angular Protractor](https://protractor.angular.io/ "Angular Protractor web site") offers an end-to-end testing framework for Angular apps.
* Angular also has an extensive [network of 3rd-party tools and libraries](https://angular.io/resources "Angular resources list").
Keep current by following the [Angular blog](https://blog.angular.io/ "Angular blog").
Keep current by following the [Angular blog](https://blog.angular.io/ "Angular blog").

View File

@ -1,6 +1,6 @@
# Forms
# Getting Started with Angular: Forms
At the end of [Managing Data](start/data "Getting Started: Managing Data"), the online store application has a product catalog and a shopping cart.
At the end of [Managing Data](start/start-data "Getting Started: Managing Data"), the online store application has a product catalog and a shopping cart.
This section walks you through adding a form-based checkout feature to collect user information as part of checkout.
@ -40,7 +40,7 @@ of the constructor.
1. For the checkout process, users need to submit their name and address. When they submit their order, the form should reset and the cart should clear.
1. In `cart.component.ts`, define an `onSubmit()` method to process the form. Use the `CartService` `clearCart()` method to empty the cart items and reset the form after it is submission. In a real-world app, this method would also submit the data to an external server. The entire cart component class is as follows:
1. In `cart.component.ts`, define an `onSubmit()` method to process the form. Use the `CartService` `clearCart()` method to empty the cart items and reset the form after its submission. In a real-world app, this method would also submit the data to an external server. The entire cart component class is as follows:
<code-example header="src/app/cart/cart.component.ts" path="getting-started/src/app/cart/cart.component.ts">
</code-example>
@ -82,4 +82,4 @@ To confirm submission, open the console where you should see an object containin
Congratulations! You have a complete online store application with a product catalog, a shopping cart, and a checkout function.
[Continue to the "Deployment" section](start/deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.
[Continue to the "Deployment" section](start/start-deployment "Getting Started: Deployment") to move to local development, or deploy your app to Firebase or your own server.

View File

@ -1,4 +1,4 @@
# Routing
# Getting Started with Angular: Routing
At the end of [Your First App](start "Getting Started: Your First App"), the online store application has a basic product catalog.
The app doesn't have any variable states or navigation.
@ -73,7 +73,7 @@ The product details component handles the display of each product. The Angular R
The `ActivatedRoute` is specific to each routed component that the Angular Router loads. It contains information about the
route, its parameters, and additional data associated with the route.
By injecting the `ActivatedRoute`, you are configuring the component to use a service. While this part of the Getting Started tutorial uses this syntax briefly, the [Managing Data](start/data "Getting Started: Managing Data") page covers services in more detail.
By injecting the `ActivatedRoute`, you are configuring the component to use a service. While this part of the Getting Started tutorial uses this syntax briefly, the [Managing Data](start/start-data "Getting Started: Managing Data") page covers services in more detail.
1. In the `ngOnInit()` method, subscribe to route parameters and fetch the product based on the `productId`.
@ -111,5 +111,5 @@ Congratulations! You have integrated routing into your online store.
* Users can click on a product name from the list to see details in a new view, with a distinct URL/route.
To continue exploring Angular, choose either of the following options:
* [Continue to the "Managing Data" section](start/data "Getting Started: Managing Data") to add a shopping cart feature, use a service to manage the cart data and use HTTP to retrieve external data for shipping prices.
* [Skip ahead to the Deployment section](start/deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development.
* [Continue to the "Managing Data" section](start/start-data "Getting Started: Managing Data") to add a shopping cart feature, use a service to manage the cart data and use HTTP to retrieve external data for shipping prices.
* [Skip ahead to the Deployment section](start/start-deployment "Getting Started: Deployment") to deploy your app to Firebase or move to local development.

View File

@ -122,7 +122,7 @@ Replace the definition of the `heroes` property with a simple declaration.
Add a private `heroService` parameter of type `HeroService` to the constructor.
<code-example path="toh-pt4/src/app/heroes/heroes.component.ts" header="src/app/heroes/heroes.component.ts" region="ctor">
<code-example path="toh-pt4/src/app/heroes/heroes.component.1.ts" header="src/app/heroes/heroes.component.ts" region="ctor">
</code-example>
The parameter simultaneously defines a private `heroService` property and identifies it as a `HeroService` injection site.

View File

@ -30,6 +30,12 @@
{"type": 301, "source": "/getting-started", "destination": "/start"},
{"type": 301, "source": "/getting-started/:rest*", "destination": "/start/:rest*"},
// Renaming of Getting Started topics
{"type": 301, "source": "/start/data", "destination": "/start/start-data"},
{"type": 301, "source": "/start/deployment", "destination": "/start/start-deployment"},
{"type": 301, "source": "/start/forms", "destination": "/start/start-forms"},
{"type": 301, "source": "/start/routing", "destination": "/start/start-routing"},
// some top level guide pages on old site were moved below the guide folder
{"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},
{"type": 301, "source": "/docs/styleguide", "destination": "/guide/styleguide"},

View File

@ -35,10 +35,10 @@ module.exports = function (config) {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
// See /integration/README.md#browser-tests for more info on these args
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
}
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
browsers: [process.env['CI'] ? 'ChromeHeadlessNoSandbox' : 'Chrome'],
browsers: ['ChromeHeadlessNoSandbox'],
browserNoActivityTimeout: 60000,
singleRun: false,
restartOnFileChange: true,

View File

@ -131,6 +131,18 @@
"!/news",
"!/news.html",
"!/news/",
"!/start/data",
"!/start/data/",
"!/start/data.html",
"!/start/deployment",
"!/start/deployment/",
"!/start/deployment.html",
"!/start/forms",
"!/start/forms/",
"!/start/forms.html",
"!/start/routing",
"!/start/routing/",
"!/start/routing.html",
"!/styleguide",
"!/styleguide/**",
"!/testing",

View File

@ -87,28 +87,28 @@
},
"private": true,
"dependencies": {
"@angular/animations": "9.0.0-rc.11",
"@angular/cdk": "9.0.0-rc.8",
"@angular/common": "9.0.0-rc.11",
"@angular/compiler": "9.0.0-rc.11",
"@angular/core": "9.0.0-rc.11",
"@angular/elements": "9.0.0-rc.11",
"@angular/forms": "9.0.0-rc.11",
"@angular/material": "9.0.0-rc.8",
"@angular/platform-browser": "9.0.0-rc.11",
"@angular/platform-browser-dynamic": "9.0.0-rc.11",
"@angular/router": "9.0.0-rc.11",
"@angular/service-worker": "9.0.0-rc.11",
"@webcomponents/custom-elements": "^1.2.0",
"@angular/animations": "9.0.0",
"@angular/cdk": "^9.0.0",
"@angular/common": "9.0.0",
"@angular/compiler": "9.0.0",
"@angular/core": "9.0.0",
"@angular/elements": "9.0.0",
"@angular/forms": "9.0.0",
"@angular/material": "^9.0.0",
"@angular/platform-browser": "9.0.0",
"@angular/platform-browser-dynamic": "9.0.0",
"@angular/router": "9.0.0",
"@angular/service-worker": "9.0.0",
"@webcomponents/custom-elements": "1.2.1",
"rxjs": "^6.5.3",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.900.0-rc.14",
"@angular/cli": "9.0.0-rc.14",
"@angular/compiler-cli": "9.0.0-rc.11",
"@angular/language-service": "9.0.0-rc.11",
"@angular-devkit/build-angular": "0.900.1",
"@angular/cli": "9.0.1",
"@angular/compiler-cli": "9.0.0",
"@angular/language-service": "9.0.0",
"@types/jasmine": "^3.4.2",
"@types/jasminewd2": "^2.0.8",
"@types/lunr": "^2.3.2",
@ -123,7 +123,7 @@
"cross-spawn": "^5.1.0",
"css-selector-parser": "^1.3.0",
"dgeni": "^0.4.11",
"dgeni-packages": "^0.27.5",
"dgeni-packages": "^0.28.3",
"entities": "^1.1.1",
"eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0",

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 455897,
"main-es2015": 450612,
"polyfills-es2015": 52195
}
}
@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2987,
"main-es2015": 448928,
"main-es2015": 451469,
"polyfills-es2015": 52195
}
}
@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3097,
"main-es2015": 426513,
"main-es2015": 429230,
"polyfills-es2015": 52195
}
}

View File

@ -27,10 +27,10 @@
*/
// Imports
const puppeteer = require('puppeteer');
const lighthouse = require('lighthouse');
const printer = require('lighthouse/lighthouse-cli/printer');
const logger = require('lighthouse-logger');
const puppeteer = require('puppeteer');
// Constants
const AUDIT_CATEGORIES = ['accessibility', 'best-practices', 'performance', 'pwa', 'seo'];
@ -83,10 +83,8 @@ function formatScore(score) {
}
async function launchChromeAndRunLighthouse(url, flags, config) {
const browser = await puppeteer.launch({
headless: true,
args: ['--remote-debugging-port=9222']});
flags.port = browser.port;
const browser = await puppeteer.launch();
flags.port = (new URL(browser.wsEndpoint())).port;
try {
return await lighthouse(url, flags, config);

View File

@ -22,7 +22,7 @@
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
</a>
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes" [currentNode]="currentNodes?.TopBar"></aio-top-menu>
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
<div class="toolbar-external-icons-container">
<a href="https://twitter.com/angular" title="Twitter" aria-label="Angular on twitter">

View File

@ -1,35 +1,32 @@
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
import { inject, ComponentFixture, TestBed, fakeAsync, flushMicrotasks, tick } from '@angular/core/testing';
import { Title } from '@angular/platform-browser';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, fakeAsync, flushMicrotasks, inject, TestBed, tick } from '@angular/core/testing';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatSidenav } from '@angular/material/sidenav';
import { By } from '@angular/platform-browser';
import { Subject, of, timer } from 'rxjs';
import { first, mapTo } from 'rxjs/operators';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { CurrentNodes } from 'app/navigation/navigation.model';
import { By, Title } from '@angular/platform-browser';
import { ElementsLoader } from 'app/custom-elements/elements-loader';
import { DocumentService } from 'app/documents/document.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { CurrentNodes } from 'app/navigation/navigation.model';
import { NavigationNode, NavigationService } from 'app/navigation/navigation.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchService } from 'app/search/search.service';
import { Deployment } from 'app/shared/deployment.service';
import { ElementsLoader } from 'app/custom-elements/elements-loader';
import { GaService } from 'app/shared/ga.service';
import { LocationService } from 'app/shared/location.service';
import { Logger } from 'app/shared/logger.service';
import { ScrollService } from 'app/shared/scroll.service';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocItem, TocService } from 'app/shared/toc.service';
import { of, Subject, timer } from 'rxjs';
import { first, mapTo } from 'rxjs/operators';
import { MockLocationService } from 'testing/location.service';
import { MockLogger } from 'testing/logger.service';
import { MockSearchService } from 'testing/search.service';
import { NavigationNode, NavigationService } from 'app/navigation/navigation.service';
import { ScrollService } from 'app/shared/scroll.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SearchService } from 'app/search/search.service';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocItem, TocService } from 'app/shared/toc.service';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
const sideBySideBreakPoint = 992;
const hideToCBreakPoint = 800;
@ -405,10 +402,12 @@ describe('AppComponent', () => {
// Older docs versions have an href
it('should navigate when change to a version with a url', async () => {
await setupSelectorForTesting();
locationService.urlSubject.next('new-page?id=1#section-1');
const versionWithUrlIndex = component.docVersions.findIndex(v => !!v.url);
const versionWithUrl = component.docVersions[versionWithUrlIndex];
const versionWithUrlAndPage = `${versionWithUrl.url}new-page?id=1#section-1`;
selectElement.triggerEventHandler('change', { option: versionWithUrl, index: versionWithUrlIndex});
expect(locationService.go).toHaveBeenCalledWith(versionWithUrl.url);
expect(locationService.go).toHaveBeenCalledWith(versionWithUrlAndPage);
});
it('should not navigate when change to a version without a url', async () => {
@ -793,7 +792,7 @@ describe('AppComponent', () => {
const searchService = TestBed.inject(SearchService) as Partial<SearchService> as MockSearchService;
const results = [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' }
];
searchService.searchResults.next({ query: 'something', results });

View File

@ -1,18 +1,15 @@
import { Component, ElementRef, HostBinding, HostListener, OnInit,
QueryList, ViewChild, ViewChildren } from '@angular/core';
import { Component, ElementRef, HostBinding, HostListener, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
import { DocumentService, DocumentContents } from 'app/documents/document.service';
import { DocumentContents, DocumentService } from 'app/documents/document.service';
import { NotificationComponent } from 'app/layout/notification/notification.component';
import { CurrentNodes, NavigationNode, NavigationService, VersionInfo } from 'app/navigation/navigation.service';
import { SearchResults } from 'app/search/interfaces';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchService } from 'app/search/search.service';
import { Deployment } from 'app/shared/deployment.service';
import { LocationService } from 'app/shared/location.service';
import { NotificationComponent } from 'app/layout/notification/notification.component';
import { ScrollService } from 'app/shared/scroll.service';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResults } from 'app/search/interfaces';
import { SearchService } from 'app/search/search.service';
import { TocService } from 'app/shared/toc.service';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
@ -77,6 +74,8 @@ export class AppComponent implements OnInit {
versionInfo: VersionInfo;
private currentUrl: string;
get isOpened() { return this.isSideBySide && this.isSideNavDoc; }
get mode() { return this.isSideBySide ? 'side' : 'over'; }
@ -148,27 +147,27 @@ export class AppComponent implements OnInit {
this.navigationService.versionInfo,
this.navigationService.navigationViews.pipe(map(views => views['docVersions'])),
]).subscribe(([versionInfo, versions]) => {
// TODO(pbd): consider whether we can lookup the stable and next versions from the internet
const computedVersions: NavigationNode[] = [
{ title: 'next', url: 'https://next.angular.io' },
{ title: 'stable', url: 'https://angular.io' },
];
if (this.deployment.mode === 'archive') {
computedVersions.push({ title: `v${versionInfo.major}` });
}
this.docVersions = [...computedVersions, ...versions];
// TODO(pbd): consider whether we can lookup the stable and next versions from the internet
const computedVersions: NavigationNode[] = [
{ title: 'next', url: 'https://next.angular.io' },
{ title: 'stable', url: 'https://angular.io' },
];
if (this.deployment.mode === 'archive') {
computedVersions.push({ title: `v${versionInfo.major}` });
}
this.docVersions = [...computedVersions, ...versions];
// Find the current version - eithers title matches the current deployment mode
// or its title matches the major version of the current version info
this.currentDocVersion = this.docVersions.find(version =>
version.title === this.deployment.mode || version.title === `v${versionInfo.major}`)!;
this.currentDocVersion.title += ` (v${versionInfo.raw})`;
});
// Find the current version - eithers title matches the current deployment mode
// or its title matches the major version of the current version info
this.currentDocVersion = this.docVersions.find(version =>
version.title === this.deployment.mode || version.title === `v${versionInfo.major}`)!;
this.currentDocVersion.title += ` (v${versionInfo.raw})`;
});
this.navigationService.navigationViews.subscribe(views => {
this.footerNodes = views['Footer'] || [];
this.footerNodes = views['Footer'] || [];
this.sideNavNodes = views['SideNav'] || [];
this.topMenuNodes = views['TopBar'] || [];
this.topMenuNodes = views['TopBar'] || [];
this.topMenuNarrowNodes = views['TopBarNarrow'] || this.topMenuNodes;
});
@ -188,6 +187,8 @@ export class AppComponent implements OnInit {
this.navigationService.currentNodes, // ...needed to determine `sidenav` state
]).pipe(first())
.subscribe(() => this.updateShell());
this.locationService.currentUrl.subscribe(url => this.currentUrl = url);
}
onDocReady() {
@ -231,7 +232,7 @@ export class AppComponent implements OnInit {
onDocVersionChange(versionIndex: number) {
const version = this.docVersions[versionIndex];
if (version.url) {
this.locationService.go(version.url);
this.locationService.go(`${version.url}${this.currentUrl}`);
}
}
@ -263,7 +264,7 @@ export class AppComponent implements OnInit {
}
// Deal with anchor clicks; climb DOM tree until anchor found (or null)
let target: HTMLElement|null = eventTarget;
let target: HTMLElement | null = eventTarget;
while (target && !(target instanceof HTMLAnchorElement)) {
target = target.parentElement;
}
@ -406,7 +407,7 @@ export class AppComponent implements OnInit {
if (key === '/' || keyCode === 191) {
this.focusSearchBox();
}
if (key === 'Escape' || keyCode === 27 ) {
if (key === 'Escape' || keyCode === 27) {
// escape key
if (this.showSearchResults) {
this.hideSearchResults();

View File

@ -111,6 +111,22 @@ describe('NotificationComponent', () => {
expect(getItemSpy).toHaveBeenCalledWith('aio-notification/survey-january-2018');
expect(component.showNotification).toBe('hide');
});
it('should not break when cookies are disabled in the browser', () => {
configTestingModule();
// Simulate `window.localStorage` being inaccessible, when cookies are disabled.
const mockWindow: MockWindow = TestBed.inject(WindowToken);
Object.defineProperty(mockWindow, 'localStorage', {
get() { throw new Error('The operation is insecure'); },
});
expect(() => createComponent()).not.toThrow();
expect(component.showNotification).toBe('show');
component.dismiss();
expect(component.showNotification).toBe('hide');
});
});
@Component({

View File

@ -20,7 +20,7 @@ const LOCAL_STORAGE_NAMESPACE = 'aio-notification/';
]
})
export class NotificationComponent implements OnInit {
private get localStorage() { return this.window.localStorage; }
private storage: Storage;
@Input() dismissOnContentClick: boolean;
@Input() notificationId: string;
@ -31,12 +31,27 @@ export class NotificationComponent implements OnInit {
showNotification: 'show'|'hide';
constructor(
@Inject(WindowToken) private window: Window,
@Inject(WindowToken) window: Window,
@Inject(CurrentDateToken) private currentDate: Date
) {}
) {
try {
this.storage = window.localStorage;
} catch {
// When cookies are disabled in the browser, even trying to access
// `window.localStorage` throws an error. Use a no-op storage.
this.storage = {
length: 0,
clear: () => undefined,
getItem: () => null,
key: () => null,
removeItem: () => undefined,
setItem: () => undefined
};
}
}
ngOnInit() {
const previouslyHidden = this.localStorage.getItem(LOCAL_STORAGE_NAMESPACE + this.notificationId) === 'hide';
const previouslyHidden = this.storage.getItem(LOCAL_STORAGE_NAMESPACE + this.notificationId) === 'hide';
const expired = this.currentDate > new Date(this.expirationDate);
this.showNotification = previouslyHidden || expired ? 'hide' : 'show';
}
@ -48,7 +63,7 @@ export class NotificationComponent implements OnInit {
}
dismiss() {
this.localStorage.setItem(LOCAL_STORAGE_NAMESPACE + this.notificationId, 'hide');
this.storage.setItem(LOCAL_STORAGE_NAMESPACE + this.notificationId, 'hide');
this.showNotification = 'hide';
this.dismissed.next();
}

View File

@ -1,42 +1,86 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BehaviorSubject } from 'rxjs';
import { TopMenuComponent } from './top-menu.component';
import { NavigationService, NavigationViews } from 'app/navigation/navigation.service';
describe('TopMenuComponent', () => {
let component: TopMenuComponent;
let fixture: ComponentFixture<TopMenuComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ TopMenuComponent ],
providers: [
{ provide: NavigationService, useClass: TestNavigationService }
]
});
});
// Helpers
const getListItems = () => {
const list: HTMLUListElement = fixture.debugElement.nativeElement.querySelector('ul');
return Array.from(list.querySelectorAll('li'));
};
const getSelected = (items: HTMLLIElement[]) =>
items.filter(item => item.classList.contains('selected'));
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TopMenuComponent,
],
});
fixture = TestBed.createComponent(TopMenuComponent);
component = fixture.componentInstance;
component.nodes = [
{url: 'api', title: 'API', tooltip: 'API docs'},
{url: 'features', title: 'Features', tooltip: 'Angular features overview'},
];
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
it('should create an item for each navigation node', () => {
const items = getListItems();
const links = items.map(item => item.querySelector('a'))
.filter((link): link is NonNullable<typeof link> => link !== null);
expect(links.length).toBe(2);
expect(links.map(link => link.pathname)).toEqual(['/api', '/features']);
expect(links.map(link => link.textContent)).toEqual(['API', 'Features']);
expect(links.map(link => link.title)).toEqual(['API docs', 'Angular features overview']);
});
it('should mark the currently selected node with `.selected`', () => {
const items = getListItems();
expect(getSelected(items)).toEqual([]);
component.currentNode = {url: 'api', view: 'foo', nodes: []};
fixture.detectChanges();
expect(getSelected(items)).toEqual([items[0]]);
component.currentNode = {url: 'features', view: 'foo', nodes: []};
fixture.detectChanges();
expect(getSelected(items)).toEqual([items[1]]);
component.currentNode = {url: 'something/else', view: 'foo', nodes: []};
fixture.detectChanges();
expect(getSelected(items)).toEqual([]);
});
it('should not mark any node with `.selected` if the current URL is undefined', () => {
component.nodes = [
{url: '', title: 'API', tooltip: 'API docs'},
{url: undefined, title: 'Features', tooltip: 'Angular features overview'},
];
fixture.detectChanges();
const items = getListItems();
component.currentNode = undefined;
fixture.detectChanges();
expect(getSelected(items)).toEqual([]);
});
it('should correctly mark a node with `.selected` even if its URL is empty', () => {
component.nodes = [
{url: '', title: 'API', tooltip: 'API docs'},
{url: undefined, title: 'Features', tooltip: 'Angular features overview'},
];
fixture.detectChanges();
const items = getListItems();
component.currentNode = {url: '', view: 'Empty url', nodes: []};
fixture.detectChanges();
expect(getSelected(items)).toEqual([items[0]]);
});
});
//// Test Helpers ////
class TestNavigationService {
navJson = {
TopBar: [
{url: 'api', title: 'API' },
{url: 'features', title: 'Features' }
],
};
navigationViews = new BehaviorSubject<NavigationViews>(this.navJson);
}

View File

@ -1,12 +1,12 @@
import { Component, Input } from '@angular/core';
import { NavigationNode } from 'app/navigation/navigation.service';
import { CurrentNode, NavigationNode } from 'app/navigation/navigation.service';
@Component({
selector: 'aio-top-menu',
template: `
<ul role="navigation">
<li *ngFor="let node of nodes">
<a class="nav-link" [href]="node.url" [title]="node.title">
<li *ngFor="let node of nodes" [ngClass]="{selected: node.url === currentUrl}">
<a class="nav-link" [href]="node.url" [title]="node.tooltip">
<span class="nav-link-inner">{{ node.title }}</span>
</a>
</li>
@ -14,5 +14,7 @@ import { NavigationNode } from 'app/navigation/navigation.service';
})
export class TopMenuComponent {
@Input() nodes: NavigationNode[];
@Input() currentNode: CurrentNode | undefined;
get currentUrl(): string | null { return this.currentNode ? this.currentNode.url : null; }
}

View File

@ -9,6 +9,7 @@ export interface SearchResult {
type: string;
titleWords: string;
keywords: string;
topics: string;
deprecated: boolean;
}

View File

@ -15,6 +15,7 @@ interface PageInfo {
type: string;
titleWords: string;
keyWords: string;
topics: string;
}
addEventListener('message', handleMessage);
@ -27,6 +28,7 @@ function createIndex(loadIndexFn: IndexLoader): lunr.Index {
queryLexer.termSeparator = lunr.tokenizer.separator = /\s+/;
return lunr(/** @this */function() {
this.ref('path');
this.field('topics', { boost: 15 });
this.field('titleWords', { boost: 10 });
this.field('headingWords', { boost: 5 });
this.field('members', { boost: 4 });

View File

@ -1,30 +1,57 @@
<div class="search-results">
<div *ngIf="searchAreas.length; then searchResults; else notFound"></div>
<div class="search-results" [ngSwitch]="searchState">
<ng-container *ngSwitchCase="'in-progress'">
<p class="no-results">Searching ...</p>
</ng-container>
<ng-container *ngSwitchCase="'results-found'">
<h2 class="visually-hidden">Search Results</h2>
<div class="search-area" *ngFor="let area of searchAreas">
<h3 class="search-section-header">{{area.name}} ({{area.pages.length + area.priorityPages.length}})</h3>
<ul class="priority-pages" >
<li class="search-page" *ngFor="let page of area.priorityPages">
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a>
</li>
</ul>
<ul>
<li class="search-page" *ngFor="let page of area.pages">
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a>
</li>
</ul>
</div>
</ng-container>
<ng-container *ngSwitchCase="'no-results-found'">
<div class="search-area">
<p class="no-results">
No results found.<br>
Here are a few links that might be helpful in finding what you are looking for:
</p>
<ul class="priority-pages">
<li class="search-page">
<a class="search-result-item" href="api">API reference</a>
</li>
<li class="search-page">
<a class="search-result-item" href="resources">Resources</a>
</li>
<li class="search-page">
<a class="search-result-item" href="guide/glossary">Glossary</a>
</li>
<li class="search-page">
<a class="search-result-item" href="guide/cheatsheet">Cheat-sheet</a>
</li>
<li class="search-page">
<a class="search-result-item" href="https://blog.angular.io/">Angular blog</a>
</li>
</ul>
</div>
</ng-container>
</div>
<ng-template #searchResults>
<h2 class="visually-hidden">Search Results</h2>
<div class="search-area" *ngFor="let area of searchAreas">
<h3 class="search-section-header">{{area.name}} ({{area.pages.length + area.priorityPages.length}})</h3>
<ul class="priority-pages" >
<li class="search-page" *ngFor="let page of area.priorityPages">
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a>
</li>
</ul>
<ul>
<li class="search-page" *ngFor="let page of area.pages">
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a>
</li>
</ul>
</div>
</ng-template>
<ng-template #notFound>
<p class="not-found">{{notFoundMessage}}</p>
</ng-template>

View File

@ -37,21 +37,21 @@ describe('SearchResultsComponent', () => {
/** Get a full set of test results. "Take" what you need */
beforeEach(() => {
apiD = { path: 'api/d', title: 'API D', deprecated: false, keywords: '', titleWords: '', type: '' };
apiC = { path: 'api/c', title: 'API C', deprecated: false, keywords: '', titleWords: '', type: '' };
guideA = { path: 'guide/a', title: 'Guide A', deprecated: false, keywords: '', titleWords: '', type: '' };
guideB = { path: 'guide/b', title: 'Guide B', deprecated: false, keywords: '', titleWords: '', type: '' };
guideAC = { path: 'guide/a/c', title: 'Guide A - C', deprecated: false, keywords: '', titleWords: '', type: '' };
guideE = { path: 'guide/e', title: 'Guide e', deprecated: false, keywords: '', titleWords: '', type: '' };
guideF = { path: 'guide/f', title: 'Guide f', deprecated: false, keywords: '', titleWords: '', type: '' };
guideG = { path: 'guide/g', title: 'Guide g', deprecated: false, keywords: '', titleWords: '', type: '' };
guideH = { path: 'guide/h', title: 'Guide h', deprecated: false, keywords: '', titleWords: '', type: '' };
guideI = { path: 'guide/i', title: 'Guide i', deprecated: false, keywords: '', titleWords: '', type: '' };
guideJ = { path: 'guide/j', title: 'Guide j', deprecated: false, keywords: '', titleWords: '', type: '' };
guideK = { path: 'guide/k', title: 'Guide k', deprecated: false, keywords: '', titleWords: '', type: '' };
guideL = { path: 'guide/l', title: 'Guide l', deprecated: false, keywords: '', titleWords: '', type: '' };
guideM = { path: 'guide/m', title: 'Guide m', deprecated: false, keywords: '', titleWords: '', type: '' };
guideN = { path: 'guide/n', title: 'Guide n', deprecated: false, keywords: '', titleWords: '', type: '' };
apiD = { path: 'api/d', title: 'API D', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
apiC = { path: 'api/c', title: 'API C', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideA = { path: 'guide/a', title: 'Guide A', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideB = { path: 'guide/b', title: 'Guide B', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideAC = { path: 'guide/a/c', title: 'Guide A - C', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideE = { path: 'guide/e', title: 'Guide e', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideF = { path: 'guide/f', title: 'Guide f', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideG = { path: 'guide/g', title: 'Guide g', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideH = { path: 'guide/h', title: 'Guide h', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideI = { path: 'guide/i', title: 'Guide i', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideJ = { path: 'guide/j', title: 'Guide j', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideK = { path: 'guide/k', title: 'Guide k', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideL = { path: 'guide/l', title: 'Guide l', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideM = { path: 'guide/m', title: 'Guide m', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideN = { path: 'guide/n', title: 'Guide n', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
standardResults = [
guideA, apiD, guideB, guideAC, apiC, guideN, guideM, guideL, guideK, guideJ, guideI, guideH, guideG, guideF, guideE,
@ -80,13 +80,13 @@ describe('SearchResultsComponent', () => {
it('should special case results that are top level folders', () => {
setSearchResults('', [
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
]);
expect(component.searchAreas).toEqual([
{ name: 'tutorial', priorityPages: [
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
], pages: [] }
]);
});
@ -116,20 +116,20 @@ describe('SearchResultsComponent', () => {
it('should put search results with no containing folder into the default area (other)', () => {
const results = [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' }
];
setSearchResults('', results);
expect(component.searchAreas).toEqual([
{ name: 'other', priorityPages: [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' }
], pages: [] }
]);
});
it('should omit search results with no title', () => {
const results = [
{ path: 'news', title: '', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
{ path: 'news', title: '', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' }
];
setSearchResults('something', results);
@ -170,6 +170,12 @@ describe('SearchResultsComponent', () => {
expect(getText()).toContain('Searching ...');
});
it('should not display default links while searching', () => {
fixture.detectChanges();
const resultLinks = fixture.debugElement.queryAll(By.css('.search-page a'));
expect(resultLinks.length).toEqual(0);
});
describe('when a search result anchor is clicked', () => {
let searchResult: SearchResult;
let selected: SearchResult|null;
@ -179,7 +185,7 @@ describe('SearchResultsComponent', () => {
component.resultSelected.subscribe((result: SearchResult) => selected = result);
selected = null;
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false };
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' };
setSearchResults('something', [searchResult]);
fixture.detectChanges();
@ -214,9 +220,25 @@ describe('SearchResultsComponent', () => {
});
describe('when no query results', () => {
it('should display "not found" message', () => {
beforeEach(() => {
setSearchResults('something', []);
});
it('should display "not found" message', () => {
expect(getText()).toContain('No results');
});
it('should contain reference links', () => {
const resultLinks = fixture.debugElement.queryAll(By.css('.search-page a'));
const resultHrefs = resultLinks.map(a => a.nativeNode.getAttribute('href'));
expect(resultHrefs.length).toEqual(5);
expect(resultHrefs).toEqual([
'api',
'resources',
'guide/glossary',
'guide/cheatsheet',
'https://blog.angular.io/',
]);
});
});
});

View File

@ -1,6 +1,12 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { SearchResult, SearchResults, SearchArea } from 'app/search/interfaces';
enum SearchState {
InProgress = 'in-progress',
ResultsFound = 'results-found',
NoResultsFound = 'no-results-found'
}
/**
* A component to display search results in groups
*/
@ -23,11 +29,18 @@ export class SearchResultsComponent implements OnChanges {
resultSelected = new EventEmitter<SearchResult>();
readonly defaultArea = 'other';
notFoundMessage = 'Searching ...';
searchState: SearchState = SearchState.InProgress;
readonly topLevelFolders = ['guide', 'tutorial'];
searchAreas: SearchArea[] = [];
ngOnChanges() {
if (this.searchResults === null) {
this.searchState = SearchState.InProgress;
} else if (this.searchResults.results.length) {
this.searchState = SearchState.ResultsFound;
} else {
this.searchState = SearchState.NoResultsFound;
}
this.searchAreas = this.processSearchResults(this.searchResults);
}
@ -43,7 +56,6 @@ export class SearchResultsComponent implements OnChanges {
if (!search) {
return [];
}
this.notFoundMessage = 'No results found.';
const searchAreaMap: { [key: string]: SearchResult[] } = {};
search.results.forEach(result => {
if (!result.title) { return; } // bad data; should fix

View File

@ -20,9 +20,9 @@
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/images/favicons/favicon-144x144.png">
<!-- NOTE: These need to be kept in sync with `ngsw-config.json`. -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons&display=block">
<!-- -->
<link rel="manifest" href="pwa-manifest.json">

View File

@ -138,7 +138,6 @@ a {
color: $white;
font-family: $main-font;
text-transform: uppercase;
padding: 21px 0;
}
strong {

View File

@ -22,20 +22,30 @@ footer {
}
a {
color: $white;
color: $white;
text-decoration: none;
z-index: 20;
position: relative;
&:hover {
text-decoration: underline;
}
&:visited {
text-decoration: none;
z-index: 20;
position: relative;
&:hover {
text-decoration: underline;
}
&:visited {
text-decoration: none;
}
}
&:focus {
// `outline-offset` is not applied on Chrome on Windows, if `outline-style` is `auto.
outline: 1px solid rgba($white, 0.8);
outline-offset: 2px;
}
}
a.action {
cursor: pointer;
}
h3 {
@include font-size(16);
text-transform: uppercase;
@ -43,6 +53,7 @@ footer {
margin: 8px 0 12px;
color: $white;
}
p {
text-align: center;
margin: 10px 0px 5px;
@ -56,9 +67,7 @@ footer {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
justify-content: center;
text-align: left;
margin: 0 0 40px;
@ -76,6 +85,7 @@ footer {
@media (max-width: 480px) {
flex-direction: column;
.footer-block {
margin: 8px 24px;
}
@ -90,16 +100,17 @@ footer {
}
footer::after {
content: '';
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
background:
url('/assets/images/logos/angular/angular_whiteTransparent_withMargin.png') top 0 left 0 repeat,
url('/assets/images/logos/angular/angular_whiteTransparent_withMargin.png') top 80px left 160px repeat;
opacity: 0.05;
background-size: 320px auto;
content: "";
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: url("/assets/images/logos/angular/angular_whiteTransparent_withMargin.png")
top 0 left 0 repeat,
url("/assets/images/logos/angular/angular_whiteTransparent_withMargin.png")
top 80px left 160px repeat;
opacity: 0.05;
background-size: 320px auto;
}

View File

@ -132,7 +132,7 @@ section#intro {
box-sizing: border-box;
transition: all 0.3s ease-in;
@media (max-width: 992px) {
@media (max-width: 991px) {
flex-direction: column;
text-align: center;
padding: 32px 16px;
@ -441,4 +441,4 @@ div[layout=row]{
.events-container{
overflow-x: auto;
}
}

View File

@ -79,7 +79,7 @@ mat-sidenav-container div.mat-sidenav-content {
}
&:focus {
outline: $accentblue auto 2px;
outline: $focus-outline-onlight auto 2px;
}
&.selected {

View File

@ -87,7 +87,7 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
}
& .mat-icon {
color: white;
color: $white;
position: inherit;
}
}
@ -96,7 +96,17 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
.nav-link.home {
cursor: pointer;
margin: 0 16px 0 0;
padding: 21px 0;
padding: 8px 0;
&:focus {
// `outline-offset` is not applied on Chrome on Windows, if `outline-style` is `auto.
outline: 1px solid $focus-outline-ondark;
outline-offset: 4px;
}
@media screen and (max-width: 991px) {
padding: 4px 0;
}
@media screen and (max-width: 480px) {
margin-right: 8px;
@ -108,7 +118,7 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
top: 12px;
height: 40px;
@media (max-width: 992px) {
@media (max-width: 991px) {
&:hover {
transform: scale(1.1);
}
@ -136,32 +146,47 @@ aio-top-menu {
list-style-type: none;
cursor: pointer;
&:hover {
opacity: 0.7;
}
&:focus {
background-color: $accentblue;
outline: none;
}
}
}
a.nav-link {
margin: 0;
padding: 24px 0px;
cursor: pointer;
a.nav-link {
margin: 0 4px;
padding: 0px;
cursor: pointer;
.nav-link-inner {
padding: 8px 16px;
}
.nav-link-inner {
border-radius: 4px;
padding: 8px 16px;
&:focus {
outline: none;
&:hover {
background: rgba($white, 0.15);
}
}
.nav-link-inner {
background: rgba($white, 0.15);
border-radius: 4px;
&:focus {
outline: none;
.nav-link-inner {
background: rgba($white, 0.15);
border-radius: 1px;
box-shadow: 0 0 1px 2px $focus-outline-ondark;
}
}
&:active {
.nav-link-inner {
background: rgba($white, 0.15);
}
}
}
&.selected {
a.nav-link {
.nav-link-inner {
background: rgba($white, 0.15);
}
}
}
}
}
@ -175,6 +200,7 @@ aio-search-box.search-container {
width: 100%;
min-width: 150px;
height: 100%;
margin-right: 16px;
input {
color: $darkgray;
@ -220,10 +246,18 @@ aio-search-box.search-container {
a {
display: flex;
align-items: center;
margin-left: 16px;
padding: 24px;
margin: 0 -16px;
&:focus {
// `outline-offset` is not applied on Chrome on Windows, if `outline-style` is `auto.
outline: 1px solid $focus-outline-ondark;
outline-offset: -16px;
}
@media screen and (max-width: 480px) {
margin-left: 8px;
margin: 0 0 0 8px;
padding: 0;
}
&:hover {

View File

@ -1,12 +1,12 @@
aio-contributor-list {
@media handheld and (max-width: 480px), screen and (max-width: 480px), screen and (max-width: 900px) {
.grid-fluid{
.grid-fluid {
width: auto;
}
}
@media handheld and (max-width: 480px), screen and (max-width: 480px), screen and (max-width: 900px) {
.grid-fluid{
.grid-fluid {
margin-left: 20px;
margin-right: 20px;
float: none;

View File

@ -67,12 +67,17 @@ aio-search-results {
}
}
.not-found {
.no-results {
color: $white;
text-align: center;
margin: 16px;
}
a {
color: $white;
font-weight: 500;
}
@media (max-width: 600px) {
display: block;
}
@ -104,6 +109,10 @@ aio-search-results {
.not-found {
color: $darkgray;
}
a {
color: $blue;
}
}
}
}

View File

@ -113,3 +113,9 @@ table {
}
}
}
.events-container {
tr > td, tr > th {
width: 33%;
}
}

View File

@ -54,8 +54,12 @@ $purple-600: #8E24AA;
$teal-500: #009688;
$lightgrey: #F5F6F7;
// STATE COLORS
$focus-outline-ondark: rgba($white, 0.8);
$focus-outline-onlight: $accentblue;
// GRADIENTS
$bluegradient: linear-gradient(145deg,#0D47A1,#42A5F5);
$bluegradient: linear-gradient(145deg,$blue-900,$blue-400);
$redgradient: linear-gradient(145deg,$darkred,$brightred);
// API LABEL COLOR AND SYMBOLS MAP

View File

@ -14,11 +14,11 @@ exports.config = {
suite: 'full',
capabilities: {
browserName: 'chrome',
chromeOptions: process.env['CI'] ? {
chromeOptions: {
binary: require('puppeteer').executablePath(),
// See /integration/README.md#browser-tests for more info on these args
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
} : {},
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
directConnect: true,
framework: 'jasmine',

View File

@ -172,10 +172,10 @@
/docs/ts/latest/api/testing/fakeAsync-function.html /api/core/testing/fakeAsync
/docs/ts/latest/cookbook/ts-to-js.html https://v2.angular.io/docs/ts/latest/cookbook/ts-to-js.html
/getting-started /start
/getting-started/routing /start/routing
/getting-started/data /start/data
/getting-started/forms /start/forms
/getting-started/deployment /start/deployment
/getting-started/routing /start/start-routing
/getting-started/data /start/start-data
/getting-started/forms /start/start-forms
/getting-started/deployment /start/start-deployment
/guide/cli-quickstart /start
/guide/learning-angular /start
/guide/learning-angular.html /start
@ -187,5 +187,9 @@
/guide/webpack https://v5.angular.io/guide/webpack
/news https://blog.angular.io/
/news.html https://blog.angular.io/
/start/data /start/start-data
/start/deployment /start/start-deployment
/start/forms /start/start-forms
/start/routing /start/start-routing
/testing /guide/testing
/testing/first-app-tests.html /guide/testing

View File

@ -14,11 +14,11 @@ exports.config = {
],
capabilities: {
browserName: 'chrome',
chromeOptions: process.env['CI'] ? {
chromeOptions: {
binary: require('puppeteer').executablePath(),
// See /integration/README.md#browser-tests for more info on these args
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio']
} : {},
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
directConnect: true,
baseUrl: 'http://localhost:4200/',

View File

@ -40,7 +40,8 @@ describe('site auto-scrolling', () => {
expect(await page.getScrollTop()).not.toBe(0);
await page.docsMenuLink.click();
expect(await page.getScrollTop()).toBe(0);
// On some environments (e.g. CI) it takes some time for the page to load (and scroll to top).
await browser.wait(async () => await page.getScrollTop() === 0, 1000);
});
it('should scroll to top when navigating to the same page via a link', async () => {

View File

@ -112,6 +112,7 @@ class ExampleZipper {
'!**/npm-debug.log',
'!**/example-config.json',
'!**/wallaby.js',
'!e2e/protractor-puppeteer.conf.js',
// AOT related files
'!**/aot/**/*.*',
'!**/*-aot.*'

View File

@ -15,8 +15,9 @@ const BOILERPLATE_PATHS = {
'src/environments/environment.prod.ts', 'src/environments/environment.ts',
'src/assets/.gitkeep', 'browserslist', 'src/favicon.ico', 'karma.conf.js',
'src/polyfills.ts', 'src/test.ts', 'tsconfig.app.json', 'tsconfig.spec.json',
'tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor.conf.js', 'e2e/tsconfig.json',
'.editorconfig', 'angular.json', 'package.json', 'tsconfig.json', 'tslint.json'
'tslint.json', 'e2e/src/app.po.ts', 'e2e/protractor-puppeteer.conf.js',
'e2e/protractor.conf.js', 'e2e/tsconfig.json', '.editorconfig', 'angular.json', 'package.json',
'tsconfig.json', 'tslint.json'
],
systemjs: [
'src/systemjs-angular-loader.js', 'src/systemjs.config.js', 'src/tsconfig.json',

View File

@ -10,7 +10,7 @@ describe('example-boilerplate tool', () => {
const sharedDir = path.resolve(__dirname, 'shared');
const sharedNodeModulesDir = path.resolve(sharedDir, 'node_modules');
const BPFiles = {
cli: 19,
cli: 20,
i18n: 2,
universal: 2,
systemjs: 7,

View File

@ -262,7 +262,13 @@ function runE2eTestsCLI(appDir, outputFile, bufferOutput, port) {
const config = loadExampleConfig(appDir);
const commands = config.e2e || [{
cmd: 'yarn',
args: ['e2e', '--prod', '--no-webdriver-update', `--port=${port || DEFAULT_CLI_EXAMPLE_PORT}`]
args: [
'e2e',
'--prod',
'--protractor-config=e2e/protractor-puppeteer.conf.js',
'--no-webdriver-update',
'--port={PORT}',
],
}];
let bufferedOutput = `\n\n============== AIO example output for: ${appDir}\n\n`;

View File

@ -0,0 +1,19 @@
// @ts-check
// A protractor config to use to run the tests using the Chrome version provided by `puppeteer`.
// This is useful to ensure deterministic runs on CI and locally. This file is ignored when creating
// StackBlitz examples and ZIP archives for each example.
const {config} = require('./protractor.conf.js');
exports.config = {
...config,
capabilities: {
...config.capabilities,
chromeOptions: {
...config.capabilities.chromeOptions,
binary: require('puppeteer').executablePath(),
// See /integration/README.md#browser-tests for more info on these args
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
};

View File

@ -6,7 +6,7 @@
"scripts": {
"http-server": "http-server",
"protractor": "protractor",
"webdriver:update": "webdriver-manager update --standalone false --gecko false $CI_CHROMEDRIVER_VERSION_ARG",
"webdriver:update": "node ../../../../scripts/webdriver-manager-update.js",
"preinstall": "node ../../../../tools/yarn/check-yarn.js",
"postinstall": "yarn webdriver:update"
},
@ -74,6 +74,7 @@
"lite-server": "^2.2.2",
"lodash": "^4.16.2",
"protractor": "^5.4.2",
"puppeteer": "2.1.1",
"rimraf": "^2.5.4",
"rollup": "^1.1.0",
"rollup-plugin-commonjs": "^9.2.1",

View File

@ -20,7 +20,12 @@ exports.config = {
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
browserName: 'chrome',
chromeOptions: {
binary: require('puppeteer').executablePath(),
// See /integration/README.md#browser-tests for more info on these args
args: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'],
},
},
// Framework to use. Jasmine is recommended.

View File

@ -450,6 +450,11 @@
dependencies:
"@types/sizzle" "*"
"@types/mime-types@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
"@types/mime@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
@ -729,6 +734,11 @@ agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@5:
version "5.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
agentkeepalive@^3.4.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
@ -1942,6 +1952,16 @@ concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
concat-stream@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^2.2.2"
typedarray "^0.0.6"
concat-stream@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
@ -2239,6 +2259,13 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
debug@^3.0.0, debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@ -2246,13 +2273,6 @@ debug@^3.0.0, debug@^3.2.5, debug@^3.2.6:
dependencies:
ms "^2.1.1"
debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@ -2943,6 +2963,16 @@ extglob@^2.0.2, extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
extract-zip@^1.6.6:
version "1.6.7"
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=
dependencies:
concat-stream "1.6.2"
debug "2.6.9"
mkdirp "0.5.1"
yauzl "2.4.1"
extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@ -2972,6 +3002,13 @@ faye-websocket@~0.11.1:
dependencies:
websocket-driver ">=0.5.1"
fd-slicer@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=
dependencies:
pend "~1.2.0"
figgy-pudding@^3.1.0, figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@ -3735,6 +3772,14 @@ https-proxy-agent@^2.2.1:
agent-base "^4.1.0"
debug "^3.1.0"
https-proxy-agent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
dependencies:
agent-base "5"
debug "4"
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@ -4887,6 +4932,11 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
"mime-db@>= 1.40.0 < 2":
version "1.40.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
@ -4906,6 +4956,13 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.16,
dependencies:
mime-db "~1.30.0"
mime-types@^2.1.25:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
dependencies:
mime-db "1.43.0"
mime-types@~2.1.18, mime-types@~2.1.19:
version "2.1.20"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
@ -4933,6 +4990,11 @@ mime@^1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mime@^2.0.3:
version "2.4.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
mime@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
@ -5039,7 +5101,7 @@ mkdirp@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@ -5804,6 +5866,11 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
@ -5939,6 +6006,11 @@ process@^0.11.0:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
progress@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
@ -6000,6 +6072,11 @@ proxy-addr@~2.0.4:
forwarded "~0.1.2"
ipaddr.js "1.9.0"
proxy-from-env@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=
prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@ -6056,6 +6133,22 @@ punycode@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
puppeteer@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-2.1.1.tgz#ccde47c2a688f131883b50f2d697bd25189da27e"
integrity sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==
dependencies:
"@types/mime-types" "^2.1.0"
debug "^4.1.0"
extract-zip "^1.6.6"
https-proxy-agent "^4.0.0"
mime "^2.0.3"
mime-types "^2.1.25"
progress "^2.0.1"
proxy-from-env "^1.0.0"
rimraf "^2.6.1"
ws "^6.1.0"
q@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
@ -8303,6 +8396,13 @@ ws@1.1.1:
options ">=0.0.5"
ultron "1.0.x"
ws@^6.1.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
dependencies:
async-limiter "~1.0.0"
ws@~3.3.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
@ -8481,6 +8581,13 @@ yargs@~3.10.0:
decamelize "^1.0.0"
window-size "0.1.0"
yauzl@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=
dependencies:
fd-slicer "~1.0.1"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"

View File

@ -45,6 +45,11 @@ module.exports = new Package('angular-base', [
.factory(require('./post-processors/add-image-dimensions'))
.factory(require('./post-processors/auto-link-code'))
// Configure jsdoc-style tag parsing
.config(function(inlineTagProcessor) {
inlineTagProcessor.inlineTagDefinitions.push(require('./inline-tag-defs/custom-search-defs/'));
})
.config(function(checkAnchorLinksProcessor) {
// This is disabled here to prevent false negatives for the `docs-watch` task.
// It is re-enabled in the main `angular.io-package`

View File

@ -0,0 +1,9 @@
module.exports = {
name: 'searchKeywords',
description: 'A shorthand for creating elements with search terms. Usage: `{@searchKeywords term1 term2 termN }`',
handler: function(doc, tagName, tagDescription) {
doc.searchKeywords = tagDescription;
return doc;
}
};

View File

@ -91,7 +91,8 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
titleWords: tokenize(doc.searchTitle).join(' '),
headingWords: headingWords.sort().join(' '),
keywords: words.sort().join(' '),
members: members.sort().join(' ')
members: members.sort().join(' '),
topics: doc.searchKeywords
};
});

View File

@ -23,7 +23,7 @@
{% block header %}
<header class="api-header">
<h1>{$ doc.name $}</h1>
<label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label>
{% if doc.isPrimaryPackage %}<label class="api-type-label package">package</label>{% else %}<label class="api-type-label {$ entry-point $}">entry-point</label>{% endif %}
{% if doc.packageDeprecated or (not doc.isPrimaryPackage and doc.deprecated !== undefined) %}<label class="api-status-label deprecated">deprecated</label>{% endif %}
{% if doc.security !== undefined %}<label class="api-status-label security">security</label>{% endif %}
{% if doc.pipeOptions.pure === 'false' %}<label class="api-status-label impure-pipe">impure</label>{% endif %}

File diff suppressed because it is too large Load Diff

View File

@ -27,18 +27,14 @@ var CIconfiguration = {
'IE10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'IE11': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.4': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android5': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android6': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android7': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Safari7': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Android8': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android9': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Safari12': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari13': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'iOS11': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
@ -98,6 +94,27 @@ var customLaunchers = {
version: '7.1',
device: 'Android GoogleAPI Emulator'
},
'SL_ANDROID8': {
base: 'SauceLabs',
browserName: 'Chrome',
platform: 'Android',
version: '8.0',
device: 'Android GoogleAPI Emulator'
},
'SL_ANDROID9': {
base: 'SauceLabs',
browserName: 'Chrome',
platform: 'Android',
version: '9.0',
device: 'Android GoogleAPI Emulator'
},
'SL_ANDROID10': {
base: 'SauceLabs',
browserName: 'Chrome',
platform: 'Android',
version: '10.0',
device: 'Android GoogleAPI Emulator'
},
'BS_CHROME': {base: 'BrowserStack', browser: 'chrome', os: 'OS X', os_version: 'Yosemite'},
'BS_FIREFOX': {base: 'BrowserStack', browser: 'firefox', os: 'Windows', os_version: '10'},

View File

@ -41,7 +41,7 @@ cef93a51b (pr/24623_top) ci: scripts to review PRs locally
637805a0c (pr/24623_base) docs: update `lowercase` pipe example in "AngularJS to Angular" guide (#24588)
```
Knowing `pr/24623_top` and `pr/24623_base` makes it convenient to refer to different SHAs in PR when rebasing or reseting.
Knowing `pr/24623_top` and `pr/24623_base` makes it convenient to refer to different SHAs in PR when rebasing or resetting.
### 2. Review PR

View File

@ -9,3 +9,4 @@ tmp/
**/*.ngsummary.json
**/*.ngsummary.ts
**/.yarn_local_cache*
**/NPM_PACKAGE_MANIFEST.json

99
integration/BUILD.bazel Normal file
View File

@ -0,0 +1,99 @@
load(":angular_integration_test.bzl", "angular_integration_test")
# Some integration ports must be managed manually to be unique and in other
# cases the tests are able to select a random free port.
#
# Where `ng e2e` is used we pass `ng e2e --port 0` which prompts the cli
# to select a random free port for the the e2e test. The protractor.conf is
# automaticaly updated to use this port.
#
# Karma automatically finds a free port so no effort is needed there.
#
# The manually configured ports are as follows:
#
# TEST PORT CONFIGURATION
# ==== ==== =============
# dynamic-compiler 4201 /e2e/browser.config.json: "port": 4201
# hello_world__closure 4202 /e2e/browser.config.json: "port": 4202
# hello_world__systemjs_umd 4203 /bs-config.e2e.json: "port": 4203
# i18n 4204 /e2e/browser.config.json: "port": 4204
# ng_elements 4205 /e2e/browser.config.json: "port": 4205
# platform-server 4206 /src/server.ts: app.listen(4206,...
# Map of integration tests to tags.
# A subset of these tests fail or are not meant to be run with ivy bundles. These are tagged
# "no-ivy-aot".
INTEGRATION_TESTS = {
"bazel": [
# Bazel-in-bazel tests are resource intensive and should not be over-parallized
# as they will compete for the resources of other parallel tests slowing
# everything down. Ask Bazel to allocate multiple CPUs for these tests with "cpu:n" tag.
"cpu:3",
"no-ivy-aot",
],
"bazel-schematics": [
# Bazel-in-bazel tests are resource intensive and should not be over-parallized
# as they will complete for the resources of other parallel tests slowing
# everything down. Ask Bazel to allocate multiple CPUs for these tests with "cpu:n" tag.
"cpu:3",
"no-ivy-aot",
],
"cli-hello-world": [],
"cli-hello-world-ivy-compat": [],
"cli-hello-world-ivy-i18n": ["no-ivy-aot"],
"cli-hello-world-ivy-minimal": [],
"cli-hello-world-lazy": [],
"cli-hello-world-lazy-rollup": [],
"dynamic-compiler": ["no-ivy-aot"],
"hello_world__closure": ["no-ivy-aot"],
"i18n": ["no-ivy-aot"],
"injectable-def": ["no-ivy-aot"],
"ivy-i18n": ["no-ivy-aot"],
"language_service_plugin": [],
"ng_elements": ["no-ivy-aot"],
"ng_elements_schematics": ["no-ivy-aot"],
"ng_update": [],
"ng_update_migrations": ["no-ivy-aot"],
"ngcc": ["no-ivy-aot"],
"platform-server": ["no-ivy-aot"],
"service-worker-schema": [],
"side-effects": ["no-ivy-aot"],
"terser": [],
}
[
angular_integration_test(
name = test_folder + "_test",
tags = INTEGRATION_TESTS[test_folder],
test_folder = test_folder,
)
for test_folder in INTEGRATION_TESTS
]
# Special case for `typings_test_ts36` test as we want to pin
# `typescript` at version 3.6.x for that test and not link to the
# root @npm//typescript package.
angular_integration_test(
name = "typings_test_ts36_test",
pinned_npm_packages = ["typescript"],
test_folder = "typings_test_ts36",
)
# Special case for `typings_test_ts37` test as we want to pin
# `typescript` at version 3.7.x for that test and not link to the
# root @npm//typescript package.
angular_integration_test(
name = "typings_test_ts37_test",
pinned_npm_packages = ["typescript"],
test_folder = "typings_test_ts37",
)
# Special case for `hello_world__systemjs_umd` test as we want to pin
# `systems` at version 0.20.2 and not link to the the root @npm//systemjs
# which is stuck at 0.18.10 and can't be updated to 0.20.2 without
# breaking the legacy saucelabs job.
angular_integration_test(
name = "hello_world__systemjs_umd_test",
pinned_npm_packages = ["systemjs"],
test_folder = "hello_world__systemjs_umd",
)

View File

@ -65,6 +65,47 @@ $ ./integration/run_tests.sh
The test runner will first re-build any stale npm packages, then `cd` into each subdirectory to
execute the test.
## Running integration tests under Bazel
The PR https://github.com/angular/angular/pull/33927 added the ability to run integration tests with Bazel. These tests can be resource intensive so it is recommended to limit the number of concurrent test jobs with the `--local_test_jobs` bazel flag.
Locally, if Bazel uses all of your cores to run the maximum number of integration tests in parallel then this can lead to test timeouts and flakes and freeze up your machine while these tests are running. You can limit the number of concurrent local integration tests that run with:
```
yarn bazel test --local_test_jobs=<N> //integration/...
```
Set a reasonable `local_test_jobs` limit for your local machine to prevent full cpu utilization during local development test runs.
To avoid having to specify this command line flag, you may want to include it in your `.bazelrc.user` file:
```
test --local_test_jobs=<N>
```
The downside of this is that this will apply to all tests and not just the resource intensive integration tests.
### Bazel-in-bazel integration tests
Two of the integration tests that run Bazel-in-Bazel are particularly resource intensive and are tagged "manual" and "exclusive". To run these tests use,
```
yarn bazel test //integration:bazel_test
yarn bazel test //integration:bazel-schematics_test
```
## Adding a new integration test
When adding a new integration test, follow the steps below to add a bazel test target for the new test.
1. Add new test to `INTEGRATION_TESTS` object in `/integration/BUILD.bazel` (and tag as `"no-ivy-aot"` if not meant to be run against ivy bundles). *NB: if the test requires any special attribute then make a new angular_integration_test target instead.*
2. If test requires ports and does not support ethereal ports then make sure the port is unique and add it to the "manually configured ports" comment to document which port it is using
3. Add at least the following two entries `.bazelignore` (as they may contain BUILD files)
1. `integration/new_test/node_modules`
2. `integration/new_test/.yarn_local_cache`
4. Add any other untracked folders to `.bazelignore` that may contain `BUILD` files
5. If there are tracked BUILD files in the integration test folder (`integration/bazel` has these for example) add those folders to the `build --deleted_packages` and `query --deleted_packages` lines in `.bazelrc`
## Browser tests
For integration tests we use the puppeteer provisioned version of Chrome. For both Karma and Protractor tests we set a number of browser testing flags. To avoid duplication, they will be listed and explained here and the code will reference this file for more information.

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 141000,
"main-es2015": 141569,
"polyfills-es2015": 36657
}
}
@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 16312,
"main-es2015": 16514,
"polyfills-es2015": 36657
}
}
@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 147082,
"main-es2015": 147647,
"polyfills-es2015": 36657
}
}
@ -30,7 +30,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 1485,
"main-es2015": 136250,
"main-es2015": 136777,
"polyfills-es2015": 37334
}
}
@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 246583,
"main-es2015": 247198,
"polyfills-es2015": 36657,
"5-es2015": 751
}
@ -49,7 +49,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2289,
"main-es2015": 225584,
"main-es2015": 226144,
"polyfills-es2015": 36657,
"5-es2015": 779
}
@ -64,4 +64,4 @@
}
}
}
}
}

View File

@ -0,0 +1,155 @@
# Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license
"""Angular integration testing
"""
load("//tools/npm_integration_test:npm_integration_test.bzl", "npm_integration_test")
# The @npm packages at the root node_modules are used by integration tests
# with `file:../../node_modules/foobar` references
NPM_PACKAGE_ARCHIVES = [
"check-side-effects",
"core-js",
"jasmine",
"typescript",
"rxjs",
"systemjs",
"tsickle",
"tslib",
"protractor",
"puppeteer",
"rollup",
"rollup-plugin-commonjs",
"rollup-plugin-node-resolve",
"webdriver-manager",
"@angular/cli",
"@angular-devkit/build-angular",
"@bazel/bazel",
"@types/jasmine",
"@types/jasminewd2",
"@types/node",
]
# The generated npm packages should ALWAYS be replaced in integration tests
# so we pass them to the `check_npm_packages` attribute of npm_integration_test
GENERATED_NPM_PACKAGES = [
"@angular/animations",
"@angular/bazel",
"@angular/benchpress",
"@angular/common",
"@angular/compiler",
"@angular/compiler-cli",
"@angular/core",
"@angular/elements",
"@angular/forms",
"@angular/http",
"@angular/language-service",
"@angular/localize",
"@angular/platform-browser",
"@angular/platform-browser-dynamic",
"@angular/platform-server",
"@angular/platform-webworker",
"@angular/platform-webworker-dynamic",
"@angular/router",
"@angular/service-worker",
"@angular/upgrade",
"zone.js",
]
def npm_package_archives():
"""Function to generate pkg_tar definitions for WORKSPACE yarn_install manual_build_file_contents"""
npm_packages_to_archive = NPM_PACKAGE_ARCHIVES
result = """load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
"""
for name in npm_packages_to_archive:
label_name = _npm_package_archive_label(name)
last_segment_name = name if name.find("/") == -1 else name.split("/")[-1]
result += """pkg_tar(
name = "{label_name}",
srcs = ["//{name}:{last_segment_name}__all_files"],
extension = "tar.gz",
strip_prefix = "./node_modules/{name}",
# should not be built unless it is a dependency of another rule
tags = ["manual"],
)
""".format(name = name, label_name = label_name, last_segment_name = last_segment_name)
return result
def _npm_package_archive_label(package_name):
return package_name.replace("/", "_").replace("@", "") + "_archive"
def _angular_integration_test(pinned_npm_packages = [], **kwargs):
"Set defaults for the npm_integration_test common to the angular repo"
commands = kwargs.pop("commands", None)
if not commands:
# By default run `yarn install` followed by `yarn test` using
# the bazel managed hermetic version of yarn inside
commands = [
# Workaround https://github.com/yarnpkg/yarn/issues/2165
# Yarn will cache file://dist URIs and not update Angular code
"rm -rf ./.yarn_local_cache",
"mkdir .yarn_local_cache",
"patch-package-json",
"$(rootpath @nodejs//:yarn_bin) install --cache-folder ./.yarn_local_cache",
"$(rootpath @nodejs//:yarn_bin) test",
"rm -rf ./.yarn_local_cache",
]
# Complete list of npm packages to override in the test's package.json file mapped to
# tgz archive to use for the replacement. This is the full list for all integration
# tests. Any given integration does not need to use all of these packages.
npm_packages = {}
for name in NPM_PACKAGE_ARCHIVES:
if name not in pinned_npm_packages:
npm_packages["@npm//:" + _npm_package_archive_label(name)] = name
for name in GENERATED_NPM_PACKAGES:
last_segment_name = name if name.find("/") == -1 else name.split("/")[-1]
npm_packages["//packages/%s:npm_package_archive" % last_segment_name] = name
npm_integration_test(
check_npm_packages = GENERATED_NPM_PACKAGES,
commands = commands,
npm_packages = npm_packages,
tags = kwargs.pop("tags", []) + [
# `integration` tag is used for filtering out these tests from the normal
# developer workflow
"integration",
# Integration do not work inside of a sandbox as they may run host applications such
# as chrome (which is run by ng) that require access to files outside of the sandbox.
"no-sandbox",
# Remote doesn't work as it needs network access right now
"no-remote-exec",
],
data = kwargs.pop("data", []) + [
# We need the yarn_bin & yarn_files available at runtime
"@nodejs//:yarn_bin",
"@nodejs//:yarn_files",
],
# 15-minute timeout
timeout = "long",
# Tells bazel that this test should be allocated a large amount of memory.
# See https://docs.bazel.build/versions/2.0.0/be/common-definitions.html#common-attributes-tests.
size = "enormous",
**kwargs
)
def angular_integration_test(name, test_folder, pinned_npm_packages = [], **kwargs):
"Sets up the integration test target based on the test folder name"
native.filegroup(
name = "_%s_sources" % name,
srcs = native.glob(
include = ["%s/**" % test_folder],
exclude = [
"%s/node_modules/**" % test_folder,
"%s/.yarn_local_cache/**" % test_folder,
],
),
)
_angular_integration_test(
name = name,
test_files = kwargs.pop("test_files", "_%s_sources" % name),
pinned_npm_packages = pinned_npm_packages,
**kwargs
)

View File

@ -4,9 +4,29 @@
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@angular-devkit/build-angular": "file:../../node_modules/@angular-devkit/build-angular",
"@angular/animations": "file:../../dist/packages-dist/animations",
"@angular/bazel": "file:../../dist/packages-dist/bazel",
"@angular/cli": "file:../../node_modules/@angular/cli",
"@bazel/bazel": "file:../../node_modules/@bazel/bazel"
"@angular/common": "file:../../node_modules/@angular/common",
"@angular/compiler": "file:../../node_modules/@angular/compiler",
"@angular/compiler-cli": "file:../../node_modules/@angular/compiler-cli",
"@angular/core": "file:../../node_modules/@angular/core",
"@angular/forms": "file:../../node_modules/@angular/forms",
"@angular/language-service": "file:../../node_modules/@angular/language-service",
"@angular/platform-browser": "file:../../node_modules/@angular/platform-browser",
"@angular/platform-browser-dynamic": "file:../../node_modules/@angular/platform-browser-dynamic",
"@angular/router": "file:../../node_modules/@angular/router",
"@bazel/bazel": "file:../../node_modules/@bazel/bazel",
"@types/node": "file:../../node_modules/@types/node",
"protractor": "file:../../node_modules/protractor",
"puppeteer": "file:../../node_modules/puppeteer",
"typescript": "file:../../node_modules/typescript",
"tslib": "file:../../node_modules/tslib"
},
"//resolutions-comment": "Ensure a single version of webdriver-manager which comes from root node_modules that has already run webdriver-manager update",
"resolutions": {
"**/webdriver-manager": "file:../../node_modules/webdriver-manager"
},
"scripts": {
"test": "./test.sh"

View File

@ -2,6 +2,17 @@
set -eux -o pipefail
# sedi makes `sed -i` work on both OSX & Linux
# See https://stackoverflow.com/questions/2320564/i-need-my-sed-i-command-for-in-place-editing-to-work-with-both-gnu-sed-and-bsd
function sedi () {
case $(uname) in
Darwin*) sedi=('-i' '') ;;
*) sedi='-i' ;;
esac
sed "${sedi[@]}" "$@"
}
function installLocalPackages() {
# Install Angular packages that are built locally from HEAD.
# This also gets around the bug whereby yarn caches local `file://` urls.
@ -13,17 +24,31 @@ function installLocalPackages() {
)
local local_packages=()
for package in "${packages[@]}"; do
local_packages+=("@angular/${package}@file:${pwd}/../../../dist/packages-dist/${package}")
local_packages+=("@angular/${package}@file:${pwd}/../node_modules/@angular/${package}")
done
# keep typescript, tslib, and @types/node versions in sync with the ones used in this repo
local_packages+=("typescript@file:${pwd}/../../../node_modules/typescript")
local_packages+=("tslib@file:${pwd}/../../../node_modules/tslib")
local_packages+=("@types/node@file:${pwd}/../../../node_modules/@types/node")
# keep protractor, typescript, tslib, and @types/node versions in sync with the ones used in this repo
local_packages+=("protractor@file:${pwd}/../node_modules/protractor")
local_packages+=("typescript@file:${pwd}/../node_modules/typescript")
local_packages+=("tslib@file:${pwd}/../node_modules/tslib")
local_packages+=("@types/node@file:${pwd}/../node_modules/@types/node")
# add protractor, puppeteer & webdriver-manager so we get the chrome & chromedriver binaries
# that have already been downloaded at the root
local_packages+=("puppeteer@file:${pwd}/../node_modules/puppeteer")
local_packages+=("webdriver-manager@file:${pwd}/../node_modules/webdriver-manager")
yarn add --ignore-scripts --silent "${local_packages[@]}"
}
function patchKarmaConf() {
sedi "s#module.exports#process.env.CHROME_BIN = require\('puppeteer'\).executablePath\(\); module.exports#" ./karma.conf.js
sedi "s#browsers\: \['Chrome'\],#customLaunchers\: \{ ChromeHeadlessNoSandbox\: \{ base\: 'ChromeHeadless', flags\: \['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'\] \} \}, browsers\: \['ChromeHeadlessNoSandbox'\],#" ./karma.conf.js
}
function patchProtractorConf() {
sedi "s#browserName\: 'chrome'#browserName\: 'chrome', chromeOptions\: \{ binary: require\('puppeteer'\).executablePath\(\), args: \['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage', '--hide-scrollbars', '--mute-audio'\] \},#" ./e2e/protractor.conf.js
}
function testBazel() {
# Set up
@ -33,6 +58,8 @@ function testBazel() {
# Create project
ng new demo --collection=@angular/bazel --routing --skip-git --skip-install --style=scss
cd demo
patchKarmaConf
patchProtractorConf
installLocalPackages
ng generate component widget --style=css
ng build
@ -52,11 +79,10 @@ function testNonBazel() {
# disable CLI's version check (if version is 0.0.0, then no version check happens)
yarn --cwd node_modules/@angular/cli version --new-version 0.0.0 --no-git-tag-version
# re-add build-angular
yarn add --dev file:../../../node_modules/@angular-devkit/build-angular
yarn webdriver-manager update --gecko=false --standalone=false ${CI_CHROMEDRIVER_VERSION_ARG:---versions.chrome 2.45}
yarn add --dev file:../node_modules/@angular-devkit/build-angular
ng build --progress=false
ng test --progress=false --watch=false
ng e2e --configuration=production --webdriver-update=false
ng e2e --port 0 --configuration=production --webdriver-update=false
}
testBazel

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Fetch rules_nodejs so we can install our npm dependencies
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "6bcef105e75cac3c5f8212e0d0431b6ec1aaa1963e093b0091474ab98ecf29d2",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.2.2/rules_nodejs-1.2.2.tar.gz"],
sha256 = "b6670f9f43faa66e3009488bbd909bc7bc46a5a9661a33f6bc578068d1837f37",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.3.0/rules_nodejs-1.3.0.tar.gz"],
)
# Fetch sass rules for compiling sass files

View File

@ -23,11 +23,11 @@
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@bazel/bazel": "file:../../node_modules/@bazel/bazel",
"@bazel/karma": "1.2.2",
"@bazel/protractor": "1.2.2",
"@bazel/rollup": "1.2.2",
"@bazel/terser": "1.2.2",
"@bazel/typescript": "1.2.2",
"@bazel/karma": "1.3.0",
"@bazel/protractor": "1.3.0",
"@bazel/rollup": "1.3.0",
"@bazel/terser": "1.3.0",
"@bazel/typescript": "1.3.0",
"@types/jasmine": "2.8.8",
"http-server": "0.12.0",
"karma": "4.4.1",

View File

@ -93,32 +93,32 @@
resolved "https://registry.yarnpkg.com/@bazel/hide-bazel-files/-/hide-bazel-files-0.38.3.tgz#e98231d3d360d51860d9c1a7c3345b40dab4cf81"
integrity sha512-o+dNkfDm3qxWQ8h/04cWuTcjR7qnjZi3pQGv4aklVb16oPWx2jF8BzbkwvWuIkdbOl9VnqYP0vaHzwQVJRRcIA==
"@bazel/karma@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-1.2.2.tgz#545c0f86f921229879511a9a0b2edba1329a1fb8"
integrity sha512-jUTv6DKoLkU3VUIFHCTnRU94qbOBPRZYbg7//2AnzHAS77SQyIAEUG5xU/W26+kle+sj7nBMavlMOxLjsrpMFA==
"@bazel/karma@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-1.3.0.tgz#8d4c336f04357ccc9633b9b6cdf8c79b15793ef1"
integrity sha512-TyGoLBGTt9Mlp/FhwAytYaolUfrwMYuhNNzOYQ0lNzF73oEXOD1G9vIYLn2NvxQLPAaa2guBmNWSf0EkLhMuiw==
dependencies:
tmp "0.1.0"
"@bazel/protractor@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-1.2.2.tgz#33bd58dfbfa33f1ec4b037ef5d4edc5de130aa2b"
integrity sha512-PgE/VAwgvtlFPKdCR1o2ofZyc4khLeEu+nztzuohZ1MqD5Yn5tx6QGhOAhLDLNCim1DRqRXct7LerAy1VNm5iA==
"@bazel/protractor@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-1.3.0.tgz#050edc3480a6b5cf2b4c96daeced5cb871839298"
integrity sha512-EwTlHbMwHIyPy7FhDiKLum2nlmmy27p/yQzWzN8dRnCIjmq4ezTfgNMnyZ5Z9HiQ2HM4kyzNx1f65eKNXXZK7w==
"@bazel/rollup@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-1.2.2.tgz#f5522ac308c41c6e11cbbdf3f0300a1db885410d"
integrity sha512-TGdL06eO8ARHEGVYdwA4MXoVM/V8EeFhi2DAMK+VrN609B7/CvtukdlT9KnuAONo5W7mttPzyuCgsvkaADrNOA==
"@bazel/rollup@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-1.3.0.tgz#266b5621bee430efdeb453ade97aa76700c78fd2"
integrity sha512-8DnFc4ZUT8kJmZ1bZ86wkqVJYYGBt22p30NhpKUEKUQxROvSr+9lKoAEW+vdJ16rtu8UZDOvHlxr1MqkgnD8Jg==
"@bazel/terser@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-1.2.2.tgz#a245e345028fd0e15b76b67da1a60c0d9635c76f"
integrity sha512-IsHxbSjbNesSjIQDRpm1tDPwiHadTQIIIOUgtQQN1hDlzmDufTNvqGpB+34MeSLNzn2KVHObqYbYs6LVpW1a8A==
"@bazel/terser@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-1.3.0.tgz#cb9baf996d87dc5632828943293bfa8e0d779e6f"
integrity sha512-esCmSwLH2PLi5Lnq/88hFpAYyYpvsfmhGe7TQI7C27hLTX2jK0eKEm5DU15oJIEjMM1r7zA015eomsNsUScKEQ==
"@bazel/typescript@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.2.2.tgz#9acb8e07d1bd2b5d9c7afd01b434687b277f9dcb"
integrity sha512-qEkkkLOsKvcTvyToiMLVTU67iEHTOzVUIJGDo3+7g8vRL4OqG9EWk0ooNzVLWZ129Caz9TyVyleh1RhuReJWOw==
"@bazel/typescript@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.3.0.tgz#6df7409ad8b2fcd80dfe859904fe91aacfb1a2cf"
integrity sha512-F1Cjnjby+b3cO+rVuZY/9tzepf8wgoXZP9PtFmVWTQ+NtBkKEYx6IQ3AXZJl33mYVoN/Zb5qBTrGW/QfHUvakw==
dependencies:
protobufjs "6.8.8"
semver "5.6.0"

View File

@ -4,7 +4,7 @@
"license": "MIT",
"scripts": {
"build": "ng build --prod",
"e2e": "ng e2e",
"e2e": "ng e2e --port 0",
"lint": "ng lint",
"ng": "ng",
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
@ -31,8 +31,8 @@
"@angular/cli": "file:../../node_modules/@angular/cli",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/language-service": "file:../../dist/packages-dist/language-service",
"@types/jasmine": "3.4.6",
"@types/jasminewd2": "2.0.6",
"@types/jasmine": "file:../../node_modules/@types/jasmine",
"@types/jasminewd2": "file:../../node_modules/@types/jasminewd2",
"@types/node": "file:../../node_modules/@types/node",
"codelyzer": "5.2.0",
"jasmine-core": "3.5.0",

View File

@ -4,13 +4,13 @@
"license": "MIT",
"scripts": {
"build": "ng build --prod",
"e2e": "ng e2e",
"e2e": "ng e2e --port 0",
"lint": "ng lint",
"ng": "ng",
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
"start": "ng serve",
"pretest": "ng version",
"test": "ng e2e --prod && ng xi18n && yarn translate && ng e2e --configuration fr && ng e2e --configuration de",
"test": "ng e2e --port 0 --prod && ng xi18n && yarn translate && ng e2e --port 0 --configuration fr && ng e2e --port 0 --configuration de",
"translate": "cp src/locale/messages.xlf src/locale/messages.fr.xlf && cp src/locale/messages.xlf src/locale/messages.de.xlf && sed -i.bak -e 's/source>/target>/g' -e 's/Hello/Bonjour/' src/locale/messages.fr.xlf && sed -i.bak -e 's/source>/target>/g' -e 's/Hello/Hallo/' src/locale/messages.de.xlf",
"serve": "serve --no-clipboard --listen 4200"
},
@ -35,8 +35,8 @@
"@angular/cli": "file:../../node_modules/@angular/cli",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/language-service": "file:../../dist/packages-dist/language-service",
"@types/jasmine": "3.4.4",
"@types/jasminewd2": "2.0.8",
"@types/jasmine": "file:../../node_modules/@types/jasmine",
"@types/jasminewd2": "file:../../node_modules/@types/jasminewd2",
"@types/node": "file:../../node_modules/@types/node",
"codelyzer": "5.2.0",
"jasmine-core": "3.5.0",

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