Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
a8134dcfd4 | |||
a1bf0de711 | |||
3dbc076159 | |||
3409efbeb3 | |||
3812f57789 | |||
8292e1cc51 | |||
d8de689080 | |||
d53cfb510c | |||
fb51e10954 | |||
7813529f4e | |||
066fca07f4 | |||
31be06a6f6 | |||
fd795da9d9 | |||
69302adc02 | |||
9891cef6e4 | |||
65d4e7a8af | |||
0fa208f624 | |||
15fa4bbdaf | |||
ed35adbea6 | |||
4645f43c3c | |||
b613639e8a | |||
bc6ff7745e | |||
33aaa9e7d0 | |||
689651b52f | |||
875fcfe5a9 | |||
0d238aa9d9 | |||
398b44640f | |||
5b1c714068 | |||
6df71d52c1 | |||
be25ccb94c | |||
6e994272e8 | |||
79ac811550 | |||
bbe6cf38ff | |||
c95fabf96d | |||
3fad0ffb3a | |||
ef0be182bb | |||
139c5b4eab | |||
1328236810 | |||
5453772648 | |||
7c5f89d2c3 | |||
bc0d140a1d | |||
14ecc9ead2 | |||
62a2fc8981 | |||
8f59e3750b | |||
3d9ebb4a52 | |||
ea141f86d3 | |||
c100dbe860 | |||
9c82e27ab6 | |||
4035e472d0 | |||
d03e2e35cb | |||
b29c32b758 | |||
3e9986871c | |||
617858df61 | |||
68cb77d9ab | |||
5cdf2e4e30 | |||
39bfa349c7 | |||
b3752e6524 | |||
9833b0b31c | |||
2eb8447c95 | |||
78c9972195 | |||
5bf55132fb | |||
50f5827fb4 | |||
e98f11ac9e | |||
1f2c7f6728 | |||
3d2799b73c | |||
685281337b | |||
9e8fa0748f | |||
525af1e5f0 | |||
58f2abef01 | |||
9d918d8e6c | |||
8236904933 | |||
73550967e4 | |||
c6505001a9 | |||
73c7882629 | |||
32e32e5bdc | |||
0dda8bf265 | |||
8db865d9b0 | |||
667c10a0f9 | |||
02862338fa | |||
8f523c1658 | |||
790e483982 | |||
a7650b0f76 | |||
04d0aa6781 | |||
7d5b5153cf | |||
27a6e5a31c | |||
2700d88912 | |||
3902ec0dbe | |||
6f579b20f8 | |||
c4a7516747 | |||
33055da5c7 | |||
fb163df6dc | |||
6b05dc432b | |||
093c3a10f6 | |||
a0756e9fa4 | |||
d8657ddb5c | |||
2f71995ef7 | |||
32a8713620 | |||
0958a8da61 | |||
e64c0c3730 | |||
730277806b | |||
674620a5ed | |||
997713e2bb | |||
234e5af636 | |||
e43c701388 | |||
2e1264fb5d | |||
a89dcba0d6 | |||
bd9f441370 |
2
.github/angular-robot.yml
vendored
2
.github/angular-robot.yml
vendored
@ -48,6 +48,7 @@ merge:
|
||||
- "packages/bazel/src/protractor/**"
|
||||
- "packages/bazel/src/schematics/**"
|
||||
- "packages/compiler-cli/ngcc/**"
|
||||
- "packages/compiler-cli/src/ngtsc/sourcemaps/**",
|
||||
- "packages/docs/**"
|
||||
- "packages/elements/schematics/**"
|
||||
- "packages/examples/**"
|
||||
@ -68,6 +69,7 @@ merge:
|
||||
- "packages/**/integrationtest/**"
|
||||
- "packages/**/test/**"
|
||||
- "packages/zone.js/*"
|
||||
- "packages/zone.js/dist/**"
|
||||
- "packages/zone.js/doc/**"
|
||||
- "packages/zone.js/example/**"
|
||||
- "packages/zone.js/scripts/**"
|
||||
|
@ -3,6 +3,7 @@ import {commitMessage} from './commit-message';
|
||||
import {format} from './format';
|
||||
import {github} from './github';
|
||||
import {merge} from './merge';
|
||||
import {release} from './release';
|
||||
|
||||
module.exports = {
|
||||
commitMessage,
|
||||
@ -10,4 +11,5 @@ module.exports = {
|
||||
github,
|
||||
merge,
|
||||
caretaker,
|
||||
release,
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {DevInfraMergeConfig} from '../dev-infra/pr/merge/config';
|
||||
import {getDefaultTargetLabelConfiguration} from '../dev-infra/pr/merge/defaults';
|
||||
import {github} from './github';
|
||||
import {release} from './release';
|
||||
|
||||
/**
|
||||
* Configuration for the merge tool in `ng-dev`. This sets up the labels which
|
||||
@ -13,7 +14,9 @@ export const merge: DevInfraMergeConfig['merge'] = async api => {
|
||||
mergeReadyLabel: /^action: merge(-assistance)?/,
|
||||
caretakerNoteLabel: 'action: merge-assistance',
|
||||
commitMessageFixupLabel: 'commit message fixup',
|
||||
labels: await getDefaultTargetLabelConfiguration(api, github, '@angular/core'),
|
||||
// We can pick any of the NPM packages as we are in a monorepo where all packages are
|
||||
// published together with the same version and branching.
|
||||
labels: await getDefaultTargetLabelConfiguration(api, github, release),
|
||||
requiredBaseCommits: {
|
||||
// PRs that target either `master` or the patch branch, need to be rebased
|
||||
// on top of the latest commit message validation fix.
|
||||
|
33
.ng-dev/release.ts
Normal file
33
.ng-dev/release.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import {join} from 'path';
|
||||
import {exec} from 'shelljs';
|
||||
import {ReleaseConfig} from '../dev-infra/release/config';
|
||||
|
||||
/** Configuration for the `ng-dev release` command. */
|
||||
export const release: ReleaseConfig = {
|
||||
npmPackages: [
|
||||
'@angular/animations',
|
||||
'@angular/bazel',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/compiler-cli',
|
||||
'@angular/core',
|
||||
'@angular/elements',
|
||||
'@angular/forms',
|
||||
'@angular/language-service',
|
||||
'@angular/localize',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@angular/platform-server',
|
||||
'@angular/platform-webworker',
|
||||
'@angular/platform-webworker-dynamic',
|
||||
'@angular/router',
|
||||
'@angular/service-worker',
|
||||
'@angular/upgrade',
|
||||
],
|
||||
// TODO: Implement release package building here.
|
||||
buildPackages: async () => [],
|
||||
// TODO: This can be removed once there is a org-wide tool for changelog generation.
|
||||
generateReleaseNotesForHead: async () => {
|
||||
exec('yarn -s gulp changelog', {cwd: join(__dirname, '../')});
|
||||
},
|
||||
};
|
@ -284,7 +284,7 @@ groups:
|
||||
users:
|
||||
- alxhub
|
||||
- crisbeto
|
||||
- devversion
|
||||
# OOO as of 2020-09-28 - devversion
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -419,7 +419,7 @@ groups:
|
||||
- atscott
|
||||
- ~kara # do not request reviews from Kara, but allow her to approve PRs
|
||||
- mhevery
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -662,7 +662,7 @@ groups:
|
||||
users:
|
||||
- AndrewKushnir
|
||||
- IgorMinar
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -679,7 +679,7 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -697,7 +697,7 @@ groups:
|
||||
users:
|
||||
- IgorMinar
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -723,7 +723,7 @@ groups:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
@ -1150,7 +1150,7 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- devversion
|
||||
# OOO as of 2020-09-28 - devversion
|
||||
- filipesilva
|
||||
- gkalpak
|
||||
- IgorMinar
|
||||
@ -1184,7 +1184,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: 4 # Request reviews from four people
|
||||
required: 3 # Require that three people approve
|
||||
@ -1212,7 +1212,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: 4 # Request reviews from four people
|
||||
required: 2 # Require that two people approve
|
||||
@ -1240,7 +1240,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
####################################################################################
|
||||
|
@ -44654,7 +44654,7 @@ const FOLDERS_IGNORE = [
|
||||
const DEFAULT_IGNORE = (0, (_filter || _load_filter()).ignoreLinesToRegex)([...FOLDERS_IGNORE,
|
||||
|
||||
// ignore cruft
|
||||
'yarn.lock', '.lock-wscript', '.wafpickle-{0..9}', '*.swp', '._*', 'npm-debug.log', 'yarn-error.log', '.npmrc', '.yarnrc', '.npmignore', '.gitignore', '.DS_Store']);
|
||||
'yarn.lock', '.lock-wscript', '.wafpickle-{0..9}', '*.swp', '._*', 'npm-debug.log', 'yarn-error.log', '.npmrc', '.yarnrc', '.yarnrc.yml', '.npmignore', '.gitignore', '.DS_Store']);
|
||||
|
||||
const NEVER_IGNORE = (0, (_filter || _load_filter()).ignoreLinesToRegex)([
|
||||
// never ignore these files
|
||||
@ -44663,6 +44663,7 @@ const NEVER_IGNORE = (0, (_filter || _load_filter()).ignoreLinesToRegex)([
|
||||
function packWithIgnoreAndHeaders(cwd, ignoreFunction, { mapHeader } = {}) {
|
||||
return tar.pack(cwd, {
|
||||
ignore: ignoreFunction,
|
||||
sort: true,
|
||||
map: header => {
|
||||
const suffix = header.name === '.' ? '' : `/${header.name}`;
|
||||
header.name = `package${suffix}`;
|
||||
@ -46678,7 +46679,7 @@ function mkdirfix (name, opts, cb) {
|
||||
/* 194 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = {"name":"yarn","installationMethod":"unknown","version":"1.22.4","license":"BSD-2-Clause","preferGlobal":true,"description":"📦🐈 Fast, reliable, and secure dependency management.","dependencies":{"@zkochan/cmd-shim":"^3.1.0","babel-runtime":"^6.26.0","bytes":"^3.0.0","camelcase":"^4.0.0","chalk":"^2.1.0","cli-table3":"^0.4.0","commander":"^2.9.0","death":"^1.0.0","debug":"^3.0.0","deep-equal":"^1.0.1","detect-indent":"^5.0.0","dnscache":"^1.0.1","glob":"^7.1.1","gunzip-maybe":"^1.4.0","hash-for-dep":"^1.2.3","imports-loader":"^0.8.0","ini":"^1.3.4","inquirer":"^6.2.0","invariant":"^2.2.0","is-builtin-module":"^2.0.0","is-ci":"^1.0.10","is-webpack-bundle":"^1.0.0","js-yaml":"^3.13.1","leven":"^2.0.0","loud-rejection":"^1.2.0","micromatch":"^2.3.11","mkdirp":"^0.5.1","node-emoji":"^1.6.1","normalize-url":"^2.0.0","npm-logical-tree":"^1.2.1","object-path":"^0.11.2","proper-lockfile":"^2.0.0","puka":"^1.0.0","read":"^1.0.7","request":"^2.87.0","request-capture-har":"^1.2.2","rimraf":"^2.5.0","semver":"^5.1.0","ssri":"^5.3.0","strip-ansi":"^4.0.0","strip-bom":"^3.0.0","tar-fs":"^1.16.0","tar-stream":"^1.6.1","uuid":"^3.0.1","v8-compile-cache":"^2.0.0","validate-npm-package-license":"^3.0.4","yn":"^2.0.0"},"devDependencies":{"babel-core":"^6.26.0","babel-eslint":"^7.2.3","babel-loader":"^6.2.5","babel-plugin-array-includes":"^2.0.3","babel-plugin-inline-import":"^3.0.0","babel-plugin-transform-builtin-extend":"^1.1.2","babel-plugin-transform-inline-imports-commonjs":"^1.0.0","babel-plugin-transform-runtime":"^6.4.3","babel-preset-env":"^1.6.0","babel-preset-flow":"^6.23.0","babel-preset-stage-0":"^6.0.0","babylon":"^6.5.0","commitizen":"^2.9.6","cz-conventional-changelog":"^2.0.0","eslint":"^4.3.0","eslint-config-fb-strict":"^22.0.0","eslint-plugin-babel":"^5.0.0","eslint-plugin-flowtype":"^2.35.0","eslint-plugin-jasmine":"^2.6.2","eslint-plugin-jest":"^21.0.0","eslint-plugin-jsx-a11y":"^6.0.2","eslint-plugin-prefer-object-spread":"^1.2.1","eslint-plugin-prettier":"^2.1.2","eslint-plugin-react":"^7.1.0","eslint-plugin-relay":"^0.0.28","eslint-plugin-yarn-internal":"file:scripts/eslint-rules","execa":"^0.11.0","fancy-log":"^1.3.2","flow-bin":"^0.66.0","git-release-notes":"^3.0.0","gulp":"^4.0.0","gulp-babel":"^7.0.0","gulp-if":"^2.0.1","gulp-newer":"^1.0.0","gulp-plumber":"^1.0.1","gulp-sourcemaps":"^2.2.0","jest":"^22.4.4","jsinspect":"^0.12.6","minimatch":"^3.0.4","mock-stdin":"^0.3.0","prettier":"^1.5.2","string-replace-loader":"^2.1.1","temp":"^0.8.3","webpack":"^2.1.0-beta.25","yargs":"^6.3.0"},"resolutions":{"sshpk":"^1.14.2"},"engines":{"node":">=4.0.0"},"repository":"yarnpkg/yarn","bin":{"yarn":"./bin/yarn.js","yarnpkg":"./bin/yarn.js"},"scripts":{"build":"gulp build","build-bundle":"node ./scripts/build-webpack.js","build-chocolatey":"powershell ./scripts/build-chocolatey.ps1","build-deb":"./scripts/build-deb.sh","build-dist":"bash ./scripts/build-dist.sh","build-win-installer":"scripts\\build-windows-installer.bat","changelog":"git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md","dupe-check":"yarn jsinspect ./src","lint":"eslint . && flow check","pkg-tests":"yarn --cwd packages/pkg-tests jest yarn.test.js","prettier":"eslint src __tests__ --fix","release-branch":"./scripts/release-branch.sh","test":"yarn lint && yarn test-only","test-only":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose","test-only-debug":"node --inspect-brk --max_old_space_size=4096 node_modules/jest/bin/jest.js --runInBand --verbose","test-coverage":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose","watch":"gulp watch","commit":"git-cz"},"jest":{"collectCoverageFrom":["src/**/*.js"],"testEnvironment":"node","modulePathIgnorePatterns":["__tests__/fixtures/","packages/pkg-tests/pkg-tests-fixtures","dist/"],"testPathIgnorePatterns":["__tests__/(fixtures|__mocks__)/","updates/","_(temp|mock|install|init|helpers).js$","packages/pkg-tests"]},"config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}}}
|
||||
module.exports = {"name":"yarn","installationMethod":"unknown","version":"1.22.5","license":"BSD-2-Clause","preferGlobal":true,"description":"📦🐈 Fast, reliable, and secure dependency management.","dependencies":{"@zkochan/cmd-shim":"^3.1.0","babel-runtime":"^6.26.0","bytes":"^3.0.0","camelcase":"^4.0.0","chalk":"^2.1.0","cli-table3":"^0.4.0","commander":"^2.9.0","death":"^1.0.0","debug":"^3.0.0","deep-equal":"^1.0.1","detect-indent":"^5.0.0","dnscache":"^1.0.1","glob":"^7.1.1","gunzip-maybe":"^1.4.0","hash-for-dep":"^1.2.3","imports-loader":"^0.8.0","ini":"^1.3.4","inquirer":"^6.2.0","invariant":"^2.2.0","is-builtin-module":"^2.0.0","is-ci":"^1.0.10","is-webpack-bundle":"^1.0.0","js-yaml":"^3.13.1","leven":"^2.0.0","loud-rejection":"^1.2.0","micromatch":"^2.3.11","mkdirp":"^0.5.1","node-emoji":"^1.6.1","normalize-url":"^2.0.0","npm-logical-tree":"^1.2.1","object-path":"^0.11.2","proper-lockfile":"^2.0.0","puka":"^1.0.0","read":"^1.0.7","request":"^2.87.0","request-capture-har":"^1.2.2","rimraf":"^2.5.0","semver":"^5.1.0","ssri":"^5.3.0","strip-ansi":"^4.0.0","strip-bom":"^3.0.0","tar-fs":"^1.16.0","tar-stream":"^1.6.1","uuid":"^3.0.1","v8-compile-cache":"^2.0.0","validate-npm-package-license":"^3.0.4","yn":"^2.0.0"},"devDependencies":{"babel-core":"^6.26.0","babel-eslint":"^7.2.3","babel-loader":"^6.2.5","babel-plugin-array-includes":"^2.0.3","babel-plugin-inline-import":"^3.0.0","babel-plugin-transform-builtin-extend":"^1.1.2","babel-plugin-transform-inline-imports-commonjs":"^1.0.0","babel-plugin-transform-runtime":"^6.4.3","babel-preset-env":"^1.6.0","babel-preset-flow":"^6.23.0","babel-preset-stage-0":"^6.0.0","babylon":"^6.5.0","commitizen":"^2.9.6","cz-conventional-changelog":"^2.0.0","eslint":"^4.3.0","eslint-config-fb-strict":"^22.0.0","eslint-plugin-babel":"^5.0.0","eslint-plugin-flowtype":"^2.35.0","eslint-plugin-jasmine":"^2.6.2","eslint-plugin-jest":"^21.0.0","eslint-plugin-jsx-a11y":"^6.0.2","eslint-plugin-prefer-object-spread":"^1.2.1","eslint-plugin-prettier":"^2.1.2","eslint-plugin-react":"^7.1.0","eslint-plugin-relay":"^0.0.28","eslint-plugin-yarn-internal":"file:scripts/eslint-rules","execa":"^0.11.0","fancy-log":"^1.3.2","flow-bin":"^0.66.0","git-release-notes":"^3.0.0","gulp":"^4.0.0","gulp-babel":"^7.0.0","gulp-if":"^2.0.1","gulp-newer":"^1.0.0","gulp-plumber":"^1.0.1","gulp-sourcemaps":"^2.2.0","jest":"^22.4.4","jsinspect":"^0.12.6","minimatch":"^3.0.4","mock-stdin":"^0.3.0","prettier":"^1.5.2","string-replace-loader":"^2.1.1","temp":"^0.8.3","webpack":"^2.1.0-beta.25","yargs":"^6.3.0"},"resolutions":{"sshpk":"^1.14.2"},"engines":{"node":">=4.0.0"},"repository":"yarnpkg/yarn","bin":{"yarn":"./bin/yarn.js","yarnpkg":"./bin/yarn.js"},"scripts":{"build":"gulp build","build-bundle":"node ./scripts/build-webpack.js","build-chocolatey":"powershell ./scripts/build-chocolatey.ps1","build-deb":"./scripts/build-deb.sh","build-dist":"bash ./scripts/build-dist.sh","build-win-installer":"scripts\\build-windows-installer.bat","changelog":"git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md","dupe-check":"yarn jsinspect ./src","lint":"eslint . && flow check","pkg-tests":"yarn --cwd packages/pkg-tests jest yarn.test.js","prettier":"eslint src __tests__ --fix","release-branch":"./scripts/release-branch.sh","test":"yarn lint && yarn test-only","test-only":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose","test-only-debug":"node --inspect-brk --max_old_space_size=4096 node_modules/jest/bin/jest.js --runInBand --verbose","test-coverage":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose","watch":"gulp watch","commit":"git-cz"},"jest":{"collectCoverageFrom":["src/**/*.js"],"testEnvironment":"node","modulePathIgnorePatterns":["__tests__/fixtures/","packages/pkg-tests/pkg-tests-fixtures","dist/"],"testPathIgnorePatterns":["__tests__/(fixtures|__mocks__)/","updates/","_(temp|mock|install|init|helpers).js$","packages/pkg-tests"]},"config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}}}
|
||||
|
||||
/***/ }),
|
||||
/* 195 */
|
||||
@ -98338,7 +98339,7 @@ var _buildSubCommands = (0, (_buildSubCommands2 || _load_buildSubCommands()).def
|
||||
|
||||
const bundle = yield fetchBundle(config, bundleUrl);
|
||||
|
||||
const yarnPath = path.resolve(config.lockfileFolder, `.yarn/releases/yarn-${bundleVersion}.js`);
|
||||
const yarnPath = path.resolve(config.lockfileFolder, `.yarn/releases/yarn-${bundleVersion}.cjs`);
|
||||
reporter.log(`Saving it into ${chalk.magenta(yarnPath)}...`);
|
||||
yield (_fs || _load_fs()).mkdirp(path.dirname(yarnPath));
|
||||
yield (_fs || _load_fs()).writeFile(yarnPath, bundle);
|
||||
@ -100190,7 +100191,7 @@ let main = exports.main = (() => {
|
||||
|
||||
const config = new (_config || _load_config()).default(reporter);
|
||||
const outputWrapperEnabled = (0, (_conversion || _load_conversion()).boolifyWithDefault)(process.env.YARN_WRAP_OUTPUT, true);
|
||||
const shouldWrapOutput = outputWrapperEnabled && !(_commander || _load_commander()).default.json && command.hasWrapper((_commander || _load_commander()).default, (_commander || _load_commander()).default.args);
|
||||
const shouldWrapOutput = outputWrapperEnabled && !(_commander || _load_commander()).default.json && command.hasWrapper((_commander || _load_commander()).default, (_commander || _load_commander()).default.args) && !(commandName === 'init' && (_commander || _load_commander()).default[`2`]);
|
||||
|
||||
if (shouldWrapOutput) {
|
||||
reporter.header(commandName, { name: 'yarn', version: (_yarnVersion || _load_yarnVersion()).version });
|
||||
@ -100604,7 +100605,7 @@ let start = (() => {
|
||||
});
|
||||
|
||||
try {
|
||||
if (yarnPath.endsWith(`.js`)) {
|
||||
if (/\.[cm]?js$/.test(yarnPath)) {
|
||||
exitCode = yield (0, (_child || _load_child()).spawnp)(process.execPath, [yarnPath, ...argv], opts);
|
||||
} else {
|
||||
exitCode = yield (0, (_child || _load_child()).spawnp)(yarnPath, argv, opts);
|
2
.yarnrc
2
.yarnrc
@ -2,4 +2,4 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
yarn-path ".yarn/releases/yarn-1.22.4.js"
|
||||
yarn-path ".yarn/releases/yarn-1.22.5.js"
|
||||
|
@ -34,7 +34,7 @@ filegroup(
|
||||
filegroup(
|
||||
name = "angularjs_scripts",
|
||||
srcs = [
|
||||
# We also declare the unminfied AngularJS files since these can be used for
|
||||
# We also declare the unminified AngularJS files since these can be used for
|
||||
# local debugging (e.g. see: packages/upgrade/test/common/test_helpers.ts)
|
||||
"@npm//:node_modules/angular/angular.js",
|
||||
"@npm//:node_modules/angular/angular.min.js",
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,3 +1,29 @@
|
||||
<a name="10.1.4"></a>
|
||||
## 10.1.4 (2020-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** enable [@types](https://github.com/types) discovery in incremental rebuilds ([#39011](https://github.com/angular/angular/issues/39011)) ([6e99427](https://github.com/angular/angular/commit/6e99427)), closes [#38979](https://github.com/angular/angular/issues/38979)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.3"></a>
|
||||
## 10.1.3 (2020-09-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **http:** Fix error message when we call jsonp without importing HttpClientJsonpModule ([#38756](https://github.com/angular/angular/issues/38756)) ([3902ec0](https://github.com/angular/angular/commit/3902ec0))
|
||||
* **ngcc:** fix compilation of `ChangeDetectorRef` in pipe constructors ([#38892](https://github.com/angular/angular/issues/38892)) ([093c3a1](https://github.com/angular/angular/commit/093c3a1)), closes [#38666](https://github.com/angular/angular/issues/38666) [#38883](https://github.com/angular/angular/issues/38883)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* feat(router): better warning message when a router outlet has not been instantiated ([#38920](https://github.com/angular/angular/issues/38920)) ([04d0aa6](https://github.com/angular/angular/commit/04d0aa6))
|
||||
|
||||
|
||||
|
||||
<a name="10.1.2"></a>
|
||||
## 10.1.2 (2020-09-16)
|
||||
|
||||
|
@ -6,6 +6,7 @@ Everything in this folder is part of the documentation project. This includes
|
||||
* the dgeni configuration for converting source files to rendered files that can be viewed in the web site.
|
||||
* the tooling for setting up examples for development; and generating live-example and zip files from the examples.
|
||||
|
||||
<a name="developer-tasks"></a>
|
||||
## Developer tasks
|
||||
|
||||
We use [Yarn](https://yarnpkg.com) to manage the dependencies and to run build tasks.
|
||||
|
@ -22,7 +22,7 @@ you don't need to specify values for those.
|
||||
The domain name of the server.
|
||||
|
||||
- `AIO_GITHUB_ORGANIZATION`:
|
||||
The GitHub organization whose teams are whitelisted for accepting build artifacts.
|
||||
The GitHub organization whose teams are trusted for accepting build artifacts.
|
||||
See also `AIO_GITHUB_TEAM_SLUGS`.
|
||||
|
||||
- `AIO_GITHUB_REPO`:
|
||||
|
@ -98,7 +98,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
Such a label can only have been added by a maintainer (with the necessary rights) and
|
||||
designates that they have manually verified the PR contents.
|
||||
2. We can verify (again using the GitHub API) the author's membership in one of the
|
||||
whitelisted/trusted GitHub teams. For this operation, we need a Personal Access Token with the
|
||||
trusted GitHub teams. For this operation, we need a Personal Access Token with the
|
||||
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
||||
Here too, we use the token by @mary-poppins.
|
||||
|
||||
|
2
aio/content/examples/.gitignore
vendored
2
aio/content/examples/.gitignore
vendored
@ -17,6 +17,7 @@
|
||||
**/e2e/tsconfig.e2e.json
|
||||
**/src/karma.conf.js
|
||||
**/.angular-cli.json
|
||||
**/.browserslistrc
|
||||
**/.editorconfig
|
||||
**/.gitignore
|
||||
**/angular.json
|
||||
@ -30,7 +31,6 @@
|
||||
**/tslint.json
|
||||
**/karma-test-shim.js
|
||||
**/browser-test-shim.js
|
||||
**/browserslist
|
||||
**/node_modules
|
||||
|
||||
# built files
|
||||
|
@ -21,11 +21,13 @@ import { ItemDirective } from './item.directive';
|
||||
ItemDirective
|
||||
],
|
||||
// #enddocregion declarations
|
||||
// #docregion imports
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpClientModule
|
||||
],
|
||||
// #enddocregion imports
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
describe('Component Communication Cookbook Tests', () => {
|
||||
|
||||
// Note: '?e2e' which app can read to know it is running in protractor
|
||||
// e.g. `if (!/e2e/.test(location.search)) { ...`
|
||||
beforeAll(() => {
|
||||
browser.get('?e2e');
|
||||
});
|
||||
beforeEach(() => browser.get(browser.baseUrl));
|
||||
|
||||
describe('Parent-to-child communication', () => {
|
||||
// #docregion parent-to-child
|
||||
@ -15,7 +11,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
const masterName = 'Master';
|
||||
|
||||
it('should pass properties to children properly', () => {
|
||||
const parent = element.all(by.tagName('app-hero-parent')).get(0);
|
||||
const parent = element(by.tagName('app-hero-parent'));
|
||||
const heroes = parent.all(by.tagName('app-hero-child'));
|
||||
|
||||
for (let i = 0; i < heroNames.length; i++) {
|
||||
@ -35,7 +31,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
it('should display trimmed, non-empty names', () => {
|
||||
const nonEmptyNameIndex = 0;
|
||||
const nonEmptyName = '"Dr IQ"';
|
||||
const parent = element.all(by.tagName('app-name-parent')).get(0);
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(nonEmptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
@ -45,7 +41,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
it('should replace empty name with default name', () => {
|
||||
const emptyNameIndex = 1;
|
||||
const defaultName = '"<no name set>"';
|
||||
const parent = element.all(by.tagName('app-name-parent')).get(0);
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(emptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
@ -70,12 +66,13 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
expect(actual.logs.get(0).getText()).toBe(initialLog);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Minor\' twice', () => {
|
||||
it('should set expected values after clicking \'Minor\' twice', async () => {
|
||||
const repoTag = element(by.tagName('app-version-parent'));
|
||||
const newMinorButton = repoTag.all(by.tagName('button')).get(0);
|
||||
|
||||
newMinorButton.click().then(() => {
|
||||
newMinorButton.click().then(() => {
|
||||
await newMinorButton.click();
|
||||
await newMinorButton.click();
|
||||
|
||||
const actual = getActual();
|
||||
|
||||
const labelAfter2Minor = 'Version 1.25';
|
||||
@ -85,23 +82,20 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
expect(actual.count).toBe(3);
|
||||
expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Major\' once', () => {
|
||||
it('should set expected values after clicking \'Major\' once', async () => {
|
||||
const repoTag = element(by.tagName('app-version-parent'));
|
||||
const newMajorButton = repoTag.all(by.tagName('button')).get(1);
|
||||
|
||||
newMajorButton.click().then(() => {
|
||||
await newMajorButton.click();
|
||||
const actual = getActual();
|
||||
|
||||
const labelAfterMajor = 'Version 2.0';
|
||||
const logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0';
|
||||
const logAfterMajor = 'major changed from 1 to 2, minor changed from 23 to 0';
|
||||
|
||||
expect(actual.label).toBe(labelAfterMajor);
|
||||
expect(actual.count).toBe(4);
|
||||
expect(actual.logs.get(3).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
expect(actual.count).toBe(2);
|
||||
expect(actual.logs.get(1).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
|
||||
function getActual() {
|
||||
@ -118,110 +112,125 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
}
|
||||
// ...
|
||||
// #enddocregion parent-to-child-onchanges
|
||||
|
||||
});
|
||||
|
||||
describe('Child-to-parent communication', () => {
|
||||
// #docregion child-to-parent
|
||||
// ...
|
||||
it('should not emit the event initially', () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 0, Disagree: 0');
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Agree vote', () => {
|
||||
it('should process Agree vote', async () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
const agreeButton1 = element.all(by.tagName('app-voter')).get(0)
|
||||
.all(by.tagName('button')).get(0);
|
||||
agreeButton1.click().then(() => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Disagree vote', () => {
|
||||
it('should process Disagree vote', async () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
const agreeButton1 = element.all(by.tagName('app-voter')).get(1)
|
||||
.all(by.tagName('button')).get(1);
|
||||
agreeButton1.click().then(() => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 1');
|
||||
});
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 1');
|
||||
});
|
||||
// ...
|
||||
// #enddocregion child-to-parent
|
||||
});
|
||||
|
||||
// Can't run timer tests in protractor because
|
||||
// interaction w/ zones causes all tests to freeze & timeout.
|
||||
xdescribe('Parent calls child via local var', () => {
|
||||
countDownTimerTests('countdown-parent-lv');
|
||||
describe('Parent calls child via local var', () => {
|
||||
countDownTimerTests('app-countdown-parent-lv');
|
||||
});
|
||||
|
||||
xdescribe('Parent calls ViewChild', () => {
|
||||
countDownTimerTests('countdown-parent-vc');
|
||||
describe('Parent calls ViewChild', () => {
|
||||
countDownTimerTests('app-countdown-parent-vc');
|
||||
});
|
||||
|
||||
function countDownTimerTests(parentTag: string) {
|
||||
// #docregion countdown-timer-tests
|
||||
// ...
|
||||
it('timer and parent seconds should match', () => {
|
||||
// The tests trigger periodic asynchronous operations (via `setInterval()`), which will prevent
|
||||
// the app from stabilizing. See https://angular.io/api/core/ApplicationRef#is-stable-examples
|
||||
// for more details.
|
||||
// To allow the tests to complete, we will disable automatically waiting for the Angular app to
|
||||
// stabilize.
|
||||
beforeEach(() => browser.waitForAngularEnabled(false));
|
||||
afterEach(() => browser.waitForAngularEnabled(true));
|
||||
|
||||
it('timer and parent seconds should match', async () => {
|
||||
const parent = element(by.tagName(parentTag));
|
||||
const message = parent.element(by.tagName('app-countdown-timer')).getText();
|
||||
browser.sleep(10); // give `seconds` a chance to catchup with `message`
|
||||
const seconds = parent.element(by.className('seconds')).getText();
|
||||
expect(message).toContain(seconds);
|
||||
const startButton = parent.element(by.buttonText('Start'));
|
||||
const seconds = parent.element(by.className('seconds'));
|
||||
const timer = parent.element(by.tagName('app-countdown-timer'));
|
||||
|
||||
await startButton.click();
|
||||
|
||||
// Wait for `<app-countdown-timer>` to be populated with any text.
|
||||
await browser.wait(() => timer.getText(), 2000);
|
||||
|
||||
expect(await timer.getText()).toContain(await seconds.getText());
|
||||
});
|
||||
|
||||
it('should stop the countdown', () => {
|
||||
it('should stop the countdown', async () => {
|
||||
const parent = element(by.tagName(parentTag));
|
||||
const stopButton = parent.all(by.tagName('button')).get(1);
|
||||
const startButton = parent.element(by.buttonText('Start'));
|
||||
const stopButton = parent.element(by.buttonText('Stop'));
|
||||
const timer = parent.element(by.tagName('app-countdown-timer'));
|
||||
|
||||
stopButton.click().then(() => {
|
||||
const message = parent.element(by.tagName('app-countdown-timer')).getText();
|
||||
expect(message).toContain('Holding');
|
||||
});
|
||||
await startButton.click();
|
||||
expect(await timer.getText()).not.toContain('Holding');
|
||||
|
||||
await stopButton.click();
|
||||
expect(await timer.getText()).toContain('Holding');
|
||||
});
|
||||
// ...
|
||||
// #enddocregion countdown-timer-tests
|
||||
}
|
||||
|
||||
|
||||
describe('Parent and children communicate via a service', () => {
|
||||
// #docregion bidirectional-service
|
||||
// ...
|
||||
it('should announce a mission', () => {
|
||||
it('should announce a mission', async () => {
|
||||
const missionControl = element(by.tagName('app-mission-control'));
|
||||
const announceButton = missionControl.all(by.tagName('button')).get(0);
|
||||
announceButton.click().then(() => {
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
|
||||
await announceButton.click();
|
||||
|
||||
expect(history.count()).toBe(1);
|
||||
expect(history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
});
|
||||
|
||||
it('should confirm the mission by Lovell', async () => {
|
||||
await testConfirmMission(1, 'Lovell');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Lovell', () => {
|
||||
testConfirmMission(1, 2, 'Lovell');
|
||||
it('should confirm the mission by Haise', async () => {
|
||||
await testConfirmMission(3, 'Haise');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Haise', () => {
|
||||
testConfirmMission(3, 3, 'Haise');
|
||||
it('should confirm the mission by Swigert', async () => {
|
||||
await testConfirmMission(2, 'Swigert');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Swigert', () => {
|
||||
testConfirmMission(2, 4, 'Swigert');
|
||||
});
|
||||
|
||||
function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
|
||||
const confirmedLog = ' confirmed the mission';
|
||||
async function testConfirmMission(buttonIndex: number, astronaut: string) {
|
||||
const missionControl = element(by.tagName('app-mission-control'));
|
||||
const announceButton = missionControl.all(by.tagName('button')).get(0);
|
||||
const confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
|
||||
confirmButton.click().then(() => {
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
expect(history.count()).toBe(expectedLogCount);
|
||||
expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + confirmedLog);
|
||||
});
|
||||
|
||||
await announceButton.click();
|
||||
await confirmButton.click();
|
||||
|
||||
expect(history.count()).toBe(2);
|
||||
expect(history.get(1).getText()).toBe(`${astronaut} confirmed the mission`);
|
||||
}
|
||||
// ...
|
||||
// #enddocregion bidirectional-service
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--protractor-config=e2e/protractor-puppeteer.conf.js",
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -30,22 +30,21 @@
|
||||
<app-vote-taker></app-vote-taker>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-child-local-var">
|
||||
<app-countdown-parent-lv></app-countdown-parent-lv>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-view-child">
|
||||
<app-countdown-parent-vc></app-countdown-parent-vc>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="bidirectional-service">
|
||||
<app-mission-control></app-mission-control>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -15,10 +15,17 @@ import { VersionParentComponent } from './version-parent.component';
|
||||
import { VoterComponent } from './voter.component';
|
||||
import { VoteTakerComponent } from './votetaker.component';
|
||||
|
||||
const directives: any[] = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AstronautComponent,
|
||||
CountdownLocalVarParentComponent,
|
||||
CountdownTimerComponent,
|
||||
CountdownViewChildParentComponent,
|
||||
HeroChildComponent,
|
||||
HeroParentComponent,
|
||||
MissionControlComponent,
|
||||
@ -27,28 +34,8 @@ const directives: any[] = [
|
||||
VersionChildComponent,
|
||||
VersionParentComponent,
|
||||
VoterComponent,
|
||||
VoteTakerComponent
|
||||
];
|
||||
|
||||
const schemas: any[] = [];
|
||||
|
||||
// Include Countdown examples
|
||||
// unless in e2e tests which they break.
|
||||
if (!/e2e/.test(location.search)) {
|
||||
console.log('adding countdown timer examples');
|
||||
directives.push(CountdownLocalVarParentComponent);
|
||||
directives.push(CountdownViewChildParentComponent);
|
||||
} else {
|
||||
// In e2e test use CUSTOM_ELEMENTS_SCHEMA to suppress unknown element errors
|
||||
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
VoteTakerComponent,
|
||||
],
|
||||
declarations: directives,
|
||||
bootstrap: [ AppComponent ],
|
||||
schemas
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -1,19 +1,16 @@
|
||||
// #docregion
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-countdown-timer',
|
||||
template: '<p>{{message}}</p>'
|
||||
})
|
||||
export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
export class CountdownTimerComponent implements OnDestroy {
|
||||
|
||||
intervalId = 0;
|
||||
message = '';
|
||||
seconds = 11;
|
||||
|
||||
clearTimer() { clearInterval(this.intervalId); }
|
||||
|
||||
ngOnInit() { this.start(); }
|
||||
ngOnDestroy() { this.clearTimer(); }
|
||||
|
||||
start() { this.countDown(); }
|
||||
@ -22,6 +19,8 @@ export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
this.message = `Holding at T-${this.seconds} seconds`;
|
||||
}
|
||||
|
||||
private clearTimer() { clearInterval(this.intervalId); }
|
||||
|
||||
private countDown() {
|
||||
this.clearTimer();
|
||||
this.intervalId = window.setInterval(() => {
|
||||
|
@ -24,7 +24,7 @@ export class UploaderService {
|
||||
// }
|
||||
|
||||
upload(file: File) {
|
||||
if (!file) { return; }
|
||||
if (!file) { return of<string>(); }
|
||||
|
||||
// COULD HAVE WRITTEN:
|
||||
// return this.http.post('/upload/file', file, {
|
||||
|
@ -1,3 +1,4 @@
|
||||
// TODO: Add unit tests for this file.
|
||||
// tslint:disable: no-output-native
|
||||
// #docregion
|
||||
import { Component, Output, OnInit, EventEmitter, NgModule } from '@angular/core';
|
||||
|
@ -2,7 +2,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [ "tsc", "--project", "./tsconfig.app.json" ]
|
||||
"args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"]
|
||||
},
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": ["jasmine", "out-tsc/**/*.spec.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
55
aio/content/examples/observables/src/creating.spec.ts
Normal file
55
aio/content/examples/observables/src/creating.spec.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { docRegionFromEvent, docRegionSubscriber } from './creating';
|
||||
|
||||
describe('observables', () => {
|
||||
it('should create an observable using the constructor', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionSubscriber(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(4);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[2],
|
||||
[3],
|
||||
['Finished sequence'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should listen to input changes', () => {
|
||||
let triggerInputChange;
|
||||
const input = {
|
||||
value: 'Test',
|
||||
addEventListener: jasmine
|
||||
.createSpy('addEvent')
|
||||
.and.callFake((eventName: string, cb: (e) => void) => {
|
||||
if (eventName === 'keydown') {
|
||||
triggerInputChange = cb;
|
||||
}
|
||||
}),
|
||||
removeEventListener: jasmine.createSpy('removeEventListener'),
|
||||
};
|
||||
|
||||
const document = { getElementById: () => input };
|
||||
docRegionFromEvent(document);
|
||||
triggerInputChange({keyCode: 65});
|
||||
expect(input.value).toBe('Test');
|
||||
|
||||
triggerInputChange({keyCode: 27});
|
||||
expect(input.value).toBe('');
|
||||
});
|
||||
|
||||
it('should call removeEventListener when unsubscribing', (doneFn: DoneFn) => {
|
||||
const input = {
|
||||
addEventListener: jasmine.createSpy('addEvent'),
|
||||
removeEventListener: jasmine
|
||||
.createSpy('removeEvent')
|
||||
.and.callFake((eventName: string, cb: (e) => void) => {
|
||||
if (eventName === 'keydown') {
|
||||
doneFn();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const document = { getElementById: () => input };
|
||||
const subscription = docRegionFromEvent(document);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
@ -1,8 +1,9 @@
|
||||
// #docplaster
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export function docRegionSubscriber(console) {
|
||||
// #docregion subscriber
|
||||
|
||||
// This function runs when subscribe() is called
|
||||
function sequenceSubscriber(observer) {
|
||||
// synchronously deliver 1, 2, and 3, then complete
|
||||
@ -30,8 +31,8 @@ sequence.subscribe({
|
||||
// 2
|
||||
// 3
|
||||
// Finished sequence
|
||||
|
||||
// #enddocregion subscriber
|
||||
}
|
||||
|
||||
// #docregion fromevent
|
||||
|
||||
@ -51,16 +52,18 @@ function fromEvent(target, eventName) {
|
||||
|
||||
// #enddocregion fromevent
|
||||
|
||||
export function docRegionFromEvent(document) {
|
||||
// #docregion fromevent_use
|
||||
|
||||
const ESC_KEY = 27;
|
||||
const nameInput = document.getElementById('name') as HTMLInputElement;
|
||||
|
||||
const subscription = fromEvent(nameInput, 'keydown')
|
||||
.subscribe((e: KeyboardEvent) => {
|
||||
const subscription = fromEvent(nameInput, 'keydown').subscribe((e: KeyboardEvent) => {
|
||||
if (e.keyCode === ESC_KEY) {
|
||||
nameInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// #enddocregion fromevent_use
|
||||
return subscription;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// TODO: Add unit tests for this file.
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// #docregion
|
||||
|
||||
// Create an Observable that will start listening to geolocation updates
|
||||
|
48
aio/content/examples/observables/src/multicasting.spec.ts
Normal file
48
aio/content/examples/observables/src/multicasting.spec.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { docRegionDelaySequence, docRegionMulticastSequence } from './multicasting';
|
||||
|
||||
describe('multicasting', () => {
|
||||
let console;
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
console = {log: jasmine.createSpy('log')};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('should create an observable and emit in sequence', () => {
|
||||
docRegionDelaySequence(console);
|
||||
jasmine.clock().tick(10000);
|
||||
expect(console.log).toHaveBeenCalledTimes(12);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
['1st subscribe: 1'],
|
||||
['2nd subscribe: 1'],
|
||||
[2],
|
||||
['1st subscribe: 2'],
|
||||
['2nd subscribe: 2'],
|
||||
[3],
|
||||
['Finished sequence'],
|
||||
['1st subscribe: 3'],
|
||||
['1st sequence finished.'],
|
||||
['2nd subscribe: 3'],
|
||||
['2nd sequence finished.']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create an observable and multicast the emissions', () => {
|
||||
docRegionMulticastSequence(console);
|
||||
jasmine.clock().tick(10000);
|
||||
expect(console.log).toHaveBeenCalledTimes(7);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
['1st subscribe: 1'],
|
||||
['1st subscribe: 2'],
|
||||
['2nd subscribe: 2'],
|
||||
['1st subscribe: 3'],
|
||||
['2nd subscribe: 3'],
|
||||
['1st sequence finished.'],
|
||||
['2nd sequence finished.']
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,8 +1,9 @@
|
||||
// #docplaster
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export function docRegionDelaySequence(console) {
|
||||
// #docregion delay_sequence
|
||||
|
||||
function sequenceSubscriber(observer) {
|
||||
const seq = [1, 2, 3];
|
||||
let timeoutId;
|
||||
@ -23,9 +24,11 @@ function sequenceSubscriber(observer) {
|
||||
doInSequence(seq, 0);
|
||||
|
||||
// Unsubscribe should clear the timeout to stop execution
|
||||
return {unsubscribe() {
|
||||
return {
|
||||
unsubscribe() {
|
||||
clearTimeout(timeoutId);
|
||||
}};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Create a new Observable that will deliver the above sequence
|
||||
@ -71,9 +74,10 @@ setTimeout(() => {
|
||||
// (at 3.5 seconds): 2nd sequence finished
|
||||
|
||||
// #enddocregion subscribe_twice
|
||||
}
|
||||
|
||||
export function docRegionMulticastSequence(console) {
|
||||
// #docregion multicast_sequence
|
||||
|
||||
function multicastSequenceSubscriber() {
|
||||
const seq = [1, 2, 3];
|
||||
// Keep track of each observer (one for every active subscription)
|
||||
@ -84,7 +88,7 @@ function multicastSequenceSubscriber() {
|
||||
|
||||
// Return the subscriber function (runs when subscribe()
|
||||
// function is invoked)
|
||||
return (observer) => {
|
||||
return observer => {
|
||||
observers.push(observer);
|
||||
// When this is the first subscription, start the sequence
|
||||
if (observers.length === 1) {
|
||||
@ -153,3 +157,4 @@ setTimeout(() => {
|
||||
// (at 3 seconds): 2nd sequence finished
|
||||
|
||||
// #enddocregion multicast_sequence
|
||||
}
|
||||
|
19
aio/content/examples/observables/src/subscribing.spec.ts
Normal file
19
aio/content/examples/observables/src/subscribing.spec.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { docRegionObserver } from './subscribing';
|
||||
|
||||
describe('subscribing', () => {
|
||||
it('should subscribe and emit', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionObserver(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(8);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
['Observer got a next value: 1'],
|
||||
['Observer got a next value: 2'],
|
||||
['Observer got a next value: 3'],
|
||||
['Observer got a complete notification'],
|
||||
['Observer got a next value: 1'],
|
||||
['Observer got a next value: 2'],
|
||||
['Observer got a next value: 3'],
|
||||
['Observer got a complete notification'],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,6 +1,7 @@
|
||||
// #docplaster
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export function docRegionObserver(console) {
|
||||
// #docregion observer
|
||||
|
||||
// Create simple observable that emits three values
|
||||
@ -15,6 +16,7 @@ const myObserver = {
|
||||
|
||||
// Execute with the observer object
|
||||
myObservable.subscribe(myObserver);
|
||||
|
||||
// Logs:
|
||||
// Observer got a next value: 1
|
||||
// Observer got a next value: 2
|
||||
@ -30,3 +32,4 @@ myObservable.subscribe(
|
||||
() => console.log('Observer got a complete notification')
|
||||
);
|
||||
// #enddocregion sub_fn
|
||||
}
|
||||
|
@ -2,7 +2,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [ "tsc", "--project", "./tsconfig.app.json" ]
|
||||
"args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"]
|
||||
},
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": ["jasmine", "out-tsc/**/*.spec.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { interval } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { backoff } from './backoff';
|
||||
|
||||
describe('backoff()', () => {
|
||||
beforeEach(() => jasmine.clock().install());
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('should retry in case of error', () => {
|
||||
const mockConsole = {log: jasmine.createSpy('log')};
|
||||
const source = interval(10).pipe(
|
||||
tap(i => {
|
||||
if (i > 0) {
|
||||
throw new Error('Test error');
|
||||
}
|
||||
}),
|
||||
backoff(3, 100),
|
||||
);
|
||||
source.subscribe({
|
||||
next: v => mockConsole.log(`Emitted: ${v}`),
|
||||
error: e => mockConsole.log(`Errored: ${e.message || e}`),
|
||||
complete: () => mockConsole.log('Completed'),
|
||||
});
|
||||
|
||||
// Initial try:
|
||||
// Errors on second emission and schedules retrying (with delay).
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// First re-attempt after 100ms:
|
||||
// Errors again on second emission and schedules retrying (with larger delay).
|
||||
jasmine.clock().tick(100);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// Second re-attempt after 400ms:
|
||||
// Errors again on second emission and schedules retrying (with even larger delay).
|
||||
jasmine.clock().tick(400);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// Third re-attempt after 900ms:
|
||||
// Errors again on second emission and gives up (no retrying).
|
||||
jasmine.clock().tick(900);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Errored: Test error']]);
|
||||
});
|
||||
});
|
@ -1,23 +1,32 @@
|
||||
|
||||
import { pipe, range, timer, zip } from 'rxjs';
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { of, pipe, range, throwError, timer, zip } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { retryWhen, map, mergeMap } from 'rxjs/operators';
|
||||
import { map, mergeMap, retryWhen } from 'rxjs/operators';
|
||||
|
||||
function backoff(maxTries, ms) {
|
||||
export function backoff(maxTries, delay) {
|
||||
return pipe(
|
||||
retryWhen(attempts => zip(range(1, maxTries), attempts)
|
||||
.pipe(
|
||||
map(([i]) => i * i),
|
||||
mergeMap(i => timer(i * ms))
|
||||
)
|
||||
)
|
||||
retryWhen(attempts =>
|
||||
zip(range(1, maxTries + 1), attempts).pipe(
|
||||
mergeMap(([i, err]) => (i > maxTries) ? throwError(err) : of(i)),
|
||||
map(i => i * i),
|
||||
mergeMap(v => timer(v * delay)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #enddocregion
|
||||
/*
|
||||
This function declaration is necessary to ensure that it does not get called
|
||||
when running the unit tests. It will not get rendered into the docs.
|
||||
The indentation needs to start in the leftmost level position as well because of how
|
||||
the docplaster combines the different regions together.
|
||||
*/
|
||||
function docRegionAjaxCall() {
|
||||
// #docregion
|
||||
ajax('/api/endpoint')
|
||||
.pipe(backoff(3, 250))
|
||||
.subscribe(data => handleData(data));
|
||||
|
||||
function handleData(data) {
|
||||
// ...
|
||||
.subscribe(function handleData(data) { /* ... */ });
|
||||
// #enddocregion
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
import { of } from 'rxjs';
|
||||
import { docRegionTypeahead } from './typeahead';
|
||||
|
||||
describe('typeahead', () => {
|
||||
let document;
|
||||
let ajax;
|
||||
let triggertInputChange;
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
const input = {
|
||||
addEventListener: jasmine
|
||||
.createSpy('addEvent')
|
||||
.and.callFake((eventName: string, cb: (e) => void) => {
|
||||
if (eventName === 'input') {
|
||||
triggertInputChange = cb;
|
||||
}
|
||||
}),
|
||||
removeEventListener: jasmine.createSpy('removeEvent'),
|
||||
};
|
||||
|
||||
document = { getElementById: (id: string) => input };
|
||||
ajax = jasmine.createSpy('ajax').and.callFake((url: string) => of('foo bar'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('should make an ajax call to the corrent endpoint', () => {
|
||||
docRegionTypeahead(document, ajax);
|
||||
triggertInputChange({ target: { value: 'foo' } });
|
||||
jasmine.clock().tick(11);
|
||||
expect(ajax).toHaveBeenCalledWith('/api/endpoint?search=foo');
|
||||
});
|
||||
|
||||
it('should not make an ajax call, when the input length < 3', () => {
|
||||
docRegionTypeahead(document, ajax);
|
||||
triggertInputChange({ target: { value: '' } });
|
||||
jasmine.clock().tick(11);
|
||||
expect(ajax).not.toHaveBeenCalled();
|
||||
triggertInputChange({ target: { value: 'fo' } });
|
||||
jasmine.clock().tick(11);
|
||||
expect(ajax).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not make an ajax call for intermediate values when debouncing', () => {
|
||||
docRegionTypeahead(document, ajax);
|
||||
triggertInputChange({ target: { value: 'foo' } });
|
||||
jasmine.clock().tick(9);
|
||||
triggertInputChange({ target: { value: 'bar' } });
|
||||
jasmine.clock().tick(9);
|
||||
triggertInputChange({ target: { value: 'baz' } });
|
||||
jasmine.clock().tick(9);
|
||||
triggertInputChange({ target: { value: 'qux' } });
|
||||
expect(ajax).not.toHaveBeenCalled();
|
||||
jasmine.clock().tick(10);
|
||||
expect(ajax).toHaveBeenCalledTimes(1);
|
||||
expect(ajax).toHaveBeenCalledWith('/api/endpoint?search=qux');
|
||||
});
|
||||
|
||||
it('should not make an ajax call, when the input value has not changed', () => {
|
||||
docRegionTypeahead(document, ajax);
|
||||
triggertInputChange({ target: { value: 'foo' } });
|
||||
jasmine.clock().tick(11);
|
||||
expect(ajax).toHaveBeenCalled();
|
||||
ajax.calls.reset();
|
||||
triggertInputChange({ target: { value: 'foo' } });
|
||||
jasmine.clock().tick(11);
|
||||
expect(ajax).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,8 +1,18 @@
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { fromEvent } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { debounceTime, distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
|
||||
|
||||
|
||||
// #enddocregion
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
export function docRegionTypeahead(document, ajax) {
|
||||
// #docregion
|
||||
const searchBox = document.getElementById('search-box');
|
||||
|
||||
const typeahead = fromEvent(searchBox, 'input').pipe(
|
||||
@ -10,9 +20,13 @@ const typeahead = fromEvent(searchBox, 'input').pipe(
|
||||
filter(text => text.length > 2),
|
||||
debounceTime(10),
|
||||
distinctUntilChanged(),
|
||||
switchMap(() => ajax('/api/endpoint'))
|
||||
switchMap(searchTerm => ajax(`/api/endpoint?search=${searchTerm}`))
|
||||
);
|
||||
|
||||
typeahead.subscribe(data => {
|
||||
// Handle the data from the API
|
||||
});
|
||||
|
||||
// #enddocregion
|
||||
return typeahead;
|
||||
}
|
||||
|
@ -2,7 +2,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [ "tsc", "--project", "./tsconfig.app.json" ]
|
||||
"args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"]
|
||||
},
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": ["jasmine", "out-tsc/**/*.spec.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
46
aio/content/examples/rx-library/src/error-handling.spec.ts
Normal file
46
aio/content/examples/rx-library/src/error-handling.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Subject, throwError } from 'rxjs';
|
||||
import { docRegionDefault } from './error-handling';
|
||||
|
||||
describe('error-handling', () => {
|
||||
let mockConsole;
|
||||
let ajaxSubject;
|
||||
let ajax;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConsole = {log: jasmine.createSpy('log')};
|
||||
ajaxSubject = new Subject();
|
||||
ajax = jasmine
|
||||
.createSpy('ajax')
|
||||
.and.callFake((url: string) => ajaxSubject);
|
||||
});
|
||||
|
||||
afterEach(() => ajaxSubject.unsubscribe());
|
||||
|
||||
it('should return the response object', () => {
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
ajaxSubject.next({response: {foo: 'bar'}});
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', {foo: 'bar'}]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when using an object without a `response` property', () => {
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
ajaxSubject.next({foo: 'bar'});
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when the ajax observable errors', () => {
|
||||
ajax.and.returnValue(throwError('Test Error'));
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []]
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,14 +1,23 @@
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console, ajax) {
|
||||
// #docregion
|
||||
// Return "response" from the API. If an error happens,
|
||||
// return an empty array.
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
map(res => {
|
||||
map((res: any) => {
|
||||
if (!res.response) {
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
@ -23,3 +32,5 @@ apiData.subscribe({
|
||||
});
|
||||
|
||||
// #enddocregion
|
||||
return apiData;
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.1.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.1.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators.1';
|
||||
|
||||
describe('squareOdd - operators.1.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[9],
|
||||
[25],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,9 +1,17 @@
|
||||
import { of, pipe } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { of, pipe } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const nums = of(1, 2, 3, 4, 5);
|
||||
|
||||
// Create a function that accepts an Observable.
|
||||
@ -19,5 +27,4 @@ const squareOdd = squareOddVals(nums);
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
|
||||
// #enddocregion
|
||||
|
||||
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.2.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.2.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators.2';
|
||||
|
||||
describe('squareOdd - operators.2.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[9],
|
||||
[25],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,9 +1,17 @@
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const squareOdd = of(1, 2, 3, 4, 5)
|
||||
.pipe(
|
||||
filter(n => n % 2 !== 0),
|
||||
@ -14,3 +22,4 @@ const squareOdd = of(1, 2, 3, 4, 5)
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators';
|
||||
|
||||
describe('squaredNums - operators.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[4],
|
||||
[9],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,10 +1,17 @@
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const nums = of(1, 2, 3);
|
||||
|
||||
const squareValues = map((val: number) => val * val);
|
||||
@ -18,3 +25,4 @@ squaredNums.subscribe(x => console.log(x));
|
||||
// 9
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
69
aio/content/examples/rx-library/src/retry-on-error.spec.ts
Normal file
69
aio/content/examples/rx-library/src/retry-on-error.spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mergeMap, tap } from 'rxjs/operators';
|
||||
import { docRegionDefault } from './retry-on-error';
|
||||
|
||||
describe('retry-on-error', () => {
|
||||
let mockConsole;
|
||||
beforeEach(() => mockConsole = { log: jasmine.createSpy('log') });
|
||||
|
||||
it('should return the response object', () => {
|
||||
const ajax = () => of({ response: { foo: 'bar' } });
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', { foo: 'bar' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array after 3 retries + 1 initial request', () => {
|
||||
const ajax = () => {
|
||||
return of({ noresponse: true }).pipe(tap(() => mockConsole.log('Subscribed to AJAX')));
|
||||
};
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['data: ', []],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the response if the request succeeds upon retrying', () => {
|
||||
// Fail on the first two requests, but succeed from the 3rd onwards.
|
||||
let failCount = 2;
|
||||
const ajax = () => of(null).pipe(
|
||||
tap(() => mockConsole.log('Subscribed to AJAX')),
|
||||
// Fail on the first 2 requests, but succeed from the 3rd onwards.
|
||||
mergeMap(() => {
|
||||
if (failCount > 0) {
|
||||
failCount--;
|
||||
return throwError('Test error');
|
||||
}
|
||||
return of({ response: { foo: 'bar' } });
|
||||
}),
|
||||
);
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Subscribed to AJAX'], // Initial request | 1st attempt overall
|
||||
['Subscribed to AJAX'], // 1st retry attempt | 2nd attempt overall
|
||||
['Subscribed to AJAX'], // 2nd retry attempt | 3rd attempt overall
|
||||
['data: ', { foo: 'bar' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when the ajax observable throws an error', () => {
|
||||
const ajax = () => throwError('Test Error');
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,20 +1,28 @@
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, retry, catchError } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console, ajax) {
|
||||
// #docregion
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
retry(3), // Retry up to 3 times before failing
|
||||
map(res => {
|
||||
map((res: any) => {
|
||||
if (!res.response) {
|
||||
console.log('Error occured.');
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
return res.response;
|
||||
}),
|
||||
retry(3), // Retry up to 3 times before failing
|
||||
catchError(err => of([]))
|
||||
);
|
||||
|
||||
@ -24,3 +32,4 @@ apiData.subscribe({
|
||||
});
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { of } from 'rxjs';
|
||||
import { docRegionPromise } from './simple-creation.1';
|
||||
|
||||
describe('simple-creation.1', () => {
|
||||
it('should create a promise from an observable and return an empty object', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const fetch = () => of({foo: 42});
|
||||
docRegionPromise(console, fetch);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[{foo: 42}],
|
||||
['Completed'],
|
||||
]);
|
||||
});
|
||||
});
|
24
aio/content/examples/rx-library/src/simple-creation.1.ts
Normal file
24
aio/content/examples/rx-library/src/simple-creation.1.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion promise
|
||||
import { from } from 'rxjs';
|
||||
|
||||
// #enddocregion promise
|
||||
|
||||
export function docRegionPromise(console, fetch) {
|
||||
// #docregion promise
|
||||
// Create an Observable out of a promise
|
||||
const data = from(fetch('/api/endpoint'));
|
||||
// Subscribe to begin listening for async result
|
||||
data.subscribe({
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
});
|
||||
|
||||
// #enddocregion promise
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { docRegionInterval } from './simple-creation.2';
|
||||
|
||||
describe('simple-creation.2', () => {
|
||||
beforeEach(() => jasmine.clock().install());
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('should create an Observable that will publish a value on an interval', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const subscription = docRegionInterval(console);
|
||||
jasmine.clock().tick(1000);
|
||||
expect(console.log).toHaveBeenCalledWith('It\'s been 1 seconds since subscribing!');
|
||||
console.log.calls.reset();
|
||||
|
||||
jasmine.clock().tick(999);
|
||||
expect(console.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(1);
|
||||
expect(console.log).toHaveBeenCalledWith('It\'s been 2 seconds since subscribing!');
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
22
aio/content/examples/rx-library/src/simple-creation.2.ts
Normal file
22
aio/content/examples/rx-library/src/simple-creation.2.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion interval
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
// #enddocregion interval
|
||||
|
||||
export function docRegionInterval(console) {
|
||||
// #docregion interval
|
||||
// Create an Observable that will publish a value on an interval
|
||||
const secondsCounter = interval(1000);
|
||||
// Subscribe to begin publishing values
|
||||
const subscription = secondsCounter.subscribe(n =>
|
||||
console.log(`It's been ${n + 1} seconds since subscribing!`));
|
||||
|
||||
// #enddocregion interval
|
||||
return subscription;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { docRegionEvent } from './simple-creation.3';
|
||||
|
||||
describe('simple-creation.3', () => {
|
||||
let triggerMousemove;
|
||||
let mockConsole;
|
||||
let input;
|
||||
let mockDocument;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConsole = {log: jasmine.createSpy('log')};
|
||||
input = {
|
||||
addEventListener: jasmine
|
||||
.createSpy('addEventListener')
|
||||
.and.callFake((eventName, cb) => {
|
||||
if (eventName === 'mousemove') {
|
||||
triggerMousemove = cb;
|
||||
}
|
||||
}),
|
||||
removeEventListener: jasmine.createSpy('removeEventListener'),
|
||||
};
|
||||
mockDocument = { getElementById: () => input };
|
||||
});
|
||||
|
||||
it('should log coords when subscribing', () => {
|
||||
docRegionEvent(mockConsole, mockDocument);
|
||||
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
triggerMousemove({ clientX: 50, clientY: 50 });
|
||||
triggerMousemove({ clientX: 30, clientY: 50 });
|
||||
triggerMousemove({ clientX: 50, clientY: 30 });
|
||||
expect(mockConsole.log).toHaveBeenCalledTimes(3);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Coords: 50 X 50'],
|
||||
['Coords: 30 X 50'],
|
||||
['Coords: 50 X 30']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call unsubscribe when clientX and clientY are below < 40 ', () => {
|
||||
docRegionEvent(mockConsole, mockDocument);
|
||||
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
// Ensure that we have unsubscribed.
|
||||
triggerMousemove({ clientX: 30, clientY: 30 });
|
||||
expect(input.removeEventListener).toHaveBeenCalledWith('mousemove', triggerMousemove, undefined);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
triggerMousemove({ clientX: 50, clientY: 50 });
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
32
aio/content/examples/rx-library/src/simple-creation.3.ts
Normal file
32
aio/content/examples/rx-library/src/simple-creation.3.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion event
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
// #enddocregion event
|
||||
|
||||
export function docRegionEvent(console, document) {
|
||||
// #docregion event
|
||||
const el = document.getElementById('my-element');
|
||||
|
||||
// Create an Observable that will publish mouse movements
|
||||
const mouseMoves = fromEvent(el, 'mousemove');
|
||||
|
||||
// Subscribe to start listening for mouse-move events
|
||||
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
|
||||
// Log coords of mouse movements
|
||||
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
|
||||
|
||||
// When the mouse is over the upper-left of the screen,
|
||||
// unsubscribe to stop listening for mouse movements
|
||||
if (evt.clientX < 40 && evt.clientY < 40) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
// #enddocregion event
|
||||
}
|
14
aio/content/examples/rx-library/src/simple-creation.spec.ts
Normal file
14
aio/content/examples/rx-library/src/simple-creation.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { of } from 'rxjs';
|
||||
import { docRegionAjax } from './simple-creation';
|
||||
|
||||
describe('ajax', () => {
|
||||
it('should make a request and console log the status and response', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const ajax = jasmine.createSpy('ajax').and.callFake((url: string) => {
|
||||
return of({status: 200, response: 'foo bar'});
|
||||
});
|
||||
|
||||
docRegionAjax(console, ajax);
|
||||
expect(console.log).toHaveBeenCalledWith(200, 'foo bar');
|
||||
});
|
||||
});
|
@ -1,65 +1,19 @@
|
||||
|
||||
// #docregion promise
|
||||
|
||||
import { from } from 'rxjs';
|
||||
|
||||
// Create an Observable out of a promise
|
||||
const data = from(fetch('/api/endpoint'));
|
||||
// Subscribe to begin listening for async result
|
||||
data.subscribe({
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
});
|
||||
|
||||
// #enddocregion promise
|
||||
|
||||
// #docregion interval
|
||||
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
// Create an Observable that will publish a value on an interval
|
||||
const secondsCounter = interval(1000);
|
||||
// Subscribe to begin publishing values
|
||||
secondsCounter.subscribe(n =>
|
||||
console.log(`It's been ${n} seconds since subscribing!`));
|
||||
|
||||
// #enddocregion interval
|
||||
|
||||
|
||||
// #docregion event
|
||||
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
const el = document.getElementById('my-element');
|
||||
|
||||
// Create an Observable that will publish mouse movements
|
||||
const mouseMoves = fromEvent(el, 'mousemove');
|
||||
|
||||
// Subscribe to start listening for mouse-move events
|
||||
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
|
||||
// Log coords of mouse movements
|
||||
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
|
||||
|
||||
// When the mouse is over the upper-left of the screen,
|
||||
// unsubscribe to stop listening for mouse movements
|
||||
if (evt.clientX < 40 && evt.clientY < 40) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
// #enddocregion event
|
||||
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion ajax
|
||||
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
|
||||
// Create an Observable that will create an AJAX request
|
||||
// #enddocregion ajax
|
||||
export function docRegionAjax(console, ajax) {
|
||||
// #docregion ajax
|
||||
const apiData = ajax('/api/data');
|
||||
// Subscribe to create the request
|
||||
apiData.subscribe(res => console.log(res.status, res.response));
|
||||
|
||||
// #enddocregion ajax
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { addMatchers, newEvent, click } from '../../testing';
|
||||
import { addMatchers, click } from '../../testing';
|
||||
|
||||
export class NotProvided extends ValueService { /* example below */ }
|
||||
beforeEach(addMatchers);
|
||||
@ -274,8 +274,10 @@ describe('demo (with TestBed):', () => {
|
||||
expect(comp.name).toBe(expectedOrigName,
|
||||
`comp.name should still be ${expectedOrigName} after value change, before binding happens`);
|
||||
|
||||
// dispatch a DOM event so that Angular learns of input value change.
|
||||
// Dispatch a DOM event so that Angular learns of input value change.
|
||||
// then wait while ngModel pushes input.box value to comp.name
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
input.dispatchEvent(new Event('input'));
|
||||
return fixture.whenStable();
|
||||
})
|
||||
@ -312,8 +314,10 @@ describe('demo (with TestBed):', () => {
|
||||
expect(comp.name).toBe(expectedOrigName,
|
||||
`comp.name should still be ${expectedOrigName} after value change, before binding happens`);
|
||||
|
||||
// dispatch a DOM event so that Angular learns of input value change.
|
||||
// Dispatch a DOM event so that Angular learns of input value change.
|
||||
// then wait a tick while ngModel pushes input.box value to comp.name
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
input.dispatchEvent(new Event('input'));
|
||||
tick();
|
||||
expect(comp.name).toBe(expectedNewName,
|
||||
@ -335,9 +339,11 @@ describe('demo (with TestBed):', () => {
|
||||
// simulate user entering new name in input
|
||||
input.value = inputText;
|
||||
|
||||
// dispatch a DOM event so that Angular learns of input value change.
|
||||
// Dispatch a DOM event so that Angular learns of input value change.
|
||||
// then wait a tick while ngModel pushes input.box value to comp.text
|
||||
// and Angular updates the output span
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
input.dispatchEvent(new Event('input'));
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
|
@ -3,7 +3,7 @@ import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {
|
||||
ActivatedRoute, ActivatedRouteStub, asyncData, click, newEvent
|
||||
ActivatedRoute, ActivatedRouteStub, asyncData, click
|
||||
} from '../../testing';
|
||||
|
||||
import { Hero } from '../model/hero';
|
||||
@ -99,6 +99,9 @@ function overrideSetup() {
|
||||
const newName = 'New Name';
|
||||
|
||||
page.nameInput.value = newName;
|
||||
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
page.nameInput.dispatchEvent(new Event('input')); // tell Angular
|
||||
|
||||
expect(component.hero.name).toBe(newName, 'component hero has new name');
|
||||
@ -197,8 +200,9 @@ function heroModuleSetup() {
|
||||
// simulate user entering a new name into the input box
|
||||
nameInput.value = 'quick BROWN fOx';
|
||||
|
||||
// dispatch a DOM event so that Angular learns of input value change.
|
||||
// use newEvent utility function (not provided by Angular) for better browser compatibility
|
||||
// Dispatch a DOM event so that Angular learns of input value change.
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
nameInput.dispatchEvent(new Event('input'));
|
||||
|
||||
// Tell Angular to update the display binding through the title pipe
|
||||
|
@ -6,7 +6,7 @@ import { DebugElement } from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { addMatchers, newEvent } from '../../testing';
|
||||
import { addMatchers } from '../../testing';
|
||||
import { HeroService } from '../model/hero.service';
|
||||
import { getTestHeroes, TestHeroService } from '../model/testing/test-hero.service';
|
||||
|
||||
@ -53,6 +53,9 @@ describe('HeroListComponent', () => {
|
||||
it('should select hero on click', fakeAsync(() => {
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
li.dispatchEvent(new Event('click'));
|
||||
tick();
|
||||
// `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService
|
||||
@ -62,6 +65,9 @@ describe('HeroListComponent', () => {
|
||||
it('should navigate to selected hero detail on click', fakeAsync(() => {
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
li.dispatchEvent(new Event('click'));
|
||||
tick();
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { HighlightDirective } from './highlight.directive';
|
||||
import { newEvent } from '../../testing';
|
||||
|
||||
// #docregion test-component
|
||||
@Component({
|
||||
@ -59,8 +58,11 @@ describe('HighlightDirective', () => {
|
||||
const input = des[2].nativeElement as HTMLInputElement;
|
||||
expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor');
|
||||
|
||||
// dispatch a DOM event so that Angular responds to the input value change.
|
||||
input.value = 'green';
|
||||
|
||||
// Dispatch a DOM event so that Angular responds to the input value change.
|
||||
// In older browsers, such as IE, you might need a CustomEvent instead. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
input.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
|
@ -14,18 +14,6 @@ export function advance(f: ComponentFixture<any>): void {
|
||||
f.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom DOM event the old fashioned way
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent
|
||||
* Although officially deprecated, some browsers (phantom) don't accept the preferred "new Event(eventName)"
|
||||
*/
|
||||
export function newEvent(eventName: string, bubbles = false, cancelable = false) {
|
||||
const evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent'
|
||||
evt.initCustomEvent(eventName, bubbles, cancelable, null);
|
||||
return evt;
|
||||
}
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
// #docregion click-event
|
||||
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
@ -13,22 +16,6 @@
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
// TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense).
|
||||
"",
|
||||
"kebab-case"
|
||||
],
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
["app", "toh"],
|
||||
"camelCase"
|
||||
],
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
@ -56,6 +43,8 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
// TODO(gkalpak): Fix the code and enable this.
|
||||
// "no-any": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
@ -95,6 +84,11 @@
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
// TODO(gkalpak): Fix the code and enable this.
|
||||
// "typedef": [
|
||||
// true,
|
||||
// "call-signature"
|
||||
// ],
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
@ -130,6 +124,9 @@
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
@ -141,9 +138,19 @@
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
"use-pipe-transform-interface": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
["app", "toh"],
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
// TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense).
|
||||
"",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import * as angular from 'angular';
|
||||
import 'angular-route';
|
||||
|
||||
const appName = 'myApp';
|
||||
|
||||
angular.module(appName, [
|
||||
const appModule = angular.module('myApp', [
|
||||
'ngRoute'
|
||||
])
|
||||
.config(['$routeProvider', '$locationProvider',
|
||||
@ -25,5 +23,5 @@ angular.module(appName, [
|
||||
);
|
||||
|
||||
export function bootstrap(el: HTMLElement) {
|
||||
return angular.bootstrap(el, [appName]);
|
||||
return angular.bootstrap(el, [appModule.name]);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ or in the `@NgModule()` or `@Component()` metadata
|
||||
Registering the provider in the `@Injectable()` metadata also allows Angular to optimize an app
|
||||
by removing the service from the compiled app if it isn't used.
|
||||
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator,
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator.
|
||||
|
||||
```
|
||||
@NgModule({
|
||||
|
@ -18,8 +18,6 @@ When you use the [Angular CLI](cli) command `ng new` to generate an app, the def
|
||||
/* JavaScript imports */
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@ -29,9 +27,7 @@ import { AppComponent } from './app.component';
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpClientModule
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
@ -120,9 +116,6 @@ Now you could use your `ItemDirective` in a component. This example uses `AppMod
|
||||
|
||||
Remember, components, directives, and pipes belong to one module only. You only need to declare them once in your app because you share them by importing the necessary modules. This saves you time and helps keep your app lean.
|
||||
|
||||
|
||||
|
||||
|
||||
{@a imports}
|
||||
|
||||
## The `imports` array
|
||||
@ -130,6 +123,12 @@ Remember, components, directives, and pipes belong to one module only. You only
|
||||
The module's `imports` array appears exclusively in the `@NgModule` metadata object.
|
||||
It tells Angular about other NgModules that this particular module needs to function properly.
|
||||
|
||||
<code-example
|
||||
path="bootstrapping/src/app/app.module.ts"
|
||||
region="imports"
|
||||
header="src/app/app.module.ts (excerpt)">
|
||||
</code-example>
|
||||
|
||||
This list of modules are those that export components, directives, or pipes
|
||||
that component templates in this module reference. In this case, the component is
|
||||
`AppComponent`, which references components, directives, or pipes in `BrowserModule`,
|
||||
@ -138,6 +137,8 @@ A component template can reference another component, directive,
|
||||
or pipe when the referenced class is declared in this module or
|
||||
the class was imported from another module.
|
||||
|
||||
|
||||
|
||||
{@a bootstrap-array}
|
||||
|
||||
## The `providers` array
|
||||
|
@ -387,7 +387,7 @@ List the generated bundles in the `dist/` folder.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
ls dist/*.bundle.js
|
||||
ls dist/*.js
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -396,7 +396,7 @@ The following example displays the graph for the _main_ bundle.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
node_modules/.bin/source-map-explorer dist/main.*.bundle.js
|
||||
node_modules/.bin/source-map-explorer dist/main*
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -603,18 +603,6 @@ In practical terms, the `package.json` of all `@angular` packages has changed in
|
||||
|
||||
For more information about the npm package format, see the [Angular Package Format spec](https://goo.gl/jB3GVv).
|
||||
|
||||
{@a removed}
|
||||
## Removed APIs
|
||||
|
||||
The following APIs have been removed starting with version 10.0.0*:
|
||||
|
||||
| Package | API | Replacement | Notes |
|
||||
| ---------------- | -------------- | ----------- | ----- |
|
||||
| `@angular/core` | Undecorated base classes that use Angular features | Add Angular decorator | See [migration guide](guide/migration-undecorated-classes) for more info |
|
||||
| `@angular/core` | `ModuleWithProviders` without a generic | `ModuleWithProviders` with a generic | See [migration guide](guide/migration-module-with-providers) for more info |
|
||||
|
||||
*To see APIs removed in version 9, check out this guide on the [version 9 docs site](https://v9.angular.io/guide/deprecations#removed).
|
||||
|
||||
{@a style-sanitization}
|
||||
### Style Sanitization for `[style]` and `[style.prop]` bindings
|
||||
Angular used to sanitize `[style]` and `[style.prop]` bindings to prevent malicious code from being inserted through `javascript:` expressions in CSS `url()` entries. However, most modern browsers no longer support the usage of these expressions, so sanitization was only maintained for the sake of IE 6 and 7. Given that Angular does not support either IE 6 or 7 and sanitization has a performance cost, we will no longer sanitize style bindings as of version 10 of Angular.
|
||||
|
@ -15,11 +15,11 @@ RxJS provides an implementation of the `Observable` type, which is needed until
|
||||
RxJS offers a number of functions that can be used to create new observables. These functions can simplify the process of creating observables from things such as events, timers, promises, and so on. For example:
|
||||
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="promise" header="Create an observable from a promise"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.1.ts" region="promise" header="Create an observable from a promise"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="interval" header="Create an observable from a counter"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.2.ts" region="interval" header="Create an observable from a counter"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="event" header="Create an observable from an event"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.3.ts" region="event" header="Create an observable from an event"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="ajax" header="Create an observable that creates an AJAX request"></code-example>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Template statements
|
||||
|
||||
A template **statement** responds to an **event** raised by a binding target
|
||||
such as an element, component, or directive.
|
||||
Template statements are methods or properties that you can use in your HTML to respond to user events.
|
||||
With template statements, your application can engage users through actions such as displaying dynamic content or submitting forms.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -10,24 +10,30 @@ the syntax and code snippets in this guide.
|
||||
|
||||
</div>
|
||||
|
||||
The following template statement appears in quotes to the right of the `=` symbol as in `(event)="statement"`.
|
||||
In the following example, the template statement `deleteHero()` appears in quotes to the right of the `=` symbol as in `(event)="statement"`.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-component-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
A template statement *has a side effect*.
|
||||
That's the whole point of an event.
|
||||
It's how you update application state from user action.
|
||||
When the user clicks the **Delete hero** button, Angular calls the `deleteHero()` function in the component class.
|
||||
|
||||
Responding to events is the other side of Angular's "unidirectional data flow".
|
||||
You're free to change anything, anywhere, during this turn of the event loop.
|
||||
You can use template statements with elements, components, or directives in response to events.
|
||||
|
||||
Like template expressions, template *statements* use a language that looks like JavaScript.
|
||||
The template statement parser differs from the template expression parser and
|
||||
specifically supports both basic assignment (`=`) and chaining expressions with <code>;</code>.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
However, certain JavaScript and template expression syntax is not allowed:
|
||||
Responding to events is an aspect of Angular's [unidirectional data flow](guide/glossary#unidirectional-data-flow).
|
||||
You can change anything in your application during a single event loop.
|
||||
|
||||
* <code>new</code>
|
||||
</div>
|
||||
|
||||
## Syntax
|
||||
|
||||
Like [template expressions](guide/interpolation), template statements use a language that looks like JavaScript.
|
||||
However, the parser for template statements differs from the parser for template expressions.
|
||||
In addition, the template statements parser specifically supports both basic assignment, `=`, and chaining expressions with semicolons, `;`.
|
||||
|
||||
The following JavaScript and template expression syntax is not allowed:
|
||||
|
||||
* `new`
|
||||
* increment and decrement operators, `++` and `--`
|
||||
* operator assignment, such as `+=` and `-=`
|
||||
* the bitwise operators, such as `|` and `&`
|
||||
@ -35,31 +41,32 @@ However, certain JavaScript and template expression syntax is not allowed:
|
||||
|
||||
## Statement context
|
||||
|
||||
As with expressions, statements can refer only to what's in the statement context
|
||||
such as an event handling method of the component instance.
|
||||
Statements have a context—a particular part of the application to which the statement belongs.
|
||||
|
||||
The *statement context* is typically the component instance.
|
||||
The *deleteHero* in `(click)="deleteHero()"` is a method of the data-bound component.
|
||||
Statements can refer only to what's in the statement context, which is typically the component instance.
|
||||
For example, `deleteHero()` of `(click)="deleteHero()"` is a method of the component in the following snippet.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-component-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
The statement context may also refer to properties of the template's own context.
|
||||
In the following examples, the template `$event` object,
|
||||
a [template input variable](guide/built-in-directives#template-input-variable) (`let hero`),
|
||||
and a [template reference variable](guide/template-reference-variables) (`#heroForm`)
|
||||
are passed to an event handling method of the component.
|
||||
In the following example, the component's event handling method, `onSave()` takes the template's own `$event` object as an argument.
|
||||
On the next two lines, the `deleteHero()` method takes a [template input variable](guide/built-in-directives#template-input-variable), `hero`, and `onSubmit()` takes a [template reference variable](guide/template-reference-variables), `#heroForm`.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-var-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
In this example, the context of the `$event` object, `hero`, and `#heroForm` is the template.
|
||||
|
||||
Template context names take precedence over component context names.
|
||||
In `deleteHero(hero)` above, the `hero` is the template input variable,
|
||||
not the component's `hero` property.
|
||||
In the preceding `deleteHero(hero)`, the `hero` is the template input variable, not the component's `hero` property.
|
||||
|
||||
## Statement guidelines
|
||||
## Statement best practices
|
||||
|
||||
Template statements cannot refer to anything in the global namespace. They
|
||||
can't refer to `window` or `document`.
|
||||
They can't call `console.log` or `Math.max`.
|
||||
* **Conciseness**
|
||||
|
||||
As with expressions, avoid writing complex template statements.
|
||||
A method call or simple property assignment should be the norm.
|
||||
Keep template statements minimal by using method calls or basic property assignments.
|
||||
|
||||
* **Work within the context**
|
||||
|
||||
The context of a template statement can be the component class instance or the template.
|
||||
Because of this, template statements cannot refer to anything in the global namespace such as `window` or `document`.
|
||||
For example, template statements can't call `console.log()` or `Math.max()`.
|
||||
|
@ -324,5 +324,5 @@ These techniques are useful for small-scale demonstrations, but they
|
||||
quickly become verbose and clumsy when handling large amounts of user input.
|
||||
Two-way data binding is a more elegant and compact way to move
|
||||
values between data entry fields and model properties.
|
||||
The next page, `Forms`, explains how to write
|
||||
The [`Forms`](guide/forms-overview) page explains how to write
|
||||
two-way bindings with `NgModel`.
|
||||
|
@ -32,7 +32,7 @@ To do this:
|
||||
|
||||
1. Create a `typings.d.ts` file in your `src/` folder. This file is automatically included as global type definition.
|
||||
|
||||
2. Add the following code in `src/typings.d.ts`.
|
||||
2. Add the following code in `src/typings.d.ts`:
|
||||
|
||||
```
|
||||
declare module 'host' {
|
||||
@ -45,7 +45,7 @@ declare module 'host' {
|
||||
}
|
||||
```
|
||||
|
||||
3. In the component or file that uses the library, add the following code.
|
||||
3. In the component or file that uses the library, add the following code:
|
||||
|
||||
```
|
||||
import * as host from 'host';
|
||||
@ -129,7 +129,7 @@ interface JQuery {
|
||||
}
|
||||
```
|
||||
|
||||
If don't add the interface for the script-defined extension, your IDE shows an error:
|
||||
If you don't add the interface for the script-defined extension, your IDE shows an error:
|
||||
|
||||
```
|
||||
[TS][Error] Property 'myPlugin' does not exist on type 'JQuery'
|
||||
|
@ -12,7 +12,7 @@ include the following information:
|
||||
- Node and Angular CLI version (local version only).
|
||||
- How long each command took to initialize and execute.
|
||||
- Command name that was run.
|
||||
- For Schematics commands (add, generate, new and update), a list of whitelisted flags.
|
||||
- For Schematics commands (add, generate, new and update), a list of selected flags.
|
||||
- For build commands (build, serve), the number and size of bundles (initial and lazy),
|
||||
compilation units, the time it took to build and rebuild, and basic Angular-specific
|
||||
API usage.
|
||||
|
@ -3,162 +3,5 @@
|
||||
</header>
|
||||
|
||||
<article class="events-container">
|
||||
<p>Where we'll be presenting:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Where we already presented:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ng-vikings 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Oslo, Norway</td>
|
||||
<td>May 25-26 conference, 27 workshops, 2020</td>
|
||||
</tr>
|
||||
<!-- ng-conf 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>April 1-3, 2020</td>
|
||||
</tr>
|
||||
<!-- ngIndia 2020 -->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
|
||||
<td>Delhi, India</td>
|
||||
<td>Feb 29, 2020</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf 2019 -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
<td>Prague, Czech Republic</td>
|
||||
<td>October 30 - November 1, 2019</td>
|
||||
</tr>
|
||||
<!-- NG Rome 2019-->
|
||||
<tr>
|
||||
<th>
|
||||
<a href="https://ngrome.io" title="NG Rome MMXIX - The Italian Angular Conference">NG Rome MMXIX</a>
|
||||
</th>
|
||||
<td>Rome, Italy</td>
|
||||
<td>Oct 6th workshops, 7th conference, 2019</td>
|
||||
</tr>
|
||||
<!-- AngularConnect 2019-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral"
|
||||
title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, UK</td>
|
||||
<td>September 19-20, 2019</td>
|
||||
</tr>
|
||||
<!-- NG-DE 2019-->
|
||||
<tr>
|
||||
<th><a href="https://ng-de.org/" title="NG-DE">NG-DE</a></th>
|
||||
<td>Berlin, Germany</td>
|
||||
<td>August 29th workshops, 30-31 conference, 2019</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
<tr>
|
||||
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>July 13, 2019</td>
|
||||
</tr>
|
||||
<!-- ngVikings 2019-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Copenhagen, Denmark</td>
|
||||
<td>May 26 (workshops), 27-28 (conference), 2019</td>
|
||||
</tr>
|
||||
<!-- ng-conf 2019-->
|
||||
<tr>
|
||||
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>May 1-3, 2019</td>
|
||||
</tr>
|
||||
<!-- ng-India 2019-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-ind.com/" title="ng-India">ng-India</a></th>
|
||||
<td>Gurgaon, India</td>
|
||||
<td>February 23, 2019</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta 2019 -->
|
||||
<tr>
|
||||
<th><a href="https://ng-atl.org/" title="ngAtlanta">ngAtlanta</a></th>
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 9-12, 2019</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th>
|
||||
<a href="https://past.angularconnect.com/2018" title="AngularConnect">AngularConnect</a>
|
||||
</th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 5-7, 2018</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
<td>Prague, Czech Republic</td>
|
||||
<td>October 29-31, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularMix -->
|
||||
<tr>
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Orlando, Florida</td>
|
||||
<td>October 10-12, 2018</td>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th>
|
||||
<a href="https://www.angularconf.com.au/" title="Angular Conf Australia">Angular Conf Australia</a>
|
||||
</th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- ngJapan-->
|
||||
<tr>
|
||||
<th><a href="https://ngjapan.org/en.html" title="ng-japan">ng-japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna, Austria</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, Utah</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
<th><a href="http://ng-atl.org/" title="ngAtlanta">ngAtlanta</a></th>
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<aio-events></aio-events>
|
||||
</article>
|
||||
|
245
aio/content/marketing/events.json
Normal file
245
aio/content/marketing/events.json
Normal file
@ -0,0 +1,245 @@
|
||||
[
|
||||
{
|
||||
"name": "ng-china",
|
||||
"location": "Online",
|
||||
"linkUrl": "https://ng-china.org/",
|
||||
"date": {
|
||||
"start": "2020-11-21",
|
||||
"end": "2020-11-22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "EnterpriseNG",
|
||||
"location": "Online",
|
||||
"linkUrl": "https://www.ng-conf.org/",
|
||||
"date": {
|
||||
"start": "2020-11-19",
|
||||
"end": "2020-11-20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngrome",
|
||||
"location": "Online",
|
||||
"linkUrl": "https://ngrome.io/",
|
||||
"date": {
|
||||
"start": "2020-10-20",
|
||||
"end": "2020-10-20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DevReach",
|
||||
"location": "Online",
|
||||
"linkUrl": "https://www.telerik.com/devreach/online/agenda-thursday#sessions",
|
||||
"date": {
|
||||
"start": "2020-10-19",
|
||||
"end": "2020-10-23"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngVikings",
|
||||
"location": "Oslo, Norway",
|
||||
"linkUrl": "https://ngvikings.org/",
|
||||
"date": {
|
||||
"start": "2020-05-25",
|
||||
"end": "2020-05-26"
|
||||
},
|
||||
"workshopsDate": {
|
||||
"start": "2020-05-27",
|
||||
"end": "2020-05-27"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-conf",
|
||||
"location": "Salt Lake City, Utah",
|
||||
"linkUrl": "https://ng-conf.org/",
|
||||
"date": {
|
||||
"start": "2020-04-01",
|
||||
"end": "2020-04-03"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngIndia",
|
||||
"location": "Delhi, India",
|
||||
"linkUrl": "https://www.ng-ind.com/",
|
||||
"date": {
|
||||
"start": "2020-02-29",
|
||||
"end": "2020-02-29"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ReactiveConf",
|
||||
"location": "Prague, Czech Republic",
|
||||
"linkUrl": "https://reactiveconf.com/",
|
||||
"date": {
|
||||
"start": "2019-10-30",
|
||||
"end": "2019-11-01"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "NG Rome MMXIX",
|
||||
"location": "Rome, Italy",
|
||||
"linkUrl": "https://ngrome.io",
|
||||
"tooltip": "NG Rome MMXIX - The Italian Angular Conference",
|
||||
"date": {
|
||||
"start": "2019-10-07",
|
||||
"end": "2019-10-07"
|
||||
},
|
||||
"workshopsDate": {
|
||||
"start": "2019-10-06",
|
||||
"end": "2019-10-06"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AngularConnect",
|
||||
"location": "London, UK",
|
||||
"linkUrl": "https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral",
|
||||
"date": {
|
||||
"start": "2019-09-19",
|
||||
"end": "2019-09-20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "NG-DE",
|
||||
"location": "Berlin, Germany",
|
||||
"linkUrl": "https://ng-de.org/",
|
||||
"date": {
|
||||
"start": "2019-08-30",
|
||||
"end": "2019-08-31"
|
||||
},
|
||||
"workshopsDate": {
|
||||
"start": "2019-08-29",
|
||||
"end": "2019-08-29"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-japan",
|
||||
"location": "Tokyo, Japan",
|
||||
"linkUrl": "https://ngjapan.org/",
|
||||
"date": {
|
||||
"start": "2019-07-13",
|
||||
"end": "2019-07-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngVikings",
|
||||
"location": "Copenhagen, Denmark",
|
||||
"linkUrl": "https://ngvikings.org/",
|
||||
"date": {
|
||||
"start": "2019-05-27",
|
||||
"end": "2019-05-28"
|
||||
},
|
||||
"workshopsDate": {
|
||||
"start": "2019-05-26",
|
||||
"end": "2019-05-26"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-conf",
|
||||
"location": "Salt Lake City, Utah",
|
||||
"linkUrl": "https://ng-conf.org/",
|
||||
"date": {
|
||||
"start": "2019-05-01",
|
||||
"end": "2019-05-03"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-India",
|
||||
"location": "Gurgaon, India",
|
||||
"linkUrl": "https://www.ng-ind.com/",
|
||||
"date": {
|
||||
"start": "2019-02-23",
|
||||
"end": "2019-02-23"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngAtlanta",
|
||||
"location": "Atlanta, Georgia",
|
||||
"linkUrl": "https://ng-atl.org/",
|
||||
"date": {
|
||||
"start": "2019-01-09",
|
||||
"end": "2019-01-12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AngularConnect",
|
||||
"location": "London, United Kingdom",
|
||||
"linkUrl": "https://past.angularconnect.com/2018",
|
||||
"date": {
|
||||
"start": "2018-11-05",
|
||||
"end": "2018-11-07"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ReactiveConf",
|
||||
"location": "Prague, Czech Republic",
|
||||
"linkUrl": "https://reactiveconf.com/",
|
||||
"date": {
|
||||
"start": "2018-10-29",
|
||||
"end": "2018-10-31"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "AngularMix",
|
||||
"location": "Orlando, Florida",
|
||||
"linkUrl": "https://angularmix.com/",
|
||||
"date": {
|
||||
"start": "2018-10-10",
|
||||
"end": "2018-10-12"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Angular Conf Australia",
|
||||
"location": "Melbourne, Australia",
|
||||
"linkUrl": "https://www.angularconf.com.au/",
|
||||
"date": {
|
||||
"start": "2018-06-22",
|
||||
"end": "2018-06-22"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-japan",
|
||||
"location": "Tokyo, Japan",
|
||||
"linkUrl": "https://ngjapan.org/en.html",
|
||||
"date": {
|
||||
"start": "2018-06-16",
|
||||
"end": "2018-06-16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "WeAreDevelopers",
|
||||
"location": "Vienna, Austria",
|
||||
"linkUrl": "https://www.wearedevelopers.com/",
|
||||
"tooltip": "WeAreDevs",
|
||||
"date": {
|
||||
"start": "2018-05-16",
|
||||
"end": "2018-05-18"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ng-conf",
|
||||
"location": "Salt Lake City, Utah",
|
||||
"linkUrl": "https://ng-conf.org/",
|
||||
"date": {
|
||||
"start": "2018-04-18",
|
||||
"end": "2018-04-20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngVikings",
|
||||
"location": "Helsinki, Finland",
|
||||
"linkUrl": "https://ngvikings.org/",
|
||||
"date": {
|
||||
"start": "2018-03-01",
|
||||
"end": "2018-03-02"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngAtlanta",
|
||||
"location": "Atlanta, Georgia",
|
||||
"linkUrl": "https://ng-atl.org/",
|
||||
"date": {
|
||||
"start": "2018-01-30",
|
||||
"end": "2018-01-30"
|
||||
}
|
||||
}
|
||||
]
|
@ -61,27 +61,27 @@
|
||||
"children": [
|
||||
{
|
||||
"url": "start",
|
||||
"title": "A Sample App",
|
||||
"title": "Getting started",
|
||||
"tooltip": "Take a look at Angular's component model, template syntax, and component communication."
|
||||
},
|
||||
{
|
||||
"url": "start/start-routing",
|
||||
"title": "In-app Navigation",
|
||||
"title": "Adding navigation",
|
||||
"tooltip": "Navigate among different page views using the browser's URL."
|
||||
},
|
||||
{
|
||||
"url": "start/start-data",
|
||||
"title": "Manage Data",
|
||||
"title": "Managing Data",
|
||||
"tooltip": "Use services and access external data via HTTP."
|
||||
},
|
||||
{
|
||||
"url": "start/start-forms",
|
||||
"title": "Forms for User Input",
|
||||
"title": "Using Forms for User Input",
|
||||
"tooltip": "Learn about fetching and managing data from users with forms."
|
||||
},
|
||||
{
|
||||
"url": "start/start-deployment",
|
||||
"title": "Deployment",
|
||||
"title": "Deploying an application",
|
||||
"tooltip": "Move to local development, or deploy your application to Firebase or your own server."
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Part 1: Getting started with a basic Angular app
|
||||
# Getting started with a basic Angular app
|
||||
|
||||
Welcome to Angular!
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Try it: Manage data
|
||||
# Managing data
|
||||
|
||||
At the end of [In-app Navigation](start/start-routing "Try it: In-app Navigation"), the online store application has a product catalog with two views: a product list and product details.
|
||||
Users can click on a product name from the list to see details in a new view, with a distinct URL, or route.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Try it: Deployment
|
||||
# Deploying an application
|
||||
|
||||
|
||||
To deploy your application, you have to compile it, and then host the JavaScript, CSS, and HTML on a web server. Built Angular applications are very portable and can live in any environment or served by any technology, such as Node, Java, .NET, PHP, and many others.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Try it: Use forms for user input
|
||||
# Using forms for user input
|
||||
|
||||
At the end of [Managing Data](start/start-data "Try it: Managing Data"), the online store application has a product catalog and a shopping cart.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# In-app navigation
|
||||
# Adding navigation
|
||||
|
||||
At the end of [part 1](start "Get started with a basic Angular app"), the online store application has a basic product catalog.
|
||||
The app doesn't have any variable states or navigation.
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-local-with-viewengine": "yarn ~~build",
|
||||
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
||||
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ef770f1cb",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ab97bc382",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
@ -87,28 +87,27 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "10.0.2",
|
||||
"@angular/cdk": "10.0.1",
|
||||
"@angular/common": "10.0.2",
|
||||
"@angular/compiler": "10.0.2",
|
||||
"@angular/core": "10.0.2",
|
||||
"@angular/elements": "10.0.2",
|
||||
"@angular/forms": "10.0.2",
|
||||
"@angular/material": "10.0.1",
|
||||
"@angular/platform-browser": "10.0.2",
|
||||
"@angular/platform-browser-dynamic": "10.0.2",
|
||||
"@angular/router": "10.0.2",
|
||||
"@angular/service-worker": "10.0.2",
|
||||
"@angular/animations": "10.1.3",
|
||||
"@angular/cdk": "10.2.2",
|
||||
"@angular/common": "10.1.3",
|
||||
"@angular/compiler": "10.1.3",
|
||||
"@angular/core": "10.1.3",
|
||||
"@angular/elements": "10.1.3",
|
||||
"@angular/forms": "10.1.3",
|
||||
"@angular/material": "10.2.2",
|
||||
"@angular/platform-browser": "10.1.3",
|
||||
"@angular/platform-browser-dynamic": "10.1.3",
|
||||
"@angular/router": "10.1.3",
|
||||
"@angular/service-worker": "10.1.3",
|
||||
"@webcomponents/custom-elements": "1.2.1",
|
||||
"rxjs": "^6.5.3",
|
||||
"tslib": "^1.10.0",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.1000.1",
|
||||
"@angular/cli": "10.0.1",
|
||||
"@angular/compiler-cli": "10.0.2",
|
||||
"@angular/language-service": "10.0.2",
|
||||
"@angular-devkit/build-angular": "0.1001.3",
|
||||
"@angular/cli": "10.1.3",
|
||||
"@angular/compiler-cli": "10.1.3",
|
||||
"@types/jasmine": "^3.4.2",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/lunr": "^2.3.2",
|
||||
@ -119,7 +118,7 @@
|
||||
"canonical-path": "1.0.0",
|
||||
"chalk": "^2.1.0",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "^6.0.0-next.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
@ -136,8 +135,8 @@
|
||||
"html": "^1.0.0",
|
||||
"ignore": "^3.3.3",
|
||||
"image-size": "^0.5.1",
|
||||
"jasmine": "^3.4.0",
|
||||
"jasmine-core": "^3.4.0",
|
||||
"jasmine": "~3.6.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"jsdom": "^9.12.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
@ -145,7 +144,7 @@
|
||||
"karma": "~5.0.0",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "^2.1.0",
|
||||
"karma-jasmine": "^3.1.1",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"light-server": "^2.6.2",
|
||||
"lighthouse": "6.1.0",
|
||||
@ -165,7 +164,7 @@
|
||||
"tree-kill": "^1.1.0",
|
||||
"ts-node": "^8.4.1",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~3.9.5",
|
||||
"typescript": "~4.0.2",
|
||||
"uglify-js": "^3.0.15",
|
||||
"unist-util-filter": "^0.2.1",
|
||||
"unist-util-source": "^1.0.1",
|
||||
|
@ -40,6 +40,10 @@ export const ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES = [
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: () => import('./live-example/live-example.module').then(m => m.LiveExampleModule)
|
||||
},
|
||||
{
|
||||
selector: 'aio-events',
|
||||
loadChildren: () => import('./events/events.module').then(m => m.EventsModule)
|
||||
}
|
||||
];
|
||||
|
||||
|
43
aio/src/app/custom-elements/events/events.component.html
Normal file
43
aio/src/app/custom-elements/events/events.component.html
Normal file
@ -0,0 +1,43 @@
|
||||
<p>Where we'll be presenting:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody >
|
||||
<tr *ngFor="let event of upcomingEvents">
|
||||
<th><a href="{{event.linkUrl}}" title="{{event.tooltip}}">{{event.name}}</a></th>
|
||||
<td>{{event.location}}</td>
|
||||
<td>
|
||||
<div>
|
||||
{{getEventDates(event)}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Where we already presented:</p>
|
||||
<table class="is-full-width">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let event of pastEvents">
|
||||
<th><a href="{{event.linkUrl}}" title="{{event.tooltip}}">{{event.name}}</a></th>
|
||||
<td>{{event.location}}</td>
|
||||
<td>
|
||||
<div>
|
||||
{{getEventDates(event)}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
231
aio/src/app/custom-elements/events/events.component.spec.ts
Normal file
231
aio/src/app/custom-elements/events/events.component.spec.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Duration, Event, EventsComponent } from './events.component';
|
||||
import { EventsService } from './events.service';
|
||||
|
||||
describe('EventsComponent', () => {
|
||||
let component: EventsComponent;
|
||||
let injector: Injector;
|
||||
let eventsService: TestEventsService;
|
||||
|
||||
beforeEach(() => {
|
||||
injector = Injector.create({
|
||||
providers: [
|
||||
{ provide: EventsComponent, deps: [EventsService] } ,
|
||||
{ provide: EventsService, useClass: TestEventsService, deps: [] },
|
||||
]
|
||||
});
|
||||
eventsService = injector.get(EventsService) as unknown as TestEventsService;
|
||||
component = injector.get(EventsComponent) as unknown as EventsComponent;
|
||||
});
|
||||
|
||||
it('should have no pastEvents when first created', () => {
|
||||
expect(component.pastEvents).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have no upcoming when first created', () => {
|
||||
expect(component.upcomingEvents).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('ngOnInit()', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
jasmine.clock().mockDate(new Date(2020, 5, 15, 23, 59, 59));
|
||||
component.ngOnInit();
|
||||
});
|
||||
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('should separate past and upcoming events', () => {
|
||||
eventsService.events.next([
|
||||
createMockEvent(
|
||||
'Upcoming event 1',
|
||||
{start: '2020-06-16', end: '2020-06-17'},
|
||||
{start: '2020-06-18', end: '2020-06-18'}),
|
||||
createMockEvent(
|
||||
'Upcoming event 3',
|
||||
{start: '2222-01-01', end: '2222-01-02'}),
|
||||
createMockEvent(
|
||||
'Past event 2',
|
||||
{start: '2020-06-13', end: '2020-06-14'}),
|
||||
createMockEvent(
|
||||
'Upcoming event 2',
|
||||
{start: '2020-06-17', end: '2020-06-18'},
|
||||
{start: '2020-06-16', end: '2020-06-16'}),
|
||||
createMockEvent(
|
||||
'Past event 1',
|
||||
{start: '2020-05-30', end: '2020-05-31'}),
|
||||
createMockEvent(
|
||||
'Past event 3',
|
||||
{start: '2020-06-14', end: '2020-06-14'},
|
||||
{start: '2020-06-16', end: '2020-06-17'}),
|
||||
]);
|
||||
|
||||
expect(component.pastEvents.map(evt => evt.name)).toEqual(jasmine.arrayWithExactContents(
|
||||
['Past event 1', 'Past event 2', 'Past event 3']));
|
||||
|
||||
expect(component.upcomingEvents.map(evt => evt.name)).toEqual(jasmine.arrayWithExactContents(
|
||||
['Upcoming event 1', 'Upcoming event 2', 'Upcoming event 3']));
|
||||
});
|
||||
|
||||
it('should order past events in reverse chronological order (ignoring workshops dates)', () => {
|
||||
eventsService.events.next([
|
||||
createMockEvent(
|
||||
'Past event 2',
|
||||
{start: '1999-12-13', end: '1999-12-14'},
|
||||
{start: '1999-12-11', end: '1999-12-11'}),
|
||||
createMockEvent(
|
||||
'Past event 4',
|
||||
{start: '2020-01-16', end: '2020-01-17'},
|
||||
{start: '2020-01-14', end: '2020-01-15'}),
|
||||
createMockEvent(
|
||||
'Past event 3',
|
||||
{start: '2020-01-15', end: '2020-01-16'},
|
||||
{start: '2020-01-17', end: '2020-01-18'}),
|
||||
createMockEvent(
|
||||
'Past event 1',
|
||||
{start: '1999-12-12', end: '1999-12-15'}),
|
||||
]);
|
||||
|
||||
expect(component.pastEvents.map(evt => evt.name)).toEqual(
|
||||
['Past event 4', 'Past event 3', 'Past event 2', 'Past event 1']);
|
||||
});
|
||||
|
||||
it('should order upcoming events in chronological order (ignoring workshops dates)', () => {
|
||||
eventsService.events.next([
|
||||
createMockEvent(
|
||||
'Upcoming event 2',
|
||||
{start: '2020-12-13', end: '2020-12-14'},
|
||||
{start: '2020-12-11', end: '2020-12-11'}),
|
||||
createMockEvent(
|
||||
'Upcoming event 4',
|
||||
{start: '2021-01-16', end: '2021-01-17'},
|
||||
{start: '2021-01-14', end: '2021-01-15'}),
|
||||
createMockEvent(
|
||||
'Upcoming event 3',
|
||||
{start: '2021-01-15', end: '2021-01-16'},
|
||||
{start: '2021-01-17', end: '2021-01-18'}),
|
||||
createMockEvent(
|
||||
'Upcoming event 1',
|
||||
{start: '2020-12-12', end: '2020-12-15'}),
|
||||
]);
|
||||
|
||||
expect(component.upcomingEvents.map(evt => evt.name)).toEqual(
|
||||
['Upcoming event 1', 'Upcoming event 2', 'Upcoming event 3', 'Upcoming event 4']);
|
||||
});
|
||||
|
||||
it('should treat ongoing events as upcoming', () => {
|
||||
eventsService.events.next([
|
||||
createMockEvent(
|
||||
'Ongoing event 1',
|
||||
{start: '2020-06-14', end: '2020-06-16'}),
|
||||
createMockEvent(
|
||||
'Ongoing event 2',
|
||||
{start: '2020-06-14', end: '2020-06-15'},
|
||||
{start: '2020-06-13', end: '2020-06-13'}),
|
||||
]);
|
||||
|
||||
expect(component.pastEvents).toEqual([]);
|
||||
expect(component.upcomingEvents.map(evt => evt.name)).toEqual(jasmine.arrayWithExactContents(
|
||||
['Ongoing event 1', 'Ongoing event 2']));
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEventDates()', () => {
|
||||
describe('(without workshops)', () => {
|
||||
it('should correctly format the main event date', () => {
|
||||
const testEvent = createMockEvent('Test', {start: '2020-06-20', end: '2020-06-20'});
|
||||
expect(component.getEventDates(testEvent)).toBe('June 20, 2020');
|
||||
});
|
||||
|
||||
it('should correctly format the main event date spanning mupliple days', () => {
|
||||
const testEvent = createMockEvent('Test', {start: '2019-09-19', end: '2019-09-21'});
|
||||
expect(component.getEventDates(testEvent)).toBe('September 19-21, 2019');
|
||||
});
|
||||
|
||||
it('should correctly format the main event date spanning mupliple months', () => {
|
||||
const testEvent = createMockEvent('Test', {start: '2019-10-30', end: '2019-11-01'});
|
||||
expect(component.getEventDates(testEvent)).toBe('October 30 - November 1, 2019');
|
||||
});
|
||||
});
|
||||
|
||||
describe('(with workshops)', () => {
|
||||
it('should correctly format event dates with workshops after main event', () => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2020-07-25', end: '2020-07-26'},
|
||||
{start: '2020-07-27', end: '2020-07-27'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('July 25-26 (conference), July 27 (workshops), 2020');
|
||||
});
|
||||
|
||||
it('should correctly format event dates with workshops before main event', () => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2019-10-07', end: '2019-10-07'},
|
||||
{start: '2019-10-06', end: '2019-10-06'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('October 6 (workshops), October 7 (conference), 2019');
|
||||
});
|
||||
|
||||
it('should correctly format event dates spanning multiple days', () => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2019-08-30', end: '2019-08-31'},
|
||||
{start: '2019-08-28', end: '2019-08-29'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('August 28-29 (workshops), August 30-31 (conference), 2019');
|
||||
});
|
||||
|
||||
it('should correctly format event dates with workshops on different month before the main event',
|
||||
() => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2020-08-01', end: '2020-08-02'},
|
||||
{start: '2020-07-30', end: '2020-07-31'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('July 30-31 (workshops), August 1-2 (conference), 2020');
|
||||
});
|
||||
|
||||
it('should correctly format event dates with workshops on different month after the main event',
|
||||
() => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2020-07-30', end: '2020-07-31'},
|
||||
{start: '2020-08-01', end: '2020-08-02'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('July 30-31 (conference), August 1-2 (workshops), 2020');
|
||||
});
|
||||
|
||||
it('should correctly format event dates spanning multiple months', () => {
|
||||
const testEvent = createMockEvent(
|
||||
'Test',
|
||||
{start: '2020-07-31', end: '2020-08-01'},
|
||||
{start: '2020-07-30', end: '2020-08-01'});
|
||||
|
||||
expect(component.getEventDates(testEvent))
|
||||
.toBe('July 30 - August 1 (workshops), July 31 - August 1 (conference), 2020');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Helpers
|
||||
class TestEventsService {
|
||||
events = new Subject<Event[]>();
|
||||
}
|
||||
|
||||
function createMockEvent(name: string, date: Duration, workshopsDate?: Duration): Event {
|
||||
return {
|
||||
name,
|
||||
location: '',
|
||||
linkUrl: '',
|
||||
date,
|
||||
workshopsDate,
|
||||
};
|
||||
}
|
||||
});
|
102
aio/src/app/custom-elements/events/events.component.ts
Normal file
102
aio/src/app/custom-elements/events/events.component.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { EventsService } from './events.service';
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
const MONTHS = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
export type date = string; // of the format `YYYY-MM-DD`.
|
||||
export interface Duration {
|
||||
start: date;
|
||||
end: date;
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
name: string;
|
||||
location: string;
|
||||
linkUrl: string;
|
||||
tooltip?: string;
|
||||
date: Duration;
|
||||
workshopsDate?: Duration;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'aio-events',
|
||||
templateUrl: 'events.component.html'
|
||||
})
|
||||
export class EventsComponent implements OnInit {
|
||||
|
||||
pastEvents: Event[];
|
||||
upcomingEvents: Event[];
|
||||
|
||||
constructor(private eventsService: EventsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.eventsService.events.subscribe(events => {
|
||||
this.pastEvents = events
|
||||
.filter(event => new Date(event.date.end).getTime() < Date.now() - DAY)
|
||||
.sort((l: Event, r: Event) => isBefore(l.date, r.date) ? 1 : -1);
|
||||
|
||||
this.upcomingEvents = events
|
||||
.filter(event => new Date(event.date.end).getTime() >= Date.now() - DAY)
|
||||
.sort((l: Event, r: Event) => isBefore(l.date, r.date) ? -1 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
getEventDates(event: Event) {
|
||||
let dateString;
|
||||
|
||||
// Check if there is a workshop
|
||||
if (event.workshopsDate) {
|
||||
const mainEventDateString = `${processDate(event.date)} (conference)`;
|
||||
const workshopsDateString = `${processDate(event.workshopsDate)} (workshops)`;
|
||||
const areWorkshopsBeforeEvent = isBefore(event.workshopsDate, event.date);
|
||||
|
||||
dateString = areWorkshopsBeforeEvent ?
|
||||
`${workshopsDateString}, ${mainEventDateString}` :
|
||||
`${mainEventDateString}, ${workshopsDateString}`;
|
||||
} else {
|
||||
// If no work shop date create conference date string
|
||||
dateString = processDate(event.date);
|
||||
}
|
||||
dateString = `${dateString}, ${new Date(event.date.end).getFullYear()}`;
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
function processDate(dates: Duration) {
|
||||
// Covert Date sting to date object for comparisons
|
||||
const startDate = new Date(dates.start);
|
||||
const endDate = new Date(dates.end);
|
||||
|
||||
// Create a date string in the start like January 31
|
||||
let processedDate = `${MONTHS[startDate.getMonth()]} ${startDate.getDate()}`;
|
||||
|
||||
// If they are in different months add the string '- February 2' Making the final string January 31 - February 2
|
||||
if (startDate.getMonth() !== endDate.getMonth()) {
|
||||
processedDate = `${processedDate} - ${MONTHS[endDate.getMonth()]} ${endDate.getDate()}`;
|
||||
} else if (startDate.getDate() !== endDate.getDate()) {
|
||||
// If not add - date eg it will make // January 30-31
|
||||
processedDate = `${processedDate}-${endDate.getDate()}`;
|
||||
}
|
||||
|
||||
return processedDate;
|
||||
}
|
||||
|
||||
function isBefore(duration1: Duration, duration2: Duration): boolean {
|
||||
return (duration1.start < duration2.start) ||
|
||||
(duration1.start === duration2.start && duration1.end < duration2.end);
|
||||
}
|
15
aio/src/app/custom-elements/events/events.module.ts
Normal file
15
aio/src/app/custom-elements/events/events.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { EventsComponent } from './events.component';
|
||||
import { EventsService } from './events.service';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule ],
|
||||
declarations: [ EventsComponent ],
|
||||
entryComponents: [ EventsComponent ],
|
||||
providers: [ EventsService]
|
||||
})
|
||||
export class EventsModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any> = EventsComponent;
|
||||
}
|
56
aio/src/app/custom-elements/events/events.service.spec.ts
Normal file
56
aio/src/app/custom-elements/events/events.service.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EventsService } from './events.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
|
||||
describe('EventsService', () => {
|
||||
|
||||
let injector: Injector;
|
||||
let eventsService: EventsService;
|
||||
let httpMock: HttpTestingController;
|
||||
let mockLogger: MockLogger;
|
||||
|
||||
beforeEach(() => {
|
||||
injector = TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule],
|
||||
providers: [
|
||||
EventsService,
|
||||
{ provide: Logger, useClass: MockLogger }
|
||||
]
|
||||
});
|
||||
|
||||
eventsService = injector.get<EventsService>(EventsService);
|
||||
mockLogger = injector.get(Logger) as any;
|
||||
httpMock = injector.get(HttpTestingController);
|
||||
});
|
||||
|
||||
afterEach(() => httpMock.verify());
|
||||
|
||||
it('should make a single connection to the server', () => {
|
||||
eventsService.events.subscribe();
|
||||
eventsService.events.subscribe();
|
||||
httpMock.expectOne('generated/events.json');
|
||||
expect().nothing(); // Prevent jasmine from complaining about no expectations.
|
||||
});
|
||||
|
||||
it('should handle a failed request for `events.json`', () => {
|
||||
const request = httpMock.expectOne('generated/events.json');
|
||||
request.error(new ErrorEvent('404'));
|
||||
expect(mockLogger.output.error).toEqual([
|
||||
[jasmine.any(Error)]
|
||||
]);
|
||||
expect(mockLogger.output.error[0][0].message).toMatch(/^generated\/events\.json request failed:/);
|
||||
});
|
||||
|
||||
it('should return an empty array on a failed request for `events.json`', done => {
|
||||
const request = httpMock.expectOne('generated/events.json');
|
||||
request.error(new ErrorEvent('404'));
|
||||
eventsService.events.subscribe(results => {
|
||||
expect(results).toEqual([]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
32
aio/src/app/custom-elements/events/events.service.ts
Normal file
32
aio/src/app/custom-elements/events/events.service.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { ConnectableObservable, Observable, of } from 'rxjs';
|
||||
import { catchError, publishLast } from 'rxjs/operators';
|
||||
|
||||
import { Event } from './events.component';
|
||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
const eventsPath = CONTENT_URL_PREFIX + 'events.json';
|
||||
|
||||
@Injectable()
|
||||
export class EventsService {
|
||||
events: Observable<Event[]>;
|
||||
|
||||
constructor(private http: HttpClient, private logger: Logger) {
|
||||
this.events = this.getEvents();
|
||||
}
|
||||
|
||||
private getEvents() {
|
||||
const events = this.http.get<any>(eventsPath).pipe(
|
||||
catchError(error => {
|
||||
this.logger.error(new Error(`${eventsPath} request failed: ${error.message}`));
|
||||
return of([]);
|
||||
}),
|
||||
publishLast()
|
||||
);
|
||||
(events as ConnectableObservable<Event[]>).connect();
|
||||
return events;
|
||||
}
|
||||
}
|
@ -99,7 +99,7 @@ describe('ScrollService', () => {
|
||||
if (original !== undefined) {
|
||||
Object.defineProperty(window.history, 'scrollRestoration', original);
|
||||
} else {
|
||||
delete window.history.scrollRestoration;
|
||||
delete (window.history as any).scrollRestoration;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -25,7 +25,6 @@
|
||||
@import 'progress-bar';
|
||||
@import 'presskit';
|
||||
@import 'resources';
|
||||
@import 'scrollbar';
|
||||
@import 'search-results';
|
||||
@import 'select-menu';
|
||||
@import 'table';
|
||||
|
@ -1,27 +0,0 @@
|
||||
body::-webkit-scrollbar, mat-sidenav.sidenav::-webkit-scrollbar, .mat-sidenav-content::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-track, mat-sidenav.sidenav::-webkit-scrollbar-track, .mat-sidenav-content::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb, mat-sidenav.sidenav::-webkit-scrollbar-thumb, .mat-sidenav-content::-webkit-scrollbar-thumb {
|
||||
background-color: $mediumgray;
|
||||
outline: 1px solid $darkgray;
|
||||
}
|
||||
|
||||
.search-results::-webkit-scrollbar, .toc-container::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.search-results::-webkit-scrollbar-track, .toc-container::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.search-results::-webkit-scrollbar-thumb, .toc-container::-webkit-scrollbar-thumb {
|
||||
background-color: $mediumgray;
|
||||
outline: 1px solid slategrey;
|
||||
}
|
@ -39,31 +39,27 @@ See the [README.md](examples/README.md) for more details.
|
||||
|
||||
## example-zipper
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained runnable project packaged as a zip file.
|
||||
These zip files are generated by the utility in this folder.
|
||||
|
||||
See the [README.md](example-zipper/README.md) for more details.
|
||||
|
||||
## stackblitz-builder
|
||||
|
||||
In the AIO application, we can embed a running version of the example as a [Stackblitz](https://stackblitz.com/) session.
|
||||
We can also provide a link to create a runnable version of the example in the [Stackblitz](https://stackblitz.com/)
|
||||
editor.
|
||||
We can also provide a link to create a runnable version of the example in the [Stackblitz](https://stackblitz.com/) editor.
|
||||
|
||||
See the [README.md](stackblitz-builder/README.md) for more details.
|
||||
|
||||
## transforms
|
||||
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are
|
||||
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
|
||||
documentation generation tool.
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are generated from source files by [Dgeni](https://github.com/angular/dgeni).
|
||||
Dgeni is a general purpose documentation generation tool.
|
||||
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are
|
||||
processed and transformed into files that are consumed by the AIO application.
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are processed and transformed into files that are consumed by the AIO application.
|
||||
|
||||
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
|
||||
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
|
||||
some are specific to the AIO project.
|
||||
Dgeni is configured by "packages", which contain services and processors.
|
||||
Some of these packages are installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and some are specific to the AIO project.
|
||||
|
||||
The project specific packages are stored in the `aio/tools/transforms` folder. See the
|
||||
[README.md](transforms/README.md) for more details.
|
||||
The project specific packages are stored in the `aio/tools/transforms` folder.
|
||||
See the [README.md](transforms/README.md) for more details.
|
||||
|
@ -1,16 +0,0 @@
|
||||
# Docs releases
|
||||
|
||||
This document explains how to update the documentation examples after an Angular release. This is only needed for major and minor versions.
|
||||
|
||||
All the packages for the docs' examples are specified in `/aio/tools/examples/shared/package.json`
|
||||
|
||||
**1)** So within the `shared` folder, you need to issue the following command:
|
||||
|
||||
```
|
||||
$ yarn upgrade-interactive --tilde
|
||||
```
|
||||
|
||||
There, select all the packages that are updated on the new Angular release.
|
||||
|
||||
**2)** Changes to the tsconfig.json? There are several files in `/aio/tools/examples/shared/boilerplate/*/tsconfig.json` (based on the example type).
|
||||
|
@ -1,7 +1,5 @@
|
||||
# Overview
|
||||
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
|
||||
we apply to the library after it is installed. This folder contains git patch files that contain these new features
|
||||
and a utility to apply those patches to the CLI library.
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which we apply to the library after it is installed. This folder contains git patch files that contain these new features and a utility to apply those patches to the CLI library.
|
||||
|
||||
**Currently, we have no patches to run.**
|
@ -1,13 +1,14 @@
|
||||
# Overview
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained runnable project packaged as a zip file.
|
||||
These zip files are generated by the utility in this folder.
|
||||
|
||||
## Example zipper
|
||||
|
||||
The `exampleZipper.js` tool is very similar to the Stackblitz's `builder.js`. The latter creates an HTML file
|
||||
with all the examples' files and the former creates a zip file instead. They both use the `stackblitz.json` file
|
||||
to flag an example as something to stackblitz or zip. For example:
|
||||
The `exampleZipper.js` tool is very similar to the Stackblitz's `builder.js`.
|
||||
The latter creates an HTML file with all the examples' files and the former creates a zip file instead.
|
||||
They both use the `stackblitz.json` file to flag an example as something to stackblitz or zip.
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -41,8 +42,7 @@ The `exampleZipper.js` won't include any `System.js` related files for CLI-based
|
||||
As mentioned, the tool uses the `stackblitz.json` as a flag and also a configuration file for the zipper.
|
||||
The problem is that not all examples have a stackblitz but they could offer a zip instead.
|
||||
|
||||
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the
|
||||
stackblitz tool.
|
||||
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the stackblitz tool.
|
||||
|
||||
## Executing the zip generation
|
||||
|
||||
@ -50,5 +50,4 @@ stackblitz tool.
|
||||
|
||||
Where? At `src/generated/zips/`.
|
||||
|
||||
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for
|
||||
the example.
|
||||
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for the example.
|
||||
|
@ -1,110 +1,157 @@
|
||||
# Overview
|
||||
|
||||
Many of the documentation pages contain snippets of code examples. Extract these snippets from
|
||||
real working example applications, which are stored in subfolders of the `/aio/content/examples`
|
||||
folder. Each example can be built and run independently. Each example also provides e2e specs, which
|
||||
are run as part of our CircleCI legacy build tasks, to verify that the examples continue to work as
|
||||
expected, as changes are made to the core Angular libraries.
|
||||
Many of the documentation pages contain snippets of code examples.
|
||||
These snippets are extracted from real working example applications, which are stored in sub-folders of the [aio/content/examples/](.) folder.
|
||||
Each example can be built and run independently.
|
||||
Each example also provides tests (mostly e2e and occasionally unit tests), which are run as part of our CircleCI `test_docs_examples*` jobs, to verify that the examples continue to work as expected, as changes are made to the core Angular libraries.
|
||||
|
||||
In order to build, run and test these examples independently you need to install dependencies into
|
||||
their sub-folder. Also there are a number of common boilerplate files that are needed to configure
|
||||
each example's project. Maintain these common boilerplate files centrally to reduce the amount
|
||||
of effort if one of them needs to change.
|
||||
In order to build, run and test these examples independently, you need to install dependencies into their sub-folder.
|
||||
Also there are a number of common boilerplate files that are needed to configure each example's project.
|
||||
These common boilerplate files are maintained centrally to reduce the amount of effort if one of them needs to change.
|
||||
|
||||
> **Note for Windows users**
|
||||
>
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](#symlinked-node_modules) for details). On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](#symlinked-node_modules) for details).
|
||||
> On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
|
||||
|
||||
## Boilerplate overview
|
||||
|
||||
As mentioned, many of the documentation pages contain snippets extracted from real example applications.
|
||||
To achieve that, all those applications needs to contain a basic boilerplate. E.g. a `node_modules`
|
||||
folder, `package.json` with scripts, etc.
|
||||
As mentioned above, many of the documentation pages contain snippets extracted from real example applications.
|
||||
To achieve that, all those applications need to contain some basic boilerplate, such as a `node_modules/` folder, a `package.json` file with scripts and dependencies, etc.
|
||||
|
||||
There are also different project types, each with its own boilerplate.
|
||||
For example, there are projects based on the Angular CLI, projects that use AngularJS, Custom Elements, i18n, server-side rendering, etc.
|
||||
(See the [example configuration section](#example-config) below for more info on how to specify the project type.)
|
||||
|
||||
To avoid having to maintain the boilerplate in each example, we use the [example-boilerplate-js](./example-boilerplate.js) script to provide a set of files that works across all the examples of a specific type.
|
||||
|
||||
No one wants to maintain the boilerplate on each example, so the goal of this tool is to provide a set of files that works across all the examples.
|
||||
|
||||
### Boilerplate files
|
||||
|
||||
Inside `/aio/tools/examples/shared/boilerplate` you will find a set of folders representing each project type.
|
||||
Inside [shared/boilerplate/](./shared/boilerplate) there is a sub-folder with boilerplate files for each of the different project types.
|
||||
|
||||
Currently you will find the next project types:
|
||||
Currently, the following project types are supported:
|
||||
|
||||
* cli - For CLI based examples. This is the default one, to be used in the majority of the examples.
|
||||
* getting-started - CLI-based with its own set of styles.
|
||||
* i18n - CLI-based with additional scripts for internationalization.
|
||||
* schematics - CLI-based with additional scripts for building schematics.
|
||||
* service-worker - CLI-based with additional packages and configuration for service workers.
|
||||
* systemjs - Currently in deprecation, only used in a few examples.
|
||||
* testing - CLI-based with additional styles for jasmine testing.
|
||||
* universal - CLI-based with an extra server target.
|
||||
* viewengine - Additional configuration for running CLI-/SystemJS-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
- `cli`: For example apps based on the Angular CLI. This is the default type and is used in the majority of the examples.
|
||||
- `cli-ajs`: For CLI-based examples that also use AngularJS (but not via `@angular/upgrade`).
|
||||
- `elements`: For CLI-based examples that also use `@angular/elements`.
|
||||
- `getting-started`: For the "Getting started" tutorial. Essentially the same as `cli` but with custom CSS styles.
|
||||
- `i18n`: For CLI-based examples that also use internationalization.
|
||||
- `schematics`: For CLI-based examples that include a library with schematics.
|
||||
- `service-worker`: For CLI-based examples that also use `@angular/service-worker`.
|
||||
- `systemjs`: For non-CLI legacy examples using SystemJS. This is deprecated and only used in few examples.
|
||||
- `testing`: For CLI-based examples that are related to unit testing.
|
||||
- `universal`: For CLI-based examples that also use `@nguniversal/express-engine` for SSR.
|
||||
|
||||
There is also a `common` folder that contains files used in all different examples.
|
||||
There are also the following special folders:
|
||||
- `common`: Contains files used in many examples.
|
||||
(See the [next section](#example-config) for info on how to exclude common files in certain examples.)
|
||||
- `viewengine/cli`: Additional configuration for running CLI-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
This applies to all CLI-based examples, such as `cli-ajs`, `elements`, `getting-started`, etc.
|
||||
- `viewengine/systemjs`: Additional configuration for running SystemJS-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
|
||||
### The example-config.json
|
||||
|
||||
Each example is identified by an **example-config.json** configuration file in its root folder.
|
||||
This configuration file indicates what type of boilerplate this example needs. E.g.
|
||||
<a name="example-config"></a>
|
||||
### The `example-config.json`
|
||||
|
||||
Each example is identified by an `example-config.json` configuration file in its root folder.
|
||||
This configuration file indicates what type of boilerplate this example needs and how to test it.
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectType": "cli",
|
||||
"useCommonBoilerplate": true
|
||||
"projectType": "cli"
|
||||
}
|
||||
```
|
||||
|
||||
If the file is empty then the default type of cli is assumed.
|
||||
When the boilerplate tooling runs, it will copy into the example folder all of the appropriate files based on the project type.
|
||||
The file is expected to contain a JSON object with zero or more of the following properties:
|
||||
|
||||
- `projectType: string`: One of the supported project types (see above).
|
||||
Default: `"cli"`
|
||||
- `useCommonBoilerplate: boolean`: Whether to include common boilerplate from the [common/](./shared/boilerplate/common) folder.
|
||||
Default: `true`
|
||||
|
||||
**SystemJS-only properties:**
|
||||
- `build: string`: The npm script to run in order to build the example app.
|
||||
Default: `"build"`
|
||||
- `run: string`: The npm script to run in order to serve the example app (so that e2e test can be run against it).
|
||||
Default `"serve:e2e"`
|
||||
|
||||
**CLI-only properties:**
|
||||
- `tests: object[]`: An array of objects, each specifying a test command. This can be used to run multiple test commands in series (for example, to run unit and e2e tests).
|
||||
The commands are specified as `{cmd: string, args: string[]}` and must be in a format that could be passed to Node.js' `child_process.spawn(cmd, args)`. You can use a special `{PORT}` placeholder, that will be replaced with the port on which the app is served during the actual test.
|
||||
Default:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--prod",
|
||||
"--protractor-config=e2e/protractor-puppeteer.conf.js",
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
An empty `example-config.json` file is equivalent with `{"projectType": "cli"}`.
|
||||
|
||||
|
||||
<a name="symlinked-node_modules"></a>
|
||||
### A node_modules to share
|
||||
### A `node_modules/` to share
|
||||
|
||||
With all the boilerplate files in place, the only missing piece are the installed packages. For
|
||||
that you have a `/aio/tools/examples/shared/package.json` which contains **all** the packages
|
||||
needed to run all the examples through all different boilerplates.
|
||||
With all the boilerplate files in place, the only missing piece is the installed packages.
|
||||
For that we have [shared/package.json](./shared/package.json), which contains **all** the packages needed to run any example type.
|
||||
|
||||
After installing these dependencies, a `node_modules/` folder will be created at
|
||||
`/aio/tools/examples/shared/node_modules/`. This folder will be **symlinked** into each example.
|
||||
Upon installing these dependencies, a [shared/node_modules/](./shared/node_modules) folder is created.
|
||||
This folder will be **symlinked** into each example.
|
||||
So it is not a copy like the other boilerplate files.
|
||||
|
||||
### End to end tests
|
||||
|
||||
End to end changes between boilerplates.
|
||||
### End-to-end tests
|
||||
|
||||
For CLI applications, create a `app.e2e-spec.ts` inside the `e2e` folder. The tooling will run
|
||||
`ng e2e` for each CLI based examples.
|
||||
End-to-end infrastructure is slightly different between CLI- and SystemJS-based examples.
|
||||
|
||||
For SystemJS, each example contains an `e2e-spec.ts` file. You can find all the related configuration files
|
||||
in the `/aio/tools/examples/shared` folder.
|
||||
For CLI-based examples, create an `app.e2e-spec.ts` file inside the `e2e/` folder.
|
||||
This will be picked up by the default testing command (see the [example configuration section](#example-config) above).
|
||||
If you are using a custom test command, make sure e2e specs are picked up (if applicable).
|
||||
|
||||
### example-boilerplate.js
|
||||
For SystemJS-based examples, create an `e2e-spec.ts` file inside the example root folder.
|
||||
These apps will be tested with the following command:
|
||||
|
||||
This script installs all the dependencies that are shared among all the examples, creates the
|
||||
`node_modules` symlinks and copy all the boilerplate files where needed. It won't do anything
|
||||
about stackblitz nor e2e tests.
|
||||
```sh
|
||||
yarn protractor aio/tools/examples/shared/protractor.config.js \
|
||||
--specs=<example-folder>/e2e-spec.ts \
|
||||
--params.appDir=<example-folder>
|
||||
```
|
||||
|
||||
It also contains a function to remove all the boilerplate. It uses a `git clean -xdf` to do
|
||||
the job. It will remove all files that don't exist in the git repository, **including any
|
||||
new file that you are working on that hasn't been stage yet.** So be sure to save your work
|
||||
before removing the boilerplate.
|
||||
|
||||
### run-example-e2e.js
|
||||
### `example-boilerplate.js`
|
||||
|
||||
This script will find all the `e2e-spec.ts` files and run them.
|
||||
The [example-boilerplate.js](./example-boilerplate.js) script installs the dependencies for all examples, creates the `node_modules/` symlinks and copies the necessary boilerplate files into example folders.
|
||||
|
||||
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
|
||||
that name.
|
||||
It also contains a function to remove all the boilerplate.
|
||||
It uses `git clean -xdf` to do the job.
|
||||
It will remove all files that are not tracked by git, **including any new files that you are working on that haven't been stageg yet.**
|
||||
So, be sure to commit your work before removing the boilerplate.
|
||||
|
||||
It also has an optional `--setup` flag to run the `example-boilerplate.js` script and install
|
||||
the latest `webdriver`.
|
||||
|
||||
It will create a `/aio/protractor-results.txt` file when it finishes running tests.
|
||||
### `run-example-e2e.js`
|
||||
|
||||
The [run-example-e2e.js](./run-example-e2e.js) script will find and run the e2e tests for all examples.
|
||||
Although it only runs e2e tests by default, it can be configured to run any test command (for CLI-based examples) by using the `tests` property of the [example-config.json](#example-config) file.
|
||||
It is named `*-e2e` for historical reasons, but it is not limited to running e2e tests.
|
||||
|
||||
See [aio/README.md](../../README.md#developer-tasks) for the available command-line options.
|
||||
|
||||
Running the script will create an `aio/protractor-results.txt` file with the results of the tests.
|
||||
|
||||
|
||||
### Updating example dependencies
|
||||
|
||||
With every major release, we update the examples to be on the latest version. The following steps to update are:
|
||||
|
||||
* In the `shared/package.json` file, bump all the `@angular/*`, `@angular-devkit/*`, `rxjs`, `typescript`, and `zone.js` package versions to the version that corresponds with the [framework version](../../../package.json).
|
||||
* In the `shared` folder, run `yarn` to update the dependencies for the shared `node_modules` and the `yarn.lock` file.
|
||||
* In the `boilerplate` folder, go through each sub-folder and update the `package.json` dependencies if one is present.
|
||||
* Follow the [update guide](./shared/boilerplate/UPDATING_CLI.md) to update the common files used in the examples based on project type.
|
||||
With every major Angular release, we update the examples to be on the latest version.
|
||||
See [UPDATING.md](./UPDATING.md) for instructions.
|
||||
|
52
aio/tools/examples/UPDATING.md
Normal file
52
aio/tools/examples/UPDATING.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Update example dependencies
|
||||
|
||||
Follow these steps to update the examples to the latest versions of Angular (and related dependencies):
|
||||
|
||||
- In [shared/package.json](./shared/package.json), bump all the `@angular/*` and `@nguniversal/*` package versions to the version you want to update to and update their peer dependencies (such as `@angular-devkit/*`, `rxjs`, `typescript`, `zone.js`) and other dependencies (e.g. `@types/*`) to the latest compatible versions.
|
||||
|
||||
> NOTE:
|
||||
> The [angular-cli-diff](https://github.com/cexbrayat/angular-cli-diff) repo can be a useful resource for discovering what dependency versions are used for a basic CLI app at a specific CLI version.
|
||||
|
||||
- In the [shared/](./shared) folder, run `yarn` to update the dependencies in the [shared/node_modules/](./shared/node_modules) folder and the [shared/yarn.lock](./shared/yarn.lock) file.
|
||||
|
||||
- In the [shared/](./shared) folder, run `yarn sync-deps` to update the dependency versions of the `package.json` files in each sub-folder of [shared/boilerplate/](./shared/boilerplate) to match the ones in [shared/package.json](./shared/package.json).
|
||||
|
||||
- Follow the steps in the following section to update the rest of the boilerplate files.
|
||||
|
||||
|
||||
## Update other boilerplate files
|
||||
|
||||
The Angular CLI default setup is updated using `ng update`.
|
||||
Any necessary changes to boilerplate files will be done automatically through migration schematics.
|
||||
|
||||
> NOTE:
|
||||
> Migrations affecting source code files will not happen automatically, because `ng update` does not know about all the examples in `aio/content/examples/`.
|
||||
> You have to make these changes (if any) manually.
|
||||
> Again, the [angular-cli-diff](https://github.com/cexbrayat/angular-cli-diff) repo can be a useful resource for discovering changes between versions.
|
||||
|
||||
- In the [shared/boilerplate/cli/](./shared/boilerplate/cli) folder, run the following commands to migrate the the project to the current versions of Angular CLI and the Angular framework (updated in previous steps):
|
||||
```sh
|
||||
# Ensure dependencies are installed.
|
||||
yarn install
|
||||
|
||||
# Migrate project to new versions.
|
||||
yarn ng update @angular/cli --migrate-only --from=<previous-cli-version>
|
||||
yarn ng update @angular/core --migrate-only --from=<previous-core-version>
|
||||
```
|
||||
|
||||
> NOTE:
|
||||
> In order for `ng update` to work, there must be a `node_modules/` directory with installed dependencies inside the [shared/boilerplate/cli/](./shared/boilerplate/cli) directory.
|
||||
> This `node_modules/` directory is only needed during the update operation and is otherwise ignored (both by git and by the [example-boilerplate.js](./example-boilerplate.js) script) by means of the [shared/boilerplate/.gitignore](./shared/boilerplate/.gitignore) file.
|
||||
|
||||
- The previous command made any necessary changes to boilerplate files inside the `cli/` folder, but the same changes need to be applied to the other CLI-based boilerplate folders.
|
||||
Inspect the changes in `cli/` and manually apply the necessary ones to other CLI-based boilerplate folders.
|
||||
|
||||
- Ensure any changes to [cli/tslint.json](./shared/boilerplate/cli/tslint.json) are ported over to [systemjs/tslint.json](./shared/boilerplate/systemjs/tslint.json) and also [aio/content/examples/tslint.json](../../content/examples/tslint.json).
|
||||
This last part is important, since this file is used to lint example code on CI.
|
||||
|
||||
- Inspect the changes and determine whether some of them need to be applied to the `systemjs` boilerplate files.
|
||||
|
||||
- Inspect the changes and determine whether any updates to guides are necessary.
|
||||
For example, if a file is renamed or moved, any guides mentioning that file may need updating to refer to the new name/location.
|
||||
|
||||
- Commit all changes to the repository.
|
@ -1,5 +1,6 @@
|
||||
const fs = require('fs-extra');
|
||||
const glob = require('glob');
|
||||
const ignore = require('ignore');
|
||||
const path = require('canonical-path');
|
||||
const shelljs = require('shelljs');
|
||||
const yargs = require('yargs');
|
||||
@ -23,6 +24,8 @@ class ExampleBoilerPlate {
|
||||
// Get all the examples folders, indicated by those that contain a `example-config.json` file
|
||||
const exampleFolders =
|
||||
this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
|
||||
const gitignore = ignore().add(fs.readFileSync(path.resolve(BOILERPLATE_BASE_PATH, '.gitignore'), 'utf8'));
|
||||
const isPathIgnored = absolutePath => gitignore.ignores(path.relative(BOILERPLATE_BASE_PATH, absolutePath));
|
||||
|
||||
if (!fs.existsSync(SHARED_NODE_MODULES_PATH)) {
|
||||
throw new Error(
|
||||
@ -48,22 +51,22 @@ class ExampleBoilerPlate {
|
||||
// boilerplate files first.
|
||||
// (Some of these files might be later overwritten by type-specific files.)
|
||||
if (boilerPlateType !== 'cli' && boilerPlateType !== 'systemjs') {
|
||||
this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder);
|
||||
this.copyDirectoryContents(BOILERPLATE_CLI_PATH, exampleFolder, isPathIgnored);
|
||||
}
|
||||
|
||||
// Copy the type-specific boilerplate files.
|
||||
this.copyDirectoryContents(boilerPlateBasePath, exampleFolder);
|
||||
this.copyDirectoryContents(boilerPlateBasePath, exampleFolder, isPathIgnored);
|
||||
|
||||
// Copy the common boilerplate files (unless explicitly not used).
|
||||
if (exampleConfig.useCommonBoilerplate !== false) {
|
||||
this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder);
|
||||
this.copyDirectoryContents(BOILERPLATE_COMMON_PATH, exampleFolder, isPathIgnored);
|
||||
}
|
||||
|
||||
// Copy ViewEngine (pre-Ivy) specific files
|
||||
if (viewengine) {
|
||||
const veBoilerPlateType = boilerPlateType === 'systemjs' ? 'systemjs' : 'cli';
|
||||
const veBoilerPlateBasePath = path.resolve(BOILERPLATE_VIEWENGINE_PATH, veBoilerPlateType);
|
||||
this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder);
|
||||
this.copyDirectoryContents(veBoilerPlateBasePath, exampleFolder, isPathIgnored);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -89,16 +92,22 @@ class ExampleBoilerPlate {
|
||||
|
||||
loadJsonFile(filePath) { return fs.readJsonSync(filePath, {throws: false}) || {}; }
|
||||
|
||||
copyDirectoryContents(srcDir, dstDir) {
|
||||
copyDirectoryContents(srcDir, dstDir, isPathIgnored) {
|
||||
shelljs.ls('-Al', srcDir).forEach(stat => {
|
||||
const srcPath = path.resolve(srcDir, stat.name);
|
||||
const dstPath = path.resolve(dstDir, stat.name);
|
||||
|
||||
if (isPathIgnored(srcPath)) {
|
||||
// `srcPath` is ignored (e.g. by a `.gitignore` file): Ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
// `srcPath` is a directory: Recursively copy it to `dstDir`.
|
||||
shelljs.mkdir('-p', dstPath);
|
||||
return this.copyDirectoryContents(srcPath, dstPath);
|
||||
} else {
|
||||
return this.copyDirectoryContents(srcPath, dstPath, isPathIgnored);
|
||||
}
|
||||
|
||||
// `srcPath` is a file: Copy it to `dstDir`.
|
||||
// (Also make the file non-writable to avoid accidental editing of boilerplate files).
|
||||
if (shelljs.test('-f', dstPath)) {
|
||||
@ -107,7 +116,6 @@ class ExampleBoilerPlate {
|
||||
}
|
||||
shelljs.cp(srcPath, dstDir);
|
||||
shelljs.chmod(444, dstPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +56,10 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/systemjs`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/systemjs`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/systemjs`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/systemjs`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -71,10 +71,10 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -86,10 +86,10 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(4);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -101,12 +101,12 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/i18n`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/i18n`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/i18n`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/i18n`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -118,12 +118,12 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/universal`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/universal`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/universal`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/universal`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -148,12 +148,12 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/systemjs`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/viewengine/systemjs`, 'a/b'],
|
||||
[`${boilerplateDir}/systemjs`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/viewengine/systemjs`, 'c/d'],
|
||||
[`${boilerplateDir}/systemjs`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/systemjs`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/systemjs`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/systemjs`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -165,12 +165,12 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(6);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -182,14 +182,14 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
expect(exampleBoilerPlate.copyDirectoryContents).toHaveBeenCalledTimes(8);
|
||||
expect(exampleBoilerPlate.copyDirectoryContents.calls.allArgs()).toEqual([
|
||||
[`${boilerplateDir}/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/elements`, 'a/b'],
|
||||
[`${boilerplateDir}/common`, 'a/b'],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'a/b'],
|
||||
[`${boilerplateDir}/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/elements`, 'c/d'],
|
||||
[`${boilerplateDir}/common`, 'c/d'],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'c/d'],
|
||||
[`${boilerplateDir}/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/elements`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'a/b', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/cli`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/elements`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/common`, 'c/d', jasmine.any(Function)],
|
||||
[`${boilerplateDir}/viewengine/cli`, 'c/d', jasmine.any(Function)],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -214,10 +214,12 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
describe('copyDirectoryContents', () => {
|
||||
const spyFnFor = fnName => (...args) => { callLog.push(`${fnName}(${args.join(', ')})`); };
|
||||
let isPathIgnoredSpy;
|
||||
let callLog;
|
||||
|
||||
beforeEach(() => {
|
||||
callLog = [];
|
||||
isPathIgnoredSpy = jasmine.createSpy('isPathIgnored').and.returnValue(false);
|
||||
spyOn(shelljs, 'chmod').and.callFake(spyFnFor('chmod'));
|
||||
spyOn(shelljs, 'cp').and.callFake(spyFnFor('cp'));
|
||||
spyOn(shelljs, 'mkdir').and.callFake(spyFnFor('mkdir'));
|
||||
@ -226,17 +228,17 @@ describe('example-boilerplate tool', () => {
|
||||
|
||||
it('should list all contents of a directory', () => {
|
||||
const lsSpy = spyOn(shelljs, 'ls').and.returnValue([]);
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
expect(lsSpy).toHaveBeenCalledWith('-Al', 'source/dir');
|
||||
});
|
||||
|
||||
it('should use copy files and make them read-only', () => {
|
||||
it('should copy files and make them read-only', () => {
|
||||
spyOn(shelljs, 'ls').and.returnValue([
|
||||
{name: 'file-1.txt', isDirectory: () => false},
|
||||
{name: 'file-2.txt', isDirectory: () => false},
|
||||
]);
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
expect(callLog).toEqual([
|
||||
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
|
||||
@ -249,6 +251,22 @@ describe('example-boilerplate tool', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should skip files that are ignored', () => {
|
||||
spyOn(shelljs, 'ls').and.returnValue([
|
||||
{name: 'file-1.txt', isDirectory: () => false},
|
||||
{name: 'file-2.txt', isDirectory: () => false},
|
||||
]);
|
||||
isPathIgnoredSpy.and.callFake(path => path.endsWith('file-1.txt'));
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
expect(callLog).toEqual([
|
||||
`test(-f, ${path.resolve('destination/dir/file-2.txt')})`,
|
||||
`cp(${path.resolve('source/dir/file-2.txt')}, destination/dir)`,
|
||||
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should make existing files in destination writable before overwriting', () => {
|
||||
spyOn(shelljs, 'ls').and.returnValue([
|
||||
{name: 'new-file.txt', isDirectory: () => false},
|
||||
@ -256,7 +274,7 @@ describe('example-boilerplate tool', () => {
|
||||
]);
|
||||
shelljs.test.and.callFake((_, filePath) => filePath.endsWith('existing-file.txt'));
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
expect(callLog).toEqual([
|
||||
`cp(${path.resolve('source/dir/new-file.txt')}, destination/dir)`,
|
||||
@ -283,7 +301,7 @@ describe('example-boilerplate tool', () => {
|
||||
{name: 'file-4.txt', isDirectory: () => false},
|
||||
]);
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir');
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
expect(callLog).toEqual([
|
||||
// Copy `file-1.txt`.
|
||||
@ -317,6 +335,33 @@ describe('example-boilerplate tool', () => {
|
||||
`chmod(444, ${path.resolve('destination/dir/file-2.txt')})`,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should skip ignored directories', () => {
|
||||
spyOn(shelljs, 'ls')
|
||||
.withArgs('-Al', 'source/dir').and.returnValue([
|
||||
{name: 'file-1.txt', isDirectory: () => false},
|
||||
{name: 'sub-dir-1', isDirectory: () => true},
|
||||
])
|
||||
.withArgs('-Al', path.resolve('source/dir/sub-dir-1')).and.returnValue([
|
||||
{name: 'file-2.txt', isDirectory: () => false},
|
||||
{name: 'sub-dir-2', isDirectory: () => true},
|
||||
])
|
||||
.withArgs('-Al', path.resolve('source/dir/sub-dir-1/sub-dir-2')).and.returnValue([
|
||||
{name: 'file-3.txt', isDirectory: () => false},
|
||||
]);
|
||||
isPathIgnoredSpy.and.callFake(path => path.endsWith('sub-dir-1'));
|
||||
|
||||
exampleBoilerPlate.copyDirectoryContents('source/dir', 'destination/dir', isPathIgnoredSpy);
|
||||
|
||||
expect(callLog).toEqual([
|
||||
// Copy `file-1.txt`.
|
||||
`test(-f, ${path.resolve('destination/dir/file-1.txt')})`,
|
||||
`cp(${path.resolve('source/dir/file-1.txt')}, destination/dir)`,
|
||||
`chmod(444, ${path.resolve('destination/dir/file-1.txt')})`,
|
||||
|
||||
// Skip `sub-dir-1` and all its contents.
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadJsonFile', () => {
|
||||
|
@ -1,2 +1,3 @@
|
||||
**/yarn.lock
|
||||
**/node_modules/
|
||||
**/package-lock.json
|
||||
**/yarn.lock
|
||||
|
@ -1,50 +0,0 @@
|
||||
# How to update the CLI project
|
||||
|
||||
The Angular CLI default setup is updated using `ng update`. Any necessary file changes will be done automatically through migration schematics.
|
||||
|
||||
In the `cli` folder, update the Angular CLI depedencies to the latest version:
|
||||
|
||||
```
|
||||
ng update @angular/cli --next
|
||||
```
|
||||
|
||||
Then update the Angular Framework dependencies to the latest version:
|
||||
|
||||
```
|
||||
ng update @angular/core --next
|
||||
```
|
||||
|
||||
Commit any changes to the `cli` folder to the repository.
|
||||
|
||||
## Updating other CLI-based projects
|
||||
|
||||
Along with the boilerplate files for the `cli` folder, the other cli-based projects need to be updated also. Each cli-based project has slightly modified files specific to the project type. Make sure any necessary changes to these projects are made also to be in alignment with the `cli` project files.
|
||||
|
||||
The specific changes to each project type are listed below:
|
||||
|
||||
* i18n
|
||||
- angular.json
|
||||
- Includes additional configurations for `build`, `serve`, and `e2e` for different locales
|
||||
- package.json
|
||||
- Includes custom scripts for building and serving different locales
|
||||
* ivy
|
||||
- cli/tsconfig.app.json
|
||||
- Includes an `angularCompilerOptions` object with `enableIvy` set to `true`
|
||||
* schematics
|
||||
- angular.json
|
||||
- Includes a `my-lib` project that contains a library with example schematics
|
||||
* service-worker
|
||||
- angular.json
|
||||
- Has `serviceWorker` set to `true` in the `production` build target
|
||||
- package.json
|
||||
- Includes `@angular/service-worker` in `dependencies`
|
||||
* testing
|
||||
- angular.json
|
||||
- Includes `src/test.css` in the `styles` for the `test` target
|
||||
* universal
|
||||
- angular.json
|
||||
- Includes a `server` target in the `build` architect runners
|
||||
- package.json
|
||||
- Includes custom scripts for building the `server`
|
||||
- Includes additional `dependencies` on `@angular/platform-server`, `@nguniversal/express-engine`, and `express`
|
||||
- Includes additional `devDependencies` on `@nguniversal/builders` and `@types/express`
|
@ -13,43 +13,42 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~9.1.4",
|
||||
"@angular/common": "~9.1.4",
|
||||
"@angular/compiler": "~9.1.4",
|
||||
"@angular/core": "~9.1.4",
|
||||
"@angular/forms": "~9.1.4",
|
||||
"@angular/platform-browser": "~9.1.4",
|
||||
"@angular/platform-browser-dynamic": "~9.1.4",
|
||||
"@angular/router": "~9.1.4",
|
||||
"angular": "1.7.9",
|
||||
"@angular/animations": "~10.1.3",
|
||||
"@angular/common": "~10.1.3",
|
||||
"@angular/compiler": "~10.1.3",
|
||||
"@angular/core": "~10.1.3",
|
||||
"@angular/forms": "~10.1.3",
|
||||
"@angular/platform-browser": "~10.1.3",
|
||||
"@angular/platform-browser-dynamic": "~10.1.3",
|
||||
"@angular/router": "~10.1.3",
|
||||
"angular": "1.8.0",
|
||||
"angular-in-memory-web-api": "~0.11.0",
|
||||
"angular-route": "1.7.9",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0",
|
||||
"angular-route": "1.8.0",
|
||||
"rxjs": "~6.6.0",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.901.4",
|
||||
"@angular/cli": "~9.1.4",
|
||||
"@angular/compiler-cli": "~9.1.4",
|
||||
"@angular/language-service": "~9.1.4",
|
||||
"@types/angular": "^1.6.57",
|
||||
"@types/angular-route": "^1.7.0",
|
||||
"@angular-devkit/build-angular": "~0.1001.3",
|
||||
"@angular/cli": "~10.1.3",
|
||||
"@angular/compiler-cli": "~10.1.3",
|
||||
"@types/angular": "1.7.3",
|
||||
"@types/angular-route": "1.7.1",
|
||||
"@types/jasmine": "~3.5.0",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "^12.11.1",
|
||||
"codelyzer": "^5.1.2",
|
||||
"jasmine-core": "~3.5.0",
|
||||
"codelyzer": "^6.0.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-marbles": "~0.6.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~5.0.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.1.0",
|
||||
"karma-jasmine": "~3.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"protractor": "~5.4.3",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~3.8.3"
|
||||
"typescript": "~4.0.3"
|
||||
}
|
||||
}
|
||||
|
18
aio/tools/examples/shared/boilerplate/cli/.browserslistrc
Normal file
18
aio/tools/examples/shared/boilerplate/cli/.browserslistrc
Normal file
@ -0,0 +1,18 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user