Compare commits

...

111 Commits

Author SHA1 Message Date
81db6a3171 release: cut the v10.0.12 release 2020-08-24 15:15:10 -07:00
8224e65e70 docs(localize): fix angular.json syntax (#38553)
In chapter internationalization (i18n) at section "Deploy multiple locales" the syntax for angular.json is wrong.
This commit fixes the angular.json, when specifying the translation file and the baseHref for a locale.

PR Close #38553
2020-08-24 09:25:38 -07:00
9e50cef507 test(language-service): [Ivy] return cursor position in overwritten template (#38552)
In many testing scenarios, there is a common pattern:

1. Overwrite template (inline or external)
2. Find cursor position
3. Call one of language service APIs
4. Inspect spans in result

In order to faciliate this pattern, this commit refactors
`MockHost.overwrite()` and `MockHost.overwriteInlineTemplate()` to
allow a faux cursor symbol `¦` to be injected into the template, and
the methods will automatically remove it before updating the script snapshot.
Both methods will return the cursor position and the new text without
the cursor symbol.

This makes testing very convenient. Here's a typical example:

```ts
const {position, text} = mockHost.overwrite('template.html', `{{ ti¦tle }}`);
const quickInfo = ngLS.getQuickInfoAtPosition('template.html', position);
const {start, length} = quickInfo!.textSpan;
expect(text.substring(start, start + length)).toBe('title');
```

PR Close #38552
2020-08-24 09:25:07 -07:00
66d8c223a9 feat(language-service): introduce hybrid visitor to locate AST node (#38540)
This commit introduces two visitors, one for Template AST and the other
for Expression AST to allow us to easily find the node that most closely
corresponds to a given cursor position.

This is crucial because many language service APIs take in a `position`
parameter, and the information returned depends on how well we can find
a good candidate node.

In View Engine implementation of language service, the search for the node
and the processing of information to return the result are strongly coupled.
This makes the code hard to understand and hard to debug because the stack
trace is often littered with layers of visitor calls.

With this new feature, we could test the "searching" part separately and
colocate all the logic (aka hacks) that's required to retrieve an accurate
span for a given node.

Right now, only the most "narrow" node is returned by the main exported
function `findNodeAtPosition`. If needed, we could expose the entire AST
path, or expose other methods to provide more context for a node.

Note that due to limitations in the template AST interface, there are
a few known cases where microsyntax spans are not recorded properly.
This will be dealt with in a follow-up PR.

PR Close #38540
2020-08-24 09:24:21 -07:00
814b43647d fix(compiler-cli): adding references to const enums in runtime code (#38542)
We had a couple of places where we were assuming that if a particular
symbol has a value, then it will exist at runtime. This is true in most cases,
but it breaks down for `const` enums.

Fixes #38513.

PR Close #38542
2020-08-21 12:23:24 -07:00
6b98567b6b fix(dev-infra): ignore comments when validating commit messages (#38438)
When creating a commit with the git cli, git pre-populates the editor
used to enter the commit message with some comments (i.e. lines starting
with `#`). These comments contain helpful instructions or information
regarding the changes that are part of the commit. As happens with all
commit message comments, they are removed by git and do not end up in
the final commit message.

However, the file that is passed to the `commit-msg` to be validated
still contains these comments. This may affect the outcome of the commit
message validation. In such cases, the author will not realize that the
commit message is not in the desired format until the linting checks
fail on CI (which validates the final commit messages and is not
affected by this issue), usually several minutes later.

Possible ways in which the commit message validation outcome can be
affected:
- The minimum body length check may pass incorrectly, even if there is
  no actual body, because the comments are counted as part of the body.
- The maximum line length check may fail incorrectly due to a very long
  line in the comments.

This commit fixes the problem by removing comment lines before
validating a commit message.

Fixes #37865

PR Close #38438
2020-08-21 12:17:17 -07:00
7f2a3e4b6b docs: apply code styling in template reference variables guide (#38522)
PR Close #38522
2020-08-20 13:01:37 -07:00
1775f351d6 fix(docs-infra): fix vertical alignment of external link icons (#38410)
At some places external link icons appear as a subscript. For example
8366effeec/aio/content/guide/roadmap.md\#L37
this commit places external link icons in the middle to improve there
positioning in a line.

PR Close #38410
2020-08-20 09:40:05 -07:00
1df52d9acd docs: udpate the details (#37967)
updating my twitter handle and bio as it is changed from
Angular and Web Tech to Angular also the
twitter handle is changed to SantoshYadavDev

PR Close #37967
2020-08-20 09:39:05 -07:00
77e8db4484 docs(core): Fix typo in JSDoc for AbstractType<T> (#38541)
PR Close #38541
2020-08-20 09:30:24 -07:00
c90262e619 Revert "Revert "fix(core): remove closing body tag from inert DOM builder (#38454)""
This reverts commit 87bbf69ce8.
2020-08-20 08:56:01 -07:00
87bbf69ce8 Revert "fix(core): remove closing body tag from inert DOM builder (#38454)"
This reverts commit 552853648c.
2020-08-19 21:02:55 -07:00
552853648c fix(core): remove closing body tag from inert DOM builder (#38454)
Fix a bug in the HTML sanitizer where an unclosed iframe tag would
result in an escaped closing body tag as the output:

_sanitizeHtml(document, '<iframe>') => '&lt;/body&gt;'

This closing body tag comes from the DOMParserHelper where the HTML to be
sanitized is wrapped with surrounding body tags. When an opening iframe
tag is parsed by DOMParser, which DOMParserHelper uses, everything up
until its matching closing tag is consumed as a text node. In the above
example this includes the appended closing body tag.

By removing the explicit closing body tag from the DOMParserHelper and
relying on the body tag being closed implicitly at the end, the above
example is sanitized as expected:

_sanitizeHtml(document, '<iframe>') => ''

PR Close #38454
2020-08-19 14:18:47 -07:00
07b99f5975 fix(localize): parse all parts of a translation with nested HTML (#38452)
Previously nested container placeholders (i.e. HTML elements) were
not being fully parsed from translation files. This resulted in bad
translation of messages that contain these placeholders.

Note that this causes the canonical message ID to change for
such messages. Currently all messages generated from
templates use "legacy" message ids that are not affected by
this change, so this fix should not be seen as a breaking change.

Fixes #38422

PR Close #38452
2020-08-19 14:16:48 -07:00
57d1a483fc fix(localize): include the last placeholder in parsed translation text (#38452)
When creating a `ParsedTranslation` from a set of message parts and
placeholder names a textual representation of the message is computed.
Previously the last placeholder and text segment were missing from this
computed message string.

PR Close #38452
2020-08-19 14:16:45 -07:00
d662a6449e refactor(compiler-cli): add getTemplateOfComponent to TemplateTypeChecker (#38355)
This commit adds a `getTemplateOfComponent` method to the
`TemplateTypeChecker` API, which retrieves the actual nodes parsed and used
by the compiler for template type-checking. This is advantageous for the
language service, which may need to query other APIs in
`TemplateTypeChecker` that require the same nodes used to bind the template
while generating the TCB.

Fixes #38352

PR Close #38355
2020-08-19 14:07:07 -07:00
e57a2b3c47 docs: Typos fixes in the binding syntax guide (#38519)
PR Close #38519
2020-08-19 14:05:53 -07:00
a3f3082bf0 release: cut the v10.0.11 release 2020-08-19 09:05:11 -07:00
0570c240e4 docs: Fix typo in the inputs and outputs guide (#38524)
PR Close #38524
2020-08-19 08:27:48 -07:00
398118f708 feat(dev-infra): create a wizard for building commit messages (#38457)
Creates a wizard to walk through creating a commit message in the correct
template for commit messages in Angular repositories.

PR Close #38457
2020-08-18 17:01:18 -07:00
dcc3f6d74d feat(dev-infra): tooling to check out pending PR (#38474)
Creates a tool within ng-dev to checkout a pending PR from the upstream repository.  This automates
an action that many developers on the Angular team need to do periodically in the process of testing
and reviewing incoming PRs.

Example usage:
  ng-dev pr checkout <pr-number>

PR Close #38474
2020-08-18 16:22:52 -07:00
0af95332e9 fix(router): ensure routerLinkActive updates when associated routerLinks change (#38511)
This commit introduces a new subscription in the `routerLinkActive` directive which triggers an update
when any of its associated routerLinks have changes. `RouterLinkActive` not only needs to know when
links are added or removed, but it also needs to know about if a link it already knows about
changes in some way.

Quick note that `from...mergeAll` is used instead of just a simple
`merge` (or `scheduled...mergeAll`) to avoid introducing new rxjs
operators in order to keep bundle size down.

Fixes #18469

PR Close #38511
2020-08-18 10:21:55 -07:00
f9a76a7d06 Revert "fix(router): ensure routerLinkActive updates when associated routerLinks change (#38349)" (#38511)
This reverts commit e0e5c9f195.
Failures in Google tests were detected.

PR Close #38511
2020-08-18 10:21:52 -07:00
832a54ee42 docs(common): Wrong parameter description on TrackBy (#38495)
Track By Function receive the T[index] data, not the node id.
TrackByFunction reference description has the same issue.
PR Close #38495
2020-08-18 10:08:47 -07:00
e8f4294e7f refactor(localize): update yargs and typings for yargs (#38502)
Updating yargs and typings for the updated yargs module.

PR Close #38502
2020-08-18 10:06:32 -07:00
ce448f4341 refactor(ngcc): update yargs and typings for yargs (#38502)
Updating yargs and typings for the updated yargs module.

PR Close #38502
2020-08-18 10:06:29 -07:00
847eaa0fa3 refactor(dev-infra): update yargs and typings for yargs (#38502)
Updating yargs and typings for the updated yargs module.

PR Close #38502
2020-08-18 10:06:26 -07:00
3e80f0e526 release: cut the v10.0.10 release 2020-08-17 13:10:36 -07:00
f3dd6c224c fix(ngcc): detect synthesized delegate constructors for downleveled ES2015 classes (#38500)
Similarly to the change we landed in the `@angular/core` reflection
capabilities, we need to make sure that ngcc can detect pass-through
delegate constructors for classes using downleveled ES2015 output.

More details can be found in the preceding commit, and in the issue
outlining the problem: #38453.

Fixes #38453.

PR Close #38500
2020-08-17 12:50:19 -07:00
863acb6c21 fix(core): detect DI parameters in JIT mode for downleveled ES2015 classes (#38500)
In the Angular Package Format, we always shipped UMD bundles and previously even ES5 module output.
With V10, we removed the ES5 module output but kept the UMD ES5 output.

For this, we were able to remove our second TypeScript transpilation. Instead we started only
building ES2015 output and then downleveled it to ES5 UMD for the NPM packages. This worked
as expected but unveiled an issue in the `@angular/core` reflection capabilities.

In JIT mode, Angular determines constructor parameters (for DI) using the `ReflectionCapabilities`. The
reflection capabilities basically read runtime metadata of classes to determine the DI parameters. Such
metadata can be either stored in static class properties like `ctorParameters` or within TypeScript's `design:params`.

If Angular comes across a class that does not have any parameter metadata, it tries to detect if the
given class is actually delegating to an inherited class. It does this naively in JIT by checking if the
stringified class (function in ES5) matches a certain pattern. e.g.

```js
function MatTable() {
  var _this = _super.apply(this, arguments) || this;
```

These patterns are reluctant to changes of the class output. If a class is not recognized properly, the
DI parameters will be assumed empty and the class is **incorrectly** constructed without arguments.

This actually happened as part of v10 now. Since we downlevel ES2015 to ES5 (instead of previously
compiling sources directly to ES5), the class output changed slightly so that Angular no longer detects
it. e.g.

```js
var _this = _super.apply(this, __spread(arguments)) || this;
```

This happens because the ES2015 output will receive an auto-generated constructor if the class
defines class properties. This constructor is then already containing an explicit `super` call.

```js
export class MatTable extends CdkTable {
    constructor() {
        super(...arguments);
        this.disabled = true;
    }
}
```

If we then downlevel this file to ES5 with `--downlevelIteration`, TypeScript adjusts the `super` call so that
the spread operator is no longer used (not supported in ES5). The resulting super call is different to the
super call that would have been emitted if we would directly transpile to ES5. Ultimately, Angular no
longer detects such classes as having an delegate constructor -> and DI breaks.

We fix this by expanding the rather naive RegExp patterns used for the reflection capabilities
so that downleveled pass-through/delegate constructors are properly detected. There is a risk
of a false-positive as we cannot detect whether `__spread` is actually the TypeScript spread
helper, but given the reflection patterns already make lots of assumptions (e.g. that `super` is
actually the superclass, we should be fine making this assumption too. The false-positive would
not result in a broken app, but rather in unnecessary providers being injected (as a noop).

Fixes #38453

PR Close #38500
2020-08-17 12:50:16 -07:00
989e8a1f99 fix(router): ensure routerLinkActive updates when associated routerLinks change (#38349)
This commit introduces a new subscription in the `routerLinkActive` directive which triggers an update
when any of its associated routerLinks have changes. `RouterLinkActive` not only needs to know when
links are added or removed, but it also needs to know about if a link it already knows about
changes in some way.

Quick note that `from...mergeAll` is used instead of just a simple
`merge` (or `scheduled...mergeAll`) to avoid introducing new rxjs
operators in order to keep bundle size down.

Fixes #18469

PR Close #38349
2020-08-17 12:34:02 -07:00
84d1ba792b refactor(language-service): [Ivy] remove temporary compiler (#38310)
Now that Ivy compiler has a proper `TemplateTypeChecker` interface
(see https://github.com/angular/angular/pull/38105) we no longer need to
keep the temporary compiler implementation.

The temporary compiler was created to enable testing infrastructure to
be developed for the Ivy language service.

This commit removes the whole `ivy/compiler` directory and moves two
functions `createTypeCheckingProgramStrategy` and
`getOrCreateTypeCheckScriptInfo` to the `LanguageService` class.

Also re-enable the Ivy LS test since it's no longer blocking development.

PR Close #38310
2020-08-17 11:30:36 -07:00
b32126c335 fix(common): Allow scrolling when browser supports scrollTo (#38468)
This commit fixes a regression from "fix(common): ensure
scrollRestoration is writable (#30630)" that caused scrolling to not
happen at all in browsers that do not support scroll restoration. The
issue was that `supportScrollRestoration` was updated to return `false`
if a browser did not have a writable `scrollRestoration`. However, the
previous behavior was that the function would return `true` if
`window.scrollTo` was defined. Every scrolling function in the
`ViewportScroller` used `supportScrollRestoration` and, with the update
in bb88c9fa3d, no scrolling would be
performed if a browser did not have writable `scrollRestoration` but
_did_ have `window.scrollTo`.

Note, that this failure was detected in the saucelabs tests. IE does not
support scroll restoration so IE tests were failing.

PR Close #38468
2020-08-14 11:41:26 -07:00
d5e09f4d62 fix(core): fix multiple nested views removal from ViewContainerRef (#38317)
When removal of one view causes removal of another one from the same
ViewContainerRef it triggers an error with views length calculation. This commit
fixes this bug by removing a view from the list of available views before invoking
actual view removal (which might be recursive and relies on the length of the list
of available views).

Fixes #38201.

PR Close #38317
2020-08-13 13:35:56 -07:00
Ahn
c25c57c3a3 style(compiler-cli): remove unused constant (#38441)
Remove unused constant allDiagnostics

PR Close #38441
2020-08-13 13:32:44 -07:00
692e34d4a2 test(docs-infra): remove deprecated ReflectiveInjector (#38408)
This commit replaces the old and slow `ReflectiveInjector` that was
deprecated in v5 with the new `Injector`. Note: This change was only
done in the spec files inside the `aio` folder.

While changing this, it was not possible to directly use `Injector.get`
to get the correct typing for the mocked classes. For example:

```typescript
locationService = injector.get<TestLocationService>(LocationService);
```

Fails with:

> Argument of type 'typeof LocationService' is not assignable to parameter
of type 'Type<TestLocationService> | InjectionToken<TestLocationService> |
AbstractType<TestLocationService>'.
  Type 'typeof LocationService' is not assignable to type 'Type<TestLocationService>'.
    Property 'searchResult' is missing in type 'LocationService' but required in type
    'TestLocationService'.

Therefore, it was necessary to first convert to `unknown` and then to
`TestLocationService`.

```typescript
locationService = injector.get(LocationService) as unknown as TestLocationService;
```

PR Close #38408
2020-08-13 12:56:17 -07:00
071348eb72 build: run browsers tests on chromium locally (#38435) (#38450)
Previously we added a browser target for `firefox` into the
dev-infra package. It looks like as part of this change, we
accidentally switched the local web testing target to `firefox`.

Web tests are not commonly run locally as we use Domino and
NodeJS tests for primary development. Sometimes though we intend
to run tests in a browser. This would currently work with Firefox
but not on Windows (as Firefox is a noop there in Bazel).

This commit switches the primary browser back to `chromium`. Also
Firefox has been added as a second browser to web testing targets.

This allows us to reduce browsers in the legacy Saucelabs job. i.e.
not running Chrome and Firefox there. This should increase stability
and speed up the legacy job (+ reduced rate limit for Saucelabs).

PR Close #38435

PR Close #38450
2020-08-13 12:55:16 -07:00
8803f9f4dc ci: disable saucelabs tests on Firefox ESR while investigating failures (#37647) (#38450)
Firefox ESR tests fail running the acceptance tests on saucelabs.  These tests are being
disabled while investigating the failure as it is not entirely clear whether this is
saucelabs failure or a something like a memory pressure error in the test itself.

PR Close #37647

PR Close #38450
2020-08-13 12:55:13 -07:00
aa816d3887 ci: disable closure size tracking test (#38449)
We should define ngDevMode to false in Closure, but --define only works in the global scope.
With ngDevMode not being set to false, this size tracking test provides little value but a lot of
headache to continue updating the size.

PR Close #38449
2020-08-13 11:41:16 -07:00
a5ba40a78b refactor(router): Add annotations to correct Router documentation (#38448)
The `@HostListener` functions and lifecycle hooks aren't intended to be public API but
do need to appear in the `.d.ts` files or type checking will break. Adding the
nodoc annotation will correctly hide this function on the docs site.

Again, note that `@internal` cannot be used because the result would be
that the functions then do not appear in the `.d.ts` files. This would
break lifecycle hooks because the class would be seen as not
implementing the interface correctly. This would also break
`HostListener` because the compiled templates would attempt to call the
`onClick` functions, but those would also not appear in the `d.ts` and
would produce errors like "Property 'onClick' does not exist on type 'RouterLinkWithHref'".

PR Close #38448
2020-08-13 11:36:13 -07:00
cb83b8a887 fix(core): error if CSS custom property in host binding has number in name (#38432)
Fixes an error if a CSS custom property, used inside a host binding, has a
number in its name. The error is thrown because the styling parser only
expects characters from A to Z,dashes, underscores and a handful of other
characters.

Fixes #37292.

PR Close #38432
2020-08-13 10:35:11 -07:00
8bb726e899 build: update ng-dev config file for new commit message configuration (#38430)
Removes the commit message types from the config as they are now staticly
defined in the dev-infra code.

PR Close #38430
2020-08-13 09:11:25 -07:00
88662a540d feat(dev-infra): migrate to unified commit message types in commit message linting (#38430)
Previously commit message types were provided as part of the ng-dev config in the repository
using the ng-dev toolset.  This change removes this configuration expectation and instead
predefines the valid types for commit messages.

Additionally, with this new unified set of types requirements around providing a scope have
been put in place.  Scopes are either required, optional or forbidden for a given commit
type.

PR Close #38430
2020-08-13 09:11:23 -07:00
9b32a5917c feat(dev-infra): update to latest benchpress version (#38440)
We recently updated the benchpress package to have a more loose
Angular core peer dependency, and less other unused dependencies.

We should make sure to use that in the dev-infra package so that
peer dependencies can be satisified in consumer projects, and so
that less unused dependencies are brought into projects.

PR Close #38440
2020-08-13 09:09:34 -07:00
ffd1691ba9 feat(dev-infra): save invalid commit message attempts to be restored on next commit attempt (#38304)
When a commit message fails validation, rather than throwing out the commit message entirely
the commit message is saved into a draft file and restored on the next commit attempt.

PR Close #38304
2020-08-13 08:45:28 -07:00
afd4417a7b refactor(dev-infra): extract the commit message parsing function into its own file (#38429)
Extracts the commit message parsing function into its own file.

PR Close #38429
2020-08-12 16:10:08 -07:00
2f53bbba20 docs: remove unused Input decorator (#38306)
In the part "5. Add In-app Navigation" of the tutorial it was already removed
PR Close #38306
2020-08-12 11:26:11 -07:00
a4f99f4de9 refactor(dev-infra): use promptConfirm util in ng-dev's formatter (#38419)
Use the promptConfirm util instead of manually creating a confirm prompt with
inquirer.

PR Close #38419
2020-08-12 11:25:12 -07:00
0711128d28 docs: update web-worker CLI commands to bash style (#38421)
With this change we update the CLI generate commands to be in bash style.

PR Close #38421
2020-08-12 11:24:36 -07:00
25f79ba73e release: cut the v10.0.9 release 2020-08-12 09:35:37 -07:00
2590a9b535 docs: delete one superfluous sentence (#38339)
PR Close #38339
2020-08-12 08:23:19 -07:00
f68a1d3708 build: run formatting automatically on pre-commit hook (#38402)
Runs the `ng-dev format changed` command whenever the `git commit` command is
run.  As all changes which are checked by CI will require this check passing, this
change can prevent needless roundtrips to correct lint/formatting errors. This
automatic formatting can be bypassed with the `--no-verify` flag on the `git commit`
command.

PR Close #38402
2020-08-11 16:32:55 -07:00
89f7f63e18 feat(dev-infra): Add support for formatting all staged files (#38402)
Adds an ng-dev formatter option to format all of the staged files. This will can
be used to format only the staged files during the pre-commit hook.

PR Close #38402
2020-08-11 16:32:55 -07:00
ca2b4bcd4b fix(compiler-cli): avoid creating value expressions for symbols from type-only imports (#38415)
In TypeScript 3.8 support was added for type-only imports, which only brings in
the symbol as a type, not their value. The Angular compiler did not yet take
the type-only keyword into account when representing symbols in type positions
as value expressions. The class metadata that the compiler emits would include
the value expression for its parameter types, generating actual imports as
necessary. For type-only imports this should not be done, as it introduces an
actual import of the module that was originally just a type-only import.

This commit lets the compiler deal with type-only imports specially, preventing
a value expression from being created.

Backports #37912 to patch

PR Close #38415
2020-08-11 12:31:58 -07:00
f6cfc9294b ci: update payload size limits for Closure tests (#38411)
Currently the Closure-related tests are not tree-shaking the dev-mode-only content, thus payload
size checks are failing even if dev-mode-only content is added.
The 2e9fdbde9e commit
added some logic to JIT compiler, which is likely triggered the payload size increase. This commit
updates the payload size limits for Closure-related test to get master and patch branches back to
the "green" state.

PR Close #38411
2020-08-11 10:59:40 -07:00
5b87c67aed fix(compiler-cli): infer quote expressions as any type in type checker (#37917)
"Quote expressions" are expressions that start with an identifier followed by a
comma, allowing arbitrary syntax to follow. These kinds of expressions would
throw a an error in the template type checker, which would make them hard to
track down. As quote expressions are not generally used at all, the error would
typically occur for URLs that would inadvertently occur in a binding:

```html
<a [href]="https://example.com"></a>
```

This commit lets such bindings be inferred as the `any` type.

Fixes #36568
Resolves FW-2051

PR Close #37917
2020-08-11 09:54:54 -07:00
f5b9d87913 fix(compiler): evaluate safe navigation expressions in correct binding order (#37911)
When using the safe navigation operator in a binding expression, a temporary
variable may be used for storing the result of a side-effectful call.
For example, the following template uses a pipe and a safe property access:

```html
<app-person-view [enabled]="enabled" [firstName]="(person$ | async)?.name"></app-person-view>
```

The result of the pipe evaluation is stored in a temporary to be able to check
whether it is present. The temporary variable needs to be declared in a separate
statement and this would also cause the full expression itself to be pulled out
into a separate statement. This would compile into the following
pseudo-code instructions:

```js
var temp = null;
var firstName = (temp = pipe('async', ctx.person$)) == null ? null : temp.name;
property('enabled', ctx.enabled)('firstName', firstName);
```

Notice that the pipe evaluation happens before evaluating the `enabled` binding,
such that the runtime's internal binding index would correspond with `enabled`,
not `firstName`. This introduces a problem when the pipe uses `WrappedValue` to
force a change to be detected, as the runtime would then mark the binding slot
corresponding with `enabled` as dirty, instead of `firstName`. This results
in the `enabled` binding to be updated, triggering setters and affecting how
`OnChanges` is called.

In the pseudo-code above, the intermediate `firstName` variable is not strictly
necessary---it only improved readability a bit---and emitting it inline with
the binding itself avoids the out-of-order execution of the pipe:

```js
var temp = null;
property('enabled', ctx.enabled)
  ('firstName', (temp = pipe('async', ctx.person$)) == null ? null : temp.name);
```

This commit introduces a new `BindingForm` that results in the above code to be
generated and adds compiler and acceptance tests to verify the proper behavior.

Fixes #37194

PR Close #37911
2020-08-11 09:51:11 -07:00
3acebdcf5d fix(core): prevent NgModule scope being overwritten in JIT compiler (#37795)
In JIT compiled apps, component definitions are compiled upon first
access. For a component class `A` that extends component class `B`, the
`B` component is also compiled when the `InheritDefinitionFeature` runs
during the compilation of `A` before it has finalized. A problem arises
when the compilation of `B` would flush the NgModule scoping queue,
where the NgModule declaring `A` is still pending. The scope information
would be applied to the definition of `A`, but its compilation is still
in progress so requesting the component definition would compile `A`
again from scratch. This "inner compilation" is correctly assigned the
NgModule scope, but once the "outer compilation" of `A` finishes it
would overwrite the inner compilation's definition, losing the NgModule
scope information.

In summary, flushing the NgModule scope queue could trigger a reentrant
compilation, where JIT compilation is non-reentrant. To avoid the
reentrant compilation, a compilation depth counter is introduced to
avoid flushing the NgModule scope during nested compilations.

Fixes #37105

PR Close #37795
2020-08-11 09:50:28 -07:00
02aca135bd fix(dev-infra): update i18n-related file locations in PullApprove config (#38403)
The changes in https://github.com/angular/angular/pull/38368 split `render3/i18n.ts` files into
smaller scripts, but the PullApprove config was not updated to reflect that. This commit updates
the PullApprove config to reflect the recent changes in i18n-related files.

PR Close #38403
2020-08-10 17:29:52 -07:00
32109dc414 fix(core): queries not matching string injection tokens (#38321)
Queries weren't matching directives that provide themselves via string
injection tokens, because the assumption was that any string passed to
a query decorator refers to a template reference.

These changes make it so we match both template references and
providers while giving precedence to the template references.

Fixes #38313.
Fixes #38315.

PR Close #38321
2020-08-10 15:27:25 -07:00
c9acb7b7e2 fix(compiler-cli): preserve quotes in class member names (#38387)
When we were outputting class members for `setClassMetadata` calls,
we were using the string representation of the member name. This can
lead to us generating invalid code when the name contains dashes and
is quoted (e.g. `@Output() 'has-dashes' = new EventEmitter()`), because
the quotes will be stripped for the string representation.

These changes fix the issue by using the original name AST node that was
used for the declaration and which knows whether it's supposed to be
quoted or not.

Fixes #38311.

PR Close #38387
2020-08-10 15:26:46 -07:00
545444d86f refactor(core): break i18n.ts into smaller files (#38368)
This commit contains no changes to code. It only breaks `i18n.ts` file
into `i18n.ts` + `i18n_apply.ts` + `i18n_parse.ts` +
`i18n_postprocess.ts` for easier maintenance.

PR Close #38368
2020-08-10 15:07:44 -07:00
7f111491a8 fix(platform-server): remove styles added by ServerStylesHost on destruction (#38367)
When a ServerStylesHost instance is destroyed, all of the shared styles added to the DOM
head element by that instance should be removed.  Without this removal, over time a large
number of style rules will build up and cause extra memory pressure.  This brings the
ServerStylesHost in line with the DomStylesHost used by the platform browser, which
performs this same cleanup.

PR Close #38367
2020-08-10 13:12:24 -07:00
ee5123f5dc fix(core): Store the currently selected ICU in LView (#38345)
The currently selected ICU was incorrectly being stored it `TNode`
rather than in `LView`.

Remove: `TIcuContainerNode.activeCaseIndex`
Add: `LView[TIcu.currentCaseIndex]`

PR Close #38345
2020-08-10 12:41:18 -07:00
3dfaf56fdd docs: Remove redundant sentence from Router (#38398)
PR Close #38398
2020-08-10 09:52:31 -07:00
432a688edf docs: update team contributors page (#38384)
Removing Kara, Denny, Judy, Tony, Matias as they are no longer actively working on the project
PR Close #38384
2020-08-10 09:51:51 -07:00
42a649d80f docs: fix purpose description of "builders.json" (#36830)
PR Close #36830
2020-08-07 15:04:57 -07:00
47fbfb37bf refactor(forms): get rid of duplicate functions (#38371) (#38380)
This commit performs minor refactoring in Forms package to get rid of duplicate functions.
It looks like the functions were duplicated due to a slightly different type signatures, but
their logic is completely identical. The logic in retained functions remains the same and now
these function also accept a generic type to achieve the same level of type safety.

PR Close #38380
2020-08-07 12:11:37 -07:00
4107abba2d refactor(common): use getElementById in ViewportScroller.scrollToAnchor (#38372)
This commit uses getElementById and getElementsByName when an anchor scroll happens,
to avoid escaping the anchor and wrapping the code in a try/catch block.

Related to #28960

PR Close #38372
2020-08-07 11:17:53 -07:00
1d26bc544e test: update components repo to test against recent revision (#38273) (#38378)
The changes in angular/components#20136 are required to allow the
framework tests to succeed.

PR Close #38273

PR Close #38378
2020-08-07 11:12:49 -07:00
ef7b70a375 refactor(core): add debug ranges to LViewDebug with matchers (#38359)
This change provides better typing for the `LView.debug` property which
is intended to be used by humans while debugging the application with
`ngDevMode` turned on.

In addition this chang also adds jasmine matchers for better asserting
that `LView` is in the correct state.

PR Close #38359
2020-08-06 16:58:12 -07:00
deb290bc2c refactor(core): extract icuSwitchCase, icuUpdateCase, removeNestedIcu (#38154) (#38370)
Extract `icuSwitchCase`, `icuUpdateCase`, `removeNestedIcu` into
separate functions to align them with the `.debug` property text.

PR Close #38154

PR Close #38370
2020-08-06 16:27:28 -07:00
5c8b7d2c64 refactor(core): add human readable debug for i18n (#38154) (#38370)
I18n code breaks up internationalization into opCodes which are then stored
in arrays. To make it easier to debug the codebase this PR adds `debug`
property to the arrays which presents the data in human readable format.

PR Close #38154

PR Close #38370
2020-08-06 16:27:27 -07:00
f5d5bac076 fix(service-worker): fix the chrome debugger syntax highlighter (#38332)
The Chrome debugger is not able to render the syntax properly when the
code contains backticks. This is a known issue in Chrome and they have an
open [issue](https://bugs.chromium.org/p/chromium/issues/detail?id=659515) for that.
This commit adds the work-around to use double backslash with one
backtick ``\\` `` at the end of the line.

This can be reproduced by running the following command:

`yarn bazel test //packages/forms/test --config=debug`

When opening the chrome debugger tools, you should see the correct
code highlighting syntax.

PR Close #38332
2020-08-06 15:22:57 -07:00
ad16e2f97a docs(service-worker): describe how asset-/data-group order affects request handling (#38364)
The order of asset- and data-groups in `ngsw-config.json` affects how a
request is handled by the ServiceWorker. Previously, this was not
clearly documented.

This commit describes how the order of asset-/data-groups affects
request handling.

Closes #21189

PR Close #38364
2020-08-06 11:41:59 -07:00
d0191a7397 fix(docs-infra): correctly generate CLI commands docs when the overview page moves (#38365)
Previously, the [processCliCommands][1] dgeni processor, which is used
to generate the docs pages for the CLI commands, expected the CLI
commands overview page (with a URL of `cli`) to exist as a child of a
top-level navigation section (`CLI Commands`). If one tried to move the
`CLI Commands` section inside another section, `processCliCommnads`
would fail to find it and thus fail to generate the CLI commands docs.
This problem came up in #38353.

This commit updates the `processCliCommands` processor to be able to
find it regardless of the position of the `CLI Commands` section inside
the navigation doc.

[1]:
dca4443a8e/aio/tools/transforms/cli-docs-package/processors/processCliCommands.js (L7-L9)

PR Close #38365
2020-08-06 11:41:07 -07:00
016a41b14d fix(compiler-cli): mark eager NgModuleFactory construction as not side effectful (#38320)
Roll forward of #38147.

This allows Closure compiler to tree shake unused constructor calls to `NgModuleFactory`, which is otherwise considered
side-effectful. The Angular compiler generates factory objects which are exported but typically not used, as they are
only needed for compatibility with View Engine. This results in top-level constructor calls, such as:

```typescript
export const FooNgFactory = new NgModuleFactory(Foo);
```

`NgModuleFactory` has a side-effecting constructor, so this statement cannot be tree shaken, even if `FooNgFactory` is
never imported. The `NgModuleFactory` continues to reference its associated `NgModule` and prevents the module and all
its unused dependencies from being tree shaken, making Closure builds significantly larger than necessary.

The fix here is to wrap `NgModuleFactory` constructor with `noSideEffects(() => /* ... */)`, which tricks the Closure
compiler into assuming that the invoked function has no side effects. This allows it to tree-shake unused
`NgModuleFactory()` constructors when they aren't imported. Since the factory can be removed, the module can also be
removed (if nothing else references it), thus tree shaking unused dependencies as expected.

The one notable edge case is for lazy loaded modules. Internally, lazy loading is done as a side effect when the lazy
script is evaluated. For Angular, this side effect is registering the `NgModule`. In Ivy this is done by the
`NgModuleFactory` constructor, so lazy loaded modules **cannot** have their top-level `NgModuleFactory` constructor
call tree shaken. We handle this case by looking for the `id` field on `@NgModule` annotations. All lazy loaded modules
include an `id`. When this `id` is found, the `NgModuleFactory` is generated **without** with `noSideEffects()` call,
so Closure will not tree shake it and the module will lazy-load correctly.

PR Close #38320
2020-08-06 09:02:17 -07:00
7a527b4f95 refactor(compiler): add ModuleInfo interface (#38320)
This introduces a new `ModuleInfo` interface to represent some of the statically analyzed data from an `NgModule`. This
gets passed into transforms to give them more context around a given `NgModule` in the compilation.

PR Close #38320
2020-08-06 09:02:17 -07:00
fd28f0c219 refactor(core): add noSideEffects() as private export (#38320)
This is to enable the compiler to generate `noSideEffects()` calls. This is a private export, gated by `ɵ`.

PR Close #38320
2020-08-06 09:02:17 -07:00
7342b1ecdf docs: remove https://angular.io from internal links (#38360)
PR #36601 introduces icons on all links if the link contains
https:// or http:// but there were some internal links left
which contained https://angular.io. Removed https://angular.io
from all these links.

PR Close #38360
2020-08-06 09:01:35 -07:00
58f4b3ad80 fix(common): ensure scrollRestoration is writable (#30630) (#38357)
Some specialised browsers that do not support scroll restoration
(e.g. some web crawlers) do not allow `scrollRestoration` to be
writable.

We already sniff the browser to see if it has the `window.scrollTo`
method, so now we also check whether `window.history.scrollRestoration`
is writable too.

Fixes #30629

PR Close #30630

PR Close #38357
2020-08-05 17:49:56 -07:00
3974312c3a style(docs-infra): reformat ScrollService file (#30630) (#38357)
Pre-empting code formatting changes when the
code is updated in a subsequent commit.

PR Close #30630

PR Close #38357
2020-08-05 17:49:55 -07:00
db897f4f7a docs: add a page with the Angular roadmap (#38356)
PR Close #38356
2020-08-05 17:16:38 -07:00
77093c21b7 refactor(platform-browser): specify return type of parseEventName (#38089)
This commit refactors the argument of the `parseEventName` function
to use an object with named properties instead of using an object indexer.

PR Close #38089
2020-08-05 17:06:29 -07:00
415131413d fix(router): prevent calling unsubscribe on undefined subscription in RouterPreloader (#38344)
Previously, the `ngOnDestroy` method called `unsubscribe` regardless of if `subscription` had
been initialized.  This can lead to an error attempting to call `unsubscribe` of undefined.
This change prevents this error, and instead only attempts `unsubscribe` when the subscription
has been defined.

PR Close #38344
2020-08-05 10:54:42 -07:00
df01a82fa2 fix(compiler-cli): match wrapHost parameter types within plugin interface (#38004)
The `TscPlugin` interface using a type of `ts.CompilerHost&Partial<UnifiedModulesHost>` for the `host` parameter
of the `wrapHost` method. However, prior to this change, the interface implementing `NgTscPlugin` class used a
type of `ts.CompilerHost&UnifiedModulesHost` for the parameter. This change corrects the inconsistency and
allows `UnifiedModulesHost` members to be optional when using the `NgtscPlugin`.

PR Close #38004
2020-08-05 10:54:08 -07:00
1912fa34ba feat(dev-infra): provide organization-wide merge-tool label configuration (#38223)
Previously, each Angular repository had its own strategy/configuration
for merging pull requests and cherry-picking. We worked out a new
strategy for labeling/branching/versioning that should be the canonical
strategy for all actively maintained projects in the Angular organization.

This PR provides a `ng-dev` merge configuration that implements the
labeling/branching/merging as per the approved proposal.

See the following document for the proposal this commit is based on
for the merge script labeling/branching: https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU

The merge tool label configuration can be conveniently accesed
within each `.ng-dev` configuration, and can also be extended
if there are special labels on individual projects. This is one
of the reasons why the labels are not directly built into the
merge script. The script should remain unopinionated and flexible.

The configuration is conceptually powerful enough to achieve the
procedures as outlined in the versioning/branching/labeling proposal.

PR Close #38223
2020-08-05 10:53:18 -07:00
db5e1de07a feat(dev-infra): support user-failures when computing branches for target label (#38223)
The merge tool provides a way for configurations to determine the branches
for a label lazily. This is supported because it allows labels to respect
the currently selected base branch through the Github UI. e.g. if `target: label`
is applied on a PR and the PR is based on the patch branch, then the change
could only go into the selected target branch, while if it would be based on
`master`, the change would be cherry-picked to `master` too. This allows for
convenient back-porting of changes if they did not apply cleanly to the primary
development branch (`master`).

We want to expand this function so that it is possible to report failures if an
invalid target label is appplied (e.g. `target: major` not allowed in
some situations), or if the Github base branch is not valid for the given target
label (e.g. if `target: lts` is used, but it's not based on a LTS branch).

PR Close #38223
2020-08-05 10:53:18 -07:00
84661eac64 feat(dev-infra): provide github API instance to lazy merge configuration (#38223)
The merge script currently accepts a configuration function that will
be invoked _only_ when the `ng-dev merge` command is executed. This
has been done that way because the merge tooling usually relies on
external requests to Git or NPM for constructing the branch configurations.

We do not want to perform these slow external queries on any `ng-dev` command
though, so this became a lazily invoked function.

This commit adds support for these configuration functions to run
asynchronously (by returning a Promise that will be awaited), so that
requests could also be made to the Github API. This is benefical as it
could avoid dependence on the local Git state and the HTTP requests
are more powerful/faster.

Additionally, in order to be able to perform Github API requests
with an authenticated instance, the merge tool will pass through
a `GithubClient` instance that uses the specified `--github-token`
(or from the environment). This ensures that all API requests use
the same `GithubClient` instance and can be authenticated (mitigating
potential rate limits).

PR Close #38223
2020-08-05 10:53:18 -07:00
629203f17a docs: fix typo in glossary (#38318)
Add missing comma  in structural directive section that made dash display incorrectly.

PR Close #38318
2020-08-05 10:52:28 -07:00
2ab8e88924 release: cut the v10.0.8 release 2020-08-04 15:51:57 -07:00
a91dd2e42a build: cleanup .bazelrc file to no longer set unused flags (#38124)
This option is no longer needed in Bazel and will be an error in the future

PR Close #38124
2020-08-03 12:53:11 -07:00
6eca80b4aa revert: docs(core): correct SomeService to SomeComponent (#38325)
This reverts commit b4449e35bf.

The example given from the previous change was for a component selector and not a provider selector.

This change fixes it.

Fixes #38323.

PR Close #38325
2020-08-03 12:52:20 -07:00
cea46786a2 fix(compiler): update unparsable character reference entity error messages (#38319)
Within an angular template, when a character entity is unable to be parsed, previously a generic
unexpected character error was thrown.  This does not properly express the issue that was discovered
as the issue is actually caused by the discovered character making the whole of the entity unparsable.
The compiler will now instead inform via the error message what string was attempted to be parsed
and what it was attempted to be parsed as.

Example, for this template:
```
<p>
  &#x123p
</p>
```
Before this change:
`Unexpected character "p"`

After this change:
`Unable to parse entity "&#x123p" - hexadecimal character reference entities must end with ";"`

Fixes #26067

PR Close #38319
2020-07-31 15:32:54 -07:00
9c5fc1693a refactor(docs-infra): update docs examples tslint.json to match CLI and fix failures (#38143)
This commit updates the `tslint.json` configuration file, that is used
to lint the docs examples, to match the one generated for new Angular
CLI apps. There are some minimal differences (marked with `TODO`
comments) for things, such as component selector prefix, that would
require extensive and/or difficult to validate changes in guides.

This commit also includes the final adjustments to make the docs
examples code compatible with the new tslint rules. (The bulk of the
work has been done in previous commits.)

PR Close #38143
2020-07-31 11:00:08 -07:00
42f5770b7b refactor(docs-infra): fix docs examples for Angular-specific tslint rules (#38143)
This commit updates the docs examples to be compatible with the
following Angular-specific tslint rules:
- `component-selector`
- `directive-selector`
- `no-conflicting-lifecycle`
- `no-host-metadata-property`
- `no-input-rename`
- `no-output-native`
- `no-output-rename`

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
01db37435f refactor(docs-infra): fix docs examples for tslint rule prefer-const (#38143)
This commit updates the docs examples to be compatible with the
`prefer-const` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
07a003352d refactor(docs-infra): fix docs examples for tslint rules related to underscores in variable names (#38143)
This commit updates the docs examples to be compatible with the
`variable-name` tslint rule without requiring the
`allow-leading-underscore` and `allow-trailing-underscore` options.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
ac009d293d refactor(docs-infra): fix docs examples for tslint rules related to variable names (#38143)
This commit updates the docs examples to be compatible with the
`no-shadowed-variable` and `variable-name` tslint rules.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
eb8f8c93c8 refactor(docs-infra): fix docs examples for tslint rule member-ordering (#38143)
This commit updates the docs examples to be compatible with the
`member-ordering` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
18a5117d1e refactor(docs-infra): fix docs examples for tslint rule no-angle-bracket-type-assertion (#38143)
This commit updates the docs examples to be compatible with the
`no-angle-bracket-type-assertion` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
ff72da60d3 refactor(docs-infra): fix docs examples for tslint rules related to object properties (#38143)
This commit updates the docs examples to be compatible with the
`no-string-literal`, `object-literal-key-quotes` and
`object-literal-shorthand` tslint rules.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
d4a723a464 refactor(docs-infra): fix docs examples for tslint rule only-arrow-functions (#38143)
This commit updates the docs examples to be compatible with the
`only-arrow-functions` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:08 -07:00
9c2f0b8ac4 style(docs-infra): fix docs examples for tslint rule jsdoc-format (#38143)
This commit updates the docs examples to be compatible with the
`jsdoc-format` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:07 -07:00
dc081fb0bf style(docs-infra): fix docs examples for tslint rule semicolon (#38143)
This commit updates the docs examples to be compatible with the
`semicolon` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:07 -07:00
f882099f9d style(docs-infra): fix docs examples for tslint rules related to whitespace (#38143)
This commit updates the docs examples to be compatible with the `align`,
`space-before-function-paren` and `typedef-whitespace` tslint rules.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:07 -07:00
24498eb416 style(docs-infra): fix docs examples for tslint rule import-spacing (#38143)
This commit updates the docs examples to be compatible with the
`import-spacing` tslint rule.

This is in preparation of updating the docs examples `tslint.json` to
match the one generated for new Angular CLI apps in a future commit.

PR Close #38143
2020-07-31 11:00:07 -07:00
4c2cdc682b refactor(docs-infra): remove unnecessary use strict from docs examples TS files (#38143)
By default, TypeScript will emit `"use strict"` directives, so it is not
necessary to include `'use strict'` in `.ts` files:
https://www.typescriptlang.org/docs/handbook/compiler-options.html#:~:text=--noImplicitUseStrict

PR Close #38143
2020-07-31 11:00:07 -07:00
1cb66bb39f refactor(docs-infra): remove unused styleguide examples (#38143)
The `03-*` code style rule have been removed from the style guide in
be0bc799f3.

This commit removes the corresponding files and related unused code from
the`styleguide` example project.

PR Close #38143
2020-07-31 11:00:07 -07:00
7ff5ef27c7 fix(language-service): [ivy] do not retrieve ts.Program at startup (#38120)
This commit removes compiler instantiation at startup.
This is because the constructor is invoked during the plugin loading phase,
in which the project has not been completely loaded.

Retrieving `ts.Program` at startup will trigger an `updateGraph` operation,
which could only be called after the Project has loaded completely.
Without this change, the Ivy LS cannot be loaded as a tsserver plugin.

Note that the whole `Compiler` class is temporary, so changes made there are
only for development. Once we have proper integration with ngtsc the
`Compiler` class would be removed.

PR Close #38120
2020-07-30 16:54:20 -07:00
03d8e317c4 fix(compiler): add PURE annotation to getInheritedFactory calls (#38291)
Currently the `getInheritedFactory` function is implemented to allow
closure to remove the call if the base factory is unused.  However, this
method does not work with terser.  By adding the PURE annotation,
terser will also be able to remove the call when unused.

PR Close #38291
2020-07-30 16:53:52 -07:00
677 changed files with 13020 additions and 6790 deletions

View File

@ -136,15 +136,6 @@ build:remote --remote_executor=remotebuildexecution.googleapis.com
# retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times. # retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times.
test:saucelabs --flaky_test_attempts=1 test:saucelabs --flaky_test_attempts=1
###############################
# NodeJS rules settings
# These settings are required for rules_nodejs
###############################
# Turn on managed directories feature in Bazel
# This allows us to avoid installing a second copy of node_modules
common --experimental_allow_incremental_repository_updates
#################################################### ####################################################
# User bazel configuration # User bazel configuration
# NOTE: This needs to be the *last* entry in the config. # NOTE: This needs to be the *last* entry in the config.

View File

@ -32,8 +32,8 @@ var_4_win: &cache_key_win_fallback v7-angular-win-node-12-{{ checksum ".bazelver
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the # Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable. # cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
var_5: &components_repo_unit_tests_cache_key v7-angular-components-f428c00465dfcf8a020237f22532480eedbd2cb6 var_5: &components_repo_unit_tests_cache_key v9-angular-components-09e68db8ed5b1253f2fe38ff954ef0df019fc25a
var_6: &components_repo_unit_tests_cache_key_fallback v7-angular-components- var_6: &components_repo_unit_tests_cache_key_fallback v9-angular-components-
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and # Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
# `build-ivy-npm-packages`. # `build-ivy-npm-packages`.
@ -656,6 +656,18 @@ jobs:
- run: yarn tsc -p packages - run: yarn tsc -p packages
- run: yarn tsc -p modules - run: yarn tsc -p modules
- run: yarn bazel build //packages/zone.js:npm_package - run: yarn bazel build //packages/zone.js:npm_package
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- run:
name: Preparing Bazel-generated fixtures required in legacy tests
command: |
yarn bazel build //packages/core/test:downleveled_es5_fixture
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
dist/all/@angular/core/test/reflection/es5_downleveled_inheritance_fixture.js
- run: - run:
# Waiting on ready ensures that we don't run tests too early without Saucelabs not being ready. # Waiting on ready ensures that we don't run tests too early without Saucelabs not being ready.
name: Waiting for Saucelabs tunnel to connect name: Waiting for Saucelabs tunnel to connect

View File

@ -74,7 +74,7 @@ setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git" setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
setPublicVar COMPONENTS_REPO_BRANCH "master" setPublicVar COMPONENTS_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`. # **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
setPublicVar COMPONENTS_REPO_COMMIT "f428c00465dfcf8a020237f22532480eedbd2cb6" setPublicVar COMPONENTS_REPO_COMMIT "09e68db8ed5b1253f2fe38ff954ef0df019fc25a"
#################################################################################################### ####################################################################################################

View File

@ -7,18 +7,6 @@ export const commitMessage: CommitMessageConfig = {
maxLineLength: 120, maxLineLength: 120,
minBodyLength: 20, minBodyLength: 20,
minBodyLengthTypeExcludes: ['docs'], minBodyLengthTypeExcludes: ['docs'],
types: [
'build',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'release',
'style',
'test',
],
scopes: [ scopes: [
'animations', 'animations',
'bazel', 'bazel',

View File

@ -505,8 +505,8 @@ groups:
- > - >
contains_any_globs(files, [ contains_any_globs(files, [
'packages/core/src/i18n/**', 'packages/core/src/i18n/**',
'packages/core/src/render3/i18n.ts', 'packages/core/src/render3/i18n/**',
'packages/core/src/render3/i18n.md', 'packages/core/src/render3/instructions/i18n.ts',
'packages/core/src/render3/interfaces/i18n.ts', 'packages/core/src/render3/interfaces/i18n.ts',
'packages/common/locales/**', 'packages/common/locales/**',
'packages/common/src/i18n/**', 'packages/common/src/i18n/**',
@ -863,6 +863,7 @@ groups:
- *can-be-global-docs-approved - *can-be-global-docs-approved
- > - >
contains_any_globs(files, [ contains_any_globs(files, [
'aio/content/guide/roadmap.md',
'aio/content/marketing/**', 'aio/content/marketing/**',
'aio/content/images/bios/**', 'aio/content/images/bios/**',
'aio/content/images/marketing/**', 'aio/content/images/marketing/**',

View File

@ -1,3 +1,79 @@
<a name="10.0.12"></a>
## 10.0.12 (2020-08-24)
### Bug Fixes
* **compiler-cli:** adding references to const enums in runtime code ([#38542](https://github.com/angular/angular/issues/38542)) ([814b436](https://github.com/angular/angular/commit/814b436)), closes [#38513](https://github.com/angular/angular/issues/38513)
* **core:** remove closing body tag from inert DOM builder ([#38454](https://github.com/angular/angular/issues/38454)) ([5528536](https://github.com/angular/angular/commit/5528536))
* **localize:** include the last placeholder in parsed translation text ([#38452](https://github.com/angular/angular/issues/38452)) ([57d1a48](https://github.com/angular/angular/commit/57d1a48))
* **localize:** parse all parts of a translation with nested HTML ([#38452](https://github.com/angular/angular/issues/38452)) ([07b99f5](https://github.com/angular/angular/commit/07b99f5)), closes [#38422](https://github.com/angular/angular/issues/38422)
### Features
* **language-service:** introduce hybrid visitor to locate AST node ([#38540](https://github.com/angular/angular/issues/38540)) ([66d8c22](https://github.com/angular/angular/commit/66d8c22))
<a name="10.0.11"></a>
## 10.0.11 (2020-08-19)
### Bug Fixes
* **router:** ensure routerLinkActive updates when associated routerLinks change (resubmit of [#38349](https://github.com/angular/angular/issues/38349)) ([#38511](https://github.com/angular/angular/issues/38511)) ([0af9533](https://github.com/angular/angular/commit/0af9533)), closes [#18469](https://github.com/angular/angular/issues/18469)
<a name="10.0.10"></a>
## 10.0.10 (2020-08-17)
### Bug Fixes
* **common:** Allow scrolling when browser supports scrollTo ([#38468](https://github.com/angular/angular/issues/38468)) ([b32126c](https://github.com/angular/angular/commit/b32126c)), closes [#30630](https://github.com/angular/angular/issues/30630)
* **core:** detect DI parameters in JIT mode for downleveled ES2015 classes ([#38500](https://github.com/angular/angular/issues/38500)) ([863acb6](https://github.com/angular/angular/commit/863acb6)), closes [#38453](https://github.com/angular/angular/issues/38453)
* **core:** error if CSS custom property in host binding has number in name ([#38432](https://github.com/angular/angular/issues/38432)) ([cb83b8a](https://github.com/angular/angular/commit/cb83b8a)), closes [#37292](https://github.com/angular/angular/issues/37292)
* **core:** fix multiple nested views removal from ViewContainerRef ([#38317](https://github.com/angular/angular/issues/38317)) ([d5e09f4](https://github.com/angular/angular/commit/d5e09f4)), closes [#38201](https://github.com/angular/angular/issues/38201)
* **ngcc:** detect synthesized delegate constructors for downleveled ES2015 classes ([#38500](https://github.com/angular/angular/issues/38500)) ([f3dd6c2](https://github.com/angular/angular/commit/f3dd6c2)), closes [#38453](https://github.com/angular/angular/issues/38453) [#38453](https://github.com/angular/angular/issues/38453)
* **router:** ensure routerLinkActive updates when associated routerLinks change ([#38349](https://github.com/angular/angular/issues/38349)) ([989e8a1](https://github.com/angular/angular/commit/989e8a1)), closes [#18469](https://github.com/angular/angular/issues/18469)
<a name="10.0.9"></a>
## 10.0.9 (2020-08-12)
### Bug Fixes
* **common:** ensure scrollRestoration is writable ([#30630](https://github.com/angular/angular/issues/30630)) ([#38357](https://github.com/angular/angular/issues/38357)) ([58f4b3a](https://github.com/angular/angular/commit/58f4b3a)), closes [#30629](https://github.com/angular/angular/issues/30629)
* **compiler:** evaluate safe navigation expressions in correct binding order ([#37911](https://github.com/angular/angular/issues/37911)) ([f5b9d87](https://github.com/angular/angular/commit/f5b9d87)), closes [#37194](https://github.com/angular/angular/issues/37194)
* **compiler-cli:** avoid creating value expressions for symbols from type-only imports ([#38415](https://github.com/angular/angular/issues/38415)) ([ca2b4bc](https://github.com/angular/angular/commit/ca2b4bc)), closes [#37912](https://github.com/angular/angular/issues/37912)
* **compiler-cli:** infer quote expressions as any type in type checker ([#37917](https://github.com/angular/angular/issues/37917)) ([5b87c67](https://github.com/angular/angular/commit/5b87c67)), closes [#36568](https://github.com/angular/angular/issues/36568)
* **compiler-cli:** mark eager `NgModuleFactory` construction as not side effectful ([#38320](https://github.com/angular/angular/issues/38320)) ([016a41b](https://github.com/angular/angular/commit/016a41b)), closes [#38147](https://github.com/angular/angular/issues/38147)
* **compiler-cli:** match wrapHost parameter types within plugin interface ([#38004](https://github.com/angular/angular/issues/38004)) ([df01a82](https://github.com/angular/angular/commit/df01a82))
* **compiler-cli:** preserve quotes in class member names ([#38387](https://github.com/angular/angular/issues/38387)) ([c9acb7b](https://github.com/angular/angular/commit/c9acb7b)), closes [#38311](https://github.com/angular/angular/issues/38311)
* **core:** prevent NgModule scope being overwritten in JIT compiler ([#37795](https://github.com/angular/angular/issues/37795)) ([3acebdc](https://github.com/angular/angular/commit/3acebdc)), closes [#37105](https://github.com/angular/angular/issues/37105)
* **core:** queries not matching string injection tokens ([#38321](https://github.com/angular/angular/issues/38321)) ([32109dc](https://github.com/angular/angular/commit/32109dc)), closes [#38313](https://github.com/angular/angular/issues/38313) [#38315](https://github.com/angular/angular/issues/38315)
* **core:** Store the currently selected ICU in `LView` ([#38345](https://github.com/angular/angular/issues/38345)) ([ee5123f](https://github.com/angular/angular/commit/ee5123f))
* **platform-server:** remove styles added by ServerStylesHost on destruction ([#38367](https://github.com/angular/angular/issues/38367)) ([7f11149](https://github.com/angular/angular/commit/7f11149))
* **router:** prevent calling unsubscribe on undefined subscription in RouterPreloader ([#38344](https://github.com/angular/angular/issues/38344)) ([4151314](https://github.com/angular/angular/commit/4151314))
* **service-worker:** fix the chrome debugger syntax highlighter ([#38332](https://github.com/angular/angular/issues/38332)) ([f5d5bac](https://github.com/angular/angular/commit/f5d5bac))
<a name="10.0.8"></a>
## 10.0.8 (2020-08-04)
### Bug Fixes
* **compiler:** add PURE annotation to getInheritedFactory calls ([#38291](https://github.com/angular/angular/issues/38291)) ([03d8e31](https://github.com/angular/angular/commit/03d8e31))
* **compiler:** update unparsable character reference entity error messages ([#38319](https://github.com/angular/angular/issues/38319)) ([cea4678](https://github.com/angular/angular/commit/cea4678)), closes [#26067](https://github.com/angular/angular/issues/26067)
<a name="10.0.7"></a> <a name="10.0.7"></a>
## 10.0.7 (2020-07-30) ## 10.0.7 (2020-07-30)

View File

@ -1,5 +1,3 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Accessibility example e2e tests', () => { describe('Accessibility example e2e tests', () => {
@ -8,11 +6,11 @@ describe('Accessibility example e2e tests', () => {
browser.get(''); browser.get('');
}); });
it('should display Accessibility Example', function () { it('should display Accessibility Example', () => {
expect(element(by.css('h1')).getText()).toEqual('Accessibility Example'); expect(element(by.css('h1')).getText()).toEqual('Accessibility Example');
}); });
it('should take a number and change progressbar width', function () { it('should take a number and change progressbar width', () => {
element(by.css('input')).sendKeys('16'); element(by.css('input')).sendKeys('16');
expect(element(by.css('input')).getAttribute('value')).toEqual('016'); expect(element(by.css('input')).getAttribute('value')).toEqual('016');
expect(element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px'); expect(element(by.css('app-example-progressbar div')).getCssValue('width')).toBe('48px');

View File

@ -1,3 +1,4 @@
// tslint:disable: no-host-metadata-property
// #docregion progressbar-component // #docregion progressbar-component
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';

View File

@ -1,20 +1,18 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('AngularJS to Angular Quick Reference Tests', function () { describe('AngularJS to Angular Quick Reference Tests', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should display no poster images after bootstrap', function () { it('should display no poster images after bootstrap', () => {
testImagesAreDisplayed(false); testImagesAreDisplayed(false);
}); });
it('should display proper movie data', function () { it('should display proper movie data', () => {
// We check only a few samples // We check only a few samples
let expectedSamples: any[] = [ const expectedSamples: any[] = [
{row: 0, column: 0, element: 'img', attr: 'src', value: 'images/hero.png', contains: true}, {row: 0, column: 0, element: 'img', attr: 'src', value: 'images/hero.png', contains: true},
{row: 0, column: 2, value: 'Celeritas'}, {row: 0, column: 2, value: 'Celeritas'},
{row: 1, column: 3, matches: /Dec 1[678], 2015/}, // absorb timezone dif; we care about date format {row: 1, column: 3, matches: /Dec 1[678], 2015/}, // absorb timezone dif; we care about date format
@ -25,18 +23,17 @@ describe('AngularJS to Angular Quick Reference Tests', function () {
]; ];
// Go through the samples // Go through the samples
let movieRows = getMovieRows(); const movieRows = getMovieRows();
for (let i = 0; i < expectedSamples.length; i++) { for (const sample of expectedSamples) {
let sample = expectedSamples[i]; const tableCell = movieRows.get(sample.row)
let tableCell = movieRows.get(sample.row)
.all(by.tagName('td')).get(sample.column); .all(by.tagName('td')).get(sample.column);
// Check the cell or its nested element // Check the cell or its nested element
let elementToCheck = sample.element const elementToCheck = sample.element
? tableCell.element(by.tagName(sample.element)) ? tableCell.element(by.tagName(sample.element))
: tableCell; : tableCell;
// Check element attribute or text // Check element attribute or text
let valueToCheck = sample.attr const valueToCheck = sample.attr
? elementToCheck.getAttribute(sample.attr) ? elementToCheck.getAttribute(sample.attr)
: elementToCheck.getText(); : elementToCheck.getText();
@ -51,42 +48,42 @@ describe('AngularJS to Angular Quick Reference Tests', function () {
} }
}); });
it('should display images after Show Poster', function () { it('should display images after Show Poster', () => {
testPosterButtonClick('Show Poster', true); testPosterButtonClick('Show Poster', true);
}); });
it('should hide images after Hide Poster', function () { it('should hide images after Hide Poster', () => {
testPosterButtonClick('Hide Poster', false); testPosterButtonClick('Hide Poster', false);
}); });
it('should display no movie when no favorite hero is specified', function () { it('should display no movie when no favorite hero is specified', () => {
testFavoriteHero(null, 'Please enter your favorite hero.'); testFavoriteHero(null, 'Please enter your favorite hero.');
}); });
it('should display no movie for Magneta', function () { it('should display no movie for Magneta', () => {
testFavoriteHero('Magneta', 'No movie, sorry!'); testFavoriteHero('Magneta', 'No movie, sorry!');
}); });
it('should display a movie for Dr Nice', function () { it('should display a movie for Dr Nice', () => {
testFavoriteHero('Dr Nice', 'Excellent choice!'); testFavoriteHero('Dr Nice', 'Excellent choice!');
}); });
function testImagesAreDisplayed(isDisplayed: boolean) { function testImagesAreDisplayed(isDisplayed: boolean) {
let expectedMovieCount = 3; const expectedMovieCount = 3;
let movieRows = getMovieRows(); const movieRows = getMovieRows();
expect(movieRows.count()).toBe(expectedMovieCount); expect(movieRows.count()).toBe(expectedMovieCount);
for (let i = 0; i < expectedMovieCount; i++) { for (let i = 0; i < expectedMovieCount; i++) {
let movieImage = movieRows.get(i).element(by.css('td > img')); const movieImage = movieRows.get(i).element(by.css('td > img'));
expect(movieImage.isDisplayed()).toBe(isDisplayed); expect(movieImage.isDisplayed()).toBe(isDisplayed);
} }
} }
function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) { function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) {
let posterButton = element(by.css('app-movie-list tr > th > button')); const posterButton = element(by.css('app-movie-list tr > th > button'));
expect(posterButton.getText()).toBe(expectedButtonText); expect(posterButton.getText()).toBe(expectedButtonText);
posterButton.click().then(function () { posterButton.click().then(() => {
testImagesAreDisplayed(isDisplayed); testImagesAreDisplayed(isDisplayed);
}); });
} }
@ -96,12 +93,12 @@ describe('AngularJS to Angular Quick Reference Tests', function () {
} }
function testFavoriteHero(heroName: string, expectedLabel: string) { function testFavoriteHero(heroName: string, expectedLabel: string) {
let movieListComp = element(by.tagName('app-movie-list')); const movieListComp = element(by.tagName('app-movie-list'));
let heroInput = movieListComp.element(by.tagName('input')); const heroInput = movieListComp.element(by.tagName('input'));
let favoriteHeroLabel = movieListComp.element(by.tagName('h3')); const favoriteHeroLabel = movieListComp.element(by.tagName('h3'));
let resultLabel = movieListComp.element(by.css('span > p')); const resultLabel = movieListComp.element(by.css('span > p'));
heroInput.clear().then(function () { heroInput.clear().then(() => {
heroInput.sendKeys(heroName || ''); heroInput.sendKeys(heroName || '');
expect(resultLabel.getText()).toBe(expectedLabel); expect(resultLabel.getText()).toBe(expectedLabel);
if (heroName) { if (heroName) {

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { MovieListComponent } from './movie-list.component'; import { MovieListComponent } from './movie-list.component';

View File

@ -1,8 +1,8 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@NgModule({ @NgModule({
imports: [ BrowserModule ], imports: [ BrowserModule ],

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { MovieListComponent } from './movie-list.component'; import { MovieListComponent } from './movie-list.component';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';

View File

@ -1,5 +1,3 @@
'use strict'; // necessary for es6 output in node
import { browser, ExpectedConditions as EC } from 'protractor'; import { browser, ExpectedConditions as EC } from 'protractor';
import { logging } from 'selenium-webdriver'; import { logging } from 'selenium-webdriver';
import * as openClose from './open-close.po'; import * as openClose from './open-close.po';

View File

@ -34,7 +34,7 @@ export class AppComponent {
// #docregion prepare-router-outlet // #docregion prepare-router-outlet
prepareRoute(outlet: RouterOutlet) { prepareRoute(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation']; return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
} }
// #enddocregion prepare-router-outlet // #enddocregion prepare-router-outlet

View File

@ -1,4 +1,6 @@
// tslint:disable: variable-name
// #docplaster // #docplaster
// #docregion
import { Component, HostBinding, OnInit } from '@angular/core'; import { Component, HostBinding, OnInit } from '@angular/core';
import { trigger, transition, animate, style, query, stagger } from '@angular/animations'; import { trigger, transition, animate, style, query, stagger } from '@angular/animations';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
@ -52,13 +54,11 @@ export class HeroListPageComponent implements OnInit {
@HostBinding('@pageAnimations') @HostBinding('@pageAnimations')
public animatePage = true; public animatePage = true;
_heroes = [];
// #docregion filter-animations // #docregion filter-animations
heroTotal = -1; heroTotal = -1;
// #enddocregion filter-animations // #enddocregion filter-animations
get heroes() { get heroes() { return this._heroes; }
return this._heroes; private _heroes = [];
}
ngOnInit() { ngOnInit() {
this._heroes = HEROES; this._heroes = HEROES;

View File

@ -8,8 +8,7 @@ import { trigger, transition, state, animate, style, AnimationEvent } from '@ang
// #docregion trigger, trigger-wildcard1, trigger-transition // #docregion trigger, trigger-wildcard1, trigger-transition
animations: [ animations: [
trigger('openClose', [ trigger('openClose', [
// #enddocregion events1 // #docregion state1
// #docregion state1, events1
// ... // ...
// #enddocregion events1 // #enddocregion events1
state('open', style({ state('open', style({
@ -34,8 +33,7 @@ import { trigger, transition, state, animate, style, AnimationEvent } from '@ang
transition('closed => open', [ transition('closed => open', [
animate('0.5s') animate('0.5s')
]), ]),
// #enddocregion trigger, component // #enddocregion transition2, trigger, component
// #enddocregion transition2
// #docregion trigger-wildcard1 // #docregion trigger-wildcard1
transition('* => closed', [ transition('* => closed', [
animate('1s') animate('1s')
@ -70,7 +68,9 @@ import { trigger, transition, state, animate, style, AnimationEvent } from '@ang
}) })
// #docregion events // #docregion events
export class OpenCloseComponent { export class OpenCloseComponent {
// #enddocregion events1, events // #enddocregion events1, events, component
@Input() logging = false;
// #docregion component
isOpen = true; isOpen = true;
toggle() { toggle() {
@ -78,9 +78,8 @@ export class OpenCloseComponent {
} }
// #enddocregion component // #enddocregion component
@Input() logging = false;
// #docregion events1, events // #docregion events1, events
onAnimationEvent ( event: AnimationEvent ) { onAnimationEvent( event: AnimationEvent ) {
// #enddocregion events1, events // #enddocregion events1, events
if (!this.logging) { if (!this.logging) {
return; return;

View File

@ -1,5 +1,3 @@
'use strict'; // necessary for es6 output in node
import { protractor, browser, element, by, ElementFinder } from 'protractor'; import { protractor, browser, element, by, ElementFinder } from 'protractor';
const nameSuffix = 'X'; const nameSuffix = 'X';
@ -21,7 +19,7 @@ describe('Architecture', () => {
}); });
it(`has h2 '${expectedH2}'`, () => { it(`has h2 '${expectedH2}'`, () => {
let h2 = element.all(by.css('h2')).map((elt: any) => elt.getText()); const h2 = element.all(by.css('h2')).map((elt: any) => elt.getText());
expect(h2).toEqual(expectedH2); expect(h2).toEqual(expectedH2);
}); });
@ -34,42 +32,42 @@ function heroTests() {
const targetHero: Hero = { id: 2, name: 'Dr Nice' }; const targetHero: Hero = { id: 2, name: 'Dr Nice' };
it('has the right number of heroes', () => { it('has the right number of heroes', () => {
let page = getPageElts(); const page = getPageElts();
expect(page.heroes.count()).toEqual(3); expect(page.heroes.count()).toEqual(3);
}); });
it('has no hero details initially', function () { it('has no hero details initially', () => {
let page = getPageElts(); const page = getPageElts();
expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail'); expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail');
}); });
it('shows selected hero details', async () => { it('shows selected hero details', async () => {
await element(by.cssContainingText('li', targetHero.name)).click(); await element(by.cssContainingText('li', targetHero.name)).click();
let page = getPageElts(); const page = getPageElts();
let hero = await heroFromDetail(page.heroDetail); const hero = await heroFromDetail(page.heroDetail);
expect(hero.id).toEqual(targetHero.id); expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(targetHero.name); expect(hero.name).toEqual(targetHero.name);
}); });
it(`shows updated hero name in details`, async () => { it(`shows updated hero name in details`, async () => {
let input = element.all(by.css('input')).first(); const input = element.all(by.css('input')).first();
input.sendKeys(nameSuffix); input.sendKeys(nameSuffix);
let page = getPageElts(); const page = getPageElts();
let hero = await heroFromDetail(page.heroDetail); const hero = await heroFromDetail(page.heroDetail);
let newName = targetHero.name + nameSuffix; const newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id); expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName); expect(hero.name).toEqual(newName);
}); });
} }
function salesTaxTests() { function salesTaxTests() {
it('has no sales tax initially', function () { it('has no sales tax initially', () => {
let page = getPageElts(); const page = getPageElts();
expect(page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info'); expect(page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info');
}); });
it('shows sales tax', async function () { it('shows sales tax', async () => {
let page = getPageElts(); const page = getPageElts();
page.salesTaxAmountInput.sendKeys('10', protractor.Key.ENTER); page.salesTaxAmountInput.sendKeys('10', protractor.Key.ENTER);
expect(page.salesTaxDetail.getText()).toEqual('The sales tax is $1.00'); expect(page.salesTaxDetail.getText()).toEqual('The sales tax is $1.00');
}); });
@ -88,13 +86,11 @@ function getPageElts() {
async function heroFromDetail(detail: ElementFinder): Promise<Hero> { async function heroFromDetail(detail: ElementFinder): Promise<Hero> {
// Get hero id from the first <div> // Get hero id from the first <div>
// let _id = await detail.all(by.css('div')).first().getText(); const id = await detail.all(by.css('div')).first().getText();
let _id = await detail.all(by.css('div')).first().getText();
// Get name from the h2 // Get name from the h2
// let _name = await detail.element(by.css('h4')).getText(); const name = await detail.element(by.css('h4')).getText();
let _name = await detail.element(by.css('h4')).getText();
return { return {
id: +_id.substr(_id.indexOf(' ') + 1), id: +id.substr(id.indexOf(' ') + 1),
name: _name.substr(0, _name.lastIndexOf(' ')) name: name.substr(0, name.lastIndexOf(' ')),
}; };
} }

View File

@ -1,15 +1,15 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
// #docregion imports // #docregion imports
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
// #enddocregion imports // #enddocregion imports
import { HeroDetailComponent } from './hero-detail.component'; import { HeroDetailComponent } from './hero-detail.component';
import { HeroListComponent } from './hero-list.component'; import { HeroListComponent } from './hero-list.component';
import { SalesTaxComponent } from './sales-tax.component'; import { SalesTaxComponent } from './sales-tax.component';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
import { BackendService } from './backend.service'; import { BackendService } from './backend.service';
import { Logger } from './logger.service'; import { Logger } from './logger.service';
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -18,7 +18,7 @@ export class BackendService {
// TODO: get from the database // TODO: get from the database
return Promise.resolve<Hero[]>(HEROES); return Promise.resolve<Hero[]>(HEROES);
} }
let err = new Error('Cannot get object of this type'); const err = new Error('Cannot get object of this type');
this.logger.error(err); this.logger.error(err);
throw err; throw err;
} }

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #docregion metadata, providers // #docregion metadata, providers
@Component({ @Component({

View File

@ -22,7 +22,7 @@ export class AppComponent {
} }
// #docregion module // #docregion module
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
// #docregion import-browser-module // #docregion import-browser-module
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
// #enddocregion import-browser-module // #enddocregion import-browser-module

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SalesTaxService } from './sales-tax.service'; import { SalesTaxService } from './sales-tax.service';
import { TaxRateService } from './tax-rate.service'; import { TaxRateService } from './tax-rate.service';
@Component({ @Component({
selector: 'app-sales-tax', selector: 'app-sales-tax',

View File

@ -7,7 +7,7 @@ export class SalesTaxService {
constructor(private rateService: TaxRateService) { } constructor(private rateService: TaxRateService) { }
getVAT(value: string | number) { getVAT(value: string | number) {
let amount = (typeof value === 'string') ? const amount = (typeof value === 'string') ?
parseFloat(value) : value; parseFloat(value) : value;
return (amount || 0) * this.rateService.getRate('VAT'); return (amount || 0) * this.rateService.getRate('VAT');
} }

View File

@ -1,34 +1,32 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Attribute binding example', function () { describe('Attribute binding example', () => {
beforeEach(function () { beforeEach(() => {
browser.get(''); browser.get('');
}); });
it('should display Property Binding with Angular', function () { it('should display Property Binding with Angular', () => {
expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings'); expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
}); });
it('should display a table', function() { it('should display a table', () => {
expect(element.all(by.css('table')).isPresent()).toBe(true); expect(element.all(by.css('table')).isPresent()).toBe(true);
}); });
it('should display an Aria button', function () { it('should display an Aria button', () => {
expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria'); expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
}); });
it('should display a blue background on div', function () { it('should display a blue background on div', () => {
expect(element.all(by.css('div')).get(1).getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)'); expect(element.all(by.css('div')).get(1).getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)');
}); });
it('should display a blue div with a red border', function () { it('should display a blue div with a red border', () => {
expect(element.all(by.css('div')).get(1).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)'); expect(element.all(by.css('div')).get(1).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
}); });
it('should display a div with many classes', function () { it('should display a div with many classes', () => {
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('special'); expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('special');
expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('clearance'); expect(element.all(by.css('div')).get(1).getAttribute('class')).toContain('clearance');
}); });

View File

@ -1,16 +1,16 @@
import { Component } from '@angular/core'; import { Component, HostBinding } from '@angular/core';
@Component({ @Component({
selector: 'comp-with-host-binding', selector: 'comp-with-host-binding',
template: 'I am a component!', template: 'I am a component!',
host: {
'[class.special]': 'isSpecial',
'[style.color]': 'color',
'[style.width]': 'width'
}
}) })
export class CompWithHostBindingComponent { export class CompWithHostBindingComponent {
@HostBinding('class.special')
isSpecial = false; isSpecial = false;
@HostBinding('style.color')
color = 'green'; color = 'green';
@HostBinding('style.width')
width = '200px'; width = '200px';
} }

View File

@ -1,17 +1,15 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Attribute directives', () => { describe('Attribute directives', () => {
let _title = 'My First Attribute Directive'; const title = 'My First Attribute Directive';
beforeAll(() => { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it(`should display correct title: ${_title}`, () => { it(`should display correct title: ${title}`, () => {
expect(element(by.css('h1')).getText()).toEqual(_title); expect(element(by.css('h1')).getText()).toEqual(title);
}); });
it('should be able to select green highlight', () => { it('should be able to select green highlight', () => {

View File

@ -3,7 +3,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component.1'; import { AppComponent } from './app.component.1';
import { HighlightDirective as HLD1 } from './highlight.directive.1'; import { HighlightDirective as HLD1 } from './highlight.directive.1';
import { HighlightDirective as HLD2 } from './highlight.directive.2'; import { HighlightDirective as HLD2 } from './highlight.directive.2';
import { HighlightDirective as HLD3 } from './highlight.directive.3'; import { HighlightDirective as HLD3 } from './highlight.directive.3';

View File

@ -3,57 +3,55 @@ import { logging } from 'selenium-webdriver';
describe('Binding syntax e2e tests', () => { describe('Binding syntax e2e tests', () => {
beforeEach(function () { beforeEach(() => browser.get(''));
browser.get('');
});
// helper function used to test what's logged to the console // helper function used to test what's logged to the console
async function logChecker(button, contents) { async function logChecker(button, contents) {
const logs = await browser.manage().logs().get(logging.Type.BROWSER); const logs = await browser.manage().logs().get(logging.Type.BROWSER);
const message = logs.filter(({ message }) => message.indexOf(contents) !== -1 ? true : false); const messages = logs.filter(({ message }) => message.indexOf(contents) !== -1 ? true : false);
expect(message.length).toBeGreaterThan(0); expect(messages.length).toBeGreaterThan(0);
} }
it('should display Binding syntax', function () { it('should display Binding syntax', () => {
expect(element(by.css('h1')).getText()).toEqual('Binding syntax'); expect(element(by.css('h1')).getText()).toEqual('Binding syntax');
}); });
it('should display Save button', function () { it('should display Save button', () => {
expect(element.all(by.css('button')).get(0).getText()).toBe('Save'); expect(element.all(by.css('button')).get(0).getText()).toBe('Save');
}); });
it('should display HTML attributes and DOM properties', function () { it('should display HTML attributes and DOM properties', () => {
expect(element.all(by.css('h2')).get(1).getText()).toBe('HTML attributes and DOM properties'); expect(element.all(by.css('h2')).get(1).getText()).toBe('HTML attributes and DOM properties');
}); });
it('should display 1. Use the inspector...', function () { it('should display 1. Use the inspector...', () => {
expect(element.all(by.css('p')).get(0).getText()).toContain('1. Use the inspector'); expect(element.all(by.css('p')).get(0).getText()).toContain('1. Use the inspector');
}); });
it('should display Disabled property vs. attribute', function () { it('should display Disabled property vs. attribute', () => {
expect(element.all(by.css('h3')).get(0).getText()).toBe('Disabled property vs. attribute'); expect(element.all(by.css('h3')).get(0).getText()).toBe('Disabled property vs. attribute');
}); });
it('should log a message including Sarah', async () => { it('should log a message including Sarah', async () => {
let attributeButton = element.all(by.css('button')).get(1); const attributeButton = element.all(by.css('button')).get(1);
await attributeButton.click(); await attributeButton.click();
const contents = 'Sarah'; const contents = 'Sarah';
logChecker(attributeButton, contents); logChecker(attributeButton, contents);
}); });
it('should log a message including Sarah for DOM property', async () => { it('should log a message including Sarah for DOM property', async () => {
let DOMPropertyButton = element.all(by.css('button')).get(2); const DOMPropertyButton = element.all(by.css('button')).get(2);
await DOMPropertyButton.click(); await DOMPropertyButton.click();
const contents = 'Sarah'; const contents = 'Sarah';
logChecker(DOMPropertyButton, contents); logChecker(DOMPropertyButton, contents);
}); });
it('should log a message including Sally for DOM property', async () => { it('should log a message including Sally for DOM property', async () => {
let DOMPropertyButton = element.all(by.css('button')).get(2); const DOMPropertyButton = element.all(by.css('button')).get(2);
let input = element(by.css('input')); const input = element(by.css('input'));
input.sendKeys('Sally'); input.sendKeys('Sally');
await DOMPropertyButton.click(); await DOMPropertyButton.click();
const contents = 'Sally'; const contents = 'Sally';
@ -61,14 +59,14 @@ describe('Binding syntax e2e tests', () => {
}); });
it('should log a message that Test Button works', async () => { it('should log a message that Test Button works', async () => {
let testButton = element.all(by.css('button')).get(3); const testButton = element.all(by.css('button')).get(3);
await testButton.click(); await testButton.click();
const contents = 'Test'; const contents = 'Test';
logChecker(testButton, contents); logChecker(testButton, contents);
}); });
it('should toggle Test Button disabled', async () => { it('should toggle Test Button disabled', async () => {
let toggleButton = element.all(by.css('button')).get(4); const toggleButton = element.all(by.css('button')).get(4);
await toggleButton.click(); await toggleButton.click();
const contents = 'true'; const contents = 'true';
logChecker(toggleButton, contents); logChecker(toggleButton, contents);

View File

@ -26,7 +26,7 @@ export class AppComponent {
toggleDisabled(): any { toggleDisabled(): any {
let testButton = <HTMLInputElement> document.getElementById('testButton'); const testButton = document.getElementById('testButton') as HTMLInputElement;
testButton.disabled = !testButton.disabled; testButton.disabled = !testButton.disabled;
console.warn(testButton.disabled); console.warn(testButton.disabled);
} }

View File

@ -1,21 +1,19 @@
'use strict';
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Built-in Directives', function () { describe('Built-in Directives', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should have title Built-in Directives', function () { it('should have title Built-in Directives', () => {
let title = element.all(by.css('h1')).get(0); const title = element.all(by.css('h1')).get(0);
expect(title.getText()).toEqual('Built-in Directives'); expect(title.getText()).toEqual('Built-in Directives');
}); });
it('should change first Teapot header', async () => { it('should change first Teapot header', async () => {
let firstLabel = element.all(by.css('p')).get(0); const firstLabel = element.all(by.css('p')).get(0);
let firstInput = element.all(by.css('input')).get(0); const firstInput = element.all(by.css('input')).get(0);
expect(firstLabel.getText()).toEqual('Current item name: Teapot'); expect(firstLabel.getText()).toEqual('Current item name: Teapot');
firstInput.sendKeys('abc'); firstInput.sendKeys('abc');
@ -23,49 +21,49 @@ describe('Built-in Directives', function () {
}); });
it('should modify sentence when modified checkbox checked', function () { it('should modify sentence when modified checkbox checked', () => {
let modifiedChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(1); const modifiedChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(1);
let modifiedSentence = element.all(by.css('div')).get(1); const modifiedSentence = element.all(by.css('div')).get(1);
modifiedChkbxLabel.click(); modifiedChkbxLabel.click();
expect(modifiedSentence.getText()).toContain('modified'); expect(modifiedSentence.getText()).toContain('modified');
}); });
it('should modify sentence when normal checkbox checked', function () { it('should modify sentence when normal checkbox checked', () => {
let normalChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(4); const normalChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(4);
let normalSentence = element.all(by.css('div')).get(7); const normalSentence = element.all(by.css('div')).get(7);
normalChkbxLabel.click(); normalChkbxLabel.click();
expect(normalSentence.getText()).toContain('normal weight and, extra large'); expect(normalSentence.getText()).toContain('normal weight and, extra large');
}); });
it('should toggle app-item-detail', function () { it('should toggle app-item-detail', () => {
let toggleButton = element.all(by.css('button')).get(3); const toggleButton = element.all(by.css('button')).get(3);
let toggledDiv = element.all(by.css('app-item-detail')).get(0); const toggledDiv = element.all(by.css('app-item-detail')).get(0);
toggleButton.click(); toggleButton.click();
expect(toggledDiv.isDisplayed()).toBe(true); expect(toggledDiv.isDisplayed()).toBe(true);
}); });
it('should hide app-item-detail', function () { it('should hide app-item-detail', () => {
let hiddenMessage = element.all(by.css('p')).get(11); const hiddenMessage = element.all(by.css('p')).get(11);
let hiddenDiv = element.all(by.css('app-item-detail')).get(2); const hiddenDiv = element.all(by.css('app-item-detail')).get(2);
expect(hiddenMessage.getText()).toContain('in the DOM'); expect(hiddenMessage.getText()).toContain('in the DOM');
expect(hiddenDiv.isDisplayed()).toBe(true); expect(hiddenDiv.isDisplayed()).toBe(true);
}); });
it('should have 10 lists each containing the string Teapot', function () { it('should have 10 lists each containing the string Teapot', () => {
let listDiv = element.all(by.cssContainingText('.box', 'Teapot')); const listDiv = element.all(by.cssContainingText('.box', 'Teapot'));
expect(listDiv.count()).toBe(10); expect(listDiv.count()).toBe(10);
}); });
it('should switch case', function () { it('should switch case', () => {
let tvRadioButton = element.all(by.css('input[type="radio"]')).get(3); const tvRadioButton = element.all(by.css('input[type="radio"]')).get(3);
let tvDiv = element(by.css('app-lost-item')); const tvDiv = element(by.css('app-lost-item'));
let fishbowlRadioButton = element.all(by.css('input[type="radio"]')).get(4); const fishbowlRadioButton = element.all(by.css('input[type="radio"]')).get(4);
let fishbowlDiv = element(by.css('app-unknown-item')); const fishbowlDiv = element(by.css('app-unknown-item'));
tvRadioButton.click(); tvRadioButton.click();
expect(tvDiv.getText()).toContain('Television'); expect(tvDiv.getText()).toContain('Television');

View File

@ -30,6 +30,14 @@ export class AppComponent implements OnInit {
itemsWithTrackByCountReset = 0; itemsWithTrackByCountReset = 0;
itemIdIncrement = 1; itemIdIncrement = 1;
// #docregion setClasses
currentClasses: {};
// #enddocregion setClasses
// #docregion setStyles
currentStyles: {};
// #enddocregion setStyles
ngOnInit() { ngOnInit() {
this.resetItems(); this.resetItems();
this.setCurrentClasses(); this.setCurrentClasses();
@ -41,20 +49,18 @@ export class AppComponent implements OnInit {
this.currentItem.name = name.toUpperCase(); this.currentItem.name = name.toUpperCase();
} }
// #docregion setClasses // #docregion setClasses
currentClasses: {};
setCurrentClasses() { setCurrentClasses() {
// CSS classes: added/removed per current state of component properties // CSS classes: added/removed per current state of component properties
this.currentClasses = { this.currentClasses = {
'saveable': this.canSave, saveable: this.canSave,
'modified': !this.isUnchanged, modified: !this.isUnchanged,
'special': this.isSpecial special: this.isSpecial
}; };
} }
// #enddocregion setClasses // #enddocregion setClasses
// #docregion setStyles // #docregion setStyles
currentStyles: {};
setCurrentStyles() { setCurrentStyles() {
// CSS styles: set per current state of component properties // CSS styles: set per current state of component properties
this.currentStyles = { this.currentStyles = {
@ -70,11 +76,7 @@ export class AppComponent implements OnInit {
} }
giveNullCustomerValue() { giveNullCustomerValue() {
!(this.nullCustomer = null) ? (this.nullCustomer = 'Kelly') : (this.nullCustomer = null); this.nullCustomer = 'Kelly';
}
resetNullItem() {
this.nullCustomer = null;
} }
resetItems() { resetItems() {
@ -84,7 +86,7 @@ export class AppComponent implements OnInit {
} }
resetList() { resetList() {
this.resetItems() this.resetItems();
this.itemsWithTrackByCountReset = 0; this.itemsWithTrackByCountReset = 0;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount; this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
} }
@ -107,7 +109,7 @@ export class AppComponent implements OnInit {
trackByItems(index: number, item: Item): number { return item.id; } trackByItems(index: number, item: Item): number { return item.id; }
// #enddocregion trackByItems // #enddocregion trackByItems
trackById(index: number, item: any): number { return item['id']; } trackById(index: number, item: any): number { return item.id; }
} }

View File

@ -1,19 +1,17 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Built Template Functions Example', function () { describe('Built Template Functions Example', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should have title Built-in Template Functions', function () { it('should have title Built-in Template Functions', () => {
let title = element.all(by.css('h1')).get(0); const title = element.all(by.css('h1')).get(0);
expect(title.getText()).toEqual('Built-in Template Functions'); expect(title.getText()).toEqual('Built-in Template Functions');
}); });
it('should display $any( ) in h2', function () { it('should display $any( ) in h2', () => {
let header = element(by.css('h2')); const header = element(by.css('h2'));
expect(header.getText()).toContain('$any( )'); expect(header.getText()).toContain('$any( )');
}); });

View File

@ -1,87 +1,85 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Component Communication Cookbook Tests', function () { describe('Component Communication Cookbook Tests', () => {
// Note: '?e2e' which app can read to know it is running in protractor // Note: '?e2e' which app can read to know it is running in protractor
// e.g. `if (!/e2e/.test(location.search)) { ...` // e.g. `if (!/e2e/.test(location.search)) { ...`
beforeAll(function () { beforeAll(() => {
browser.get('?e2e'); browser.get('?e2e');
}); });
describe('Parent-to-child communication', function() { describe('Parent-to-child communication', () => {
// #docregion parent-to-child // #docregion parent-to-child
// ... // ...
let _heroNames = ['Dr IQ', 'Magneta', 'Bombasto']; const heroNames = ['Dr IQ', 'Magneta', 'Bombasto'];
let _masterName = 'Master'; const masterName = 'Master';
it('should pass properties to children properly', function () { it('should pass properties to children properly', () => {
let parent = element.all(by.tagName('app-hero-parent')).get(0); const parent = element.all(by.tagName('app-hero-parent')).get(0);
let heroes = parent.all(by.tagName('app-hero-child')); const heroes = parent.all(by.tagName('app-hero-child'));
for (let i = 0; i < _heroNames.length; i++) { for (let i = 0; i < heroNames.length; i++) {
let childTitle = heroes.get(i).element(by.tagName('h3')).getText(); const childTitle = heroes.get(i).element(by.tagName('h3')).getText();
let childDetail = heroes.get(i).element(by.tagName('p')).getText(); const childDetail = heroes.get(i).element(by.tagName('p')).getText();
expect(childTitle).toEqual(_heroNames[i] + ' says:'); expect(childTitle).toEqual(heroNames[i] + ' says:');
expect(childDetail).toContain(_masterName); expect(childDetail).toContain(masterName);
} }
}); });
// ... // ...
// #enddocregion parent-to-child // #enddocregion parent-to-child
}); });
describe('Parent-to-child communication with setter', function() { describe('Parent-to-child communication with setter', () => {
// #docregion parent-to-child-setter // #docregion parent-to-child-setter
// ... // ...
it('should display trimmed, non-empty names', function () { it('should display trimmed, non-empty names', () => {
let _nonEmptyNameIndex = 0; const nonEmptyNameIndex = 0;
let _nonEmptyName = '"Dr IQ"'; const nonEmptyName = '"Dr IQ"';
let parent = element.all(by.tagName('app-name-parent')).get(0); const parent = element.all(by.tagName('app-name-parent')).get(0);
let hero = parent.all(by.tagName('app-name-child')).get(_nonEmptyNameIndex); const hero = parent.all(by.tagName('app-name-child')).get(nonEmptyNameIndex);
let displayName = hero.element(by.tagName('h3')).getText(); const displayName = hero.element(by.tagName('h3')).getText();
expect(displayName).toEqual(_nonEmptyName); expect(displayName).toEqual(nonEmptyName);
}); });
it('should replace empty name with default name', function () { it('should replace empty name with default name', () => {
let _emptyNameIndex = 1; const emptyNameIndex = 1;
let _defaultName = '"<no name set>"'; const defaultName = '"<no name set>"';
let parent = element.all(by.tagName('app-name-parent')).get(0); const parent = element.all(by.tagName('app-name-parent')).get(0);
let hero = parent.all(by.tagName('app-name-child')).get(_emptyNameIndex); const hero = parent.all(by.tagName('app-name-child')).get(emptyNameIndex);
let displayName = hero.element(by.tagName('h3')).getText(); const displayName = hero.element(by.tagName('h3')).getText();
expect(displayName).toEqual(_defaultName); expect(displayName).toEqual(defaultName);
}); });
// ... // ...
// #enddocregion parent-to-child-setter // #enddocregion parent-to-child-setter
}); });
describe('Parent-to-child communication with ngOnChanges', function() { describe('Parent-to-child communication with ngOnChanges', () => {
// #docregion parent-to-child-onchanges // #docregion parent-to-child-onchanges
// ... // ...
// Test must all execute in this exact order // Test must all execute in this exact order
it('should set expected initial values', function () { it('should set expected initial values', () => {
let actual = getActual(); const actual = getActual();
let initialLabel = 'Version 1.23'; const initialLabel = 'Version 1.23';
let initialLog = 'Initial value of major set to 1, Initial value of minor set to 23'; const initialLog = 'Initial value of major set to 1, Initial value of minor set to 23';
expect(actual.label).toBe(initialLabel); expect(actual.label).toBe(initialLabel);
expect(actual.count).toBe(1); expect(actual.count).toBe(1);
expect(actual.logs.get(0).getText()).toBe(initialLog); expect(actual.logs.get(0).getText()).toBe(initialLog);
}); });
it('should set expected values after clicking \'Minor\' twice', function () { it('should set expected values after clicking \'Minor\' twice', () => {
let repoTag = element(by.tagName('app-version-parent')); const repoTag = element(by.tagName('app-version-parent'));
let newMinorButton = repoTag.all(by.tagName('button')).get(0); const newMinorButton = repoTag.all(by.tagName('button')).get(0);
newMinorButton.click().then(function() { newMinorButton.click().then(() => {
newMinorButton.click().then(function() { newMinorButton.click().then(() => {
let actual = getActual(); const actual = getActual();
let labelAfter2Minor = 'Version 1.25'; const labelAfter2Minor = 'Version 1.25';
let logAfter2Minor = 'minor changed from 24 to 25'; const logAfter2Minor = 'minor changed from 24 to 25';
expect(actual.label).toBe(labelAfter2Minor); expect(actual.label).toBe(labelAfter2Minor);
expect(actual.count).toBe(3); expect(actual.count).toBe(3);
@ -90,15 +88,15 @@ describe('Component Communication Cookbook Tests', function () {
}); });
}); });
it('should set expected values after clicking \'Major\' once', function () { it('should set expected values after clicking \'Major\' once', () => {
let repoTag = element(by.tagName('app-version-parent')); const repoTag = element(by.tagName('app-version-parent'));
let newMajorButton = repoTag.all(by.tagName('button')).get(1); const newMajorButton = repoTag.all(by.tagName('button')).get(1);
newMajorButton.click().then(function() { newMajorButton.click().then(() => {
let actual = getActual(); const actual = getActual();
let labelAfterMajor = 'Version 2.0'; const labelAfterMajor = 'Version 2.0';
let logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0'; const logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0';
expect(actual.label).toBe(labelAfterMajor); expect(actual.label).toBe(labelAfterMajor);
expect(actual.count).toBe(4); expect(actual.count).toBe(4);
@ -107,14 +105,14 @@ describe('Component Communication Cookbook Tests', function () {
}); });
function getActual() { function getActual() {
let versionTag = element(by.tagName('app-version-child')); const versionTag = element(by.tagName('app-version-child'));
let label = versionTag.element(by.tagName('h3')).getText(); const label = versionTag.element(by.tagName('h3')).getText();
let ul = versionTag.element((by.tagName('ul'))); const ul = versionTag.element((by.tagName('ul')));
let logs = ul.all(by.tagName('li')); const logs = ul.all(by.tagName('li'));
return { return {
label: label, label,
logs: logs, logs,
count: logs.count() count: logs.count()
}; };
} }
@ -123,30 +121,30 @@ describe('Component Communication Cookbook Tests', function () {
}); });
describe('Child-to-parent communication', function() { describe('Child-to-parent communication', () => {
// #docregion child-to-parent // #docregion child-to-parent
// ... // ...
it('should not emit the event initially', function () { it('should not emit the event initially', () => {
let voteLabel = element(by.tagName('app-vote-taker')) const voteLabel = element(by.tagName('app-vote-taker'))
.element(by.tagName('h3')).getText(); .element(by.tagName('h3')).getText();
expect(voteLabel).toBe('Agree: 0, Disagree: 0'); expect(voteLabel).toBe('Agree: 0, Disagree: 0');
}); });
it('should process Agree vote', function () { it('should process Agree vote', () => {
let agreeButton1 = element.all(by.tagName('app-voter')).get(0) const agreeButton1 = element.all(by.tagName('app-voter')).get(0)
.all(by.tagName('button')).get(0); .all(by.tagName('button')).get(0);
agreeButton1.click().then(function() { agreeButton1.click().then(() => {
let voteLabel = element(by.tagName('app-vote-taker')) const voteLabel = element(by.tagName('app-vote-taker'))
.element(by.tagName('h3')).getText(); .element(by.tagName('h3')).getText();
expect(voteLabel).toBe('Agree: 1, Disagree: 0'); expect(voteLabel).toBe('Agree: 1, Disagree: 0');
}); });
}); });
it('should process Disagree vote', function () { it('should process Disagree vote', () => {
let agreeButton1 = element.all(by.tagName('app-voter')).get(1) const agreeButton1 = element.all(by.tagName('app-voter')).get(1)
.all(by.tagName('button')).get(1); .all(by.tagName('button')).get(1);
agreeButton1.click().then(function() { agreeButton1.click().then(() => {
let voteLabel = element(by.tagName('app-vote-taker')) const voteLabel = element(by.tagName('app-vote-taker'))
.element(by.tagName('h3')).getText(); .element(by.tagName('h3')).getText();
expect(voteLabel).toBe('Agree: 1, Disagree: 1'); expect(voteLabel).toBe('Agree: 1, Disagree: 1');
}); });
@ -157,31 +155,31 @@ describe('Component Communication Cookbook Tests', function () {
// Can't run timer tests in protractor because // Can't run timer tests in protractor because
// interaction w/ zones causes all tests to freeze & timeout. // interaction w/ zones causes all tests to freeze & timeout.
xdescribe('Parent calls child via local var', function() { xdescribe('Parent calls child via local var', () => {
countDownTimerTests('countdown-parent-lv'); countDownTimerTests('countdown-parent-lv');
}); });
xdescribe('Parent calls ViewChild', function() { xdescribe('Parent calls ViewChild', () => {
countDownTimerTests('countdown-parent-vc'); countDownTimerTests('countdown-parent-vc');
}); });
function countDownTimerTests(parentTag: string) { function countDownTimerTests(parentTag: string) {
// #docregion countdown-timer-tests // #docregion countdown-timer-tests
// ... // ...
it('timer and parent seconds should match', function () { it('timer and parent seconds should match', () => {
let parent = element(by.tagName(parentTag)); const parent = element(by.tagName(parentTag));
let message = parent.element(by.tagName('app-countdown-timer')).getText(); const message = parent.element(by.tagName('app-countdown-timer')).getText();
browser.sleep(10); // give `seconds` a chance to catchup with `message` browser.sleep(10); // give `seconds` a chance to catchup with `message`
let seconds = parent.element(by.className('seconds')).getText(); const seconds = parent.element(by.className('seconds')).getText();
expect(message).toContain(seconds); expect(message).toContain(seconds);
}); });
it('should stop the countdown', function () { it('should stop the countdown', () => {
let parent = element(by.tagName(parentTag)); const parent = element(by.tagName(parentTag));
let stopButton = parent.all(by.tagName('button')).get(1); const stopButton = parent.all(by.tagName('button')).get(1);
stopButton.click().then(function() { stopButton.click().then(() => {
let message = parent.element(by.tagName('app-countdown-timer')).getText(); const message = parent.element(by.tagName('app-countdown-timer')).getText();
expect(message).toContain('Holding'); expect(message).toContain('Holding');
}); });
}); });
@ -190,39 +188,39 @@ describe('Component Communication Cookbook Tests', function () {
} }
describe('Parent and children communicate via a service', function() { describe('Parent and children communicate via a service', () => {
// #docregion bidirectional-service // #docregion bidirectional-service
// ... // ...
it('should announce a mission', function () { it('should announce a mission', () => {
let missionControl = element(by.tagName('app-mission-control')); const missionControl = element(by.tagName('app-mission-control'));
let announceButton = missionControl.all(by.tagName('button')).get(0); const announceButton = missionControl.all(by.tagName('button')).get(0);
announceButton.click().then(function () { announceButton.click().then(() => {
let history = missionControl.all(by.tagName('li')); const history = missionControl.all(by.tagName('li'));
expect(history.count()).toBe(1); expect(history.count()).toBe(1);
expect(history.get(0).getText()).toMatch(/Mission.* announced/); expect(history.get(0).getText()).toMatch(/Mission.* announced/);
}); });
}); });
it('should confirm the mission by Lovell', function () { it('should confirm the mission by Lovell', () => {
testConfirmMission(1, 2, 'Lovell'); testConfirmMission(1, 2, 'Lovell');
}); });
it('should confirm the mission by Haise', function () { it('should confirm the mission by Haise', () => {
testConfirmMission(3, 3, 'Haise'); testConfirmMission(3, 3, 'Haise');
}); });
it('should confirm the mission by Swigert', function () { it('should confirm the mission by Swigert', () => {
testConfirmMission(2, 4, 'Swigert'); testConfirmMission(2, 4, 'Swigert');
}); });
function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) { function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
let _confirmedLog = ' confirmed the mission'; const confirmedLog = ' confirmed the mission';
let missionControl = element(by.tagName('app-mission-control')); const missionControl = element(by.tagName('app-mission-control'));
let confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex); const confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
confirmButton.click().then(function () { confirmButton.click().then(() => {
let history = missionControl.all(by.tagName('li')); const history = missionControl.all(by.tagName('li'));
expect(history.count()).toBe(expectedLogCount); expect(history.count()).toBe(expectedLogCount);
expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + _confirmedLog); expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + confirmedLog);
}); });
} }
// ... // ...

View File

@ -1,5 +1,5 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AstronautComponent } from './astronaut.component'; import { AstronautComponent } from './astronaut.component';
@ -15,7 +15,7 @@ import { VersionParentComponent } from './version-parent.component';
import { VoterComponent } from './voter.component'; import { VoterComponent } from './voter.component';
import { VoteTakerComponent } from './votetaker.component'; import { VoteTakerComponent } from './votetaker.component';
let directives: any[] = [ const directives: any[] = [
AppComponent, AppComponent,
AstronautComponent, AstronautComponent,
CountdownTimerComponent, CountdownTimerComponent,
@ -30,7 +30,7 @@ let directives: any[] = [
VoteTakerComponent VoteTakerComponent
]; ];
let schemas: any[] = []; const schemas: any[] = [];
// Include Countdown examples // Include Countdown examples
// unless in e2e tests which they break. // unless in e2e tests which they break.
@ -49,6 +49,6 @@ if (!/e2e/.test(location.search)) {
], ],
declarations: directives, declarations: directives,
bootstrap: [ AppComponent ], bootstrap: [ AppComponent ],
schemas: schemas schemas
}) })
export class AppModule { } export class AppModule { }

View File

@ -2,7 +2,7 @@
import { Component, Input, OnDestroy } from '@angular/core'; import { Component, Input, OnDestroy } from '@angular/core';
import { MissionService } from './mission.service'; import { MissionService } from './mission.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'app-astronaut', selector: 'app-astronaut',

View File

@ -2,8 +2,8 @@
// #docregion vc // #docregion vc
import { AfterViewInit, ViewChild } from '@angular/core'; import { AfterViewInit, ViewChild } from '@angular/core';
// #docregion lv // #docregion lv
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component'; import { CountdownTimerComponent } from './countdown-timer.component';
// #enddocregion lv // #enddocregion lv
// #enddocregion vc // #enddocregion vc

View File

@ -12,6 +12,6 @@ import { Hero } from './hero';
}) })
export class HeroChildComponent { export class HeroChildComponent {
@Input() hero: Hero; @Input() hero: Hero;
@Input('master') masterName: string; @Input('master') masterName: string; // tslint:disable-line: no-input-rename
} }
// #enddocregion // #enddocregion

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@Injectable() @Injectable()
export class MissionService { export class MissionService {

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { MissionService } from './mission.service'; import { MissionService } from './mission.service';
@Component({ @Component({
selector: 'app-mission-control', selector: 'app-mission-control',
@ -34,7 +34,7 @@ export class MissionControlComponent {
} }
announce() { announce() {
let mission = this.missions[this.nextMission++]; const mission = this.missions[this.nextMission++];
this.missionService.announceMission(mission); this.missionService.announceMission(mission);
this.history.push(`Mission "${mission}" announced`); this.history.push(`Mission "${mission}" announced`);
if (this.nextMission >= this.missions.length) { this.nextMission = 0; } if (this.nextMission >= this.missions.length) { this.nextMission = 0; }

View File

@ -1,3 +1,4 @@
// tslint:disable: variable-name
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
@ -6,13 +7,11 @@ import { Component, Input } from '@angular/core';
template: '<h3>"{{name}}"</h3>' template: '<h3>"{{name}}"</h3>'
}) })
export class NameChildComponent { export class NameChildComponent {
private _name = '';
@Input() @Input()
get name(): string { return this._name; }
set name(name: string) { set name(name: string) {
this._name = (name && name.trim()) || '<no name set>'; this._name = (name && name.trim()) || '<no name set>';
} }
private _name = '';
get name(): string { return this._name; }
} }
// #enddocregion // #enddocregion

View File

@ -18,14 +18,14 @@ export class VersionChildComponent implements OnChanges {
changeLog: string[] = []; changeLog: string[] = [];
ngOnChanges(changes: {[propKey: string]: SimpleChange}) { ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = []; const log: string[] = [];
for (let propName in changes) { for (const propName in changes) {
let changedProp = changes[propName]; const changedProp = changes[propName];
let to = JSON.stringify(changedProp.currentValue); const to = JSON.stringify(changedProp.currentValue);
if (changedProp.isFirstChange()) { if (changedProp.isFirstChange()) {
log.push(`Initial value of ${propName} set to ${to}`); log.push(`Initial value of ${propName} set to ${to}`);
} else { } else {
let from = JSON.stringify(changedProp.previousValue); const from = JSON.stringify(changedProp.previousValue);
log.push(`${propName} changed from ${from} to ${to}`); log.push(`${propName} changed from ${from} to ${to}`);
} }
} }

View File

@ -1,5 +1,5 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-vote-taker', selector: 'app-vote-taker',

View File

@ -1,16 +1,14 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Component Style Tests', function () { describe('Component Style Tests', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('scopes component styles to component view', function() { it('scopes component styles to component view', () => {
let componentH1 = element(by.css('app-root > h1')); const componentH1 = element(by.css('app-root > h1'));
let externalH1 = element(by.css('body > h1')); const externalH1 = element(by.css('body > h1'));
// Note: sometimes webdriver returns the fontWeight as "normal", // Note: sometimes webdriver returns the fontWeight as "normal",
// other times as "400", both of which are equal in CSS terms. // other times as "400", both of which are equal in CSS terms.
@ -19,49 +17,49 @@ describe('Component Style Tests', function () {
}); });
it('allows styling :host element', function() { it('allows styling :host element', () => {
let host = element(by.css('app-hero-details')); const host = element(by.css('app-hero-details'));
expect(host.getCssValue('borderWidth')).toEqual('1px'); expect(host.getCssValue('borderWidth')).toEqual('1px');
}); });
it('supports :host() in function form', function() { it('supports :host() in function form', () => {
let host = element(by.css('app-hero-details')); const host = element(by.css('app-hero-details'));
host.element(by.buttonText('Activate')).click(); host.element(by.buttonText('Activate')).click();
expect(host.getCssValue('borderWidth')).toEqual('3px'); expect(host.getCssValue('borderWidth')).toEqual('3px');
}); });
it('allows conditional :host-context() styling', function() { it('allows conditional :host-context() styling', () => {
let h2 = element(by.css('app-hero-details h2')); const h2 = element(by.css('app-hero-details h2'));
expect(h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff expect(h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff
}); });
it('styles both view and content children with /deep/', function() { it('styles both view and content children with /deep/', () => {
let viewH3 = element(by.css('app-hero-team h3')); const viewH3 = element(by.css('app-hero-team h3'));
let contentH3 = element(by.css('app-hero-controls h3')); const contentH3 = element(by.css('app-hero-controls h3'));
expect(viewH3.getCssValue('fontStyle')).toEqual('italic'); expect(viewH3.getCssValue('fontStyle')).toEqual('italic');
expect(contentH3.getCssValue('fontStyle')).toEqual('italic'); expect(contentH3.getCssValue('fontStyle')).toEqual('italic');
}); });
it('includes styles loaded with CSS @import', function() { it('includes styles loaded with CSS @import', () => {
let host = element(by.css('app-hero-details')); const host = element(by.css('app-hero-details'));
expect(host.getCssValue('padding')).toEqual('10px'); expect(host.getCssValue('padding')).toEqual('10px');
}); });
it('processes template inline styles', function() { it('processes template inline styles', () => {
let button = element(by.css('app-hero-controls button')); const button = element(by.css('app-hero-controls button'));
let externalButton = element(by.css('body > button')); const externalButton = element(by.css('body > button'));
expect(button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff expect(button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff
expect(externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)'); expect(externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)');
}); });
it('processes template <link>s', function() { it('processes template <link>s', () => {
let li = element(by.css('app-hero-team li:first-child')); const li = element(by.css('app-hero-team li:first-child'));
let externalLi = element(by.css('body > ul li')); const externalLi = element(by.css('body > ul li'));
expect(li.getCssValue('listStyleType')).toEqual('square'); expect(li.getCssValue('listStyleType')).toEqual('square');
expect(externalLi.getCssValue('listStyleType')).not.toEqual('square'); expect(externalLi.getCssValue('listStyleType')).not.toEqual('square');

View File

@ -1,76 +1,74 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Dependency Injection Cookbook', function () { describe('Dependency Injection Cookbook', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should render Logged in User example', function () { it('should render Logged in User example', () => {
let loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0); const loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0);
expect(loggedInUser).toBeDefined(); expect(loggedInUser).toBeDefined();
}); });
it('"Bombasto" should be the logged in user', function () { it('"Bombasto" should be the logged in user', () => {
let loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0); const loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0);
expect(loggedInUser).toBeDefined(); expect(loggedInUser).toBeDefined();
}); });
it('should render sorted heroes', function () { it('should render sorted heroes', () => {
let sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0); const sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0);
expect(sortedHeroes).toBeDefined(); expect(sortedHeroes).toBeDefined();
}); });
it('Dr Nice should be in sorted heroes', function () { it('Dr Nice should be in sorted heroes', () => {
let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Dr Nice" and position()=2]')).get(0); const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Dr Nice" and position()=2]')).get(0);
expect(sortedHero).toBeDefined(); expect(sortedHero).toBeDefined();
}); });
it('RubberMan should be in sorted heroes', function () { it('RubberMan should be in sorted heroes', () => {
let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0); const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0);
expect(sortedHero).toBeDefined(); expect(sortedHero).toBeDefined();
}); });
it('Magma should be in sorted heroes', function () { it('Magma should be in sorted heroes', () => {
let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0); const sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0);
expect(sortedHero).toBeDefined(); expect(sortedHero).toBeDefined();
}); });
it('should render Hero of the Month', function () { it('should render Hero of the Month', () => {
let heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0); const heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0);
expect(heroOfTheMonth).toBeDefined(); expect(heroOfTheMonth).toBeDefined();
}); });
it('should render Hero Bios', function () { it('should render Hero Bios', () => {
let heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0); const heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0);
expect(heroBios).toBeDefined(); expect(heroBios).toBeDefined();
}); });
it('should render Magma\'s description in Hero Bios', function () { it('should render Magma\'s description in Hero Bios', () => {
let magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0); const magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0);
expect(magmaText).toBeDefined(); expect(magmaText).toBeDefined();
}); });
it('should render Magma\'s phone in Hero Bios and Contacts', function () { it('should render Magma\'s phone in Hero Bios and Contacts', () => {
let magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0); const magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0);
expect(magmaPhone).toBeDefined(); expect(magmaPhone).toBeDefined();
}); });
it('should render Hero-of-the-Month runner-ups', function () { it('should render Hero-of-the-Month runner-ups', () => {
let runnersUp = element(by.id('rups1')).getText(); const runnersUp = element(by.id('rups1')).getText();
expect(runnersUp).toContain('RubberMan, Dr Nice'); expect(runnersUp).toContain('RubberMan, Dr Nice');
}); });
it('should render DateLogger log entry in Hero-of-the-Month', function () { it('should render DateLogger log entry in Hero-of-the-Month', () => {
let logs = element.all(by.id('logs')).get(0).getText(); const logs = element.all(by.id('logs')).get(0).getText();
expect(logs).toContain('INFO: starting up at'); expect(logs).toContain('INFO: starting up at');
}); });
it('should highlight Hero Bios and Contacts container when mouseover', function () { it('should highlight Hero Bios and Contacts container when mouseover', () => {
let target = element(by.css('div[appHighlight="yellow"]')); const target = element(by.css('div[appHighlight="yellow"]'));
let yellow = 'rgba(255, 255, 0, 1)'; const yellow = 'rgba(255, 255, 0, 1)';
expect(target.getCssValue('background-color')).not.toEqual(yellow); expect(target.getCssValue('background-color')).not.toEqual(yellow);
@ -81,25 +79,25 @@ describe('Dependency Injection Cookbook', function () {
browser.wait(() => target.getCssValue('background-color').then(c => c === yellow), 2000); browser.wait(() => target.getCssValue('background-color').then(c => c === yellow), 2000);
}); });
describe('in Parent Finder', function () { describe('in Parent Finder', () => {
let cathy1 = element(by.css('alex cathy')); const cathy1 = element(by.css('alex cathy'));
let craig1 = element(by.css('alex craig')); const craig1 = element(by.css('alex craig'));
let carol1 = element(by.css('alex carol p')); const carol1 = element(by.css('alex carol p'));
let carol2 = element(by.css('barry carol p')); const carol2 = element(by.css('barry carol p'));
it('"Cathy" should find "Alex" via the component class', function () { it('"Cathy" should find "Alex" via the component class', () => {
expect(cathy1.getText()).toContain('Found Alex via the component'); expect(cathy1.getText()).toContain('Found Alex via the component');
}); });
it('"Craig" should not find "Alex" via the base class', function () { it('"Craig" should not find "Alex" via the base class', () => {
expect(craig1.getText()).toContain('Did not find Alex via the base'); expect(craig1.getText()).toContain('Did not find Alex via the base');
}); });
it('"Carol" within "Alex" should have "Alex" parent', function () { it('"Carol" within "Alex" should have "Alex" parent', () => {
expect(carol1.getText()).toContain('Alex'); expect(carol1.getText()).toContain('Alex');
}); });
it('"Carol" within "Barry" should have "Barry" parent', function () { it('"Carol" within "Barry" should have "Barry" parent', () => {
expect(carol2.getText()).toContain('Barry'); expect(carol2.getText()).toContain('Barry');
}); });
}); });

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
const routes: Routes = []; const routes: Routes = [];

View File

@ -2,9 +2,9 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
// #docregion import-services // #docregion import-services
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
import { UserContextService } from './user-context.service'; import { UserContextService } from './user-context.service';
import { UserService } from './user.service'; import { UserService } from './user.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@ -1,26 +1,26 @@
// #docregion // #docregion
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
// import { AppRoutingModule } from './app-routing.module'; // import { AppRoutingModule } from './app-routing.module';
import { LocationStrategy, import { LocationStrategy,
HashLocationStrategy } from '@angular/common'; HashLocationStrategy } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { HeroData } from './hero-data'; import { HeroData } from './hero-data';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeroBioComponent } from './hero-bio.component'; import { HeroBioComponent } from './hero-bio.component';
import { HeroBiosComponent, import { HeroBiosComponent,
HeroBiosAndContactsComponent } from './hero-bios.component'; HeroBiosAndContactsComponent } from './hero-bios.component';
import { HeroOfTheMonthComponent } from './hero-of-the-month.component'; import { HeroOfTheMonthComponent } from './hero-of-the-month.component';
import { HeroContactComponent } from './hero-contact.component'; import { HeroContactComponent } from './hero-contact.component';
import { HeroesBaseComponent, import { HeroesBaseComponent,
SortedHeroesComponent } from './sorted-heroes.component'; SortedHeroesComponent } from './sorted-heroes.component';
import { HighlightDirective } from './highlight.directive'; import { HighlightDirective } from './highlight.directive';
import { ParentFinderComponent, import { ParentFinderComponent,
AlexComponent, AlexComponent,
AliceComponent, AliceComponent,
@ -30,8 +30,8 @@ import { ParentFinderComponent,
CathyComponent, CathyComponent,
BarryComponent, BarryComponent,
BethComponent, BethComponent,
BobComponent } from './parent-finder.component'; BobComponent } from './parent-finder.component';
import { StorageComponent } from './storage.component'; import { StorageComponent } from './storage.component';
const declarations = [ const declarations = [
AppComponent, AppComponent,
@ -42,11 +42,11 @@ const declarations = [
ParentFinderComponent, ParentFinderComponent,
]; ];
const a_components = [AliceComponent, AlexComponent ]; const componentListA = [ AliceComponent, AlexComponent ];
const b_components = [ BarryComponent, BethComponent, BobComponent ]; const componentListB = [ BarryComponent, BethComponent, BobComponent ];
const c_components = [ const componentListC = [
CarolComponent, ChrisComponent, CraigComponent, CarolComponent, ChrisComponent, CraigComponent,
CathyComponent CathyComponent
]; ];
@ -61,9 +61,9 @@ const c_components = [
], ],
declarations: [ declarations: [
declarations, declarations,
a_components, componentListA,
b_components, componentListB,
c_components, componentListC,
StorageComponent, StorageComponent,
], ],
bootstrap: [ AppComponent ], bootstrap: [ AppComponent ],

View File

@ -1,6 +1,6 @@
/* tslint:disable:one-line*/ /* tslint:disable:one-line*/
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { HeroCacheService } from './hero-cache.service'; import { HeroCacheService } from './hero-cache.service';
// #docregion component // #docregion component
@Component({ @Component({

View File

@ -1,9 +1,9 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
//////// HeroBiosComponent //// //////// HeroBiosComponent ////
// #docregion simple // #docregion simple

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #docregion service // #docregion service

View File

@ -3,7 +3,7 @@
import { Component, Host, Optional } from '@angular/core'; import { Component, Host, Optional } from '@angular/core';
import { HeroCacheService } from './hero-cache.service'; import { HeroCacheService } from './hero-cache.service';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
// #docregion component // #docregion component
@Component({ @Component({

View File

@ -3,7 +3,7 @@ import { Hero } from './hero';
export class HeroData { export class HeroData {
createDb() { createDb() {
let heroes = [ const heroes = [
new Hero(1, 'Windstorm'), new Hero(1, 'Windstorm'),
new Hero(2, 'Bombasto'), new Hero(2, 'Bombasto'),
new Hero(3, 'Magneta'), new Hero(3, 'Magneta'),

View File

@ -1,8 +1,8 @@
// Illustrative (not used), mini-version of the actual HeroOfTheMonthComponent // Illustrative (not used), mini-version of the actual HeroOfTheMonthComponent
// Injecting with the MinimalLogger "interface-class" // Injecting with the MinimalLogger "interface-class"
import { Component, NgModule } from '@angular/core'; import { Component, NgModule } from '@angular/core';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
import { MinimalLogger } from './minimal-logger.service'; import { MinimalLogger } from './minimal-logger.service';
// #docregion // #docregion
@Component({ @Component({

View File

@ -10,12 +10,12 @@ export const TITLE = new InjectionToken<string>('title');
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DateLoggerService } from './date-logger.service'; import { DateLoggerService } from './date-logger.service';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
import { MinimalLogger } from './minimal-logger.service'; import { MinimalLogger } from './minimal-logger.service';
import { RUNNERS_UP, import { RUNNERS_UP,
runnersUpFactory } from './runners-up'; runnersUpFactory } from './runners-up';
// #enddocregion hero-of-the-month // #enddocregion hero-of-the-month
// #docregion some-hero // #docregion some-hero

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'

View File

@ -1,5 +1,4 @@
/* tslint:disable:no-unused-variable component-selector-name one-line check-open-brace */ // tslint:disable: component-selector space-before-function-paren
/* tslint:disable:*/
// #docplaster // #docplaster
// #docregion // #docregion
import { Component, forwardRef, Optional, SkipSelf } from '@angular/core'; import { Component, forwardRef, Optional, SkipSelf } from '@angular/core';
@ -20,8 +19,7 @@ const DifferentParent = Parent;
// The `parentType` defaults to `Parent` when omitting the second parameter. // The `parentType` defaults to `Parent` when omitting the second parameter.
// #docregion provide-the-parent // #docregion provide-the-parent
export function provideParent export function provideParent
// #enddocregion provide-parent, provide-the-parent // #enddocregion provide-the-parent
// #docregion provide-parent
(component: any, parentType?: any) { (component: any, parentType?: any) {
return { provide: parentType || Parent, useExisting: forwardRef(() => component) }; return { provide: parentType || Parent, useExisting: forwardRef(() => component) };
} }

View File

@ -2,7 +2,7 @@
// #docregion // #docregion
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #docregion runners-up // #docregion runners-up
@ -22,5 +22,5 @@ export function runnersUpFactory(take: number) {
.join(', '); .join(', ');
// #docregion factory-synopsis // #docregion factory-synopsis
}; };
}; }
// #enddocregion factory-synopsis // #enddocregion factory-synopsis

View File

@ -2,8 +2,8 @@
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
/////// HeroesBaseComponent ///// /////// HeroesBaseComponent /////
// #docregion heroes-base, injection // #docregion heroes-base, injection

View File

@ -1,9 +1,9 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { LoggerService } from './logger.service'; import { LoggerService } from './logger.service';
import { UserService } from './user.service'; import { UserService } from './user.service';
// #docregion injectables, injectable // #docregion injectables, injectable
@Injectable({ @Injectable({
@ -24,7 +24,7 @@ export class UserContextService {
// #enddocregion ctor, injectables // #enddocregion ctor, injectables
loadUser(userId: number) { loadUser(userId: number) {
let user = this.userService.getUserById(userId); const user = this.userService.getUserById(userId);
this.name = user.name; this.name = user.name;
this.role = user.role; this.role = user.role;

View File

@ -1,202 +1,196 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by, ElementFinder } from 'protractor'; import { browser, element, by, ElementFinder } from 'protractor';
describe('Dependency Injection Tests', function () { describe('Dependency Injection Tests', () => {
let expectedMsg: string; let expectedMsg: string;
let expectedMsgRx: RegExp; let expectedMsgRx: RegExp;
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
describe('Cars:', function() { describe('Cars:', () => {
it('DI car displays as expected', function () { it('DI car displays as expected', () => {
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.'; expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#di')).getText()).toEqual(expectedMsg); expect(element(by.css('#di')).getText()).toEqual(expectedMsg);
}); });
it('No DI car displays as expected', function () { it('No DI car displays as expected', () => {
expectedMsg = 'No DI car with 4 cylinders and Flintstone tires.'; expectedMsg = 'No DI car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#nodi')).getText()).toEqual(expectedMsg); expect(element(by.css('#nodi')).getText()).toEqual(expectedMsg);
}); });
it('Injector car displays as expected', function () { it('Injector car displays as expected', () => {
expectedMsg = 'Injector car with 4 cylinders and Flintstone tires.'; expectedMsg = 'Injector car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#injector')).getText()).toEqual(expectedMsg); expect(element(by.css('#injector')).getText()).toEqual(expectedMsg);
}); });
it('Factory car displays as expected', function () { it('Factory car displays as expected', () => {
expectedMsg = 'Factory car with 4 cylinders and Flintstone tires.'; expectedMsg = 'Factory car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#factory')).getText()).toEqual(expectedMsg); expect(element(by.css('#factory')).getText()).toEqual(expectedMsg);
}); });
it('Simple car displays as expected', function () { it('Simple car displays as expected', () => {
expectedMsg = 'Simple car with 4 cylinders and Flintstone tires.'; expectedMsg = 'Simple car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#simple')).getText()).toEqual(expectedMsg); expect(element(by.css('#simple')).getText()).toEqual(expectedMsg);
}); });
it('Super car displays as expected', function () { it('Super car displays as expected', () => {
expectedMsg = 'Super car with 12 cylinders and Flintstone tires.'; expectedMsg = 'Super car with 12 cylinders and Flintstone tires.';
expect(element(by.css('#super')).getText()).toEqual(expectedMsg); expect(element(by.css('#super')).getText()).toEqual(expectedMsg);
}); });
it('Test car displays as expected', function () { it('Test car displays as expected', () => {
expectedMsg = 'Test car with 8 cylinders and YokoGoodStone tires.'; expectedMsg = 'Test car with 8 cylinders and YokoGoodStone tires.';
expect(element(by.css('#test')).getText()).toEqual(expectedMsg); expect(element(by.css('#test')).getText()).toEqual(expectedMsg);
}); });
}); });
describe('Other Injections:', function() { describe('Other Injections:', () => {
it('DI car displays as expected', function () { it('DI car displays as expected', () => {
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.'; expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
expect(element(by.css('#car')).getText()).toEqual(expectedMsg); expect(element(by.css('#car')).getText()).toEqual(expectedMsg);
}); });
it('Hero displays as expected', function () { it('Hero displays as expected', () => {
expectedMsg = 'Dr Nice'; expectedMsg = 'Dr Nice';
expect(element(by.css('#hero')).getText()).toEqual(expectedMsg); expect(element(by.css('#hero')).getText()).toEqual(expectedMsg);
}); });
it('Optional injection displays as expected', function () { it('Optional injection displays as expected', () => {
expectedMsg = 'R.O.U.S.\'s? I don\'t think they exist!'; expectedMsg = 'R.O.U.S.\'s? I don\'t think they exist!';
expect(element(by.css('#rodent')).getText()).toEqual(expectedMsg); expect(element(by.css('#rodent')).getText()).toEqual(expectedMsg);
}); });
}); });
describe('Tests:', function() { describe('Tests:', () => {
it('Tests display as expected', function () { it('Tests display as expected', () => {
expectedMsgRx = /Tests passed/; expectedMsgRx = /Tests passed/;
expect(element(by.css('#tests')).getText()).toMatch(expectedMsgRx); expect(element(by.css('#tests')).getText()).toMatch(expectedMsgRx);
}); });
}); });
describe('Provider variations:', function() { describe('Provider variations:', () => {
it('P1 (class) displays as expected', function () { it('P1 (class) displays as expected', () => {
expectedMsg = 'Hello from logger provided with Logger class'; expectedMsg = 'Hello from logger provided with Logger class';
expect(element(by.css('#p1')).getText()).toEqual(expectedMsg); expect(element(by.css('#p1')).getText()).toEqual(expectedMsg);
}); });
it('P3 (provide) displays as expected', function () { it('P3 (provide) displays as expected', () => {
expectedMsg = 'Hello from logger provided with useClass:Logger'; expectedMsg = 'Hello from logger provided with useClass:Logger';
expect(element(by.css('#p3')).getText()).toEqual(expectedMsg); expect(element(by.css('#p3')).getText()).toEqual(expectedMsg);
}); });
it('P4 (useClass:BetterLogger) displays as expected', function () { it('P4 (useClass:BetterLogger) displays as expected', () => {
expectedMsg = 'Hello from logger provided with useClass:BetterLogger'; expectedMsg = 'Hello from logger provided with useClass:BetterLogger';
expect(element(by.css('#p4')).getText()).toEqual(expectedMsg); expect(element(by.css('#p4')).getText()).toEqual(expectedMsg);
}); });
it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', function () { it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', () => {
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger'; expectedMsg = 'Message to Bob: Hello from EvenBetterlogger';
expect(element(by.css('#p5')).getText()).toEqual(expectedMsg); expect(element(by.css('#p5')).getText()).toEqual(expectedMsg);
}); });
it('P6a (no alias) displays as expected', function () { it('P6a (no alias) displays as expected', () => {
expectedMsg = 'Hello OldLogger (but we want NewLogger)'; expectedMsg = 'Hello OldLogger (but we want NewLogger)';
expect(element(by.css('#p6a')).getText()).toEqual(expectedMsg); expect(element(by.css('#p6a')).getText()).toEqual(expectedMsg);
}); });
it('P6b (alias) displays as expected', function () { it('P6b (alias) displays as expected', () => {
expectedMsg = 'Hello from NewLogger (via aliased OldLogger)'; expectedMsg = 'Hello from NewLogger (via aliased OldLogger)';
expect(element(by.css('#p6b')).getText()).toEqual(expectedMsg); expect(element(by.css('#p6b')).getText()).toEqual(expectedMsg);
}); });
it('P7 (useValue) displays as expected', function () { it('P7 (useValue) displays as expected', () => {
expectedMsg = 'Silent logger says "Shhhhh!". Provided via "useValue"'; expectedMsg = 'Silent logger says "Shhhhh!". Provided via "useValue"';
expect(element(by.css('#p7')).getText()).toEqual(expectedMsg); expect(element(by.css('#p7')).getText()).toEqual(expectedMsg);
}); });
it('P8 (useFactory) displays as expected', function () { it('P8 (useFactory) displays as expected', () => {
expectedMsg = 'Hero service injected successfully via heroServiceProvider'; expectedMsg = 'Hero service injected successfully via heroServiceProvider';
expect(element(by.css('#p8')).getText()).toEqual(expectedMsg); expect(element(by.css('#p8')).getText()).toEqual(expectedMsg);
}); });
it('P9 (InjectionToken) displays as expected', function () { it('P9 (InjectionToken) displays as expected', () => {
expectedMsg = 'APP_CONFIG Application title is Dependency Injection'; expectedMsg = 'APP_CONFIG Application title is Dependency Injection';
expect(element(by.css('#p9')).getText()).toEqual(expectedMsg); expect(element(by.css('#p9')).getText()).toEqual(expectedMsg);
}); });
it('P10 (optional dependency) displays as expected', function () { it('P10 (optional dependency) displays as expected', () => {
expectedMsg = 'Optional logger was not available'; expectedMsg = 'Optional logger was not available';
expect(element(by.css('#p10')).getText()).toEqual(expectedMsg); expect(element(by.css('#p10')).getText()).toEqual(expectedMsg);
}); });
}); });
describe('User/Heroes:', function() { describe('User/Heroes:', () => {
it('User is Bob - unauthorized', function () { it('User is Bob - unauthorized', () => {
expectedMsgRx = /Bob, is not authorized/; expectedMsgRx = /Bob, is not authorized/;
expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx); expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx);
}); });
it('should have button', function () { it('should have button', () => {
expect(element.all(by.cssContainingText('button', 'Next User')) expect(element.all(by.cssContainingText('button', 'Next User'))
.get(0).isDisplayed()).toBe(true, '\'Next User\' button should be displayed'); .get(0).isDisplayed()).toBe(true, '\'Next User\' button should be displayed');
}); });
it('unauthorized user should have multiple unauthorized heroes', function () { it('unauthorized user should have multiple unauthorized heroes', () => {
let heroes = element.all(by.css('#unauthorized app-hero-list div')); const heroes = element.all(by.css('#unauthorized app-hero-list div'));
expect(heroes.count()).toBeGreaterThan(0); expect(heroes.count()).toBeGreaterThan(0);
}); });
it('unauthorized user should have no secret heroes', function () { it('unauthorized user should have no secret heroes', () => {
let heroes = element.all(by.css('#unauthorized app-hero-list div')); const heroes = element.all(by.css('#unauthorized app-hero-list div'));
expect(heroes.count()).toBeGreaterThan(0); expect(heroes.count()).toBeGreaterThan(0);
let filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => { const filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => {
return elem.getText().then((text: string) => { return elem.getText().then((text: string) => /secret/.test(text));
return /secret/.test(text);
});
}); });
expect(filteredHeroes.count()).toEqual(0); expect(filteredHeroes.count()).toEqual(0);
}); });
it('unauthorized user should have no authorized heroes listed', function () { it('unauthorized user should have no authorized heroes listed', () => {
expect(element.all(by.css('#authorized app-hero-list div')).count()).toEqual(0); expect(element.all(by.css('#authorized app-hero-list div')).count()).toEqual(0);
}); });
describe('after button click', function() { describe('after button click', () => {
beforeAll(function (done: any) { beforeAll((done: any) => {
let buttonEle = element.all(by.cssContainingText('button', 'Next User')).get(0); const buttonEle = element.all(by.cssContainingText('button', 'Next User')).get(0);
buttonEle.click().then(done, done); buttonEle.click().then(done, done);
}); });
it('User is Alice - authorized', function () { it('User is Alice - authorized', () => {
expectedMsgRx = /Alice, is authorized/; expectedMsgRx = /Alice, is authorized/;
expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx); expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx);
}); });
it('authorized user should have multiple authorized heroes ', function () { it('authorized user should have multiple authorized heroes ', () => {
let heroes = element.all(by.css('#authorized app-hero-list div')); const heroes = element.all(by.css('#authorized app-hero-list div'));
expect(heroes.count()).toBeGreaterThan(0); expect(heroes.count()).toBeGreaterThan(0);
}); });
it('authorized user should have multiple authorized heroes with tree-shakeable HeroesService', function () { it('authorized user should have multiple authorized heroes with tree-shakeable HeroesService', () => {
let heroes = element.all(by.css('#tspAuthorized app-hero-list div')); const heroes = element.all(by.css('#tspAuthorized app-hero-list div'));
expect(heroes.count()).toBeGreaterThan(0); expect(heroes.count()).toBeGreaterThan(0);
}); });
it('authorized user should have secret heroes', function () { it('authorized user should have secret heroes', () => {
let heroes = element.all(by.css('#authorized app-hero-list div')); const heroes = element.all(by.css('#authorized app-hero-list div'));
expect(heroes.count()).toBeGreaterThan(0); expect(heroes.count()).toBeGreaterThan(0);
let filteredHeroes = heroes.filter(function(elem: ElementFinder, index: number) { const filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => {
return elem.getText().then(function(text: string) { return elem.getText().then((text: string) => /secret/.test(text));
return /secret/.test(text);
});
}); });
expect(filteredHeroes.count()).toBeGreaterThan(0); expect(filteredHeroes.count()).toBeGreaterThan(0);
}); });
it('authorized user should have no unauthorized heroes listed', function () { it('authorized user should have no unauthorized heroes listed', () => {
expect(element.all(by.css('#unauthorized app-hero-list div')).count()).toEqual(0); expect(element.all(by.css('#unauthorized app-hero-list div')).count()).toEqual(0);
}); });
}); });

View File

@ -2,7 +2,7 @@
// #docregion imports // #docregion imports
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { APP_CONFIG, AppConfig } from './app.config'; import { APP_CONFIG, AppConfig } from './app.config';
// #enddocregion imports // #enddocregion imports
@Component({ @Component({

View File

@ -1,8 +1,8 @@
// #docplaster // #docplaster
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { APP_CONFIG, HERO_DI_CONFIG } from './app.config'; import { APP_CONFIG, HERO_DI_CONFIG } from './app.config';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { CarComponent } from './car/car.component'; import { CarComponent } from './car/car.component';
import { HeroesComponent } from './heroes/heroes.component'; import { HeroesComponent } from './heroes/heroes.component';

View File

@ -7,7 +7,7 @@ import { Car, Engine, Tires } from './car';
export function simpleCar() { export function simpleCar() {
// #docregion car-ctor-instantiation // #docregion car-ctor-instantiation
// Simple car with 4 cylinders and Flintstone tires. // Simple car with 4 cylinders and Flintstone tires.
let car = new Car(new Engine(), new Tires()); const car = new Car(new Engine(), new Tires());
// #enddocregion car-ctor-instantiation // #enddocregion car-ctor-instantiation
car.description = 'Simple'; car.description = 'Simple';
return car; return car;
@ -16,30 +16,31 @@ export function simpleCar() {
///////// example 2 //////////// ///////// example 2 ////////////
// #docregion car-ctor-instantiation-with-param // #docregion car-ctor-instantiation-with-param
class Engine2 { class Engine2 {
constructor(public cylinders: number) { } constructor(public cylinders: number) { }
} }
// #enddocregion car-ctor-instantiation-with-param // #enddocregion car-ctor-instantiation-with-param
export function superCar() { export function superCar() {
// #docregion car-ctor-instantiation-with-param // #docregion car-ctor-instantiation-with-param
// Super car with 12 cylinders and Flintstone tires. // Super car with 12 cylinders and Flintstone tires.
let bigCylinders = 12; const bigCylinders = 12;
let car = new Car(new Engine2(bigCylinders), new Tires()); const car = new Car(new Engine2(bigCylinders), new Tires());
// #enddocregion car-ctor-instantiation-with-param // #enddocregion car-ctor-instantiation-with-param
car.description = 'Super'; car.description = 'Super';
return car; return car;
} }
/////////// example 3 ////////// /////////// example 3 //////////
// #docregion car-ctor-instantiation-with-mocks // #docregion car-ctor-instantiation-with-mocks
class MockEngine extends Engine { cylinders = 8; } class MockEngine extends Engine { cylinders = 8; }
class MockTires extends Tires { make = 'YokoGoodStone'; } class MockTires extends Tires { make = 'YokoGoodStone'; }
// #enddocregion car-ctor-instantiation-with-mocks // #enddocregion car-ctor-instantiation-with-mocks
export function testCar() { export function testCar() {
// #docregion car-ctor-instantiation-with-mocks // #docregion car-ctor-instantiation-with-mocks
// Test car with 8 cylinders and YokoGoodStone tires. // Test car with 8 cylinders and YokoGoodStone tires.
let car = new Car(new MockEngine(), new MockTires()); const car = new Car(new MockEngine(), new MockTires());
// #enddocregion car-ctor-instantiation-with-mocks // #enddocregion car-ctor-instantiation-with-mocks
car.description = 'Test'; car.description = 'Test';
return car; return car;

View File

@ -4,7 +4,7 @@ import { Engine, Tires, Car } from './car';
// BAD pattern! // BAD pattern!
export class CarFactory { export class CarFactory {
createCar() { createCar() {
let car = new Car(this.createEngine(), this.createTires()); const car = new Car(this.createEngine(), this.createTires());
car.description = 'Factory'; car.description = 'Factory';
return car; return car;
} }

View File

@ -1,7 +1,7 @@
import { Injector } from '@angular/core'; import { Injector } from '@angular/core';
import { Car, Engine, Tires } from './car'; import { Car, Engine, Tires } from './car';
import { Logger } from '../logger.service'; import { Logger } from '../logger.service';
// #docregion injector // #docregion injector
export function useInjector() { export function useInjector() {
@ -26,14 +26,14 @@ export function useInjector() {
] ]
}); });
// #docregion injector-call // #docregion injector-call
let car = injector.get(Car); const car = injector.get(Car);
// #enddocregion injector-call, injector-create-and-call // #enddocregion injector-call, injector-create-and-call
car.description = 'Injector'; car.description = 'Injector';
injector = Injector.create({ injector = Injector.create({
providers: [{ provide: Logger, deps: [] }] providers: [{ provide: Logger, deps: [] }]
}); });
let logger = injector.get(Logger); const logger = injector.get(Logger);
logger.log('Injector car.drive() said: ' + car.drive()); logger.log('Injector car.drive() said: ' + car.drive());
return car; return car;
} }

View File

@ -1,15 +1,15 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Car, Engine, Tires } from './car'; import { Car, Engine, Tires } from './car';
import { Car as CarNoDi } from './car-no-di'; import { Car as CarNoDi } from './car-no-di';
import { CarFactory } from './car-factory'; import { CarFactory } from './car-factory';
import { testCar, import { testCar,
simpleCar, simpleCar,
superCar } from './car-creations'; superCar } from './car-creations';
import { useInjector } from './car-injector'; import { useInjector } from './car-injector';
@Component({ @Component({
@ -27,9 +27,9 @@ import { useInjector } from './car-injector';
providers: [Car, Engine, Tires] providers: [Car, Engine, Tires]
}) })
export class CarComponent { export class CarComponent {
factoryCar = (new CarFactory).createCar(); factoryCar = (new CarFactory()).createCar();
injectorCar = useInjector(); injectorCar = useInjector();
noDiCar = new CarNoDi; noDiCar = new CarNoDi();
simpleCar = simpleCar(); simpleCar = simpleCar();
superCar = superCar(); superCar = superCar();
testCar = testCar(); testCar = testCar();

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
@Component({ @Component({
selector: 'app-hero-list', selector: 'app-hero-list',

View File

@ -1,7 +1,7 @@
// #docplaster // #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
// #enddocregion // #enddocregion
import { HeroService } from './hero.service.1'; import { HeroService } from './hero.service.1';
/* /*

View File

@ -1,7 +1,7 @@
/* tslint:disable:one-line */ /* tslint:disable:one-line */
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@Component({ @Component({

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service'; import { Logger } from '../logger.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',

View File

@ -1,11 +1,11 @@
/* tslint:disable:one-line */ /* tslint:disable:one-line */
// #docregion // #docregion
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
import { Logger } from '../logger.service'; import { Logger } from '../logger.service';
import { UserService } from '../user.service'; import { UserService } from '../user.service';
// #docregion factory // #docregion factory
let heroServiceFactory = (logger: Logger, userService: UserService) => { const heroServiceFactory = (logger: Logger, userService: UserService) => {
return new HeroService(logger, userService.user.isAuthorized); return new HeroService(logger, userService.user.isAuthorized);
}; };
// #enddocregion factory // #enddocregion factory

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes'; import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service'; import { Logger } from '../logger.service';
import { UserService } from '../user.service'; import { UserService } from '../user.service';
@Injectable({ @Injectable({
@ -17,7 +17,7 @@ export class HeroService {
private isAuthorized: boolean) { } private isAuthorized: boolean) { }
getHeroes() { getHeroes() {
let auth = this.isAuthorized ? 'authorized ' : 'unauthorized'; const auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`); this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter(hero => this.isAuthorized || !hero.isSecret); return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
} }

View File

@ -1,5 +1,5 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { heroServiceProvider } from './hero.service.provider'; import { heroServiceProvider } from './hero.service.provider';
@Component({ @Component({

View File

@ -2,11 +2,11 @@
// #docregion // #docregion
import { Component, Injector, OnInit } from '@angular/core'; import { Component, Injector, OnInit } from '@angular/core';
import { Car, Engine, Tires } from './car/car'; import { Car, Engine, Tires } from './car/car';
import { Hero } from './heroes/hero'; import { Hero } from './heroes/hero';
import { HeroService } from './heroes/hero.service'; import { HeroService } from './heroes/hero.service';
import { heroServiceProvider } from './heroes/hero.service.provider'; import { heroServiceProvider } from './heroes/hero.service.provider';
import { Logger } from './logger.service'; import { Logger } from './logger.service';
// #docregion injector // #docregion injector
@Component({ @Component({
@ -36,7 +36,7 @@ export class InjectorComponent implements OnInit {
} }
get rodent() { get rodent() {
let rousDontExist = `R.O.U.S.'s? I don't think they exist!`; const rousDontExist = `R.O.U.S.'s? I don't think they exist!`;
return this.injector.get(ROUS, rousDontExist); return this.injector.get(ROUS, rousDontExist);
} }
} }

View File

@ -18,7 +18,7 @@ const template = '{{log}}';
@Component({ @Component({
selector: 'provider-1', selector: 'provider-1',
template: template, template,
// #docregion providers-1, providers-logger // #docregion providers-1, providers-logger
providers: [Logger] providers: [Logger]
// #enddocregion providers-1, providers-logger // #enddocregion providers-1, providers-logger
@ -35,7 +35,7 @@ export class Provider1Component {
@Component({ @Component({
selector: 'provider-3', selector: 'provider-3',
template: template, template,
providers: providers:
// #docregion providers-3 // #docregion providers-3
[{ provide: Logger, useClass: Logger }] [{ provide: Logger, useClass: Logger }]
@ -54,7 +54,7 @@ export class BetterLogger extends Logger {}
@Component({ @Component({
selector: 'provider-4', selector: 'provider-4',
template: template, template,
providers: providers:
// #docregion providers-4 // #docregion providers-4
[{ provide: Logger, useClass: BetterLogger }] [{ provide: Logger, useClass: BetterLogger }]
@ -76,7 +76,7 @@ export class EvenBetterLogger extends Logger {
constructor(private userService: UserService) { super(); } constructor(private userService: UserService) { super(); }
log(message: string) { log(message: string) {
let name = this.userService.user.name; const name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`); super.log(`Message to ${name}: ${message}`);
} }
} }
@ -84,7 +84,7 @@ export class EvenBetterLogger extends Logger {
@Component({ @Component({
selector: 'provider-5', selector: 'provider-5',
template: template, template,
providers: providers:
// #docregion providers-5 // #docregion providers-5
[ UserService, [ UserService,
@ -107,12 +107,12 @@ export class OldLogger {
logs: string[] = []; logs: string[] = [];
log(message: string) { log(message: string) {
throw new Error('Should not call the old logger!'); throw new Error('Should not call the old logger!');
}; }
} }
@Component({ @Component({
selector: 'provider-6a', selector: 'provider-6a',
template: template, template,
providers: providers:
// #docregion providers-6a // #docregion providers-6a
[ NewLogger, [ NewLogger,
@ -135,7 +135,7 @@ export class Provider6aComponent {
@Component({ @Component({
selector: 'provider-6b', selector: 'provider-6b',
template: template, template,
providers: providers:
// #docregion providers-6b // #docregion providers-6b
[ NewLogger, [ NewLogger,
@ -168,7 +168,7 @@ export const SilentLogger = {
@Component({ @Component({
selector: 'provider-7', selector: 'provider-7',
template: template, template,
providers: providers:
// #docregion providers-7 // #docregion providers-7
[{ provide: Logger, useValue: SilentLogger }] [{ provide: Logger, useValue: SilentLogger }]
@ -186,7 +186,7 @@ export class Provider7Component {
@Component({ @Component({
selector: 'provider-8', selector: 'provider-8',
template: template, template,
providers: [heroServiceProvider, Logger, UserService] providers: [heroServiceProvider, Logger, UserService]
}) })
export class Provider8Component { export class Provider8Component {
@ -202,7 +202,7 @@ export class Provider8Component {
@Component({ @Component({
selector: 'provider-9', selector: 'provider-9',
template: template, template,
/* /*
// #docregion providers-9-interface // #docregion providers-9-interface
// FAIL! Can't use interface as provider token // FAIL! Can't use interface as provider token
@ -237,11 +237,11 @@ export class Provider9Component implements OnInit {
import { Optional } from '@angular/core'; import { Optional } from '@angular/core';
// #enddocregion import-optional // #enddocregion import-optional
let some_message = 'Hello from the injected logger'; const someMessage = 'Hello from the injected logger';
@Component({ @Component({
selector: 'provider-10', selector: 'provider-10',
template: template, template,
providers: [{ provide: Logger, useValue: null }] providers: [{ provide: Logger, useValue: null }]
}) })
export class Provider10Component implements OnInit { export class Provider10Component implements OnInit {
@ -249,7 +249,7 @@ export class Provider10Component implements OnInit {
// #docregion provider-10-ctor // #docregion provider-10-ctor
constructor(@Optional() private logger?: Logger) { constructor(@Optional() private logger?: Logger) {
if (this.logger) { if (this.logger) {
this.logger.log(some_message); this.logger.log(someMessage);
} }
} }
// #enddocregion provider-10-ctor // #enddocregion provider-10-ctor

View File

@ -43,7 +43,7 @@ var testResults: {pass: string; message: string};
function expect(actual: any) { function expect(actual: any) {
return { return {
toEqual: function(expected: any){ toEqual: (expected: any) => {
testResults = actual === expected ? testResults = actual === expected ?
{pass: 'passed', message: testName} : {pass: 'passed', message: testName} :
{pass: 'failed', message: `${testName}; expected ${actual} to equal ${expected}.`}; {pass: 'failed', message: `${testName}; expected ${actual} to equal ${expected}.`};

View File

@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { ServiceModule } from './service-and-module'; import { ServiceModule } from './service-and-module';
// #docregion // #docregion

View File

@ -8,8 +8,8 @@ export class User {
} }
// TODO: get the user; don't 'new' it. // TODO: get the user; don't 'new' it.
let alice = new User('Alice', true); const alice = new User('Alice', true);
let bob = new User('Bob', false); const bob = new User('Bob', false);
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'

View File

@ -1,29 +1,27 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Displaying Data Tests', function () { describe('Displaying Data Tests', () => {
let _title = 'Tour of Heroes'; const title = 'Tour of Heroes';
let _defaultHero = 'Windstorm'; const defaultHero = 'Windstorm';
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should display correct title: ' + _title, function () { it('should display correct title: ' + title, () => {
expect(element(by.css('h1')).getText()).toEqual(_title); expect(element(by.css('h1')).getText()).toEqual(title);
}); });
it('should have correct default hero: ' + _defaultHero, function () { it('should have correct default hero: ' + defaultHero, () => {
expect(element(by.css('h2')).getText()).toContain(_defaultHero); expect(element(by.css('h2')).getText()).toContain(defaultHero);
}); });
it('should have heroes', function () { it('should have heroes', () => {
let heroEls = element.all(by.css('li')); const heroEls = element.all(by.css('li'));
expect(heroEls.count()).not.toBe(0, 'should have heroes'); expect(heroEls.count()).not.toBe(0, 'should have heroes');
}); });
it('should display "there are many heroes!"', function () { it('should display "there are many heroes!"', () => {
expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!'); expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!');
}); });
}); });

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';

View File

@ -1,15 +1,13 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
describe('Docs Style Guide', function () { describe('Docs Style Guide', () => {
let _title = 'Authors Style Guide Sample'; const title = 'Authors Style Guide Sample';
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should display correct title: ' + _title, function () { it('should display correct title: ' + title, () => {
expect(element(by.css('h1')).getText()).toEqual(_title); expect(element(by.css('h1')).getText()).toEqual(title);
}); });
}); });

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
// #docregion class // #docregion class
@NgModule({ @NgModule({

View File

@ -1,18 +1,16 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
/* tslint:disable:quotemark */ /* tslint:disable:quotemark */
describe('Dynamic Component Loader', function () { describe('Dynamic Component Loader', () => {
beforeEach(function () { beforeEach(() => {
browser.get(''); browser.get('');
}); });
it('should load ad banner', function () { it('should load ad banner', () => {
let headline = element(by.xpath("//h4[text()='Featured Hero Profile']")); const headline = element(by.xpath("//h4[text()='Featured Hero Profile']"));
let name = element(by.xpath("//h3[text()='Bombasto']")); const name = element(by.xpath("//h3[text()='Bombasto']"));
let bio = element(by.xpath("//p[text()='Brave as they come']")); const bio = element(by.xpath("//p[text()='Brave as they come']"));
expect(name).toBeDefined(); expect(name).toBeDefined();
expect(headline).toBeDefined(); expect(headline).toBeDefined();

View File

@ -2,7 +2,7 @@
import { Component, Input, OnInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core'; import { Component, Input, OnInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core';
import { AdDirective } from './ad.directive'; import { AdDirective } from './ad.directive';
import { AdItem } from './ad-item'; import { AdItem } from './ad-item';
import { AdComponent } from './ad.component'; import { AdComponent } from './ad.component';
@Component({ @Component({
@ -11,7 +11,7 @@ import { AdComponent } from './ad.component';
template: ` template: `
<div class="ad-banner-example"> <div class="ad-banner-example">
<h3>Advertisements</h3> <h3>Advertisements</h3>
<ng-template ad-host></ng-template> <ng-template adHost></ng-template>
</div> </div>
` `
// #enddocregion ad-host // #enddocregion ad-host
@ -43,8 +43,8 @@ export class AdBannerComponent implements OnInit, OnDestroy {
const viewContainerRef = this.adHost.viewContainerRef; const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear(); viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory); const componentRef = viewContainerRef.createComponent<AdComponent>(componentFactory);
(<AdComponent>componentRef.instance).data = adItem.data; componentRef.instance.data = adItem.data;
} }
getAds() { getAds() {

View File

@ -1,8 +1,9 @@
// tslint:disable: directive-selector
// #docregion // #docregion
import { Directive, ViewContainerRef } from '@angular/core'; import { Directive, ViewContainerRef } from '@angular/core';
@Directive({ @Directive({
selector: '[ad-host]', selector: '[adHost]',
}) })
export class AdDirective { export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { } constructor(public viewContainerRef: ViewContainerRef) { }

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HeroJobAdComponent } from './hero-job-ad.component'; import { HeroJobAdComponent } from './hero-job-ad.component';
import { HeroProfileComponent } from './hero-profile.component'; import { HeroProfileComponent } from './hero-profile.component';
import { AdItem } from './ad-item'; import { AdItem } from './ad-item';
@Injectable() @Injectable()
export class AdService { export class AdService {

View File

@ -1,8 +1,8 @@
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { AdService } from './ad.service'; import { AdService } from './ad.service';
import { AdItem } from './ad-item'; import { AdItem } from './ad-item';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@ -1,12 +1,12 @@
// #docregion // #docregion
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeroJobAdComponent } from './hero-job-ad.component'; import { HeroJobAdComponent } from './hero-job-ad.component';
import { AdBannerComponent } from './ad-banner.component'; import { AdBannerComponent } from './ad-banner.component';
import { HeroProfileComponent } from './hero-profile.component'; import { HeroProfileComponent } from './hero-profile.component';
import { AdDirective } from './ad.directive'; import { AdDirective } from './ad.directive';
import { AdService } from './ad.service'; import { AdService } from './ad.service';
@NgModule({ @NgModule({
imports: [ BrowserModule ], imports: [ BrowserModule ],

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { AdComponent } from './ad.component'; import { AdComponent } from './ad.component';
@Component({ @Component({
template: ` template: `

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { AdComponent } from './ad.component'; import { AdComponent } from './ad.component';
@Component({ @Component({
template: ` template: `

View File

@ -1,27 +1,25 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor'; import { browser, element, by } from 'protractor';
/* tslint:disable:quotemark */ /* tslint:disable:quotemark */
describe('Dynamic Form', function () { describe('Dynamic Form', () => {
beforeAll(function () { beforeAll(() => {
browser.get(''); browser.get('');
}); });
it('should submit form', function () { it('should submit form', () => {
let firstNameElement = element.all(by.css('input[id=firstName]')).get(0); const firstNameElement = element.all(by.css('input[id=firstName]')).get(0);
expect(firstNameElement.getAttribute('value')).toEqual('Bombasto'); expect(firstNameElement.getAttribute('value')).toEqual('Bombasto');
let emailElement = element.all(by.css('input[id=emailAddress]')).get(0); const emailElement = element.all(by.css('input[id=emailAddress]')).get(0);
let email = 'test@test.com'; const email = 'test@test.com';
emailElement.sendKeys(email); emailElement.sendKeys(email);
expect(emailElement.getAttribute('value')).toEqual(email); expect(emailElement.getAttribute('value')).toEqual(email);
element(by.css('select option[value="solid"]')).click(); element(by.css('select option[value="solid"]')).click();
let saveButton = element.all(by.css('button')).get(0); const saveButton = element.all(by.css('button')).get(0);
saveButton.click().then(function() { saveButton.click().then(() => {
expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true); expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true);
}); });
}); });

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { QuestionService } from './question.service'; import { QuestionService } from './question.service';
import { QuestionBase } from './question-base'; import { QuestionBase } from './question-base';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@ -1,10 +1,10 @@
// #docregion // #docregion
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { DynamicFormComponent } from './dynamic-form.component'; import { DynamicFormComponent } from './dynamic-form.component';
import { DynamicFormQuestionComponent } from './dynamic-form-question.component'; import { DynamicFormQuestionComponent } from './dynamic-form-question.component';
@NgModule({ @NgModule({

View File

@ -1,8 +1,8 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base'; import { QuestionBase } from './question-base';
@Component({ @Component({
selector: 'app-question', selector: 'app-question',

View File

@ -1,9 +1,9 @@
// #docregion // #docregion
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { QuestionBase } from './question-base'; import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service'; import { QuestionControlService } from './question-control.service';
@Component({ @Component({
selector: 'app-dynamic-form', selector: 'app-dynamic-form',

View File

@ -10,13 +10,14 @@ export class QuestionBase<T> {
options: {key: string, value: string}[]; options: {key: string, value: string}[];
constructor(options: { constructor(options: {
value?: T, value?: T;
key?: string, key?: string;
label?: string, label?: string;
required?: boolean, required?: boolean;
order?: number, order?: number;
controlType?: string, controlType?: string;
type?: string type?: string;
options?: {key: string, value: string}[];
} = {}) { } = {}) {
this.value = options.value; this.value = options.value;
this.key = options.key || ''; this.key = options.key || '';
@ -25,5 +26,6 @@ export class QuestionBase<T> {
this.order = options.order === undefined ? 1 : options.order; this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || ''; this.controlType = options.controlType || '';
this.type = options.type || ''; this.type = options.type || '';
this.options = options.options || [];
} }
} }

View File

@ -1,5 +1,5 @@
// #docregion // #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { QuestionBase } from './question-base'; import { QuestionBase } from './question-base';
@ -9,7 +9,7 @@ export class QuestionControlService {
constructor() { } constructor() { }
toFormGroup(questions: QuestionBase<string>[] ) { toFormGroup(questions: QuestionBase<string>[] ) {
let group: any = {}; const group: any = {};
questions.forEach(question => { questions.forEach(question => {
group[question.key] = question.required ? new FormControl(question.value || '', Validators.required) group[question.key] = question.required ? new FormControl(question.value || '', Validators.required)

View File

@ -3,10 +3,4 @@ import { QuestionBase } from './question-base';
export class DropdownQuestion extends QuestionBase<string> { export class DropdownQuestion extends QuestionBase<string> {
controlType = 'dropdown'; controlType = 'dropdown';
options: {key: string, value: string}[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
} }

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