Compare commits

...

107 Commits
8.0.0 ... 8.0.2

Author SHA1 Message Date
a73b8a62c8 release: cut the v8.0.2 release 2019-06-19 10:33:28 -07:00
2fe8f2b1e0 build: update buildifier to version that supports windows (#31129)
* Updates buildifier to a version that also comes with windows binaries.
* Fixes a few new formatting/lint warnings
* Removes the `args-order` warning because it is no longer a warning.. and is now part of the formatter.

Patch version of #31112

PR Close #31129
2019-06-19 08:50:43 -07:00
f1c08c6918 docs: add core and cli version alignment note (#30976)
docs: add core and cli version alignment note
PR Close #30976
2019-06-19 08:49:25 -07:00
bf9de8ca53 test: update side-effects test to be more descriptive (#31005)
This test will now list diffs for failed expectations and the full command to update them.

Fix #30570

PR Close #31005
2019-06-18 13:58:59 -07:00
df37c47ac5 ci: add jasonaden to forms owners (#31117)
PR Close #31117
2019-06-18 13:58:30 -07:00
788d19c036 build: enable shard_count for some jasmine tests that have many specs (#31009)
This partitions the spects across multiple processes so they run in parallel.

PR Close #31009
2019-06-18 12:11:31 -07:00
8492499fe7 docs: add missing word in build and deploy guide to clarify sentence (#31093)
PR Close #31093
2019-06-18 11:00:28 -07:00
48174010a1 docs: update group() link to point to the animations group API (#30618)
PR Close #30618
2019-06-18 10:49:46 -07:00
1460e46ba1 build: ts-api-guardian npm package contains invalid references (#31096)
Currently when building the `ts-api-guardian` npm package,
the labels are not properly replaced after recent changes to
the `entry_point` attribute. This means that the `ts-api-guardian`
package is currently not usable externally.

PR Close #31096
2019-06-18 09:50:42 -07:00
a7ff7d8d27 docs: refresh TypeScript configuration guide with updated info and files (#31097)
This removes the hard-coded tsconfig.json to use a separate file.
The tsconfig.0.json is added to the getting-started example folder
because have to check it with every major release
Also updates the text regarding defaults for TypeScript compilation targets
and typings

PR Close #31097
2019-06-18 09:47:49 -07:00
eb970b4626 docs: rewrite Observable examples with pipable operators (#31074)
PR Close #31074
2019-06-18 09:47:07 -07:00
18d301258a docs: change interceptor documentation (#30969)
PR Close #30969
2019-06-18 09:46:35 -07:00
6a01fa532c docs: add mock heroes to the code review tabs for TOH pt. 2 (#31080)
PR Close #31080
2019-06-17 16:34:39 -07:00
e190a7e9de fix(docs-infra): fix search result header color (#30924)
PR Close #30924
2019-06-17 16:33:54 -07:00
53c6425954 ci: publish snapshots job is unable to decode github token (#31099)
The publish_snapshots job is currently not able to decode the Github
token because the openssl version changed. This is because the default
digest for more recent openssl version has been updated and the github
token file has been encrypted with an old digest. We need to ensure
that the md5 digest is used for decryption as that matches the
digest used for encryption.

PR Close #31099
2019-06-17 13:56:13 -07:00
c198dc6c19 fix(bazel): builder workspace should use nodejs v10.16.0 (#31088)
The generated Bazel workspace by the `@angular/bazel` builder should
use the latest stable NodeJS version. This is necessary because some
packages like `selenium-webdriver` which are part of the default bazel
setup in order to support `ng e2e` depend on a minimum NodeJS version
of `10.15.0`.. This means that running e2e tests in a plain new bazel CLI
project (`ng new {projectName} --collection=@angular/bazel`) errors.

```
command.
 (error selenium-webdriver@4.0.0-alpha.3: The engine "node" is incompatible with
 this module. Expected version ">= 10.15.0". Got "10.13.0"
error Found incompatible module
)
```

PR Close #31088
2019-06-17 13:07:28 -07:00
d1b26dd3a1 ci: update nodejs version to v10.16.0 (#31088)
Updates the NodeJS version to the latest stable version at the time of
writing (v10.16.0). We need to update our image to use a minimum NodeJS
version of v10.15.0 because new CLI apps automatically install a non-locked
version of selenium-webdriver that now requires NodeJS >= 10.15.0 since the
latest release of 17th June 2019 (4.0.0-alpha.3).

See CI failures: https://circleci.com/gh/angular/angular/359077

PR Close #31088
2019-06-17 13:07:28 -07:00
ce7131f3fb ci(docs-infra): disable payload size tracking for test_aio_local and test_aio_local_ivy (#31064)
Payload size tracking for AIO introduced in the previous commit (c596795e64) works fine for master branch, but fails in patch branch. In order to keep CI for patch branch healthy, the size tracking is temporary disabled and will be turned on again after additional investigation.

PR Close #31064
2019-06-14 16:03:39 -07:00
934702892e ci(docs-infra): check and track payload sizes for test_aio_local and test_aio_local_ivy (#31047)
PR Close #31047
2019-06-14 14:38:11 -07:00
8a830a72e2 ci(docs-infra): run PWA score tests after unit/e2e tests (#31047)
Previously, we run the PWA score tests before unit/e2e tests, because
the latter would destroy the `dist/` directory required by the former.

Since cli@6, unit/e2e tests no longer detroy the `dist/` directory, so
it is now safe to run the unit/e2e tests first. This is preferrable,
since they are conceptually lower-level and any error messages (in case
of breakage) are more specific/actionable.

Related discussion about cli behavior:
- angular/angular-cli#4366
- angular/angular-cli#14701

PR Close #31047
2019-06-14 14:38:11 -07:00
2abb54c4a1 docs: rewrite attribute binding section and add example (#26004)
PR Close #26004
2019-06-14 12:21:28 -07:00
44632bb700 docs: edit codeowners for new Template Syntax examples (#31060)
PR Close #31060
2019-06-14 11:54:30 -07:00
e33b382ab4 docs: edit and add example for Template Expression Operators section of Template Syntax (#28087)
PR Close #28087
2019-06-14 11:53:51 -07:00
c4da400db4 docs: rewrite inputs/outputs section of Template Syntax (#27685)
PR Close #27685
2019-06-14 11:53:14 -07:00
c40e6b2edc docs: edit template ref vars copy and example (#27371)
PR Close #27371
2019-06-14 11:52:34 -07:00
978fc27314 docs: rewrite built-in directives section (#27273)
PR Close #27273
2019-06-14 11:51:47 -07:00
b9f2bbb7d1 docs: add example and edit two-way-binding section of Template Syntax (#26278)
PR Close #26278
2019-06-14 11:50:04 -07:00
9654d646ae docs: rewrite property binding section and add example (#25770)
PR Close #25770
2019-06-14 11:47:35 -07:00
7f214499e8 fix(language-service): Remove 'any' in getQuickInfoAtPosition (#31014)
PR Close #31014
2019-06-14 10:46:17 -07:00
a3d55a9d27 build(docs-infra): update browserlist configuration file (#31045)
More specifically:
- Remove Chrome 41, which was needed for googlebot support but not any
  more.
- Remove IE 9-10, because we don't expect developers to be using them.
- Expand support from _last 2 versions_ to _last 2 **major** versions_.

PR Close #31045
2019-06-14 10:43:22 -07:00
e222b8761e docs: minor fix in get-commit-range.js docs (#31049)
PR Close #31049
2019-06-14 10:41:43 -07:00
1435c0b3a9 docs(aio): add missing description to dev.to link (#30960)
PR Close #30960
2019-06-14 10:41:13 -07:00
0a7aebbcf5 fix(core): temporarily remove @deprecated jsdoc tag for a TextBedStatic.get overload (#30714)
Followup to #30514 which did the same for `TestBed`, but `TestBedStatic` was necessary too.

PR Close #30714
2019-06-14 10:40:43 -07:00
bd7f91feac docs: rewrite binding-syntax section in template-syntax.md (#25561)
PR Close #25561
2019-06-14 10:25:07 -07:00
3bbc89b958 ci: temporarily disable payload size tracking (#31057)
The `CIRCLE_COMPARE_URL` is not available in builds any more since we
enabled `Pipelines` on CircleCI. We have contected CircleCI, but until
this is solved we cannot get the commit range and thus disabling
uploading of payload size data to avoid broken builds.

PR Close #31057
2019-06-14 09:19:18 -07:00
52d98e563d docs: fix broken link in singleton services doc (#31007)
PR Close #31007
2019-06-13 17:05:21 -07:00
b279f8bd62 docs: add no-auto-link instructions to docs style guide (#30980)
PR Close #30980
2019-06-13 17:00:47 -07:00
954e34cc91 docs: add angular.tw (Traditional Chinese) site to navigation.json (#30723)
PR Close #30723
2019-06-13 16:04:41 -07:00
e1f6d15387 release: cut the v8.0.1 release 2019-06-13 15:22:16 -07:00
cbb3794931 fix(docs-infra): enable only vertical scrolling for contributors bio (#30991)
- `auto` will enable scrolling only when needed
- `overflow-y` will ensure to keep the scrolling horizontally only

PR Close #30991
2019-06-12 11:47:13 -07:00
77a5790a9d docs: add platform to glossary (#30731)
PR Close #30731
2019-06-12 11:46:25 -07:00
4ca401c394 build(docs-infra): upgrade cli command docs sources to 7da10691d (#30999)
Updating [angular#8.0.x](https://github.com/angular/angular/tree/8.0.x) from [cli-builds#8.0.x](https://github.com/angular/cli-builds/tree/8.0.x).

##
Relevant changes in [commit range](e567d15ae...7da10691d):

**Modified**
- help/build.json

##

PR Close #30999
2019-06-12 11:45:07 -07:00
3dcd5ebdbc fix(ivy): unable to bind to implicit receiver in embedded views (#30994)
To provide some context: The implicit receiver is part of the
parsed Angular template AST. Any property reads in bindings,
interpolations etc. read from a given object (usually the component
instance). In that case there is an _implicit_ receiver which can also
be specified explicitly by just using `this`.

e.g.

```html
<ng-template>{{this.myProperty}}</ng-template>
```

This works as expected in Ivy and View Engine, but breaks in case the
implicit receiver is not used for property reads. For example:

```html
<my-dir [myFn]="greetFn.bind(this)"></my-dir>
```

In that case the `this` will not be properly translated into the generated
template function code because the Ivy compiler currently always treats
the `ctx` variable as the implicit receiver. This is **not correct** and breaks
compatibility with View Engine. Rather we need to ensure that we retrieve
the root context for the standalone implicit receiver similar to how it works
for property reads (as seen in the example above with `this.myProperty`)

Note that this requires some small changes to the `expression_converter`
because we only want to generate the `eenextContent()` instruction if the
implicit receiver is _actually_ used/needed. View Engine determines if that is the case by recursively walking through the converted output AST and
checking for usages of the `o.variable('_co')` variable ([see here][ve_check]). This would work too for Ivy, but involves most likely more code duplication
since templates are isolated in different functions and it another pass
through the output AST for every template expression.

[ve_check]: 0d6c9d36a1/packages/compiler/src/view_compiler/view_compiler.ts (L206-L208)

Resolves FW-1366.

**NOTE**: Patch version of 58be2ff884
(#30897)

PR Close #30994
2019-06-12 11:44:11 -07:00
49307f0595 fix(bazel): do not modify tsconfig.json (#30984)
This is a patch PR for https://github.com/angular/angular/pull/30877

PR Close #30984
2019-06-11 15:40:39 -07:00
762fc28fee feat(docs-infra): layout ui polish (#30883)
PR Close #30883
2019-06-11 14:20:36 -07:00
338e58c278 docs: use const in dynamic-component-loader example (#30888)
Use const instead of let. Some of the variables are never reassigned, so it is preferred to use const over let

PR Close #30888
2019-06-11 14:20:00 -07:00
7e2ed89208 docs: fix a grammar mistake (#30949)
Grammar mistake is in the JsonPipe section
PR Close #30949
2019-06-11 14:19:17 -07:00
56a3dcf44c build(docs-infra): upgrade cli command docs sources to e567d15ae (#30971)
Updating [angular#8.0.x](https://github.com/angular/angular/tree/8.0.x) from [cli-builds#8.0.x](https://github.com/angular/cli-builds/tree/8.0.x).

##
Relevant changes in [commit range](d09f130ce...e567d15ae):

**Modified**
- help/build.json
- help/generate.json
- help/new.json
- help/serve.json
- help/test.json

##

PR Close #30971
2019-06-11 14:17:32 -07:00
501bacdcf6 build: update golden for //packages/core/test/bundling/injection:symbol_test (#30965)
PR Close #30965
2019-06-11 10:54:28 -07:00
c5f2979a87 build(bazel): update to nodejs rules 0.31.1 & bazel 0.26.1 (#30965)
* entry_point attribute of nodejs_binary & rollup_bundle is now a label
* nodejs rules 0.30.1 has new feature to symlink node_modules with yarn_install and bazel 0.26.0 includes new managed_directories feature which enables this
* Symlinking of node_modules for yarn_install temporarily disabled (except for integration/bazel) until the fix for https://github.com/bazelbuild/bazel/issues/8487 makes it into a future bazel release. This is needed to work-around issue: yarn_install & npm_install with managed directories can't handle deleted or manually regenerated node_modules folder [https://github.com/bazelbuild/rules_nodejs/issues/802]. Underlying issue has been fixed in Bazel https://github.com/bazelbuild/bazel/issues/8487 but hasn't landed in a release yet

PR Close #30965
2019-06-11 10:54:28 -07:00
fe02462b5f build(docs-infra): update @angular/* to v8.1.0-next.1 and @angular/material to v8.0.0 (#30935)
PR Close #30935
2019-06-11 00:11:36 +00:00
3000d19ad1 build(docs-infra): update @angular/cli to v8.0.2 (#30935)
This restores named lazy chunks, which were broken during beta/rc (i.e.
the lazy chunks were named 0/1/2/...).

PR Close #30935
2019-06-11 00:11:36 +00:00
dcf9f05c9a docs: use Node.js consistently (#30934)
PR Close #30934
2019-06-11 00:09:49 +00:00
ba56f3c15b refactor(docs-infra): avoid hard-coding URLs to redirect on archive mode (#30894)
Related discussion:
https://github.com/angular/angular/pull/30894#pullrequestreview-246731995

PR Close #30894
2019-06-11 00:07:43 +00:00
865ad56e9a fix(docs-infra): do not redirect docs URLs on archive deployments (#30894)
To avoid showing outdated info (such as events, resources, etc.) but
still allow people to see docs for older versions, we redirect
non-documentation URLs to `/docs`. Recently(-ish) we have added
documentation content under the `/cli/...` and `/start/...`
path-prefixes, but we haven't added them to the list of documentation
URLs that should not be redirected. As a result, on archive deployments
(e.g. https://v7.angular.io/cli), they are redirected to `/docs`, making
it impossible to see the documentation for these versions (unless you
know about the `?mode=stable` work-around).

This commit fixes it by adding `cli` and `start` to the list of
documentation URLs that are excluded from redirection.

PR Close #30894
2019-06-11 00:07:43 +00:00
5cda2a041b refactor(docs-infra): make archive redirection tests DRY (#30894)
PR Close #30894
2019-06-11 00:07:43 +00:00
6a484853a9 build: update rules_nodejs and clean up bazel warnings (#30370) (#30763)
Preserve compatibility with rollup_bundle rule.
Add missing npm dependencies, which are now enforced by the strict_deps plugin in tsc_wrapped

PR Close #30370

PR Close #30763
2019-06-10 23:58:54 +00:00
e6ac289518 build: update to Bazel 0.26 (#30370) (#30763)
PR Close #30370

PR Close #30763
2019-06-10 23:58:54 +00:00
4e8614bb92 fix(common): expose the HttpUploadProgressEvent interface as public API (#30852)
Fixes #30814

PR Close #30852
2019-06-07 08:47:48 -07:00
9ace748d3a docs: add dev.to link to the help section (#30873)
PR Close #30873
2019-06-07 08:47:02 -07:00
08c38a1f99 fix(service-worker): avoid uncaught rejection warning when registration fails (#30876)
Service worker API `navigator.serviceWorker.register` can fail in multiple ways.
For example, in Chrome, with an unstable network connection you can have the
following error: `An unknown error occurred when fetching the script.`

In the current state, it creates an `Uncaught (in promise) TypeError:` style of
error, which cannot be caught by the user on his own.

I think it's better to log the error over raising an error that cannot be
caught.

PR Close #30876
2019-06-07 08:46:26 -07:00
9aeef0afdb build(docs-infra): fail hard if the CLI source is not what we expect (#30901)
Previously we just logged a warning but we should fail
to prevent silently allowing the docs to look wrong if
something changes on the CLI side of things.

PR Close #30901
2019-06-07 08:45:48 -07:00
8aef446373 build(docs-infra): upgrade cli command docs sources to d09f130ce (#30909)
Updating [angular#8.0.x](https://github.com/angular/angular/tree/8.0.x) from [cli-builds#8.0.x](https://github.com/angular/cli-builds/tree/8.0.x).

##
Relevant changes in [commit range](e8bdfe6b9...d09f130ce):

**Modified**
- help/serve.json

##

PR Close #30909
2019-06-07 08:44:42 -07:00
f7ee91a17c build(docs-infra): remove unneeded allowEmptyCodegenFiles option in Ivy mode (#30911)
Previously, when switching to Ivy mode (e.g. to run tests on CI), we had
to add `allowEmptyCodegenFiles: true` to the `angularCompilerOptions` in
`tsconfig.app.json`.

This isn't necessary any more (potentially since we switched to dynamic
imports for loading lazy modules in #30704), so this commit removes it
from the `switch-to-ivy.js` script.

PR Close #30911
2019-06-07 08:43:50 -07:00
8ae0afd3f8 fix(ivy): ngcc - remove unused import to make linting pass (#30914)
The failure is caused by 65f20107f. The unused [`writeFile` import][1]
seems to be a result of merge conflict resolution (since it is not
present on the original PR (#30831).

[1]: 65f20107fe (diff-4840f884874632c2fbfdb64a26213396R9)

PR Close #30914
2019-06-07 08:43:19 -07:00
be82270493 feat(docs-infra): white background and corresponding figure updates (#28396)
PR Close #28396
2019-06-06 13:48:16 -07:00
4cef2c1236 build(docs-infra): fix CLI command github links (#30889)
The "view" links were broken because the version used to
compute the git tag for the GitHub URL included a build SHA.
Now we clean that off before using it in the URL.

The links are to the JSON schema that defines the documentation for
the command. This is accurate but confusing because the content for the
long description is stored in a separate markdown file referenced from this
schema file.
This commit adds a second set of links for the long description, if it exists,
which links directly to the markdown file.

Closes #30700

PR Close #30889
2019-06-06 08:49:38 -07:00
26e3615e19 fix(core): TypeScript related migrations should cater for BOM (#30719)
fix(@schematics/angular): TypeScript related migrations should cater for BOM

In the CLI `UpdateRecorder` methods such as `insertLeft`, `remove` etc.. accepts positions which are not offset by a BOM. This is because when a file has a BOM a different recorder will be used https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts#L72 which caters for an addition offset/delta.

The main reason for this is that when a developer is writing a schematic they shouldn't need to compute the offset based if a file has a BOM or not and is handled out of the box.

Example
```ts
recorder.insertLeft(5, 'true');
```

However this is unfortunate in the case if a ts SourceFile is used and one uses `getWidth` and `getStart` method they will already be offset by 1, which at the end it results in a double offset and hence the problem.

Fixes #30713

PR Close #30719
2019-06-06 08:44:56 -07:00
65f20107fe fix(ivy): ngcc - use spaces in overwritten package.json content for readability (#30831)
When ngcc processes an entrypoint, it updates `package.json` with
metadata about the processed format. Previously, it overwrote the
`package.json` with the stringified JSON object without spaces. This
made the file difficult to read (for example when looking at the file
while debugging an ngcc failure).

This commit fixes it by using spaces in the new `package.json` content.

PR Close #30831
2019-06-05 21:24:47 -07:00
5a7bcd1862 fix(bazel): Load global stylesheet in dev and prod (#30879)
Global stylesheet should be injected as a <link> tag in index.html for
both dev and prod app.

PR Close #30879
2019-06-05 21:20:59 -07:00
152ea36c80 build: unable to run build-packages-dist script on windows (#30853)
Currently it's not possible to run the `./scripts/build-packages-dist.sh` script on Windows because
`bazel query` returns CRLF line-endings which result on array expansion in Bazel target names
that end with a carriage return (e.g. `//packages/core\r`). This then results in a build failure where
Bazel complains that target names should not end with a carriage return.

In order to fix this, we just strip off the carriage return line-endings from the bazel query stdout. Ideally
the script will be ported to a plain Node script eventually, but for now it prevents Windows users from
building the release packages and the simple workaround seems reasonable and sufficient.

PR Close #30853
2019-06-05 09:05:29 -07:00
ea2adb104b docs: correct typos and formatting in the Getting Started guide (#30758)
PR Close #30758
2019-06-04 13:37:27 -07:00
19caace2ab docs(service-worker): mention that HTTPS is required, unless on localhost (#30847)
Fixes #30823

PR Close #30847
2019-06-04 12:03:12 -07:00
ccc1c27461 refactor: Move away from index signature based "updateCache". (#30518)
We're deprecating the index signature overloads in favor of using a Map.

PR Close #30518
2019-06-04 12:02:13 -07:00
97268b95f7 docs: update example dependencies to 8.x release (#30755)
PR Close #30755
2019-06-04 11:53:56 -07:00
7c41abe64a docs: rename component for constructor example (#30845)
PR Close #30845
2019-06-04 11:52:58 -07:00
991e138650 fix(ivy): unable to project into multiple slots with default selector (#30803)
With View engine it was possible to declare multiple projection
definitions and to programmatically project nodes into the slots.

e.g.

```html
<ng-content></ng-content>
<ng-content></ng-content>
```

Using `ViewContainerRef#createComponent` allowed projecting
nodes into one of the projection defs (through index)

This no longer works with Ivy as the `projectionDef` instruction only
retrieves a list of selectors instead of also retrieving entries for
reserved projection slots which appear when using the default
selector multiple times (as seen above).

In order to fix this issue, the Ivy compiler now passes all
projection slots to the `projectionDef` instruction. Meaning that
there can be multiple projection slots with the same wildcard
selector. This allows multi-slot projection as seen in the
example above, and it also allows us to match the multi-slot node
projection order from View Engine (to avoid breaking changes).

It basically ensures that Ivy fully matches the View Engine behavior
except of a very small edge case that has already been discussed
in FW-886 (with the conclusion of working as intended).

Read more here: https://hackmd.io/s/Sy2kQlgTE

NOTE: This is the patch version of #30561

PR Close #30803
2019-06-03 11:44:44 -07:00
d00b421402 docs: fix quickstart link in readme (#30807)
PR Close #30807
2019-06-03 11:43:59 -07:00
06ffed5141 docs: document UrlTree in guard return type in cheatsheet (#30822)
Add UrlTree to the types of returned values in guard methods

PR Close #30822
2019-06-03 11:43:26 -07:00
5a15126520 docs(core): fix typo in style bindings comment (#30661)
PR Close #30661
2019-06-03 09:01:06 -07:00
9e4b2f1a4f docs: fix a small typo in injectable decorator description (#30785)
PR Close #30785
2019-06-03 08:56:22 -07:00
393529d3b5 docs: update example references from "my-app" to "app-root" (#30789)
"app" is the default prefix for CLI projects when generating components.
This updates our examples to conform that that default

Closes #19778

PR Close #30789
2019-06-03 08:55:39 -07:00
81b75590d7 docs(docs-infra): compile module first for Ivy when loading element modules (#30704)
In View Engine, NgModule factories are created for each NgModule and loaded when the module is requested. Ivy doesn't generate the
factories by design and only loads the module class, so it must be compiled after being loaded.

PR Close #30704
2019-05-31 15:24:23 -07:00
151f0f4b2c refactor(docs-infra): update loading of custom elements to use dynamic import syntax (#30704)
Removes the usage of `NgModuleFactoryLoader` and string-based imports for lazy loading.

PR Close #30704
2019-05-31 15:24:23 -07:00
f88f941008 docs: increase color contrast on elements in tutorial
This increases the color contrast of elements in the tutorial (parts 1-6)
in order to meet WCAG 2.0 AA standards.
2019-05-31 15:23:33 -07:00
0e17958640 fix(docs-infra): add v7 in versions dropdown (#30775)
Fixes #30769

PR Close #30775
2019-05-31 15:19:54 -07:00
e82b45c8a7 docs(docs-infra): remove deprecated Buffer usage from stackblitz builder (#30369)
Fixes #30364

PR Close #30369
2019-05-31 09:56:23 -07:00
8422ef2a93 fix(docs-infra): update tooltip of events link (#30643)
PR Close #30643
2019-05-31 09:50:45 -07:00
cdf586d0d2 build(docs-infra): upgrade cli command docs sources to e8bdfe6b9 (#30777)
Updating [angular#8.0.x](https://github.com/angular/angular/tree/8.0.x) from [cli-builds#8.0.x](https://github.com/angular/cli-builds/tree/8.0.x).

##
Relevant changes in [commit range](9a3d36c98...e8bdfe6b9):

**Modified**
- help/update.json

##

PR Close #30777
2019-05-31 09:45:09 -07:00
3828c89792 ci: add reviewer group for deprecations guide (#30722)
PR Close #30722
2019-05-30 20:45:59 -07:00
0f352b6350 docs: add final release date and update future schedule (#30712)
PR Close #30712
2019-05-30 19:39:24 -07:00
06bdecffb9 docs: remove mention of project settings from analytics (#30701)
This settings are not yet implemented and will be available in 8.1.

PR Close #30701
2019-05-30 13:42:35 -04:00
99ddec152b docs(ivy): fix symbol in example AST (#30745)
PR Close #30745
2019-05-30 13:41:52 -04:00
31aba96fcb docs: improve glossary for entry-point. (#29433)
PR Close #29433
2019-05-30 13:40:55 -04:00
facce2c9fd docs: fix typo in upgrade guide example (#30428)
An code snippet from a `package.json` file used `script`
rather than `scripts`.

Fixes #30418

PR Close #30428
2019-05-30 13:40:07 -04:00
edaf058548 docs: fix closing tags in deprecation summary (#30735)
fix closing tags in "Cannot assign to template variables" section

PR Close #30735
2019-05-30 13:38:54 -04:00
4bff2d0c8c docs: fix deprecation information (#30717)
PR Close #30717
2019-05-30 13:38:34 -04:00
cfc608aaf3 docs: merge duplicated platform-webworker content in Deprecation Guide (#30651)
fix issue of duplicate anchors/ids

PR Close #30651
2019-05-30 13:36:30 -04:00
6d9f5fcddb docs(elements): move clarification about custom elements (#30594)
Moved clarification that "custom elements" are a subtype of Web Components to the first use of the term "custom elements"

PR Close #30594
2019-05-30 13:35:15 -04:00
b19a05c25a docs(common): fix typo in ngSwitch directive description (#30483)
PR Close #30483
2019-05-30 13:34:22 -04:00
bd75b3b7b3 docs: fix the sequence of creating class in HTTP tutorial (#30566)
For more accurate procedure of creating a class in the "Tour of Heroes" app, updated the sequence of creating one class.

PR Close #30566
2019-05-30 13:32:53 -04:00
1c67b9063b docs: clarify usage of using /deep/ in component styles guide (#30452)
Updated the description regarding the (deprecated) /deep/ pseudo-selector to clarify its propensity to bleed styles across components and its solution

closes #29967

PR Close #30452
2019-05-30 13:32:32 -04:00
9a9ae60e0e docs: code sample closing in ivy.md (#30711)
PR Close #30711
2019-05-30 13:32:11 -04:00
ae6fa9260a docs: change wording for more clarity in TypeScript Configuration guide (#30748)
"You need to do nothing" indicates the presence of an action (the action of doing nothing) while the goal is to tell the user that no action is required from them. I think "You don’t need to do anything" is better suitable in this context.

PR Close #30748
2019-05-30 13:31:24 -04:00
f8fa2f2a6c docs(core): template-var-assignment schematic should link to deprecation guide (#30702)
Instead of linking to a markdown file explaining what the migration warnings
are about, we should link to the deprecation guide which now also contains
an entry for that schematic. This makes the deprecation explanations
consistent and more centralized.

PR Close #30702
2019-05-29 13:50:35 -04:00
f3ee9a6144 docs: add note to platform-webworker deprecation (#30705)
PR Close #30705
2019-05-28 16:37:58 -07:00
d076c51455 test: improve language service tests performance (#30683)
With this change we reduce the amount of IO operations. This is especially a huge factor in windows since IO ops are slower.

With this change mainly we cache `existsSync` and `readFileSync` calls

Here's the results

Before
```
//packages/language-service/test:test
INFO: Elapsed time: 258.755s, Critical Path: 253.91s
```

After
```
//packages/language-service/test:test
INFO: Elapsed time: 66.403s, Critical Path: 63.13s
```

PR Close #30683
2019-05-28 16:15:02 -07:00
392 changed files with 11132 additions and 7724 deletions

View File

@ -4,4 +4,5 @@ aio/content
aio/node_modules
aio/tools/examples/shared/node_modules
integration/bazel
integration/bazel-schematics/demo
packages/bazel/node_modules

View File

@ -145,3 +145,12 @@ build:remote --remote_accept_cached=false
# Load any settings specific to the current user. Needs to be last statement in this
# config, as the user configuration should be able to overwrite flags from this file.
try-import .bazelrc.user
###############################
# 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

View File

@ -15,8 +15,8 @@
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
# version that is compatible with the Chrome version in the image.
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
var_1: &default_docker_image circleci/node:10.12
var_2: &browsers_docker_image circleci/node:10.12-browsers
var_1: &default_docker_image circleci/node:10.16
var_2: &browsers_docker_image circleci/node:10.16-browsers
# We don't want to include the current branch name in the cache key because that would prevent
# PRs from being able to restore the cache since the branch names are always different for PRs.
# The cache key should only consist of dynamic values that change whenever something in the
@ -26,7 +26,7 @@ var_2: &browsers_docker_image circleci/node:10.12-browsers
# **NOTE 1 **: If you change the cache key prefix, also sync the restore_cache fallback to match.
# **NOTE 2 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
var_3: &cache_key v3-angular-node-10.12-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
var_3: &cache_key v3-angular-node-10.16-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
# Initializes the CI environment by setting up common environment variables.
var_4: &init_environment
@ -53,7 +53,10 @@ var_5: &setup_bazel_remote_execution
run:
name: "Setup bazel RBE remote execution"
command: |
openssl aes-256-cbc -d -in .circleci/gcp_token -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
# We need ensure that the same default digest is used for encoding and decoding
# with openssl. Openssl versions might have different default digests which can
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
sudo bash -c "echo 'build --config=remote' >> /etc/bazel.bazelrc"
@ -99,7 +102,7 @@ var_10: &restore_cache
keys:
- *cache_key
# This fallback should be the cache_key without variables.
- v3-angular-node-10.12-
- v3-angular-node-10.16-
# Branch filter that can be specified for jobs that should only run on publish branches
# (e.g. master or the patch branch)
@ -250,16 +253,14 @@ jobs:
- run: yarn --cwd aio build --progress=false
# Lint the code
- run: yarn --cwd aio lint
# Run PWA-score tests
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Check the bundle sizes.
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
- run: yarn --cwd aio payload-size
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests
- run: yarn --cwd aio e2e --configuration=ci
# Run PWA-score tests
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Check the bundle sizes.
- run: yarn --cwd aio payload-size
# Run unit tests for Firebase redirects
- run: yarn --cwd aio redirects-test
@ -285,13 +286,15 @@ jobs:
- *init_environment
# Build aio (with local Angular packages)
- run: yarn --cwd aio build-local --progress=false
# Run PWA-score tests
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests
- run: yarn --cwd aio e2e --configuration=ci
# Run PWA-score tests
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Check the bundle sizes.
# Temporary disabled due to a problem with patch branch (8.0.x)
# - run: yarn --cwd aio payload-size aio-local
test_aio_local_ivy:
<<: *job_defaults
@ -303,13 +306,15 @@ jobs:
- *init_environment
# Build aio with Ivy (using local Angular packages)
- run: yarn --cwd aio build-with-ivy --progress=false
# Run PWA-score tests
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Run unit tests
- run: yarn --cwd aio test --progress=false --watch=false
# Run e2e tests
- run: yarn --cwd aio e2e --configuration=ci
# Run PWA-score tests
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
# Check the bundle sizes.
# Temporary disabled due to a problem with patch branch (8.0.x)
# - run: yarn --cwd aio payload-size aio-local-ivy
test_aio_tools:
<<: *job_defaults
@ -500,7 +505,10 @@ jobs:
- run: git config --global --unset "url.ssh://git@github.com.insteadof"
- run:
name: Decrypt github credentials
command: 'openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out ~/.git_credentials'
# We need ensure that the same default digest is used for encoding and decoding with
# openssl. Openssl versions might have different default digests which can cause
# decryption failures based on the installed openssl version. https://stackoverflow.com/a/39641378/4317734
command: 'openssl aes-256-cbc -d -in .circleci/github_token -md md5 -k "${KEY}" -out ~/.git_credentials'
- run: ./scripts/ci/publish-build-artifacts.sh
aio_monitoring_stable:

View File

@ -23,7 +23,7 @@ setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 2.45";
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 75.0.3770.90";
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available (or can be
# retrieved via `get-compare-url.js`), i.e. on push builds (a.k.a. non-PR, non-scheduled builds and

View File

@ -6,9 +6,9 @@
* node get-commit-range <build-number> [<compare-url> [<circle-token>]]
* ```
*
* Returns the value of the `CIRCLE_COMPARE_URL` environment variable (if defined) or, if this is
* not a PR build (i.e. `CIRCLE_PR_NUMBER` is not defined), retrieves the equivalent of
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow.
* Returns the commit range, either extracting it from `compare-url` (if defined), which is of the
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
*
* **Context:**
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit

11
.github/CODEOWNERS vendored
View File

@ -179,6 +179,7 @@
# ===========================================================
#
# - kara
# - jasonaden
# ===========================================================
@ -533,6 +534,15 @@
/aio/content/examples/interpolation/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/images/guide/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/binding-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/property-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/attribute-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/two-way-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-reference-variables/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/template-expression-operators/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/pipes.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/examples/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
@ -812,6 +822,7 @@ testing/** @angular/fw-test
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
/aio/content/guide/deprecations.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes

View File

@ -1,3 +1,29 @@
<a name="8.0.2"></a>
## [8.0.2](https://github.com/angular/angular/compare/8.0.1...8.0.2) (2019-06-19)
### Bug Fixes
* **bazel:** builder workspace should use nodejs v10.16.0 ([#31088](https://github.com/angular/angular/issues/31088)) ([c198dc6](https://github.com/angular/angular/commit/c198dc6))
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a TextBedStatic.get overload ([#30714](https://github.com/angular/angular/issues/30714)) ([0a7aebb](https://github.com/angular/angular/commit/0a7aebb)), closes [#30514](https://github.com/angular/angular/issues/30514)
* **language-service:** Remove 'any' in getQuickInfoAtPosition ([#31014](https://github.com/angular/angular/issues/31014)) ([7f21449](https://github.com/angular/angular/commit/7f21449))
<a name="8.0.1"></a>
## [8.0.1](https://github.com/angular/angular/compare/8.0.0...8.0.1) (2019-06-13)
### Bug Fixes
* **bazel:** do not modify tsconfig.json ([#30984](https://github.com/angular/angular/issues/30984)) ([49307f0](https://github.com/angular/angular/commit/49307f0))
* **bazel:** Load global stylesheet in dev and prod ([#30879](https://github.com/angular/angular/issues/30879)) ([5a7bcd1](https://github.com/angular/angular/commit/5a7bcd1))
* **common:** expose the `HttpUploadProgressEvent` interface as public API ([#30852](https://github.com/angular/angular/issues/30852)) ([4e8614b](https://github.com/angular/angular/commit/4e8614b)), closes [#30814](https://github.com/angular/angular/issues/30814)
* **core:** TypeScript related migrations should cater for BOM ([#30719](https://github.com/angular/angular/issues/30719)) ([26e3615](https://github.com/angular/angular/commit/26e3615)), closes [/github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts#L72](https://github.com//github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts/issues/L72) [#30713](https://github.com/angular/angular/issues/30713)
* **service-worker:** avoid uncaught rejection warning when registration fails ([#30876](https://github.com/angular/angular/issues/30876)) ([08c38a1](https://github.com/angular/angular/commit/08c38a1))
<a name="8.0.0"></a>
# [8.0.0](https://github.com/angular/angular/compare/8.0.0-rc.5...8.0.0) (2019-05-28)

View File

@ -23,6 +23,6 @@ guidelines for [contributing][contributing] and then check out one of our issues
[browserstack]: https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06
[contributing]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md
[quickstart]: https://angular.io/guide/quickstart
[quickstart]: https://angular.io/start
[changelog]: https://github.com/angular/angular/blob/master/CHANGELOG.md
[ng]: https://angular.io

View File

@ -1,4 +1,7 @@
workspace(name = "angular")
workspace(
name = "angular",
managed_directories = {"@npm": ["node_modules"]},
)
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
@ -15,16 +18,15 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Fetch rules_nodejs so we can install our npm dependencies
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "3a3efbf223f6de733475602844ad3a8faa02abda25ab8cfe1d1ed0db134887cf",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.27.12/rules_nodejs-0.27.12.tar.gz"],
sha256 = "e04a82a72146bfbca2d0575947daa60fda1878c8d3a3afe868a8ec39a6b968bb",
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.31.1/rules_nodejs-0.31.1.tar.gz"],
)
# Check the bazel version and download npm dependencies
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "check_rules_nodejs_version", "node_repositories", "yarn_install")
# Bazel version must be at least v0.21.0 because:
# - 0.21.0 Using --incompatible_strict_action_env flag fixes cache when running `yarn bazel`
# (see https://github.com/angular/angular/issues/27514#issuecomment-451438271)
# Bazel version must be at least the following version because:
# - 0.26.0 managed_directories feature added which is required for nodejs rules 0.30.0
check_bazel_version(
message = """
You no longer need to install Bazel on your machine.
@ -33,22 +35,28 @@ Try running `yarn bazel` instead.
(If you did run that, check that you've got a fresh `yarn install`)
""",
minimum_bazel_version = "0.21.0",
minimum_bazel_version = "0.26.0",
)
# The NodeJS rules version must be at least v0.15.3 because:
# The NodeJS rules version must be at least the following version because:
# - 0.15.2 Re-introduced the prod_only attribute on yarn_install
# - 0.15.3 Includes a fix for the `jasmine_node_test` rule ignoring target tags
# - 0.16.8 Supports npm installed bazel workspaces
# - 0.26.0 Fix for data files in yarn_install and npm_install
# - 0.27.12 Adds NodeModuleSources provider for transtive npm deps support
check_rules_nodejs_version("0.27.12")
# - 0.30.0 yarn_install now uses symlinked node_modules with new managed directories Bazel 0.26.0 feature
# - 0.31.1 entry_point attribute of nodejs_binary & rollup_bundle is now a label
check_rules_nodejs_version(minimum_version_string = "0.31.1")
# Setup the Node.js toolchain
node_repositories(
node_version = "10.9.0",
node_repositories = {
"10.16.0-darwin_amd64": ("node-v10.16.0-darwin-x64.tar.gz", "node-v10.16.0-darwin-x64", "6c009df1b724026d84ae9a838c5b382662e30f6c5563a0995532f2bece39fa9c"),
"10.16.0-linux_amd64": ("node-v10.16.0-linux-x64.tar.xz", "node-v10.16.0-linux-x64", "1827f5b99084740234de0c506f4dd2202a696ed60f76059696747c34339b9d48"),
"10.16.0-windows_amd64": ("node-v10.16.0-win-x64.zip", "node-v10.16.0-win-x64", "aa22cb357f0fb54ccbc06b19b60e37eefea5d7dd9940912675d3ed988bf9a059"),
},
node_version = "10.16.0",
package_json = ["//:package.json"],
preserve_symlinks = True,
# yarn 1.13.0 under Bazel has a regression on Windows that causes build errors on rebuilds:
# ```
# ERROR: Source forest creation failed: C:/.../fyuc5c3n/execroot/angular/external (Directory not empty)
@ -71,6 +79,10 @@ yarn_install(
package_json = "//:package.json",
# Don't install devDependencies, they are large and not used under Bazel
prod_only = True,
# Temporarily disable node_modules symlinking until the fix for
# https://github.com/bazelbuild/bazel/issues/8487 makes it into a
# future Bazel release
symlink_node_modules = False,
yarn_lock = "//:yarn.lock",
)

View File

@ -6,8 +6,7 @@
# For additional information see: https://developers.google.com/search/docs/guides/rendering
> 0.5%
last 2 versions
last 2 major versions
Firefox ESR
not dead
IE 9-11 # For IE 9-11 support.
Chrome 41 # For Googlebot support.
IE 11

View File

@ -12,7 +12,7 @@ import { Component } from '@angular/core';
// #enddocregion import-core-component
@Component({
selector: 'my-app',
selector: 'app-root',
template: 'Welcome to Angular'
})
export class AppComponent {

View File

@ -0,0 +1,47 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor';
describe('Attribute binding example', function () {
beforeEach(function () {
browser.get('');
});
it('should display Property Binding with Angular', function () {
expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
});
it('should display a table', function() {
expect(element.all(by.css('table')).isPresent()).toBe(true);
});
it('should display an Aria button', function () {
expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
});
it('should display a blue background on div', function () {
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 () {
expect(element.all(by.css('div')).get(4).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
});
it('should display a div with replaced classes', function () {
expect(element.all(by.css('div')).get(5).getAttribute('class')).toEqual('new-class');
});
it('should display four buttons', function() {
let redButton = element.all(by.css('button')).get(1);
let saveButton = element.all(by.css('button')).get(2);
let bigButton = element.all(by.css('button')).get(3);
let smallButton = element.all(by.css('button')).get(4);
expect(redButton.getCssValue('color')).toEqual('rgba(255, 0, 0, 1)');
expect(saveButton.getCssValue('background-color')).toEqual('rgba(0, 255, 255, 1)');
expect(bigButton.getText()).toBe('Big');
expect(smallButton.getText()).toBe('Small');
});
});

View File

@ -0,0 +1,22 @@
.special {
background-color: #1976d2;
color: #ffffff;
}
.item {
font-weight: bold;
}
.clearance {
border: 2px solid #d41e2e;
}
.item-clearance {
font-style: italic;
}
.new-class {
background-color: #ed1b2f;
font-style: italic;
color: #fff;
}

View File

@ -0,0 +1,65 @@
<h1>Attribute, class, and style bindings</h1>
<h2>Attribute binding</h2>
<!-- #docregion attrib-binding-colspan -->
<table border=1>
<!-- expression calculates colspan=2 -->
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
<!-- ERROR: There is no `colspan` property to set!
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
-->
<!-- #docregion colSpan -->
<!-- Notice the colSpan property is camel case -->
<tr><td [colSpan]="1 + 1">Three-Four</td></tr>
<!-- #enddocregion colSpan -->
<tr><td>Five</td><td>Six</td></tr>
</table>
<!-- #enddocregion attrib-binding-colspan -->
<div>
<!-- #docregion attrib-binding-aria -->
<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
<!-- #enddocregion attrib-binding-aria -->
</div>
<hr />
<h2>Class binding</h2>
<!-- #docregion is-special -->
<h3>toggle the "special" class on/off with a property:</h3>
<div [class.special]="isSpecial">The class binding is special.</div>
<h3>binding to class.special overrides the class attribute:</h3>
<div class="special" [class.special]="!isSpecial">This one is not so special.</div>
<h3>Using the bind- syntax:</h3>
<div bind-class.special="isSpecial">This class binding is special too.</div>
<!-- #enddocregion is-special -->
<!-- #docregion add-class -->
<h3>Add a class:</h3>
<div class="item clearance special" [class.item-clearance]="itemClearance">Add another class</div>
<!-- #enddocregion add-class -->
<!-- #docregion class-override -->
<h3>Overwrite all existing classes with a new class:</h3>
<div class="item clearance special" [attr.class]="resetClasses">Reset all classes at once</div>
<!-- #enddocregion class-override -->
<hr />
<h2>Style binding</h2>
<!-- #docregion style-binding-->
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
<!-- #enddocregion style-binding -->
<!-- #docregion style-binding-condition-->
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
<!-- #enddocregion style-binding-condition-->

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
actionName = 'Go for it';
isSpecial = true;
itemClearance = true;
resetClasses = 'new-class';
canSave = true;
}

View File

@ -0,0 +1,18 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AttributeBinding</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Attribute Binding",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Attribute Binding"]
}

View File

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

View File

@ -0,0 +1,3 @@
div {
padding: .25rem 0;
}

View File

@ -0,0 +1,45 @@
<div>
<h1>Binding syntax</h1>
<hr />
<div>
<h2>Button disabled state bound to isUnchanged property</h2>
<!-- #docregion disabled-button -->
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>
<!-- #enddocregion disabled-button -->
</div>
<hr />
<div (keyup)="0">
<h2>HTML attributes and DOM properties</h2>
<p>1. Use the inspector to see the HTML attribute and DOM property values. Click the buttons to log values to the console.</p>
<label>HTML Attribute Initializes to "Sarah":
<input type="text" value="Sarah" #bindingInput></label>
<div>
<button (click)="getHTMLAttributeValue()">Get HTML attribute value</button> Won't change.
</div>
<div>
<button (click)="getDOMPropertyValue()">Get DOM property value</button> Changeable. Angular works with these.
</div>
<p>2. Change the name in the input and click the buttons again.</p>
</div>
<hr />
<div>
<h3>Disabled property vs. attribute</h3>
<p>Use the inspector to see the Test Button work and its disabled property toggle.</p>
<div>
<button id="testButton" (click)="working()">Test Button</button>
</div>
<div>
<button (click)="toggleDisabled()">Toggle disabled property for Test Button</button>
</div>
</div>

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,33 @@
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('bindingInput', { static: false }) bindingInput: ElementRef;
isUnchanged = true;
getHTMLAttributeValue(): any {
console.warn('HTML attribute value: ' + this.bindingInput.nativeElement.getAttribute('value'));
}
getDOMPropertyValue(): any {
console.warn('DOM property value: ' + this.bindingInput.nativeElement.value);
}
working(): any {
console.warn('Test Button works!');
}
toggleDisabled(): any {
let testButton = <HTMLInputElement> document.getElementById('testButton');
testButton.disabled = !testButton.disabled;
console.warn(testButton.disabled);
}
}

View File

@ -0,0 +1,18 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,14 @@
<!-- #docregion -->
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Angular binding syntax example</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
<!-- #enddocregion -->

View File

@ -0,0 +1,12 @@
// #docregion
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,10 @@
{
"description": "Binding Syntax",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Binding Syntax"]
}

View File

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

View File

@ -0,0 +1,75 @@
button {
font-size: 100%;
margin: 0 2px;
}
div[ng-reflect-ng-switch], app-unknown-item {
margin: .5rem 0;
display: block;
}
#noTrackByCnt,
#withTrackByCnt {
color: darkred;
max-width: 450px;
margin: 4px;
}
img {
height: 100px;
}
.box {
border: 1px solid black;
padding: 6px;
max-width: 450px;
}
.child-div {
margin-left: 1em;
font-weight: normal;
}
.context {
margin-left: 1em;
}
.hidden {
display: none;
}
.parent-div {
margin-top: 1em;
font-weight: bold;
}
.course {
font-weight: bold;
font-size: x-large;
}
.helpful {
color: red;
}
.saveable {
color: limegreen;
}
.study,
.modified {
font-family: "Brush Script MT";
font-size: 2rem;
}
.toe {
margin-left: 1em;
font-style: italic;
}
.to-toc {
margin-top: 10px;
display: block;
}

View File

@ -0,0 +1,253 @@
<h1>Built-in Directives</h1>
<h2>Built-in attribute directives</h2>
<h3 id="ngModel">NgModel (two-way) Binding</h3>
<fieldset><h4>NgModel examples</h4>
<p>Current item name: {{currentItem.name}}</p>
<p>
<!-- #docregion without-NgModel -->
<label for="without">without NgModel:</label>
<input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">
<!-- #enddocregion without-NgModel -->
</p>
<p>
<!-- #docregion NgModel-1 -->
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel">
<!-- #enddocregion NgModel-1 -->
</p>
<p>
<label for="example-bindon">bindon-ngModel: </label>
<input bindon-ngModel="currentItem.name" id="example-bindon">
</p>
<p>
<!-- #docregion NgModelChange -->
<label for="example-change">(ngModelChange)="...name=$event":</label>
<input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">
<!-- #enddocregion NgModelChange -->
</p>
<p>
<label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"
<!-- #docregion uppercase -->
<input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">
<!-- #enddocregion uppercase -->
</label>
</p>
</fieldset>
<hr><h2 id="ngClass">NgClass Binding</h2>
<p>currentClasses is {{currentClasses | json}}</p>
<!-- #docregion NgClass-1 -->
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
<!-- #enddocregion NgClass-1 -->
<ul>
<li>
<label for="saveable">saveable</label>
<input type="checkbox" [(ngModel)]="canSave" id="saveable">
</li>
<li>
<label for="modified">modified:</label>
<input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>
<li>
<label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label>
</li>
</ul>
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
<div [ngClass]="currentClasses">
This div should be {{ canSave ? "": "not"}} saveable,
{{ isUnchanged ? "unchanged" : "modified" }} and
{{ isSpecial ? "": "not"}} special after clicking "Refresh".</div>
<br><br>
<!-- #docregion special-div -->
<!-- toggle the "special" class on/off with a property -->
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
<!-- #enddocregion special-div -->
<div class="helpful study course">Helpful study course</div>
<div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div>
<!-- NgStyle binding -->
<hr><h3>NgStyle Binding</h3>
<!-- #docregion without-ng-style -->
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
This div is x-large or smaller.
</div>
<!-- #enddocregion without-ng-style -->
<h4>[ngStyle] binding to currentStyles - CSS property names</h4>
<p>currentStyles is {{currentStyles | json}}</p>
<!-- #docregion NgStyle-2 -->
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
<!-- #enddocregion NgStyle-2 -->
<br>
<label>italic: <input type="checkbox" [(ngModel)]="canSave"></label> |
<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"></label> |
<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"></label>
<button (click)="setCurrentStyles()">Refresh currentStyles</button>
<br><br>
<div [ngStyle]="currentStyles">
This div should be {{ canSave ? "italic": "plain"}},
{{ isUnchanged ? "normal weight" : "bold" }} and,
{{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div>
<hr>
<h2>Built-in structural directives</h2>
<h3 id="ngIf">NgIf Binding</h3>
<div>
<p>If isActive is true, app-item-detail will render: </p>
<!-- #docregion NgIf-1 -->
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
<!-- #enddocregion NgIf-1 -->
<button (click)="isActiveToggle()">Toggle app-item-detail</button>
</div>
<p>If currentCustomer isn't null, say hello to Laura:</p>
<!-- #docregion NgIf-2 -->
<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
<!-- #enddocregion NgIf-2 -->
<p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p>
<!-- #docregion NgIf-2b -->
<div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div>
<!-- #enddocregion NgIf-2b -->
<button (click)="giveNullCustomerValue()">Give nullCustomer a value</button>
<h4>NgIf binding with template (no *)</h4>
<ng-template [ngIf]="currentItem">Add {{currentItem.name}} with template</ng-template>
<hr>
<h4>Show/hide vs. NgIf</h4>
<!-- #docregion NgIf-3 -->
<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>
<p>ItemDetail is in the DOM but hidden</p>
<app-item-detail [class.hidden]="isSpecial"></app-item-detail>
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
<!-- #enddocregion NgIf-3 -->
<hr>
<h2 id="ngFor">NgFor Binding</h2>
<div class="box">
<!-- #docregion NgFor-1, NgFor-1-2 -->
<div *ngFor="let item of items">{{item.name}}</div>
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
</div>
<p>*ngFor with ItemDetailComponent element</p>
<div class="box">
<!-- #docregion NgFor-2, NgFor-1-2 -->
<app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
</div>
<h4 id="ngFor-index">*ngFor with index</h4>
<p>with <i>semi-colon</i> separator</p>
<div class="box">
<!-- #docregion NgFor-3 -->
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
<!-- #enddocregion NgFor-3 -->
</div>
<p>with <i>comma</i> separator</p>
<div class="box">
<div *ngFor="let item of items, let i=index">{{i + 1}} - {{item.name}}</div>
</div>
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
<button (click)="resetList()">Reset items</button>
<button (click)="changeIds()">Change ids</button>
<button (click)="clearTrackByCounts()">Clear counts</button>
<p><i>without</i> trackBy</p>
<div class="box">
<div #noTrackBy *ngFor="let item of items">({{item.id}}) {{item.name}}</div>
<div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >
Item DOM elements change #{{itemsNoTrackByCount}} without trackBy
</div>
</div>
<p>with trackBy</p>
<div class="box">
<div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
<div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">
Item DOM elements change #{{itemsWithTrackByCount}} with trackBy
</div>
</div>
<br><br><br>
<p>with trackBy and <i>semi-colon</i> separator</p>
<div class="box">
<!-- #docregion trackBy -->
<div *ngFor="let item of items; trackBy: trackByItems">
({{item.id}}) {{item.name}}
</div>
<!-- #enddocregion trackBy -->
</div>
<p>with trackBy and <i>comma</i> separator</p>
<div class="box">
<div *ngFor="let item of items, trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
</div>
<p>with trackBy and <i>space</i> separator</p>
<div class="box">
<div *ngFor="let item of items trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
</div>
<p>with <i>generic</i> trackById function</p>
<div class="box">
<div *ngFor="let item of items, trackBy: trackById">({{item.id}}) {{item.name}}</div>
</div>
<hr><h2>NgSwitch Binding</h2>
<p>Pick your favorite item</p>
<div>
<label *ngFor="let i of items">
<div><input type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{i.name}}
</div>
</label>
</div>
<!-- #docregion NgSwitch -->
<div [ngSwitch]="currentItem.feature">
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
<!-- #enddocregion NgSwitch -->
<!-- #docregion NgSwitch-div -->
<div *ngSwitchCase="'bright'"> Are you as bright as {{currentItem.name}}?</div>
<!-- #enddocregion NgSwitch-div -->
<!-- #docregion NgSwitch -->
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>
<!-- #enddocregion NgSwitch -->

View File

@ -0,0 +1,115 @@
import { Component, OnInit } from '@angular/core';
import { Item } from './item';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
canSave = true;
isSpecial = true;
isUnchanged = true;
isActive = true;
nullCustomer = null;
currentCustomer = {
name: 'Laura'
};
item: Item; // defined to demonstrate template context precedence
items: Item[];
currentItem: Item;
// trackBy change counting
itemsNoTrackByCount = 0;
itemsWithTrackByCount = 0;
itemsWithTrackByCountReset = 0;
itemIdIncrement = 1;
ngOnInit() {
this.resetItems();
this.setCurrentClasses();
this.setCurrentStyles();
this.itemsNoTrackByCount = 0;
}
setUppercaseName(name: string) {
this.currentItem.name = name.toUpperCase();
}
// #docregion setClasses
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
// #enddocregion setClasses
// #docregion setStyles
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
// #enddocregion setStyles
isActiveToggle() {
this.isActive = !this.isActive;
}
giveNullCustomerValue() {
!(this.nullCustomer = null) ? (this.nullCustomer = 'Kelly') : (this.nullCustomer = null);
}
resetNullItem() {
this.nullCustomer = null;
}
resetItems() {
this.items = Item.items.map(item => item.clone());
this.currentItem = this.items[0];
this.item = this.currentItem;
}
resetList() {
this.resetItems()
this.itemsWithTrackByCountReset = 0;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
}
changeIds() {
this.items.forEach(i => i.id += 1 * this.itemIdIncrement);
this.itemsWithTrackByCountReset = -1;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;
}
clearTrackByCounts() {
this.resetItems();
this.itemsNoTrackByCount = 0;
this.itemsWithTrackByCount = 0;
this.itemIdIncrement = 1;
}
// #docregion trackByItems
trackByItems(index: number, item: Item): number { return item.id; }
// #enddocregion trackByItems
trackById(index: number, item: any): number { return item['id']; }
}

View File

@ -0,0 +1,34 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// #docregion import-forms-module
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
// #enddocregion import-forms-module
import { AppComponent } from './app.component';
import { ItemDetailComponent } from './item-detail/item-detail.component';
import { ItemSwitchComponents } from './item-switch.component';
// #docregion import-forms-module
@NgModule({
// #enddocregion import-forms-module
declarations: [
AppComponent,
ItemDetailComponent,
ItemSwitchComponents
],
// #docregion import-forms-module
imports: [
BrowserModule,
FormsModule // <--- import into the NgModule
],
// #enddocregion import-forms-module
providers: [],
bootstrap: [AppComponent]
// #docregion import-forms-module
})
export class AppModule { }
// #enddocregion import-forms-module

View File

@ -0,0 +1,3 @@
<div>
<span>{{item?.name}}</span>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemDetailComponent } from './item-detail.component';
describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import { Component, Input } from '@angular/core';
import { Item } from '../item';
@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.css']
})
export class ItemDetailComponent {
@Input() item: Item;
constructor() { }
}

View File

@ -0,0 +1,50 @@
import { Component, Input } from '@angular/core';
import { Item } from './item';
@Component({
selector: 'app-stout-item',
template: `I'm a little {{item.name}}, short and stout!`
})
export class StoutItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-best-item',
template: `This is the brightest {{item.name}} in town.`
})
export class BestItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-device-item',
template: `Which is the slimmest {{item.name}}?`
})
export class DeviceItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-lost-item',
template: `Has anyone seen my {{item.name}}?`
})
export class LostItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-unknown-item',
template: `{{message}}`
})
export class UnknownItemComponent {
@Input() item: Item;
get message() {
return this.item && this.item.name ?
`${this.item.name} is strange and mysterious.` :
'A mystery wrapped in a fishbowl.';
}
}
export const ItemSwitchComponents =
[ StoutItemComponent, BestItemComponent, DeviceItemComponent, LostItemComponent, UnknownItemComponent ];

View File

@ -0,0 +1,30 @@
export class Item {
static nextId = 0;
static items: Item[] = [
new Item(
null,
'Teapot',
'stout'
),
new Item(1, 'Lamp', 'bright'),
new Item(2, 'Phone', 'slim' ),
new Item(3, 'Television', 'vintage' ),
new Item(4, 'Fishbowl')
];
constructor(
public id?: number,
public name?: string,
public feature?: string,
public url?: string,
public rate = 100,
) {
this.id = id ? id : Item.nextId++;
}
clone(): Item {
return Object.assign(new Item(), this);
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BuiltInDirectives</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Built-in Directives",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Built-in Directives"]
}

View File

@ -1,14 +1,14 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-ctor',
selector: 'app-root',
template: `
<h1>{{title}} [Ctor version]</h1>
<h2>My favorite hero is: {{myHero}}</h2>
`
})
// #docregion class
export class AppCtorComponent {
export class AppComponent {
title: string;
myHero: string;

View File

@ -36,14 +36,14 @@ export class AdBannerComponent implements OnInit, OnDestroy {
loadComponent() {
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
let adItem = this.ads[this.currentAdIndex];
const adItem = this.ads[this.currentAdIndex];
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
let viewContainerRef = this.adHost.viewContainerRef;
const viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent(componentFactory);
const componentRef = viewContainerRef.createComponent(componentFactory);
(<AdComponent>componentRef.instance).data = adItem.data;
}

View File

@ -9,7 +9,7 @@
<!-- #docregion prices -->
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }} </span>
<span>{{ item.name }}</span>
<span>{{ item.price | currency }}</span>
</div>
<!-- #enddocregion prices -->

View File

@ -1,6 +1,6 @@
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }} </span>
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>

View File

@ -16,7 +16,9 @@ export class ShippingComponent {
// #enddocregion props
// #docregion inject-cart-service
constructor(private cartService: CartService) {
constructor(
private cartService: CartService
) {
// #enddocregion inject-cart-service
this.shippingCosts = this.cartService.getShippingPrices();
// #docregion inject-cart-service

View File

@ -0,0 +1,29 @@
// This tsconfig is used in the TypeScript
// configuration guide (../guide/typescript-configuration.md)
// to display the latest default configuration
// Note: Update with every major release to the latest default
// #docregion
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
// #docregion lib
"lib": [
"es2018",
"dom"
]
// #enddocregion lib
}
}

View File

@ -0,0 +1,70 @@
'use strict';
import { browser, element, by } from 'protractor';
import { logging } from 'selenium-webdriver';
describe('Inputs and Outputs', function () {
beforeEach(() => {
browser.get('');
});
// helper function used to test what's logged to the console
async function logChecker(button, contents) {
const logs = await browser
.manage()
.logs()
.get(logging.Type.BROWSER);
const message = logs.filter(({ message }) =>
message.indexOf(contents) !== -1 ? true : false
);
console.log(message);
expect(message.length).toBeGreaterThan(0);
}
it('should have title Inputs and Outputs', function () {
let title = element.all(by.css('h1')).get(0);
expect(title.getText()).toEqual('Inputs and Outputs');
});
it('should add 123 to the parent list', async () => {
let addToParentButton = element.all(by.css('button')).get(0);
let addToListInput = element.all(by.css('input')).get(0);
let addedItem = element.all(by.css('li')).get(4);
await addToListInput.sendKeys('123');
await addToParentButton.click();
expect(addedItem.getText()).toEqual('123');
});
it('should delete item', async () => {
let deleteButton = element.all(by.css('button')).get(1);
const contents = 'Child';
await deleteButton.click();
await logChecker(deleteButton, contents);
});
it('should log buy the item', async () => {
let buyButton = element.all(by.css('button')).get(2);
const contents = 'Child';
await buyButton.click();
await logChecker(buyButton, contents);
});
it('should save item for later', async () => {
let saveButton = element.all(by.css('button')).get(3);
const contents = 'Child';
await saveButton.click();
await logChecker(saveButton, contents);
});
it('should add item to wishlist', async () => {
let addToParentButton = element.all(by.css('button')).get(4);
let addedItem = element.all(by.css('li')).get(6);
await addToParentButton.click();
expect(addedItem.getText()).toEqual('Television');
});
});

View File

@ -0,0 +1,7 @@
<p>Save for later item: {{input1}}</p>
<button (click)="saveIt()"> Save for later</button>
<p>Item for wishlist: {{input2}}</p>
<button (click)="wishForIt()"> Add to wishlist</button>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AliasingComponent } from './aliasing.component';
describe('AliasingComponent', () => {
let component: AliasingComponent;
let fixture: ComponentFixture<AliasingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AliasingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AliasingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,46 @@
/* tslint:disable:use-input-property-decorator */
/* tslint:disable:use-output-property-decorator */
/* tslint:disable:no-input-rename */
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-aliasing',
templateUrl: './aliasing.component.html',
styleUrls: ['./aliasing.component.css'],
// #docregion alias
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['input1: saveForLaterItem'], // propertyName:alias
outputs: ['outputEvent1: saveForLaterEvent']
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
// #enddocregion alias
})
export class AliasingComponent {
input1: string;
outputEvent1: EventEmitter<string> = new EventEmitter<string>();
// #docregion alias-input-output
@Input('wishListItem') input2: string; // @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); // @Output(alias) propertyName = ...
// #enddocregion alias-input-output
saveIt() {
console.warn('Child says: emiting outputEvent1 with', this.input1);
this.outputEvent1.emit(this.input1);
}
wishForIt() {
console.warn('Child says: emiting outputEvent2', this.input2);
this.outputEvent2.emit(this.input2);
}
}
/* tslint:enable:use-input-property-decorator */
/* tslint:enable:use-output-property-decorator */

View File

@ -0,0 +1,45 @@
<h1>Inputs and Outputs</h1>
<!-- #docregion input-parent -->
<app-item-detail [item]="currentItem"></app-item-detail>
<!-- #enddocregion input-parent -->
<hr>
<!-- #docregion output-parent -->
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>
<!-- #enddocregion output-parent -->
<h3>Parent component receiving value via @Output()</h3>
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
<hr>
<h2>Input and Output together</h2>
<p>Open the console to see the EventEmitter at work when you click Delete.</p>
<!-- #docregion together -->
<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)"></app-input-output>
<!-- #enddocregion together -->
<hr>
<h2>Input and Output in the component class metadata</h2>
<p>Open the console to see the EventEmitter at work when you click Buy.</p>
<app-in-the-metadata [clearanceItem]="lastChanceItem" (buyEvent)="buyClearanceItem($event)"></app-in-the-metadata>
<hr>
<h2>Aliasing Inputs and Outputs</h2>
<p>See aliasing.component.ts for aliases and the console for the EventEmitter console logs.</p>
<app-aliasing [saveForLaterItem]="currentItem" (saveForLaterEvent)="saveForLater($event)" [wishListItem]="currentItem" (wishEvent)="addToWishList($event)"></app-aliasing>
<h2>Wishlist:</h2>
<ul>
<li *ngFor="let wish of wishlist">{{wish}}</li>
</ul>

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,55 @@
// #docplaster
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
// #docregion parent-property
// #docregion add-new-item
export class AppComponent {
// #enddocregion add-new-item
currentItem = 'Television';
// #enddocregion parent-property
lastChanceItem = 'Beanbag';
// #docregion add-new-item
items = ['item1', 'item2', 'item3', 'item4'];
// #enddocregion add-new-item
wishlist = ['Drone', 'Computer'];
// #docregion add-new-item
addItem(newItem: string) {
this.items.push(newItem);
}
// #enddocregion add-new-item
crossOffItem(item: string) {
console.warn(`Parent says: crossing off ${item}.`);
}
buyClearanceItem(item) {
console.warn(`Parent says: buying ${item}.`);
}
saveForLater(item) {
console.warn(`Parent says: saving ${item} for later.`);
}
addToWishList(wish: string) {
console.warn(`Parent says: adding ${this.currentItem} to your wishlist.`);
this.wishlist.push(wish);
console.warn(this.wishlist);
}
// #docregion add-new-item
// #docregion parent-property
}
// #enddocregion add-new-item
// #enddocregion parent-property

View File

@ -0,0 +1,28 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ItemDetailComponent } from './item-detail/item-detail.component';
import { ItemOutputComponent } from './item-output/item-output.component';
import { InputOutputComponent } from './input-output/input-output.component';
import { InTheMetadataComponent } from './in-the-metadata/in-the-metadata.component';
import { AliasingComponent } from './aliasing/aliasing.component';
@NgModule({
declarations: [
AppComponent,
ItemDetailComponent,
ItemOutputComponent,
InputOutputComponent,
InTheMetadataComponent,
AliasingComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,3 @@
<p>Latest clearance item: {{clearanceItem}}</p>
<button (click)="buyIt()"> Buy it with an Output!</button>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InTheMetadataComponent } from './in-the-metadata.component';
describe('InTheMetadataComponent', () => {
let component: InTheMetadataComponent;
let fixture: ComponentFixture<InTheMetadataComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InTheMetadataComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InTheMetadataComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
/* tslint:disable:use-input-property-decorator */
/* tslint:disable:use-output-property-decorator */
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-in-the-metadata',
templateUrl: './in-the-metadata.component.html',
styleUrls: ['./in-the-metadata.component.css'],
// #docregion metadata
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['clearanceItem'],
outputs: ['buyEvent']
// tslint:enable: no-inputs-metadata-property no-outputs-metadata-property
// #enddocregion metadata
})
export class InTheMetadataComponent {
buyEvent = new EventEmitter<string>();
clearanceItem: string;
buyIt() {
console.warn('Child says: emiting buyEvent with', this.clearanceItem);
this.buyEvent.emit(this.clearanceItem);
}
}
/* tslint:enable:use-input-property-decorator */
/* tslint:enable:use-output-property-decorator */

View File

@ -0,0 +1,2 @@
<span [style.text-decoration]="lineThrough">Item: {{item}}</span>
<button (click)="delete()">Delete it with an Output!</button>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InputOutputComponent } from './input-output.component';
describe('InputOutputComponent', () => {
let component: InputOutputComponent;
let fixture: ComponentFixture<InputOutputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InputOutputComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InputOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,25 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-input-output',
templateUrl: './input-output.component.html',
styleUrls: ['./input-output.component.css']
})
export class InputOutputComponent {
// #docregion input-output
@Input() item: string;
// #docregion output
@Output() deleteRequest = new EventEmitter<string>();
// #enddocregion output
// #enddocregion input-output
lineThrough = '';
// #docregion delete-method
delete() {
console.warn('Child says: emiting item deleteRequest with', this.item);
this.deleteRequest.emit(this.item);
this.lineThrough = this.lineThrough ? '' : 'line-through';
}
// #enddocregion delete-method
}

View File

@ -0,0 +1,8 @@
<h2>Child component with @Input()</h2>
<!-- #docregion property-in-template -->
<p>
Today's item: {{item}}
</p>
<!-- #enddocregion property-in-template -->

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemDetailComponent } from './item-detail.component';
describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
// #docplaster
// #docregion use-input
import { Component, Input } from '@angular/core'; // First, import Input
// #enddocregion use-input
@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.css']
})
// #docregion use-input
export class ItemDetailComponent {
@Input() item: string; // decorate the property with @Input()
}
// #enddocregion use-input

View File

@ -0,0 +1,6 @@
<h2>Child component with @Output()</h2>
<!-- #docregion child-output -->
<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>
<!-- #enddocregion child-output -->

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemOutputComponent } from './item-output.component';
describe('ItemOutputComponent', () => {
let component: ItemOutputComponent;
let fixture: ComponentFixture<ItemOutputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemOutputComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,22 @@
// #docregion imports
import { Component, Output, EventEmitter } from '@angular/core';
// #enddocregion imports
@Component({
selector: 'app-item-output',
templateUrl: './item-output.component.html',
styleUrls: ['./item-output.component.css']
})
// #docregion item-output-class
export class ItemOutputComponent {
// #docregion item-output
@Output() newItemEvent = new EventEmitter<string>();
// #enddocregion item-output
addNewItem(value: string) {
this.newItemEvent.emit(value);
}
}
// #enddocregion item-output-class

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Inputs and Outputs</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Inputs and Outputs",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Inputs and Outputs"]
}

View File

@ -4,7 +4,7 @@ import { Component } from '@angular/core';
import { heroes } from './hero';
@Component({
selector: 'my-app',
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})

View File

@ -6,7 +6,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<my-app>Loading...</my-apps>
<app-root>Loading...</app-root>
</body>
</html>

View File

@ -0,0 +1,54 @@
import { browser, element, by } from 'protractor';
describe('Property binding e2e tests', () => {
beforeEach(function () {
browser.get('');
});
it('should display Property Binding with Angular', function () {
expect(element(by.css('h1')).getText()).toEqual('Property Binding with Angular');
});
it('should display four phone pictures', function() {
expect(element.all(by.css('img')).isPresent()).toBe(true);
expect(element.all(by.css('img')).count()).toBe(4);
});
it('should display Disabled button', function () {
expect(element.all(by.css('button')).get(0).getText()).toBe(`Disabled Button`);
});
it('should display Binding to a property of a directive', function () {
expect(element.all(by.css('h2')).get(4).getText()).toBe(`Binding to a property of a directive`);
});
it('should display Your item is: lamp', function () {
expect(element.all(by.css('p')).get(0).getText()).toContain(`blue`);
});
it('should display Your item is: lamp', function () {
expect(element.all(by.css('p')).get(1).getText()).toContain(`Your item is: lamp`);
});
it('should display Your item is: parentItem', function () {
expect(element.all(by.css('p')).get(2).getText()).toBe(`Your item is: parentItem`);
});
it('should display a ul', function () {
expect(element.all(by.css('ul')).get(0).getText()).toContain(`tv`);
});
it('should display a ul containing phone', function () {
expect(element.all(by.css('ul')).get(1).getText()).toBe(`21 phone`);
});
it('should display one-time initialized string', function () {
expect(element.all(by.css('p')).get(3).getText()).toContain(`one-time initialized`);
});
it('should display Malicious content', function () {
expect(element.all(by.css('h2')).get(8).getText()).toBe(`Malicious content`);
});
});

View File

@ -0,0 +1,9 @@
div {
margin: 1rem auto;
width: 90%
}
.special {
background-color: #1976d2;
color: #fff;
padding: 1rem;
}

View File

@ -0,0 +1,84 @@
<div>
<h1>Property Binding with Angular</h1>
<h2>Binding the src property of an image:</h2>
<!-- #docregion property-binding -->
<img [src]="itemImageUrl">
<!-- #enddocregion property-binding -->
<h2>Using bind- syntax:</h2>
<!-- #docregion bind-prefix -->
<img bind-src="itemImageUrl">
<!-- #enddocregion bind-prefix -->
<hr />
<h2>Binding to the colSpan property</h2>
<table border=1>
<tr><td>Column 1</td><td>Column 2</td></tr>
<!-- #docregion colSpan -->
<!-- Notice the colSpan property is camel case -->
<tr><td [colSpan]="2">Span 2 columns</td></tr>
<!-- #enddocregion colSpan -->
</table>
<hr />
<h2>Button disabled state bound to isUnchanged property:</h2>
<!-- #docregion disabled-button -->
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Disabled Button</button>
<!-- #enddocregion disabled-button -->
<hr />
<h2>Binding to a property of a directive</h2>
<!-- #docregion class-binding -->
<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>
<!-- #enddocregion class-binding -->
<hr />
<h2>Model property of a custom component:</h2>
<!-- #docregion model-property-binding -->
<app-item-detail [childItem]="parentItem"></app-item-detail>
<!-- #enddocregion model-property-binding -->
<!-- #docregion no-evaluation -->
<app-item-detail childItem="parentItem"></app-item-detail>
<!-- #enddocregion no-evaluation -->
<h3>Pass objects:</h3>
<!-- #docregion pass-object -->
<app-list-item [items]="currentItem"></app-list-item>
<!-- #enddocregion pass-object -->
<hr />
<h2>Initialized string:</h2>
<!-- #docregion string-init -->
<app-string-init prefix="This is a one-time initialized string."></app-string-init>
<!-- #enddocregion string-init -->
<hr />
<h2>Property binding and interpolation</h2>
<!-- #docregion property-binding-interpolation -->
<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>
<!-- #enddocregion property-binding-interpolation -->
<hr />
<h2>Malicious content</h2>
<!-- #docregion malicious-interpolated -->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<!-- #enddocregion malicious-interpolated -->
<!-- #docregion malicious-content -->
<!--
Angular generates a warning for the following line as it sanitizes them
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
<!-- #enddocregion malicious-content -->
</div>

View File

@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
});

View File

@ -0,0 +1,30 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
itemImageUrl = '../assets/phone.png';
isUnchanged = true;
classes = 'special';
// #docregion parent-data-type
parentItem = 'lamp';
// #enddocregion parent-data-type
// #docregion pass-object
currentItem = [{
id: 21,
name: 'phone'
}];
// #enddocregion pass-object
interpolationTitle = 'Interpolation';
propertyTitle = 'Property binding';
// #docregion malicious-content
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';
// #enddocregion malicious-content
}

View File

@ -0,0 +1,24 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ItemDetailComponent } from './item-detail/item-detail.component';
import { ListItemComponent } from './list-item/list-item.component';
import { StringInitComponent } from './string-init/string-init.component';
@NgModule({
declarations: [
AppComponent,
ItemDetailComponent,
ListItemComponent,
StringInitComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,4 @@
<p>Your item is: {{ childItem }} </p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemDetailComponent } from './item-detail.component';
describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,26 @@
import { Component, OnInit, Input } from '@angular/core';
// import { Item } from '../item';
// import { ITEMS } from '../mock-items';
@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.css']
})
export class ItemDetailComponent implements OnInit {
// #docregion input-type
@Input() childItem: string;
// #enddocregion input-type
// items = ITEMS;
currentItem = 'bananas in boxes';
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,7 @@
// #docregion item-class
export class Item {
id: number;
name: string;
}
// #enddocregion item-class

View File

@ -0,0 +1,11 @@
<h4>Nested component's list of items:</h4>
<ul>
<li *ngFor="let item of listItems">{{item.id}} {{item.name}}</li>
</ul>
<h4>Pass an object from parent to nested component:</h4>
<ul>
<li *ngFor="let item of items">{{item.id}} {{item.name}}</li>
</ul>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ListItemComponent } from './list-item.component';
describe('ItemListComponent', () => {
let component: ListItemComponent;
let fixture: ComponentFixture<ListItemComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ListItemComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

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