mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59680c2644 | |||
| 34092a5052 | |||
| b8e47e277e | |||
| 2731a4b633 | |||
| 3e134e441f | |||
| 2c5ad5cbe8 | |||
| 106df618bc | |||
| d1c542b5c3 | |||
| 2047d632d8 | |||
| 4468ac2999 | |||
| 5578e23e54 | |||
| ad2b50bc81 | |||
| 524415ef8a | |||
| 8e0b01eec3 | |||
| 0a00b7db93 | |||
| 6675b6fb35 | |||
| 9c595b0fe5 | |||
| c8464eded1 | |||
| 861fa84566 | |||
| d5f696c187 | |||
| 76f363381f | |||
| 63bf613785 | |||
| 798aae45a8 | |||
| e2e3862548 | |||
| b1ae2396e1 | |||
| 32bf23a489 | |||
| 873bdf0345 | |||
| 30a7351933 | |||
| 968b2bbfc6 | |||
| 17f6b78969 | |||
| f4ed12211e | |||
| c6c7e9a4e3 | |||
| 76cabb21f6 | |||
| 0664d750b6 | |||
| a5c4adcdf0 | |||
| bbda311f32 | |||
| f675dbffac | |||
| b12b648517 | |||
| 71c5e75d9c | |||
| 7a9bc9958a | |||
| ebd94ef41b | |||
| 1ec5f869e0 |
+1
-1
@@ -107,7 +107,7 @@ echo "export CI_SECRET_PAYLOAD_FIREBASE_TOKEN=\"${CI_SECRET_PAYLOAD_FIREBASE_TOK
|
||||
####################################################################################################
|
||||
####################################################################################################
|
||||
## Source `$BASH_ENV` to make the variables available immediately. ##
|
||||
## ***NOTE: This must remain the the last action in this script*** ##
|
||||
## ***NOTE: This must remain the last action in this script*** ##
|
||||
####################################################################################################
|
||||
####################################################################################################
|
||||
source $BASH_ENV;
|
||||
|
||||
@@ -950,6 +950,7 @@ groups:
|
||||
users:
|
||||
- aikidave
|
||||
- IgorMinar
|
||||
- mgechev
|
||||
- StephenFluin
|
||||
|
||||
# =========================================================
|
||||
@@ -1231,6 +1232,7 @@ groups:
|
||||
'goldens/public-api/**',
|
||||
'CHANGELOG.md',
|
||||
'docs/NAMING.md',
|
||||
'aio/content/errors/*.md',
|
||||
'aio/content/guide/glossary.md',
|
||||
'aio/content/guide/styleguide.md',
|
||||
'aio/content/examples/styleguide/**',
|
||||
|
||||
+116
-144
@@ -1,3 +1,119 @@
|
||||
<a name="11.1.0"></a>
|
||||
# 11.1.0 (2021-01-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** getAnimationStyle causes exceptions in older browsers ([#29709](https://github.com/angular/angular/issues/29709)) ([66d863f](https://github.com/angular/angular/commit/66d863f))
|
||||
* **animations:** replace copy of query selector node-list from "spread" to "for" ([#39646](https://github.com/angular/angular/issues/39646)) ([bfa197f](https://github.com/angular/angular/commit/bfa197f)), closes [#38551](https://github.com/angular/angular/issues/38551)
|
||||
* **common:** add `HttpParamsOptions` to the public api ([#35829](https://github.com/angular/angular/issues/35829)) ([b33b89d](https://github.com/angular/angular/commit/b33b89d)), closes [#20276](https://github.com/angular/angular/issues/20276)
|
||||
* **common:** Prefer to use pageXOffset / pageYOffset instance of scrollX / scrollY ([#28262](https://github.com/angular/angular/issues/28262)) ([b1d300d](https://github.com/angular/angular/commit/b1d300d))
|
||||
* **compiler:** correct the `KeySpan` for animation events and properties ([#40347](https://github.com/angular/angular/issues/40347)) ([524415e](https://github.com/angular/angular/commit/524415e))
|
||||
* **compiler:** incorrectly interpreting some HostBinding names ([#40233](https://github.com/angular/angular/issues/40233)) ([1045465](https://github.com/angular/angular/commit/1045465)), closes [#40220](https://github.com/angular/angular/issues/40220) [#40230](https://github.com/angular/angular/issues/40230) [#18698](https://github.com/angular/angular/issues/18698)
|
||||
* **compiler:** recover event parse when animation event name is empty ([#39925](https://github.com/angular/angular/issues/39925)) ([15b15be](https://github.com/angular/angular/commit/15b15be))
|
||||
* **compiler:** avoid duplicate i18n blocks for i18n attrs on elements with structural directives ([#40077](https://github.com/angular/angular/issues/40077)) ([caa4666](https://github.com/angular/angular/commit/caa4666)), closes [#39942](https://github.com/angular/angular/issues/39942)
|
||||
* **compiler:** handle strings inside bindings that contain binding characters ([#39826](https://github.com/angular/angular/issues/39826)) ([dc6d40e](https://github.com/angular/angular/commit/dc6d40e)), closes [#39601](https://github.com/angular/angular/issues/39601)
|
||||
* **compiler:** disallow i18n of security-sensitive attributes ([#39554](https://github.com/angular/angular/issues/39554)) ([c8a99ef](https://github.com/angular/angular/commit/c8a99ef))
|
||||
* **compiler:** ensure that placeholders have the correct sourceSpan ([#39717](https://github.com/angular/angular/issues/39717)) ([0462a61](https://github.com/angular/angular/commit/0462a61)), closes [#39671](https://github.com/angular/angular/issues/39671)
|
||||
* **compiler:** only promote Trusted Types to constants when necessary ([#39554](https://github.com/angular/angular/issues/39554)) ([4916870](https://github.com/angular/angular/commit/4916870))
|
||||
* **compiler:** report better error on interpolation in an expression ([#30300](https://github.com/angular/angular/issues/30300)) ([94e790d](https://github.com/angular/angular/commit/94e790d))
|
||||
* **compiler-cli:** do not duplicate repeated source-files in rendered source-maps ([#40237](https://github.com/angular/angular/issues/40237)) ([3158858](https://github.com/angular/angular/commit/3158858))
|
||||
* **compiler-cli:** remove classes in .d.ts files from provider checks ([#40118](https://github.com/angular/angular/issues/40118)) ([973bb40](https://github.com/angular/angular/commit/973bb40))
|
||||
* **animations:** implement getPosition in browser animation builder ([#39983](https://github.com/angular/angular/issues/39983)) ([ca08625](https://github.com/angular/angular/commit/ca08625))
|
||||
* **compiler-cli:** correct incremental behavior even with broken imports ([#39923](https://github.com/angular/angular/issues/39923)) ([c7c5b2f](https://github.com/angular/angular/commit/c7c5b2f))
|
||||
* **compiler-cli:** remove the concept of an errored trait ([#39923](https://github.com/angular/angular/issues/39923)) ([6d42954](https://github.com/angular/angular/commit/6d42954))
|
||||
* **compiler-cli:** track poisoned scopes with a flag ([#39923](https://github.com/angular/angular/issues/39923)) ([0823622](https://github.com/angular/angular/commit/0823622))
|
||||
* **compiler-cli:** report error when a reference target is missing instead of crashing ([#39805](https://github.com/angular/angular/issues/39805)) ([453b32f](https://github.com/angular/angular/commit/453b32f)), closes [#38618](https://github.com/angular/angular/issues/38618) [#39744](https://github.com/angular/angular/issues/39744)
|
||||
* **core:** fix possible XSS attack in development through SSR. ([#40136](https://github.com/angular/angular/issues/40136)) ([47d9b6d](https://github.com/angular/angular/commit/47d9b6d))
|
||||
* **core:** set `ngDevMode` to `false` when calling `enableProdMode()` ([#40124](https://github.com/angular/angular/issues/40124)) ([70b4816](https://github.com/angular/angular/commit/70b4816))
|
||||
* **core:** Allow passing AbstractType to the inject function ([#37958](https://github.com/angular/angular/issues/37958)) ([a1b6ad0](https://github.com/angular/angular/commit/a1b6ad0))
|
||||
* **core:** Ensure OnPush ancestors are marked dirty when events occur ([#39833](https://github.com/angular/angular/issues/39833)) ([68d4a74](https://github.com/angular/angular/commit/68d4a74)), closes [#39832](https://github.com/angular/angular/issues/39832)
|
||||
* **core:** meta addTag() adds incorrect attribute for httpEquiv ([#32531](https://github.com/angular/angular/issues/32531)) ([ff0a90e](https://github.com/angular/angular/commit/ff0a90e))
|
||||
* **core:** migration error if program contains files outside of the project ([#39790](https://github.com/angular/angular/issues/39790)) ([1a26f6d](https://github.com/angular/angular/commit/1a26f6d)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** not invoking object's toString when rendering to the DOM ([#39843](https://github.com/angular/angular/issues/39843)) ([11cd37f](https://github.com/angular/angular/commit/11cd37f)), closes [#38839](https://github.com/angular/angular/issues/38839)
|
||||
* **core:** remove duplicated noop function ([#39761](https://github.com/angular/angular/issues/39761)) ([066126a](https://github.com/angular/angular/commit/066126a))
|
||||
* **core:** support `Attribute` DI decorator in `deps` section of a token ([#37085](https://github.com/angular/angular/issues/37085)) ([f5cbf0b](https://github.com/angular/angular/commit/f5cbf0b)), closes [#36479](https://github.com/angular/angular/issues/36479)
|
||||
* **core:** remove application from the testability registry when the root view is removed ([#39876](https://github.com/angular/angular/issues/39876)) ([df27027](https://github.com/angular/angular/commit/df27027)), closes [#22106](https://github.com/angular/angular/issues/22106)
|
||||
* **core:** Support extending differs from root `NgModule` ([#39981](https://github.com/angular/angular/issues/39981)) ([5fc4508](https://github.com/angular/angular/commit/5fc4508)), closes [#25015](https://github.com/angular/angular/issues/25015) [#11309](https://github.com/angular/angular/issues/11309) [#18554](https://github.com/angular/angular/issues/18554)
|
||||
* **core:** unsubscribe from the `onError` when the root view is removed ([#39940](https://github.com/angular/angular/issues/39940)) ([5a3a154](https://github.com/angular/angular/commit/5a3a154))
|
||||
* **core:** `QueryList` should not fire changes if the underlying list did not change. ([#40091](https://github.com/angular/angular/issues/40091)) ([76f3633](https://github.com/angular/angular/commit/76f3633))
|
||||
* **core:** fix fakeAsync() error messages ([#40442](https://github.com/angular/angular/issues/40442)) ([ad2b50b](https://github.com/angular/angular/commit/ad2b50b))
|
||||
* **forms:** error if control is removed as a result of another one being reset ([#40462](https://github.com/angular/angular/issues/40462)) ([2c5ad5c](https://github.com/angular/angular/commit/2c5ad5c)), closes [#33401](https://github.com/angular/angular/issues/33401)
|
||||
* **forms:** clean up connection between FormControl/FormGroup and corresponding directive instances ([#39235](https://github.com/angular/angular/issues/39235)) ([a384961](https://github.com/angular/angular/commit/a384961)), closes [#20007](https://github.com/angular/angular/issues/20007) [#37431](https://github.com/angular/angular/issues/37431) [#39590](https://github.com/angular/angular/issues/39590)
|
||||
* **language-service:** fix go to definition for template variables and references ([#40455](https://github.com/angular/angular/issues/40455)) ([#40491](https://github.com/angular/angular/issues/40491)) ([3e134e4](https://github.com/angular/angular/commit/3e134e4))
|
||||
* **language-service:** Paths on Windows should be normalized ([#40492](https://github.com/angular/angular/issues/40492)) ([b8e47e2](https://github.com/angular/angular/commit/b8e47e2)), closes [/github.com/angular/vscode-ng-language-service/blob/9fca9c66510974c26d5c21b31adb9fa39ac0a38a/server/src/session.ts#L594](https://github.com//github.com/angular/vscode-ng-language-service/blob/9fca9c66510974c26d5c21b31adb9fa39ac0a38a/server/src/session.ts/issues/L594)
|
||||
* **language-service:** report non-template diagnostics ([#40331](https://github.com/angular/angular/issues/40331)) ([4db89f4](https://github.com/angular/angular/commit/4db89f4))
|
||||
* **language-service:** diagnostic and definition should work for absolute url ([#40406](https://github.com/angular/angular/issues/40406)) ([625d2c2](https://github.com/angular/angular/commit/625d2c2))
|
||||
* **language-service:** reinstate overridden compiler option after change ([#40364](https://github.com/angular/angular/issues/40364)) ([811cacc](https://github.com/angular/angular/commit/811cacc))
|
||||
* **language-service:** Support 'find references' for two-way bindings ([#40185](https://github.com/angular/angular/issues/40185)) ([ebb7ac5](https://github.com/angular/angular/commit/ebb7ac5))
|
||||
* **language-service:** Support 'go to definition' for two-way bindings ([#40185](https://github.com/angular/angular/issues/40185)) ([a9d8c22](https://github.com/angular/angular/commit/a9d8c22))
|
||||
* **language-service:** Support completions of two-way bindings ([#40185](https://github.com/angular/angular/issues/40185)) ([7d74853](https://github.com/angular/angular/commit/7d74853))
|
||||
* **language-service:** Do not include $event parameter in reference results ([#40158](https://github.com/angular/angular/issues/40158)) ([d466db8](https://github.com/angular/angular/commit/d466db8)), closes [#40157](https://github.com/angular/angular/issues/40157)
|
||||
* **language-service:** include compilerOptions.rootDir in rootDirs ([#40243](https://github.com/angular/angular/issues/40243)) ([a62416c](https://github.com/angular/angular/commit/a62416c)), closes [angular/vscode-ng-language-service#1039](https://github.com/angular/vscode-ng-language-service/issues/1039)
|
||||
* **language-service:** LSParseConfigHost.resolve should not concat abs paths ([#40242](https://github.com/angular/angular/issues/40242)) ([0264f76](https://github.com/angular/angular/commit/0264f76))
|
||||
* **language-service:** return all typecheck files via getExternalFiles ([#40162](https://github.com/angular/angular/issues/40162)) ([183fb7e](https://github.com/angular/angular/commit/183fb7e))
|
||||
* **language-service:** shorthand syntax with variables ([#40239](https://github.com/angular/angular/issues/40239)) ([12cb39c](https://github.com/angular/angular/commit/12cb39c))
|
||||
* **language-service:** force compileNonExportedClasses: false in LS ([#40092](https://github.com/angular/angular/issues/40092)) ([028e4f7](https://github.com/angular/angular/commit/028e4f7))
|
||||
* **language-service:** Prevent matching nodes after finding a keySpan ([#40047](https://github.com/angular/angular/issues/40047)) ([1bf1b68](https://github.com/angular/angular/commit/1bf1b68))
|
||||
* **language-service:** do not return external template that does not exist ([#39898](https://github.com/angular/angular/issues/39898)) ([2b84882](https://github.com/angular/angular/commit/2b84882))
|
||||
* **language-service:** do not treat file URIs as general URLs ([#39917](https://github.com/angular/angular/issues/39917)) ([3b70098](https://github.com/angular/angular/commit/3b70098))
|
||||
* **localize:** ensure extracted messages are serialized in a consistent order ([#40192](https://github.com/angular/angular/issues/40192)) ([212245f](https://github.com/angular/angular/commit/212245f)), closes [#39262](https://github.com/angular/angular/issues/39262)
|
||||
* **ngcc:** copy (and update) source-maps for unmodified source files ([#40429](https://github.com/angular/angular/issues/40429)) ([e2e3862](https://github.com/angular/angular/commit/e2e3862)), closes [#40358](https://github.com/angular/angular/issues/40358)
|
||||
* **service-worker:** handle error with ErrorHandler ([#39990](https://github.com/angular/angular/issues/39990)) ([74e42cf](https://github.com/angular/angular/commit/74e42cf)), closes [#39913](https://github.com/angular/angular/issues/39913)
|
||||
* **router:** correctly handle string command in outlets ([#39728](https://github.com/angular/angular/issues/39728)) ([c33a823](https://github.com/angular/angular/commit/c33a823)), closes [#18928](https://github.com/angular/angular/issues/18928)
|
||||
* **router:** lazy loaded modules without RouterModule.forChild() won't cause an infinite loop ([#36605](https://github.com/angular/angular/issues/36605)) ([6675b6f](https://github.com/angular/angular/commit/6675b6f)), closes [#29164](https://github.com/angular/angular/issues/29164)
|
||||
* **router:** remove duplicated getOutlet function ([#39764](https://github.com/angular/angular/issues/39764)) ([5fa7673](https://github.com/angular/angular/commit/5fa7673))
|
||||
* **service-worker:** correctly handle failed cache-busted request ([#39786](https://github.com/angular/angular/issues/39786)) ([6046419](https://github.com/angular/angular/commit/6046419)), closes [#39775](https://github.com/angular/angular/issues/39775) [#39775](https://github.com/angular/angular/issues/39775)
|
||||
* **upgrade:** avoid memory leak when removing downgraded components ([#39965](https://github.com/angular/angular/issues/39965)) ([6dc43a4](https://github.com/angular/angular/commit/6dc43a4)), closes [#26209](https://github.com/angular/angular/issues/26209) [#39911](https://github.com/angular/angular/issues/39911) [#39921](https://github.com/angular/angular/issues/39921)
|
||||
* **upgrade:** fix HMR for hybrid applications ([#40045](https://github.com/angular/angular/issues/40045)) ([b4b21bd](https://github.com/angular/angular/commit/b4b21bd)), closes [#39935](https://github.com/angular/angular/issues/39935)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** use `ngDevMode` to tree-shake warning ([#39964](https://github.com/angular/angular/issues/39964)) ([9ebe423](https://github.com/angular/angular/commit/9ebe423))
|
||||
* **common:** use `ngDevMode` to tree-shake warnings ([#39964](https://github.com/angular/angular/issues/39964)) ([f022efa](https://github.com/angular/angular/commit/f022efa))
|
||||
* **core:** use `ngDevMode` to tree-shake `checkNoChanges` ([#39964](https://github.com/angular/angular/issues/39964)) ([e1fe9ec](https://github.com/angular/angular/commit/e1fe9ec))
|
||||
* **core:** use `ngDevMode` to tree-shake warnings ([#39959](https://github.com/angular/angular/issues/39959)) ([8b0cccc](https://github.com/angular/angular/commit/8b0cccc))
|
||||
* **core:** make DI decorators tree-shakable when used for `useFactory` deps config ([#40145](https://github.com/angular/angular/issues/40145)) ([0664d75](https://github.com/angular/angular/commit/0664d75)), closes [#40143](https://github.com/angular/angular/issues/40143)
|
||||
* **forms:** use `ngDevMode` to tree-shake `_ngModelWarning` ([#39964](https://github.com/angular/angular/issues/39964)) ([7954c8d](https://github.com/angular/angular/commit/7954c8d))
|
||||
* **ngcc:** do not copy files that have been processed ([#40429](https://github.com/angular/angular/issues/40429)) ([798aae4](https://github.com/angular/angular/commit/798aae4))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** allow any Subscribable in async pipe ([#39627](https://github.com/angular/angular/issues/39627)) ([c7f4abf](https://github.com/angular/angular/commit/c7f4abf))
|
||||
* **compiler:** recover expression parsing in more malformed pipe cases ([#39437](https://github.com/angular/angular/issues/39437)) ([e336572](https://github.com/angular/angular/commit/e336572))
|
||||
* **compiler:** support recovery of malformed property writes ([#39103](https://github.com/angular/angular/issues/39103)) ([e44e10b](https://github.com/angular/angular/commit/e44e10b))
|
||||
* **compiler:** add schema for Trusted Types sinks ([#39554](https://github.com/angular/angular/issues/39554)) ([358c50e](https://github.com/angular/angular/commit/358c50e))
|
||||
* **compiler:** support error reporting in I18nMetaVisitor ([#39554](https://github.com/angular/angular/issues/39554)) ([bb70a9b](https://github.com/angular/angular/commit/bb70a9b))
|
||||
* **compiler:** support tagged template literals in code generator ([#39122](https://github.com/angular/angular/issues/39122)) ([ef89274](https://github.com/angular/angular/commit/ef89274))
|
||||
* **compiler:** allow trailing comma in array literal ([#22277](https://github.com/angular/angular/issues/22277)) ([8d613c1](https://github.com/angular/angular/commit/8d613c1)), closes [#20773](https://github.com/angular/angular/issues/20773)
|
||||
* **compiler-cli:** implement partial directive declaration linking ([#39518](https://github.com/angular/angular/issues/39518)) ([87e9cd6](https://github.com/angular/angular/commit/87e9cd6))
|
||||
* **compiler-cli:** partial compilation of directives ([#39518](https://github.com/angular/angular/issues/39518)) ([8c0a92b](https://github.com/angular/angular/commit/8c0a92b))
|
||||
* **compiler-cli:** add support for using TypeScript 4.1 ([#39571](https://github.com/angular/angular/issues/39571)) ([a7e7c21](https://github.com/angular/angular/commit/a7e7c21))
|
||||
* **compiler-cli:** support for partial compilation of components ([#39707](https://github.com/angular/angular/issues/39707)) ([e75244e](https://github.com/angular/angular/commit/e75244e))
|
||||
* **compiler-cli:** expose function to allow short-circuiting of linking ([#40137](https://github.com/angular/angular/issues/40137)) ([7dcf286](https://github.com/angular/angular/commit/7dcf286))
|
||||
* **compiler-cli:** JIT compilation of component declarations ([#40127](https://github.com/angular/angular/issues/40127)) ([d4327d5](https://github.com/angular/angular/commit/d4327d5))
|
||||
* **compiler-cli:** JIT compilation of directive declarations ([#40101](https://github.com/angular/angular/issues/40101)) ([9186f1f](https://github.com/angular/angular/commit/9186f1f))
|
||||
* **core:** add shouldCoalesceRunChangeDetection option to coalesce change detections in the same event loop. ([#39422](https://github.com/angular/angular/issues/39422)) ([5e92d64](https://github.com/angular/angular/commit/5e92d64)), closes [#39348](https://github.com/angular/angular/issues/39348) [#39348](https://github.com/angular/angular/issues/39348)
|
||||
* **core:** adds get method to QueryList ([#36907](https://github.com/angular/angular/issues/36907)) ([a965589](https://github.com/angular/angular/commit/a965589)), closes [#29467](https://github.com/angular/angular/issues/29467)
|
||||
* **core:** Add schematic to fix invalid `Route` configs ([#40067](https://github.com/angular/angular/issues/40067)) ([805b4f9](https://github.com/angular/angular/commit/805b4f9))
|
||||
* **language-service:** log Angular compiler options ([#40364](https://github.com/angular/angular/issues/40364)) ([6a9e328](https://github.com/angular/angular/commit/6a9e328))
|
||||
* **language-service:** autocomplete pipe binding expressions ([#40032](https://github.com/angular/angular/issues/40032)) ([cbb6eae](https://github.com/angular/angular/commit/cbb6eae))
|
||||
* **language-service:** autocompletion of element tags ([#40032](https://github.com/angular/angular/issues/40032)) ([e42250f](https://github.com/angular/angular/commit/e42250f))
|
||||
* **language-service:** autocompletion within expression contexts ([#39727](https://github.com/angular/angular/issues/39727)) ([93a8326](https://github.com/angular/angular/commit/93a8326))
|
||||
* **language-service:** complete attributes on elements ([#40032](https://github.com/angular/angular/issues/40032)) ([66378ed](https://github.com/angular/angular/commit/66378ed))
|
||||
* **language-service:** completions for structural directives ([#40032](https://github.com/angular/angular/issues/40032)) ([2a74431](https://github.com/angular/angular/commit/2a74431))
|
||||
* **language-service:** enable get references for directive and component from template ([#40054](https://github.com/angular/angular/issues/40054)) ([973f797](https://github.com/angular/angular/commit/973f797))
|
||||
* **language-service:** Add "find references" capability to Ivy integrated LS ([#39768](https://github.com/angular/angular/issues/39768)) ([06a782a](https://github.com/angular/angular/commit/06a782a))
|
||||
* **language-service:** implement autocompletion for global properties (Ivy) ([#39250](https://github.com/angular/angular/issues/39250)) ([28a0bcb](https://github.com/angular/angular/commit/28a0bcb))
|
||||
* **language-service:** Implement go to definition for style and template urls ([#39202](https://github.com/angular/angular/issues/39202)) ([563fb6c](https://github.com/angular/angular/commit/563fb6c))
|
||||
* **localize:** support Application Resource Bundle (ARB) translation file format ([#36795](https://github.com/angular/angular/issues/36795)) ([5684ac5](https://github.com/angular/angular/commit/5684ac5))
|
||||
* **platform-browser:** add doubletap HammerJS support ([#26362](https://github.com/angular/angular/issues/26362)) ([b5c0f9d](https://github.com/angular/angular/commit/b5c0f9d)), closes [#23954](https://github.com/angular/angular/issues/23954)
|
||||
* **router:** add `relativeTo` as an input to `routerLink` ([#39720](https://github.com/angular/angular/issues/39720)) ([112324a](https://github.com/angular/angular/commit/112324a)), closes [#13523](https://github.com/angular/angular/issues/13523)
|
||||
|
||||
|
||||
|
||||
<a name="11.0.9"></a>
|
||||
## 11.0.9 (2021-01-13)
|
||||
|
||||
@@ -36,30 +152,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="11.1.0-next.4"></a>
|
||||
# 11.1.0-next.4 (2021-01-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **forms:** clean up connection between FormControl/FormGroup and corresponding directive instances ([#39235](https://github.com/angular/angular/issues/39235)) ([a384961](https://github.com/angular/angular/commit/a384961)), closes [#20007](https://github.com/angular/angular/issues/20007) [#37431](https://github.com/angular/angular/issues/37431) [#39590](https://github.com/angular/angular/issues/39590)
|
||||
* **language-service:** Do not include $event parameter in reference results ([#40158](https://github.com/angular/angular/issues/40158)) ([d466db8](https://github.com/angular/angular/commit/d466db8)), closes [#40157](https://github.com/angular/angular/issues/40157)
|
||||
* **language-service:** include compilerOptions.rootDir in rootDirs ([#40243](https://github.com/angular/angular/issues/40243)) ([a62416c](https://github.com/angular/angular/commit/a62416c)), closes [angular/vscode-ng-language-service#1039](https://github.com/angular/vscode-ng-language-service/issues/1039)
|
||||
* **language-service:** LSParseConfigHost.resolve should not concat abs paths ([#40242](https://github.com/angular/angular/issues/40242)) ([0264f76](https://github.com/angular/angular/commit/0264f76))
|
||||
* **language-service:** return all typecheck files via getExternalFiles ([#40162](https://github.com/angular/angular/issues/40162)) ([183fb7e](https://github.com/angular/angular/commit/183fb7e))
|
||||
* **language-service:** shorthand syntax with variables ([#40239](https://github.com/angular/angular/issues/40239)) ([12cb39c](https://github.com/angular/angular/commit/12cb39c))
|
||||
* **localize:** ensure extracted messages are serialized in a consistent order ([#40192](https://github.com/angular/angular/issues/40192)) ([212245f](https://github.com/angular/angular/commit/212245f)), closes [#39262](https://github.com/angular/angular/issues/39262)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-cli:** expose function to allow short-circuiting of linking ([#40137](https://github.com/angular/angular/issues/40137)) ([7dcf286](https://github.com/angular/angular/commit/7dcf286))
|
||||
* **compiler-cli:** JIT compilation of component declarations ([#40127](https://github.com/angular/angular/issues/40127)) ([d4327d5](https://github.com/angular/angular/commit/d4327d5))
|
||||
* **compiler-cli:** JIT compilation of directive declarations ([#40101](https://github.com/angular/angular/issues/40101)) ([9186f1f](https://github.com/angular/angular/commit/9186f1f))
|
||||
* **core:** Add schematic to fix invalid `Route` configs ([#40067](https://github.com/angular/angular/issues/40067)) ([805b4f9](https://github.com/angular/angular/commit/805b4f9))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.6"></a>
|
||||
## 11.0.6 (2021-01-06)
|
||||
|
||||
@@ -91,32 +183,6 @@
|
||||
* **core:** set `ngDevMode` to `false` when calling `enableProdMode()` ([#40124](https://github.com/angular/angular/issues/40124)) ([922f492](https://github.com/angular/angular/commit/922f492))
|
||||
* **upgrade:** fix HMR for hybrid applications ([#40045](https://github.com/angular/angular/issues/40045)) ([c4c7509](https://github.com/angular/angular/commit/c4c7509)), closes [#39935](https://github.com/angular/angular/issues/39935)
|
||||
|
||||
<a name="11.1.0-next.3"></a>
|
||||
# 11.1.0-next.3 (2020-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** avoid duplicate i18n blocks for i18n attrs on elements with structural directives ([#40077](https://github.com/angular/angular/issues/40077)) ([caa4666](https://github.com/angular/angular/commit/caa4666)), closes [#39942](https://github.com/angular/angular/issues/39942)
|
||||
* **compiler:** handle strings inside bindings that contain binding characters ([#39826](https://github.com/angular/angular/issues/39826)) ([dc6d40e](https://github.com/angular/angular/commit/dc6d40e)), closes [#39601](https://github.com/angular/angular/issues/39601)
|
||||
* **compiler-cli:** remove classes in .d.ts files from provider checks ([#40118](https://github.com/angular/angular/issues/40118)) ([973bb40](https://github.com/angular/angular/commit/973bb40))
|
||||
* **core:** fix possible XSS attack in development through SSR. ([#40136](https://github.com/angular/angular/issues/40136)) ([47d9b6d](https://github.com/angular/angular/commit/47d9b6d))
|
||||
* **core:** set `ngDevMode` to `false` when calling `enableProdMode()` ([#40124](https://github.com/angular/angular/issues/40124)) ([70b4816](https://github.com/angular/angular/commit/70b4816))
|
||||
* **language-service:** force compileNonExportedClasses: false in LS ([#40092](https://github.com/angular/angular/issues/40092)) ([028e4f7](https://github.com/angular/angular/commit/028e4f7))
|
||||
* **language-service:** Prevent matching nodes after finding a keySpan ([#40047](https://github.com/angular/angular/issues/40047)) ([1bf1b68](https://github.com/angular/angular/commit/1bf1b68))
|
||||
* **upgrade:** fix HMR for hybrid applications ([#40045](https://github.com/angular/angular/issues/40045)) ([b4b21bd](https://github.com/angular/angular/commit/b4b21bd)), closes [#39935](https://github.com/angular/angular/issues/39935)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **language-service:** autocomplete pipe binding expressions ([#40032](https://github.com/angular/angular/issues/40032)) ([cbb6eae](https://github.com/angular/angular/commit/cbb6eae))
|
||||
* **language-service:** autocompletion of element tags ([#40032](https://github.com/angular/angular/issues/40032)) ([e42250f](https://github.com/angular/angular/commit/e42250f))
|
||||
* **language-service:** autocompletion within expression contexts ([#39727](https://github.com/angular/angular/issues/39727)) ([93a8326](https://github.com/angular/angular/commit/93a8326))
|
||||
* **language-service:** complete attributes on elements ([#40032](https://github.com/angular/angular/issues/40032)) ([66378ed](https://github.com/angular/angular/commit/66378ed))
|
||||
* **language-service:** completions for structural directives ([#40032](https://github.com/angular/angular/issues/40032)) ([2a74431](https://github.com/angular/angular/commit/2a74431))
|
||||
* **language-service:** enable get references for directive and component from template ([#40054](https://github.com/angular/angular/issues/40054)) ([973f797](https://github.com/angular/angular/commit/973f797))
|
||||
* **router:** add `relativeTo` as an input to `routerLink` ([#39720](https://github.com/angular/angular/issues/39720)) ([112324a](https://github.com/angular/angular/commit/112324a)), closes [#13523](https://github.com/angular/angular/issues/13523)
|
||||
|
||||
|
||||
|
||||
<a name="11.0.4"></a>
|
||||
@@ -147,43 +213,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="11.1.0-next.2"></a>
|
||||
# 11.1.0-next.2 (2020-12-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** implement getPosition in browser animation builder ([#39983](https://github.com/angular/angular/issues/39983)) ([ca08625](https://github.com/angular/angular/commit/ca08625))
|
||||
* **compiler-cli:** correct incremental behavior even with broken imports ([#39923](https://github.com/angular/angular/issues/39923)) ([c7c5b2f](https://github.com/angular/angular/commit/c7c5b2f))
|
||||
* **compiler-cli:** remove the concept of an errored trait ([#39923](https://github.com/angular/angular/issues/39923)) ([6d42954](https://github.com/angular/angular/commit/6d42954))
|
||||
* **compiler-cli:** track poisoned scopes with a flag ([#39923](https://github.com/angular/angular/issues/39923)) ([0823622](https://github.com/angular/angular/commit/0823622))
|
||||
* **core:** remove application from the testability registry when the root view is removed ([#39876](https://github.com/angular/angular/issues/39876)) ([df27027](https://github.com/angular/angular/commit/df27027)), closes [#22106](https://github.com/angular/angular/issues/22106)
|
||||
* **core:** Support extending differs from root `NgModule` ([#39981](https://github.com/angular/angular/issues/39981)) ([5fc4508](https://github.com/angular/angular/commit/5fc4508)), closes [#25015](https://github.com/angular/angular/issues/25015) [#11309](https://github.com/angular/angular/issues/11309) [#18554](https://github.com/angular/angular/issues/18554)
|
||||
* **core:** unsubscribe from the `onError` when the root view is removed ([#39940](https://github.com/angular/angular/issues/39940)) ([5a3a154](https://github.com/angular/angular/commit/5a3a154))
|
||||
* **language-service:** do not return external template that does not exist ([#39898](https://github.com/angular/angular/issues/39898)) ([2b84882](https://github.com/angular/angular/commit/2b84882))
|
||||
* **language-service:** do not treat file URIs as general URLs ([#39917](https://github.com/angular/angular/issues/39917)) ([3b70098](https://github.com/angular/angular/commit/3b70098))
|
||||
* **service-worker:** handle error with ErrorHandler ([#39990](https://github.com/angular/angular/issues/39990)) ([74e42cf](https://github.com/angular/angular/commit/74e42cf)), closes [#39913](https://github.com/angular/angular/issues/39913)
|
||||
* **upgrade:** avoid memory leak when removing downgraded components ([#39965](https://github.com/angular/angular/issues/39965)) ([6dc43a4](https://github.com/angular/angular/commit/6dc43a4)), closes [#26209](https://github.com/angular/angular/issues/26209) [#39911](https://github.com/angular/angular/issues/39911) [#39921](https://github.com/angular/angular/issues/39921)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** allow trailing comma in array literal ([#22277](https://github.com/angular/angular/issues/22277)) ([8d613c1](https://github.com/angular/angular/commit/8d613c1)), closes [#20773](https://github.com/angular/angular/issues/20773)
|
||||
* **compiler:** support tagged template literals in code generator ([#39122](https://github.com/angular/angular/issues/39122)) ([ef89274](https://github.com/angular/angular/commit/ef89274))
|
||||
* **language-service:** Add "find references" capability to Ivy integrated LS ([#39768](https://github.com/angular/angular/issues/39768)) ([06a782a](https://github.com/angular/angular/commit/06a782a))
|
||||
* **language-service:** implement autocompletion for global properties (Ivy) ([#39250](https://github.com/angular/angular/issues/39250)) ([28a0bcb](https://github.com/angular/angular/commit/28a0bcb))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** use `ngDevMode` to tree-shake warning ([#39964](https://github.com/angular/angular/issues/39964)) ([9ebe423](https://github.com/angular/angular/commit/9ebe423))
|
||||
* **common:** use `ngDevMode` to tree-shake warnings ([#39964](https://github.com/angular/angular/issues/39964)) ([f022efa](https://github.com/angular/angular/commit/f022efa))
|
||||
* **core:** use `ngDevMode` to tree-shake `checkNoChanges` ([#39964](https://github.com/angular/angular/issues/39964)) ([e1fe9ec](https://github.com/angular/angular/commit/e1fe9ec))
|
||||
* **core:** use `ngDevMode` to tree-shake warnings ([#39959](https://github.com/angular/angular/issues/39959)) ([8b0cccc](https://github.com/angular/angular/commit/8b0cccc))
|
||||
* **forms:** use `ngDevMode` to tree-shake `_ngModelWarning` ([#39964](https://github.com/angular/angular/issues/39964)) ([7954c8d](https://github.com/angular/angular/commit/7954c8d))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.3"></a>
|
||||
## 11.0.3 (2020-12-02)
|
||||
|
||||
@@ -211,43 +240,6 @@
|
||||
* **forms:** Mark the {[key: string]: any} type for the options property of the FormBuilder.group method as deprecated. Using AbstractControlOptions gives the same functionality and is type-safe.
|
||||
|
||||
|
||||
<a name="11.1.0-next.1"></a>
|
||||
# 11.1.0-next.1 (2020-12-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** getAnimationStyle causes exceptions in older browsers ([#29709](https://github.com/angular/angular/issues/29709)) ([66d863f](https://github.com/angular/angular/commit/66d863f))
|
||||
* **animations:** replace copy of query selector node-list from "spread" to "for" ([#39646](https://github.com/angular/angular/issues/39646)) ([bfa197f](https://github.com/angular/angular/commit/bfa197f)), closes [#38551](https://github.com/angular/angular/issues/38551)
|
||||
* **common:** Prefer to use pageXOffset / pageYOffset instance of scrollX / scrollY ([#28262](https://github.com/angular/angular/issues/28262)) ([b1d300d](https://github.com/angular/angular/commit/b1d300d))
|
||||
* **compiler:** disallow i18n of security-sensitive attributes ([#39554](https://github.com/angular/angular/issues/39554)) ([c8a99ef](https://github.com/angular/angular/commit/c8a99ef))
|
||||
* **compiler:** ensure that placeholders have the correct sourceSpan ([#39717](https://github.com/angular/angular/issues/39717)) ([0462a61](https://github.com/angular/angular/commit/0462a61)), closes [#39671](https://github.com/angular/angular/issues/39671)
|
||||
* **compiler:** only promote Trusted Types to constants when necessary ([#39554](https://github.com/angular/angular/issues/39554)) ([4916870](https://github.com/angular/angular/commit/4916870))
|
||||
* **compiler:** report better error on interpolation in an expression ([#30300](https://github.com/angular/angular/issues/30300)) ([94e790d](https://github.com/angular/angular/commit/94e790d))
|
||||
* **compiler-cli:** report error when a reference target is missing instead of crashing ([#39805](https://github.com/angular/angular/issues/39805)) ([453b32f](https://github.com/angular/angular/commit/453b32f)), closes [#38618](https://github.com/angular/angular/issues/38618) [#39744](https://github.com/angular/angular/issues/39744)
|
||||
* **core:** Allow passing AbstractType to the inject function ([#37958](https://github.com/angular/angular/issues/37958)) ([a1b6ad0](https://github.com/angular/angular/commit/a1b6ad0))
|
||||
* **core:** Ensure OnPush ancestors are marked dirty when events occur ([#39833](https://github.com/angular/angular/issues/39833)) ([68d4a74](https://github.com/angular/angular/commit/68d4a74)), closes [#39832](https://github.com/angular/angular/issues/39832)
|
||||
* **core:** meta addTag() adds incorrect attribute for httpEquiv ([#32531](https://github.com/angular/angular/issues/32531)) ([ff0a90e](https://github.com/angular/angular/commit/ff0a90e))
|
||||
* **core:** migration error if program contains files outside of the project ([#39790](https://github.com/angular/angular/issues/39790)) ([1a26f6d](https://github.com/angular/angular/commit/1a26f6d)), closes [#39778](https://github.com/angular/angular/issues/39778)
|
||||
* **core:** not invoking object's toString when rendering to the DOM ([#39843](https://github.com/angular/angular/issues/39843)) ([11cd37f](https://github.com/angular/angular/commit/11cd37f)), closes [#38839](https://github.com/angular/angular/issues/38839)
|
||||
* **core:** remove duplicated noop function ([#39761](https://github.com/angular/angular/issues/39761)) ([066126a](https://github.com/angular/angular/commit/066126a))
|
||||
* **core:** support `Attribute` DI decorator in `deps` section of a token ([#37085](https://github.com/angular/angular/issues/37085)) ([f5cbf0b](https://github.com/angular/angular/commit/f5cbf0b)), closes [#36479](https://github.com/angular/angular/issues/36479)
|
||||
* **router:** correctly handle string command in outlets ([#39728](https://github.com/angular/angular/issues/39728)) ([c33a823](https://github.com/angular/angular/commit/c33a823)), closes [#18928](https://github.com/angular/angular/issues/18928)
|
||||
* **router:** remove duplicated getOutlet function ([#39764](https://github.com/angular/angular/issues/39764)) ([5fa7673](https://github.com/angular/angular/commit/5fa7673))
|
||||
* **service-worker:** correctly handle failed cache-busted request ([#39786](https://github.com/angular/angular/issues/39786)) ([6046419](https://github.com/angular/angular/commit/6046419)), closes [#39775](https://github.com/angular/angular/issues/39775) [#39775](https://github.com/angular/angular/issues/39775)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** allow any Subscribable in async pipe ([#39627](https://github.com/angular/angular/issues/39627)) ([c7f4abf](https://github.com/angular/angular/commit/c7f4abf))
|
||||
* **compiler:** add schema for Trusted Types sinks ([#39554](https://github.com/angular/angular/issues/39554)) ([358c50e](https://github.com/angular/angular/commit/358c50e))
|
||||
* **compiler:** support error reporting in I18nMetaVisitor ([#39554](https://github.com/angular/angular/issues/39554)) ([bb70a9b](https://github.com/angular/angular/commit/bb70a9b))
|
||||
* **compiler-cli:** add support for using TypeScript 4.1 ([#39571](https://github.com/angular/angular/issues/39571)) ([a7e7c21](https://github.com/angular/angular/commit/a7e7c21))
|
||||
* **compiler-cli:** support for partial compilation of components ([#39707](https://github.com/angular/angular/issues/39707)) ([e75244e](https://github.com/angular/angular/commit/e75244e))
|
||||
* **core:** adds get method to QueryList ([#36907](https://github.com/angular/angular/issues/36907)) ([a965589](https://github.com/angular/angular/commit/a965589)), closes [#29467](https://github.com/angular/angular/issues/29467)
|
||||
* **localize:** support Application Resource Bundle (ARB) translation file format ([#36795](https://github.com/angular/angular/issues/36795)) ([5684ac5](https://github.com/angular/angular/commit/5684ac5))
|
||||
* **platform-browser:** add doubletap HammerJS support ([#26362](https://github.com/angular/angular/issues/26362)) ([b5c0f9d](https://github.com/angular/angular/commit/b5c0f9d)), closes [#23954](https://github.com/angular/angular/issues/23954)
|
||||
|
||||
|
||||
<a name="11.0.2"></a>
|
||||
## 11.0.2 (2020-11-19)
|
||||
@@ -259,26 +251,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="11.1.0-next.0"></a>
|
||||
# 11.1.0-next.0 (2020-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add `HttpParamsOptions` to the public api ([#35829](https://github.com/angular/angular/issues/35829)) ([b33b89d](https://github.com/angular/angular/commit/b33b89d)), closes [#20276](https://github.com/angular/angular/issues/20276)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** recover expression parsing in more malformed pipe cases ([#39437](https://github.com/angular/angular/issues/39437)) ([e336572](https://github.com/angular/angular/commit/e336572))
|
||||
* **compiler:** support recovery of malformed property writes ([#39103](https://github.com/angular/angular/issues/39103)) ([e44e10b](https://github.com/angular/angular/commit/e44e10b))
|
||||
* **compiler-cli:** implement partial directive declaration linking ([#39518](https://github.com/angular/angular/issues/39518)) ([87e9cd6](https://github.com/angular/angular/commit/87e9cd6))
|
||||
* **compiler-cli:** partial compilation of directives ([#39518](https://github.com/angular/angular/issues/39518)) ([8c0a92b](https://github.com/angular/angular/commit/8c0a92b))
|
||||
* **core:** add shouldCoalesceRunChangeDetection option to coalesce change detections in the same event loop. ([#39422](https://github.com/angular/angular/issues/39422)) ([5e92d64](https://github.com/angular/angular/commit/5e92d64)), closes [#39348](https://github.com/angular/angular/issues/39348) [#39348](https://github.com/angular/angular/issues/39348)
|
||||
* **language-service:** Implement go to definition for style and template urls ([#39202](https://github.com/angular/angular/issues/39202)) ([563fb6c](https://github.com/angular/angular/commit/563fb6c))
|
||||
|
||||
|
||||
|
||||
<a name="11.0.1"></a>
|
||||
## 11.0.1 (2020-11-18)
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@name Expression Changed After Checked
|
||||
@category runtime
|
||||
@videoUrl https://www.youtube.com/embed/O47uUnJjbJc
|
||||
@shortDescription Expression has changed after it was checked
|
||||
|
||||
@description
|
||||
Angular throws an `ExpressionChangedAfterItHasBeenCheckedError` when an expression value has been changed after change detection has completed. Angular only throws this error in development mode.
|
||||
|
||||
In dev mode, Angular performs an additional check after each change detection run, to ensure the bindings haven’t changed. This catches errors where the view is left in an inconsistent state. This can occur, for example, if a method or getter returns a different value each time it is called, or if a child component changes values on its parent. If either of these occur, this is a sign that change detection is not stabilized. Angular throws the error to ensure data is always reflected correctly in the view, which prevents erratic UI behavior or a possible infinite loop.
|
||||
|
||||
This error commonly occurs when you’ve added template expressions or begun to implement lifecycle hooks like `ngAfterViewInit` or `ngOnChanges`. It is also common when dealing with loading status and asynchronous operations, or a child component changes its parent bindings.
|
||||
|
||||
@debugging
|
||||
The [source maps](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map) generated by the CLI are very useful when debugging. Navigate up the call stack until you find a template expression where the value displayed in the error has changed.
|
||||
|
||||
Ensure that there are no changes to the bindings in the template after change detection is run. This often means refactoring to use the correct [component lifecycle hook](https://angular.io/guide/lifecycle-hooks) for your use case. If the issue exists within `ngAfterViewInit`, the recommended solution is to use a constructor or `ngOnInit` to set initial values, or use `ngAfterContentInit` for other value bindings.
|
||||
|
||||
If you are binding to methods in the view, ensure that the invocation does not update any of the other bindings in the template.
|
||||
|
||||
Read more about which solution is right for you in ['Everything you need to know about the "ExpressionChangedAfterItHasBeenCheckedError" error'](https://indepth.dev/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error/) and why this is useful at ['Angular Debugging "Expression has changed after it was checked": Simple Explanation (and Fix)'](https://blog.angular-university.io/angular-debugging/).
|
||||
@@ -0,0 +1,12 @@
|
||||
@name Circular Dependency in DI
|
||||
@category runtime
|
||||
@videoUrl https://www.youtube.com/embed/CpLOm4o_FzM
|
||||
@shortDescription Circular dependency in DI detected while instantiating a provider
|
||||
|
||||
@description
|
||||
A cyclic dependency exists when a [dependency of a service](https://angular.io/guide/hierarchical-dependency-injection) directly or indirectly depends on the service itself. For example, if `UserService` depends on `EmployeeService`, which also depends on `UserService`. Angular will have to instantiate `EmployeeService` to create `UserService`, which depends on `UserService`, itself.
|
||||
|
||||
@debugging
|
||||
Use the call stack to determine where the cyclical dependency exists. You will be able to see if any child dependencies rely on the original file by [mapping out](https://angular.io/guide/dependency-injection-in-action) the component, module, or service’s dependencies and identify the loop causing the problem.
|
||||
|
||||
Break this loop (or circle) of dependency to resolve this error. This most commonly means removing or refactoring the dependencies to not be reliant on one another.
|
||||
@@ -0,0 +1,20 @@
|
||||
@name No Provider Found
|
||||
@category runtime
|
||||
@videoUrl https://www.youtube.com/embed/lAlOryf1-WU
|
||||
@shortDescription No provider for found injectorDetails
|
||||
|
||||
@description
|
||||
You see this error when you try to inject a service but have not declared a corresponding provider. A provider is a mapping that supplies a value that you can inject into the constructor of a class in your application.
|
||||
|
||||
Read more on providers in our [Dependency Injection guide](https://angular.io/guide/dependency-injection).
|
||||
|
||||
@debugging
|
||||
Work backwards from the object where the error states that a [provider](https://angular.io/guide/architecture-services) is missing: `No provider for ${this}!`. This is commonly thrown in [services](https://angular.io/tutorial/toh-pt4), which require non-existing providers.
|
||||
|
||||
To fix the error ensure that your service is registered in the list of providers of an `NgModule` or has the `@Injectable` decorator with a `providedIn` property at top.
|
||||
|
||||
The most common solution is to add a provider in `@Injectable` using `providedIn`:
|
||||
|
||||
```typescript
|
||||
@Injectable({ providedIn: 'app' })
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
@name Selector Collision
|
||||
@category runtime
|
||||
@videoUrl https://www.youtube.com/embed/z_3Z5mOm59I
|
||||
@shortDescription Multiple components match with the same tagname
|
||||
|
||||
@description
|
||||
Two or more components use the same [element selector](https://angular.io/guide/component-overview#specifying-a-components-css-selector). Because there can only be a single component associated with an element, selectors must be unique strings to prevent ambiguity for Angular.
|
||||
|
||||
@debugging
|
||||
Use the element name from the error message to search for places where you’re using the same [selector declaration](https://angular.io/guide/architecture-components) in your codebase:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'YOUR_STRING',
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
Ensure that each component has a unique CSS selector. This will guarantee that Angular renders the component you expect.
|
||||
|
||||
If you’re having trouble finding multiple components with this selector tag name, check for components from imported component libraries, such as Angular Material. Make sure you're following the [best practices](https://angular.io/guide/styleguide#component-selectors) for your selectors to prevent collisions.
|
||||
@@ -0,0 +1,32 @@
|
||||
@name Export Not Found
|
||||
@category runtime
|
||||
@videoUrl https://www.youtube.com/embed/fUSAg4kp2WQ
|
||||
@shortDescription Export not found!
|
||||
|
||||
@description
|
||||
Angular can’t find a directive with `{{ PLACEHOLDER }}` export name. The export name is specified in the `exportAs` property of the directive decorator. This is common when using FormsModule or Material modules in templates, and you’ve forgotten to [import the corresponding modules](https://angular.io/guide/sharing-ngmodules).
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This is the runtime equivalent of a common compiler error [NG8003: No directive found with export](errors/NG8003).
|
||||
|
||||
</div>
|
||||
|
||||
@debugging
|
||||
Use the export name to trace the templates or modules using this export.
|
||||
|
||||
Ensure that all dependencies are [properly imported and declared in your NgModules](https://angular.io/guide/sharing-ngmodules). For example, if the export not found is `ngForm`, we need to import `FormsModule` and declare it in the list of imports in `*.module.ts` to resolve the error.
|
||||
|
||||
```typescript
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
...
|
||||
imports: [
|
||||
FormsModule,
|
||||
…
|
||||
```
|
||||
|
||||
If you recently added an import, you may need to restart your server to see these changes.
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@name Argument Not Literal
|
||||
@category compiler
|
||||
@shortDescription Decorator argument is not an object literal
|
||||
|
||||
@description
|
||||
To make the metadata extraction in the Angular compiler faster, the decorators `@NgModule`, `@Pipe`, `@Component`, `@Directive`, and `@Injectable` accept only object literals as arguments.
|
||||
|
||||
This is an [intentional change in Ivy](https://github.com/angular/angular/issues/30840#issuecomment-498869540), which enforces stricter argument requirements for decorators than View Engine. Ivy requires this approach because it compiles decorators by moving the expressions into other locations in the class output.
|
||||
|
||||
@debugging
|
||||
Move all declarations:
|
||||
|
||||
```typescript
|
||||
const moduleDefinition = {...}
|
||||
|
||||
@NgModule(moduleDefinition)
|
||||
export class AppModule {
|
||||
constructor() {}
|
||||
}
|
||||
```
|
||||
|
||||
into the decorator:
|
||||
|
||||
```typescript
|
||||
@NgModule({...})
|
||||
export class AppModule {
|
||||
constructor() {}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
@name Missing Token
|
||||
@category compiler
|
||||
@shortDescription No suitable injection token for parameter
|
||||
|
||||
@description
|
||||
There is no injection token for a constructor parameter at compile time. [InjectionTokens](https://angular.io/api/core/InjectionToken) are tokens that can be used in a Dependency Injection Provider.
|
||||
|
||||
@debugging
|
||||
Look at the parameter that throws the error and all uses of the class. This error is commonly thrown when a constructor defines parameters with primitive types like `string`, `number`, `boolean`, and `Object`.
|
||||
|
||||
Use the [@Injectable](https://angular.io/api/core/Injectable) method or [@Inject](https://angular.io/api/core/Inject) decorator from `@angular/core` to ensure that the type you are injecting is reified (has a runtime representation). Make sure to add a provider to this decorator so that you do not throw [NG0201: No Provider Found](https://angular.io/errors/NG0201).
|
||||
@@ -0,0 +1,21 @@
|
||||
@name Invalid Element
|
||||
@category compiler
|
||||
@shortDescription Unknown HTML element or component
|
||||
|
||||
@description
|
||||
One or more elements cannot be resolved during compilation because the element is not defined by the HTML spec, or there is no component or directive with such element selector.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This is the compiler equivalent of a common runtime error `NG0304: '${tagName}' is not a known element: …`.
|
||||
|
||||
</div>
|
||||
|
||||
@debugging
|
||||
Use the element name in the error to find the file(s) where the element is being used.
|
||||
|
||||
Check that the name and selector are correct. If the component is from a different module or import, check that the component is exported from its origin module and imported into the correct `*.modules.ts` file, and declared in the imports list.
|
||||
|
||||
When using custom elements or web components, ensure that you add [`CUSTOM_ELEMENTS_SCHEMA`](https://angular.io/api/core/CUSTOM_ELEMENTS_SCHEMA) to the app module.
|
||||
|
||||
If this does not resolve the error, check the imported libraries for any recent changes to the exports and properties you are using, and restart your server.
|
||||
@@ -0,0 +1,16 @@
|
||||
@name Invalid Attribute
|
||||
@category compiler
|
||||
@videoUrl https://www.youtube.com/embed/wfLkB3RsSJM
|
||||
@shortDescription Unknown attribute or input
|
||||
|
||||
@description
|
||||
An attribute or property cannot be resolved during compilation.
|
||||
|
||||
This error arises when attempting to bind to a property that does not exist. Any property binding must correspond to either:
|
||||
* A native property on the HTML element, or
|
||||
* An `@Input()` property of a component or directive applied to the element.
|
||||
|
||||
The runtime error for this is `NG0304: '${tagName}' is not a known element: …’`.
|
||||
|
||||
@debugging
|
||||
Look at documentation for the specific [binding syntax](https://angular.io/guide/binding-syntax) used. This is usually a typo or incorrect import. There may also be a missing direction with property selector ‘name’ or missing input.
|
||||
@@ -0,0 +1,31 @@
|
||||
@name Missing Reference Target
|
||||
@category compiler
|
||||
@videoUrl https://www.youtube.com/embed/fUSAg4kp2WQ
|
||||
@shortDescription No directive found with export
|
||||
|
||||
@description
|
||||
Angular can’t find a directive with `{{ PLACEHOLDER }}` export name. This is common with a missing import or a missing [`exportAs`](https://angular.io/api/core/Directive#exportAs) on a directive.
|
||||
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
This is the compiler equivalent of a common runtime error [NG0301: Export Not Found](errors/NG0301).
|
||||
|
||||
</div>
|
||||
|
||||
@debugging
|
||||
Use the string name of the export not found to trace the templates or modules using this export.
|
||||
|
||||
Ensure that all dependencies are properly imported and declared in our Modules. For example, if the export not found is `ngForm`, we will need to import `FormsModule` and declare it in our list of imports in `*.module.ts` to resolve the missing export error.
|
||||
|
||||
```typescript
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
...
|
||||
imports: [
|
||||
FormsModule,
|
||||
…
|
||||
```
|
||||
|
||||
If you recently added an import, you will need to restart your server to see these changes.
|
||||
@@ -0,0 +1 @@
|
||||
# Errors List
|
||||
@@ -1,6 +1,6 @@
|
||||
// #docregion
|
||||
import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { Directive, Input } from '@angular/core';
|
||||
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
|
||||
|
||||
// #docregion custom-validator
|
||||
/** A hero's name can't match the given regular expression */
|
||||
@@ -22,7 +22,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@Input('appForbiddenName') forbiddenName: string;
|
||||
|
||||
validate(control: AbstractControl): {[key: string]: any} | null {
|
||||
validate(control: AbstractControl): ValidationErrors | null {
|
||||
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator,
|
||||
|
||||
// #docregion cross-validation-validator
|
||||
/** A hero's name can't match the hero's alter ego */
|
||||
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
|
||||
export const identityRevealedValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
|
||||
const name = control.get('name');
|
||||
const alterEgo = control.get('alterEgo');
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ The following table summarizes class binding syntax.
|
||||
<td><code>"my-class-1 my-class-2 my-class-3"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{[key: string]: boolean | undefined | null}</code></td>
|
||||
<td><code>Record<string, boolean | undefined | null></code></td>
|
||||
<td><code>{foo: true, bar: false}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -248,7 +248,7 @@ The following table summarizes style binding syntax.
|
||||
<td><code>"width: 100px; height: 100px"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{[key: string]: string | undefined | null}</code></td>
|
||||
<td><code>Record<string, string | undefined | null></code></td>
|
||||
<td><code>{width: '100px', height: '100px'}</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -362,4 +362,4 @@ Another example is the [RouterOutlet](api/router/RouterOutlet) directive, which
|
||||
|
||||
Remember, use [@Input()](api/core/Input) when you want to keep track of the attribute value and update the associated property. Use [@Attribute()](api/core/Attribute) when you want to inject the value of an HTML attribute to a component or directive constructor.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -603,10 +603,9 @@ If your Browserslist configuration includes support for any legacy browsers, the
|
||||
|
||||
## Local development in older browsers
|
||||
|
||||
In Angular CLI version 8 and higher, differential loading is enabled by default for the `ng build` command.
|
||||
Differential loading is not enabled by default for application projects that were generated with Angular CLI 10 and above.
|
||||
The `ng serve`, `ng test`, and `ng e2e` commands, however, generate a single ES2015 build which cannot run in older browsers that don't support the modules, such as IE 11.
|
||||
|
||||
If you want to run ES5 code during development, you could disable differential loading completely.
|
||||
To maintain the benefits of differential loading, however, a better option is to define multiple configurations for `ng serve`, `ng e2e`, and `ng test`.
|
||||
|
||||
{@a differential-serve}
|
||||
|
||||
@@ -37,7 +37,7 @@ See the <live-example name="dynamic-form"></live-example>.
|
||||
|
||||
Before doing this tutorial, you should have a basic understanding to the following.
|
||||
|
||||
* [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") and HTML5 programming.
|
||||
|
||||
* Fundamental concepts of [Angular app design](guide/architecture "Introduction to Angular app-design concepts").
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Entry components
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Entry components have been deprecated with the [Ivy rendering engine](https://angular.io/guide/ivy).
|
||||
For more information, see [entryComponents deprecation](https://angular.io/guide/deprecations#entrycomponents-and-analyze_for_entry_components-no-longer-required) in the [Deprecated APIs and features](https://angular.io/guide/deprecations).
|
||||
|
||||
</div>
|
||||
|
||||
An entry component is any component that Angular loads imperatively, (which means you’re not referencing it in the template), by type. You specify an entry component by bootstrapping it in an NgModule, or including it in a routing definition.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@@ -8,7 +8,7 @@ in both reactive and template-driven forms.
|
||||
|
||||
Before reading about form validation, you should have a basic understanding of the following.
|
||||
|
||||
* [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") and HTML5 programming.
|
||||
|
||||
* Fundamental concepts of [Angular app design](guide/architecture "Introduction to Angular app-design concepts").
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ This guide provides information to help you decide which type of form works best
|
||||
|
||||
This guide assumes that you have a basic understanding of the following.
|
||||
|
||||
* [TypeScript](https://www.typescriptlang.org/docs/home.html "The TypeScript language") and HTML5 programming.
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") and HTML5 programming.
|
||||
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ This tutorial teaches you how to do the following:
|
||||
|
||||
Before going further into template-driven forms, you should have a basic understanding of the following.
|
||||
|
||||
* TypeScript and HTML5 programming.
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") and HTML5 programming.
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
* The basics of [Angular template syntax](guide/template-syntax "Template syntax guide").
|
||||
* The form-design concepts that are presented in [Introduction to Forms](guide/forms-overview "Overview of Angular forms.").
|
||||
|
||||
@@ -995,7 +995,7 @@ consider moving it to a utility function or into the `PackageSearchService` itse
|
||||
## Security: XSRF protection
|
||||
|
||||
[Cross-Site Request Forgery (XSRF or CSRF)](https://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website.
|
||||
`HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-Header_Token) used to prevent XSRF attacks.
|
||||
`HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) used to prevent XSRF attacks.
|
||||
When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`.
|
||||
Since only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Try this <live-example title="Reactive Forms in Stackblitz">Reactive Forms live-
|
||||
|
||||
Before going further into reactive forms, you should have a basic understanding of the following:
|
||||
|
||||
* TypeScript programming.
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") programming.
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
* The form-design concepts that are presented in [Introduction to Forms](guide/forms-overview "Overview of Angular forms.").
|
||||
|
||||
|
||||
+99
-109
@@ -1,34 +1,29 @@
|
||||
# Security
|
||||
|
||||
This page describes Angular's built-in
|
||||
This topic describes Angular's built-in
|
||||
protections against common web-application vulnerabilities and attacks such as cross-site
|
||||
scripting attacks. It doesn't cover application-level security, such as authentication (_Who is
|
||||
this user?_) and authorization (_What can this user do?_).
|
||||
scripting attacks. It doesn't cover application-level security, such as authentication and authorization.
|
||||
|
||||
For more information about the attacks and mitigations described below, see [OWASP Guide Project](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).
|
||||
|
||||
You can run the <live-example></live-example> in Stackblitz and download the code from there.
|
||||
|
||||
<div class="callout is-important">
|
||||
|
||||
|
||||
<h2 id='report-issues'>
|
||||
Reporting vulnerabilities
|
||||
</h2>
|
||||
|
||||
|
||||
{@a report-issues}
|
||||
<header>Reporting vulnerabilities</header>
|
||||
|
||||
To report vulnerabilities in Angular itself, email us at [security@angular.io](mailto:security@angular.io).
|
||||
|
||||
For more information about how Google handles security issues, see [Google's security
|
||||
philosophy](https://www.google.com/about/appsecurity/).
|
||||
|
||||
</div>
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<h2 id='best-practices'>
|
||||
Best practices
|
||||
</h2>
|
||||
|
||||
|
||||
{@a best-practices}
|
||||
<header>Best practices</header>
|
||||
|
||||
* **Keep current with the latest Angular library releases.**
|
||||
We regularly update the Angular libraries, and these updates may fix security defects discovered in
|
||||
@@ -43,13 +38,9 @@ community and make a pull request.
|
||||
* **Avoid Angular APIs marked in the documentation as “_Security Risk_.”**
|
||||
For more information, see the [Trusting safe values](guide/security#bypass-security-apis) section of this page.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h2 id='xss'>
|
||||
Preventing cross-site scripting (XSS)
|
||||
</h2>
|
||||
|
||||
|
||||
## Preventing cross-site scripting (XSS)
|
||||
|
||||
[Cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) enables attackers
|
||||
to inject malicious code into web pages. Such code can then, for example, steal user data (in
|
||||
@@ -64,16 +55,11 @@ attacker-controlled data enters the DOM, expect security vulnerabilities.
|
||||
|
||||
### Angular’s cross-site scripting security model
|
||||
|
||||
To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value
|
||||
is inserted into the DOM from a template, via property, attribute, style, class binding, or interpolation,
|
||||
Angular sanitizes and escapes untrusted values.
|
||||
To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value is inserted into the DOM from a template binding, or interpolation, Angular sanitizes and escapes untrusted values. If a value was already sanitized outside of Angular and is considered safe, you can communicate this to Angular by marking the [value as trusted](#bypass-security-apis).
|
||||
|
||||
_Angular templates are the same as executable code_: HTML, attributes, and binding expressions
|
||||
(but not the values bound) in templates are trusted to be safe. This means that applications must
|
||||
prevent values that an attacker can control from ever making it into the source code of a
|
||||
template. Never generate template source code by concatenating user input and templates.
|
||||
To prevent these vulnerabilities, use
|
||||
the [offline template compiler](guide/security#offline-template-compiler), also known as _template injection_.
|
||||
Unlike values to be used for rendering, Angular templates are considered trusted by default, and should be treated as executable code. Never generate templates by concatenating user input and template syntax. Doing this would enable attackers to [inject arbitrary code](https://en.wikipedia.org/wiki/Code_injection) into your application. To prevent these vulnerabilities, always use the default [AOT template compiler](/guide/security#offline-template-compiler) in production deployments.
|
||||
|
||||
An additional layer of protection can be provided through the use of Content security policy and Trusted Types. These web platform features operate at the DOM level which is the most effective place to prevent XSS issues because they can't be bypassed using other, lower-level APIs. For this reason, we strongly encourage developers to take advantage of these features by configuring the [content security policy](#content-security-policy) for their application and enabling [trusted types enforcement](#trusted-types).
|
||||
|
||||
### Sanitization and security contexts
|
||||
|
||||
@@ -97,35 +83,26 @@ when it has to change a value during sanitization.
|
||||
The following template binds the value of `htmlSnippet`, once by interpolating it into an element's
|
||||
content, and once by binding it to the `innerHTML` property of an element:
|
||||
|
||||
|
||||
<code-example path="security/src/app/inner-html-binding.component.html" header="src/app/inner-html-binding.component.html"></code-example>
|
||||
|
||||
|
||||
|
||||
Interpolated content is always escaped—the HTML isn't interpreted and the browser displays
|
||||
angle brackets in the element's text content.
|
||||
|
||||
For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`. But binding
|
||||
a value that an attacker might control into `innerHTML` normally causes an XSS
|
||||
vulnerability. For example, code contained in a `<script>` tag is executed:
|
||||
|
||||
vulnerability. For example, one could execute JavaScript in a following way:
|
||||
|
||||
<code-example path="security/src/app/inner-html-binding.component.ts" header="src/app/inner-html-binding.component.ts (class)" region="class"></code-example>
|
||||
|
||||
|
||||
|
||||
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `<script>`
|
||||
tag but keeps safe content such as the `<b>` element.
|
||||
|
||||
Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `onerror` attribute but keeps safe content such as the `<b>` element.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src='generated/images/guide/security/binding-inner-html.png' alt='A screenshot showing interpolated and bound HTML values'>
|
||||
</div>
|
||||
|
||||
|
||||
### Direct use of the DOM APIs and explicit sanitization calls
|
||||
|
||||
The built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
|
||||
Unless you enforce Trusted Types, the built-in browser DOM APIs don't automatically protect you from security vulnerabilities.
|
||||
For example, `document`, the node available through `ElementRef`, and many third-party APIs
|
||||
contain unsafe methods. In the same way, if you interact with other libraries that manipulate
|
||||
the DOM, you likely won't have the same automatic sanitization as with Angular interpolations.
|
||||
@@ -137,43 +114,9 @@ method and the appropriate `SecurityContext`. That function also accepts values
|
||||
marked as trusted using the `bypassSecurityTrust`... functions, and will not sanitize them,
|
||||
as [described below](#bypass-security-apis).
|
||||
|
||||
### Content security policy
|
||||
|
||||
Content Security Policy (CSP) is a defense-in-depth
|
||||
technique to prevent XSS. To enable CSP, configure your web server to return an appropriate
|
||||
`Content-Security-Policy` HTTP header. Read more about content security policy at the
|
||||
[Web Fundamentals guide](https://developers.google.com/web/fundamentals/security/csp) on the
|
||||
Google Developers website.
|
||||
|
||||
|
||||
{@a offline-template-compiler}
|
||||
|
||||
|
||||
### Use the offline template compiler
|
||||
|
||||
The offline template compiler prevents a whole class of vulnerabilities called template injection,
|
||||
and greatly improves application performance. Use the offline template compiler in production
|
||||
deployments; don't dynamically generate templates. Angular trusts template code, so generating
|
||||
templates, in particular templates containing user data, circumvents Angular's built-in protections.
|
||||
For information about dynamically constructing forms in a safe way, see the
|
||||
[Dynamic Forms](guide/dynamic-form) guide page.
|
||||
|
||||
### Server-side XSS protection
|
||||
|
||||
HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an
|
||||
Angular application is the same as injecting executable code into the
|
||||
application: it gives the attacker full control over the application. To prevent this,
|
||||
use a templating language that automatically escapes values to prevent XSS vulnerabilities on
|
||||
the server. Don't generate Angular templates on the server side using a templating language; doing this
|
||||
carries a high risk of introducing template-injection vulnerabilities.
|
||||
|
||||
|
||||
|
||||
<h2 id='bypass-security-apis'>
|
||||
Trusting safe values
|
||||
</h2>
|
||||
|
||||
{@a bypass-security-apis}
|
||||
|
||||
### Trusting safe values
|
||||
|
||||
Sometimes applications genuinely need to include executable code, display an `<iframe>` from some
|
||||
URL, or construct potentially dangerous URLs. To prevent automatic sanitization in any of these
|
||||
@@ -195,26 +138,18 @@ Remember, whether a value is safe depends on context, so choose the right contex
|
||||
your intended use of the value. Imagine that the following template needs to bind a URL to a
|
||||
`javascript:alert(...)` call:
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.html" header="src/app/bypass-security.component.html (URL)" region="URL"></code-example>
|
||||
|
||||
|
||||
|
||||
Normally, Angular automatically sanitizes the URL, disables the dangerous code, and
|
||||
in development mode, logs this action to the console. To prevent
|
||||
this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.ts" header="src/app/bypass-security.component.ts (trust-url)" region="trust-url"></code-example>
|
||||
|
||||
|
||||
|
||||
<div class="lightbox">
|
||||
<img src='generated/images/guide/security/bypass-security-component.png' alt='A screenshot showing an alert box created from a trusted URL'>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
If you need to convert user input into a trusted value, use a
|
||||
controller method. The following template allows users to enter a YouTube video ID and load the
|
||||
corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security
|
||||
@@ -225,29 +160,93 @@ Angular to allow binding into `<iframe src>`:
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.html" header="src/app/bypass-security.component.html (iframe)" region="iframe"></code-example>
|
||||
|
||||
|
||||
|
||||
<code-example path="security/src/app/bypass-security.component.ts" header="src/app/bypass-security.component.ts (trust-video-url)" region="trust-video-url"></code-example>
|
||||
|
||||
{@a content-security-policy}
|
||||
### Content security policy
|
||||
|
||||
Content Security Policy (CSP) is a defense-in-depth
|
||||
technique to prevent XSS. To enable CSP, configure your web server to return an appropriate
|
||||
`Content-Security-Policy` HTTP header. Read more about content security policy at the
|
||||
[Web Fundamentals guide](https://developers.google.com/web/fundamentals/security/csp) on the
|
||||
Google Developers website.
|
||||
|
||||
{@a trusted-types}
|
||||
### Enforcing Trusted Types
|
||||
|
||||
<h2 id='http'>
|
||||
HTTP-level vulnerabilities
|
||||
</h2>
|
||||
We recommend the use of [Trusted Types](https://w3c.github.io/webappsec-trusted-types/dist/spec/) as a way to help secure your applications from cross-site scripting attacks. Trusted Types is a [web platform](https://en.wikipedia.org/wiki/Web_platform)
|
||||
feature that can help you prevent cross-site scripting attacks by enforcing
|
||||
safer coding practices. Trusted Types can also help simplify the auditing of application code.
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
Trusted Types might not yet be available in all browsers your application targets. In the case your Trusted-Types-enabled application runs in a browser that doesn't support Trusted Types, the functionality of the application will be preserved, and your application will be guarded against XSS via Angular's DomSanitizer. See [caniuse.com/trusted-types](https://caniuse.com/trusted-types) for the current browser support.
|
||||
|
||||
</div>
|
||||
|
||||
To enforce Trusted Types for your application, you must configure your application's web server to emit HTTP headers with one of the following Angular policies:
|
||||
|
||||
* `angular` - This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy.
|
||||
* `angular#unsafe-bypass` - This policy is used for applications that use any of the methods in Angular's [DomSanitizer](api/platform-browser/DomSanitizer) that bypass security, such as `bypassSecurityTrustHtml`. Any application that uses these methods must enable this policy.
|
||||
* `angular#unsafe-jit` - This policy is used by the [JIT compiler](api/core/Compiler). You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the [platform browser dynamic](api/platform-browser-dynamic/platformBrowserDynamic).
|
||||
|
||||
You should configure the HTTP headers for Trusted Types in the following locations:
|
||||
|
||||
* Production serving infrastructure
|
||||
* Angular CLI (`ng serve`), using the `headers` property in the `angular.json` file, for local development and end-to-end testing
|
||||
* Karma (`ng test`), using the `customHeaders` property in the `karma.config.js` file, for unit testing
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular:
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular applications that use any of the methods in Angular's [DomSanitizer](api/platform-browser/DomSanitizer) that bypasses security.
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular angular#unsafe-bypass; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
The following is an example of a header specifically configured for Trusted Types and Angular applications using JIT:
|
||||
|
||||
<code-example language="html">
|
||||
Content-Security-Policy: trusted-types angular angular#unsafe-jit; require-trusted-types-for 'script';
|
||||
</code-example>
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
<header>Community contributions</header>
|
||||
|
||||
To learn more about troubleshooting Trusted Type configurations, the following resource might be helpful:
|
||||
|
||||
[Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types](https://web.dev/trusted-types/#how-to-use-trusted-types)
|
||||
|
||||
</div>
|
||||
|
||||
{@a offline-template-compiler}
|
||||
|
||||
### Use the AOT template compiler
|
||||
|
||||
The AOT template compiler prevents a whole class of vulnerabilities called template injection,
|
||||
and greatly improves application performance. The AOT template compiler is the default compiler used by Angular CLI applications, and you should use it in all production deployments.
|
||||
|
||||
An alternative to the AOT compiler is the JIT compiler which compiles templates to executable template code within the browser at runtime. Angular trusts template code, so dynamically generating templates and compiling them, in particular templates containing user data, circumvents Angular's built-in protections and is a security anti-pattern. For information about dynamically constructing forms in a safe way, see the [Dynamic Forms](guide/dynamic-form) guide.
|
||||
|
||||
{@a server-side-xss}
|
||||
### Server-side XSS protection
|
||||
|
||||
HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an Angular application is the same as injecting executable code into the application: it gives the attacker full control over the application. To prevent this, use a templating language that automatically escapes values to prevent XSS vulnerabilities on the server. Don't generate Angular templates on the server side using a templating language; doing this carries a high risk of introducing template-injection vulnerabilities.
|
||||
|
||||
{@a http}
|
||||
## HTTP-level vulnerabilities
|
||||
|
||||
Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request
|
||||
forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily
|
||||
on the server side, but Angular provides helpers to make integration on the client side easier.
|
||||
|
||||
|
||||
<h3 id='xsrf'>
|
||||
Cross-site request forgery
|
||||
</h3>
|
||||
|
||||
|
||||
{@a xsrf}
|
||||
### Cross-site request forgery
|
||||
|
||||
In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting
|
||||
a different web page (such as `evil.com`) with malignant code that secretly sends a malicious request
|
||||
@@ -287,12 +286,8 @@ The Stanford University paper
|
||||
See also Dave Smith's easy-to-understand
|
||||
[talk on XSRF at AngularConnect 2016](https://www.youtube.com/watch?v=9inczw6qtpY "Cross Site Request Funkery Securing Your Angular Apps From Evil Doers").
|
||||
|
||||
|
||||
<h3 id='xssi'>
|
||||
Cross-site script inclusion (XSSI)
|
||||
</h3>
|
||||
|
||||
|
||||
{@a xssi}
|
||||
### Cross-site script inclusion (XSSI)
|
||||
|
||||
Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to
|
||||
read data from a JSON API. The attack works on older browsers by overriding native JavaScript
|
||||
@@ -308,13 +303,8 @@ Angular's `HttpClient` library recognizes this convention and automatically stri
|
||||
For more information, see the XSSI section of this [Google web security blog
|
||||
post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).
|
||||
|
||||
|
||||
|
||||
<h2 id='code-review'>
|
||||
Auditing Angular applications
|
||||
</h2>
|
||||
|
||||
|
||||
{@a code-review}
|
||||
## Auditing Angular applications
|
||||
|
||||
Angular applications must follow the same security principles as regular web applications, and
|
||||
must be audited as such. Angular-specific APIs that should be audited in a security review,
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
# SVG in templates
|
||||
# SVG as templates
|
||||
|
||||
<div class="callout is-critical">
|
||||
|
||||
<header>Marked for archiving</header>
|
||||
|
||||
To ensure that you have the best experience possible, this topic is marked for archiving until we determine that it clearly conveys the most accurate information possible.
|
||||
|
||||
If you think this content should not be archived, please file a [GitHub issue](https://github.com/angular/angular/issues/new?template=3-docs-bug.md).
|
||||
|
||||
</div>
|
||||
|
||||
It is possible to use SVG as valid templates in Angular. All of the template syntax below is
|
||||
applicable to both SVG and HTML. Learn more in the SVG [1.1](https://www.w3.org/TR/SVG11/) and
|
||||
[2.0](https://www.w3.org/TR/SVG2/) specifications.
|
||||
You can use SVG files as templates in your Angular applications. When you use an SVG as the template, you are able to use directives and bindings just like with HTML templates. With these features, you can dynamically generate interactive graphics.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@@ -20,18 +8,15 @@ See the <live-example name="template-syntax"></live-example> for a working examp
|
||||
|
||||
</div>
|
||||
|
||||
Why would you use SVG as template, instead of simply adding it as image to your application?
|
||||
## SVG syntax example
|
||||
|
||||
When you use an SVG as the template, you are able to use directives and bindings just like with HTML
|
||||
templates. This means that you will be able to dynamically generate interactive graphics.
|
||||
|
||||
Refer to the sample code snippet below for a syntax example:
|
||||
The following example shows the syntax for using an SVG as a template.
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.ts" header="src/app/svg.component.ts"></code-example>
|
||||
|
||||
Add the following code to your `svg.component.svg` file:
|
||||
To see property and event binding in action, add the following code to your `svg.component.svg` file:
|
||||
|
||||
<code-example path="template-syntax/src/app/svg.component.svg" header="src/app/svg.component.svg"></code-example>
|
||||
|
||||
Here you can see the use of a `click()` event binding and the property binding syntax
|
||||
The example given uses a `click()` event binding and the property binding syntax
|
||||
(`[attr.fill]="fillColor"`).
|
||||
|
||||
@@ -34,6 +34,16 @@
|
||||
"desc": "Peer-reviewed Angular articles and tutorials.",
|
||||
"url": "https://indepth.dev/angular/",
|
||||
"title": "Angular inDepth"
|
||||
},
|
||||
"angular-nation": {
|
||||
"desc": "Angular Nation is a free private network created for Angular developers to network, collaborate, and ask questions.",
|
||||
"url": "https://www.angularnation.net/",
|
||||
"title": "Angular Nation"
|
||||
},
|
||||
"angular-builders": {
|
||||
"desc": "A collection of Angular builders for your Angular projects",
|
||||
"url": "https://angular-builders.dev/",
|
||||
"title": "Angular Builders"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -190,6 +200,13 @@
|
||||
"Tooling": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"ub": {
|
||||
"desc": "UI Bakery is a low code platform to generate Angular applications.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "UI Bakery",
|
||||
"url": "https://uibakery.io/"
|
||||
},
|
||||
"a1": {
|
||||
"desc": "A Google Chrome Dev Tools extension for debugging Angular applications.",
|
||||
"logo": "https://augury.angular.io/images/augury-logo.svg",
|
||||
|
||||
+45
-29
@@ -56,35 +56,35 @@
|
||||
"tooltip": "Set up your environment and learn basic concepts",
|
||||
"children": [
|
||||
{
|
||||
"title": "Try it",
|
||||
"tooltip": "Examine and work with a ready-made sample app, with no setup.",
|
||||
"children": [
|
||||
{
|
||||
"url": "start",
|
||||
"title": "Getting started",
|
||||
"tooltip": "Take a look at Angular's component model, template syntax, and component communication."
|
||||
},
|
||||
{
|
||||
"url": "start/start-routing",
|
||||
"title": "Adding navigation",
|
||||
"tooltip": "Navigate among different page views using the browser's URL."
|
||||
},
|
||||
{
|
||||
"url": "start/start-data",
|
||||
"title": "Managing Data",
|
||||
"tooltip": "Use services and access external data via HTTP."
|
||||
},
|
||||
{
|
||||
"url": "start/start-forms",
|
||||
"title": "Using Forms for User Input",
|
||||
"tooltip": "Learn about fetching and managing data from users with forms."
|
||||
},
|
||||
{
|
||||
"url": "start/start-deployment",
|
||||
"title": "Deploying an application",
|
||||
"tooltip": "Move to local development, or deploy your application to Firebase or your own server."
|
||||
}
|
||||
]
|
||||
"title": "Try it",
|
||||
"tooltip": "Examine and work with a ready-made sample app, with no setup.",
|
||||
"children": [
|
||||
{
|
||||
"url": "start",
|
||||
"title": "Getting started",
|
||||
"tooltip": "Take a look at Angular's component model, template syntax, and component communication."
|
||||
},
|
||||
{
|
||||
"url": "start/start-routing",
|
||||
"title": "Adding navigation",
|
||||
"tooltip": "Navigate among different page views using the browser's URL."
|
||||
},
|
||||
{
|
||||
"url": "start/start-data",
|
||||
"title": "Managing Data",
|
||||
"tooltip": "Use services and access external data via HTTP."
|
||||
},
|
||||
{
|
||||
"url": "start/start-forms",
|
||||
"title": "Using Forms for User Input",
|
||||
"tooltip": "Learn about fetching and managing data from users with forms."
|
||||
},
|
||||
{
|
||||
"url": "start/start-deployment",
|
||||
"title": "Deploying an application",
|
||||
"tooltip": "Move to local development, or deploy your application to Firebase or your own server."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "guide/setup-local",
|
||||
@@ -196,6 +196,11 @@
|
||||
"url": "guide/inputs-outputs",
|
||||
"title": "Inputs and Outputs",
|
||||
"tooltip": "Introductory guide to sharing data between parent and child directives or components."
|
||||
},
|
||||
{
|
||||
"url": "guide/svg-in-templates",
|
||||
"title": "SVG as templates",
|
||||
"tooltip": "Learn how to use SVGs as templates for Angular applications."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -916,6 +921,17 @@
|
||||
"tooltip": "Details of the Angular packages, classes, interfaces, and other types.",
|
||||
"url": "api"
|
||||
},
|
||||
{
|
||||
"title": "Error Reference",
|
||||
"tooltip": "Details of the errors that can be thrown by Angular.",
|
||||
"children": [
|
||||
{
|
||||
"title": "Overview",
|
||||
"url": "errors",
|
||||
"hidden": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Example applications",
|
||||
"tooltip": "List of all of the example applications in the Angular documentation.",
|
||||
|
||||
@@ -13,7 +13,7 @@ To get the most out of this tutorial you should already have a basic understandi
|
||||
|
||||
* [HTML](https://developer.mozilla.org/en-US/docs/Learn/HTML "Learning HTML: Guides and tutorials")
|
||||
* [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript "JavaScript")
|
||||
* [TypeScript](https://www.typescriptlang.org/docs/home.html "TypeScript documentation")
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language")
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
"codelyzer": "^6.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
"dgeni": "^0.4.13",
|
||||
"dgeni-packages": "^0.28.4",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"short_name": "angular.io",
|
||||
"name": "Angular Documentation",
|
||||
"short_name": "angular.io",
|
||||
"background_color": "#1976d2",
|
||||
"theme_color": "#1976d2",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/images/favicons/favicon-194x194.png",
|
||||
@@ -20,7 +23,30 @@
|
||||
}
|
||||
],
|
||||
"start_url": "/?utm_source=homescreen",
|
||||
"background_color": "#1976d2",
|
||||
"theme_color": "#1976d2",
|
||||
"display": "standalone"
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Go to API Reference",
|
||||
"short_name": "API",
|
||||
"description": "Go to the Angular API reference page.",
|
||||
"url": "/api?utm_source=homescreen"
|
||||
},
|
||||
{
|
||||
"name": "Go to Glossary",
|
||||
"short_name": "Glossary",
|
||||
"description": "Go to the glossary page: A list of common Angular terms and their explanation.",
|
||||
"url": "/guide/glossary?utm_source=homescreen"
|
||||
},
|
||||
{
|
||||
"name": "Go to Resources",
|
||||
"short_name": "Resources",
|
||||
"description": "Go to the resources page: A list of Angular resouces, such as development tooling, UI libraries, books, courses, community publications, podcasts, etc.",
|
||||
"url": "/resources?utm_source=homescreen"
|
||||
},
|
||||
{
|
||||
"name": "Go to Tutorial: Tour of Heroes",
|
||||
"short_name": "Tutorial",
|
||||
"description": "Go to the \"Tour of Heroes\" tutorial page: Learn how to create your first Angular application.",
|
||||
"url": "/tutorial?utm_source=homescreen"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-cli mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-guide mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-errors mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-start mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
@media (min-width: $showTopMenuWidth) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
.error-list {
|
||||
display: grid;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
margin: 0 0 0 -8px;
|
||||
}
|
||||
|
||||
li {
|
||||
@include font-size(14);
|
||||
margin: 8px 0;
|
||||
@include line-height(14);
|
||||
padding: 0;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
min-width: 220px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.symbol {
|
||||
margin-right: 8px;
|
||||
|
||||
&.runtime {
|
||||
background: $green-500;
|
||||
}
|
||||
|
||||
&.compiler {
|
||||
background: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
.symbol.runtime:before {
|
||||
content: "R";
|
||||
}
|
||||
|
||||
.symbol.compiler:before {
|
||||
content: "C";
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue-grey-600;
|
||||
display: inline-block;
|
||||
@include line-height(16);
|
||||
padding: 0 16px 0;
|
||||
text-decoration: none;
|
||||
transition: all .3s;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background: $blue-grey-50;
|
||||
color: $blue-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-video-container {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
@import 'deploy-theme';
|
||||
@import 'details';
|
||||
@import 'edit-page-cta';
|
||||
@import 'errors';
|
||||
@import 'features';
|
||||
@import 'filetree';
|
||||
@import 'heading-anchors';
|
||||
|
||||
@@ -41,7 +41,7 @@ export class SitePage {
|
||||
|
||||
async navigateTo(pageUrl: string) {
|
||||
// Navigate to the page, disable animations, and wait for Angular.
|
||||
await browser.get(`/${pageUrl}`);
|
||||
await browser.get(`/${pageUrl.replace(/^\//, '')}`);
|
||||
await browser.executeScript('document.body.classList.add(\'no-animations\')');
|
||||
await browser.waitForAngular();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { PwaManifestPage, PwaShortcutItem } from './pwa-manifest.po';
|
||||
|
||||
|
||||
describe('PWA manifest', () => {
|
||||
const page = new PwaManifestPage();
|
||||
|
||||
describe('shortcuts', () => {
|
||||
let shortcuts: PwaShortcutItem[];
|
||||
|
||||
// Helpers
|
||||
const pageExists = async (url: string) => {
|
||||
await page.navigateTo(url);
|
||||
const content = await page.getDocViewerText();
|
||||
return !/page not found/i.test(content);
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
shortcuts = await page.getPwaShortcuts();
|
||||
});
|
||||
|
||||
it('should exist', async () => {
|
||||
for (const {short_name, url} of shortcuts) {
|
||||
expect(await pageExists(url)).toBe(
|
||||
true,
|
||||
`Page for shortcut '${short_name}' (from '${page.pwaManifestUrl}') does not exist. (URL: ${url})`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { get as httpGet } from 'http';
|
||||
import { get as httpsGet } from 'https';
|
||||
import { browser } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
|
||||
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
||||
|
||||
/**
|
||||
* The shape of a PWA manifest.
|
||||
* For simplicity, we only define types for the properties we care about in tests.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest
|
||||
*/
|
||||
export type PwaManifest = Json & {
|
||||
shortcuts?: PwaShortcutItem[],
|
||||
};
|
||||
|
||||
/**
|
||||
* The shape of an item in a PWA manifest's `shortcuts` list.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest/shortcuts
|
||||
*/
|
||||
export type PwaShortcutItem = Json & {
|
||||
url: string,
|
||||
name: string,
|
||||
short_name?: string,
|
||||
description?: string,
|
||||
icons?: PwaImageResource[],
|
||||
};
|
||||
|
||||
/**
|
||||
* The shape of an item in a PWA manifest's icons list (such as the value of the top-level `icons` property or that of
|
||||
* the `icons` property of a shortcut item).
|
||||
* @see https://w3c.github.io/manifest/#manifestimageresource-and-its-members
|
||||
*/
|
||||
export type PwaImageResource = Json & {
|
||||
src: string,
|
||||
sizes?: string,
|
||||
type?: string,
|
||||
purpose?: string,
|
||||
};
|
||||
|
||||
|
||||
export class PwaManifestPage extends SitePage {
|
||||
/** The base URL with the trailing `/` stripped off (if any). */
|
||||
baseUrl = browser.baseUrl.replace(/\/$/, '');
|
||||
|
||||
/** The URL to the app's PWA manifest. */
|
||||
pwaManifestUrl = `${this.baseUrl}/pwa-manifest.json`;
|
||||
|
||||
private pwaManifestText: string | null = null;
|
||||
|
||||
/** Get the app's PWA manifest as an object. */
|
||||
async getPwaManifest(): Promise<PwaManifest> {
|
||||
if (this.pwaManifestText === null) {
|
||||
const get = /^https:/.test(this.pwaManifestUrl) ? httpsGet : httpGet;
|
||||
|
||||
this.pwaManifestText = await new Promise<string>((resolve, reject) => {
|
||||
let responseText = '';
|
||||
get(this.pwaManifestUrl, res => res
|
||||
.on('data', chunk => responseText += chunk)
|
||||
.on('end', () => resolve(responseText))
|
||||
.on('error', reject));
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.parse(this.pwaManifestText);
|
||||
}
|
||||
|
||||
/** Get a list of PWA shortcuts as extracted from the app's PWA manifest. */
|
||||
async getPwaShortcuts(): Promise<PwaShortcutItem[]> {
|
||||
const {shortcuts = []} = await this.getPwaManifest();
|
||||
return shortcuts;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ module.exports =
|
||||
// Register the processors
|
||||
.processor(require('./processors/mergeParameterInfo'))
|
||||
.processor(require('./processors/processPseudoClasses'))
|
||||
.processor(require('./processors/splitDescription'))
|
||||
.processor(require('./processors/convertPrivateClassesToInterfaces'))
|
||||
.processor(require('./processors/generateApiListDoc'))
|
||||
.processor(require('./processors/addNotYetDocumentedProperty'))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'default'};
|
||||
};
|
||||
@@ -32,6 +32,7 @@ module.exports = new Package('angular-base', [
|
||||
.processor(require('./processors/copyContentAssets'))
|
||||
.processor(require('./processors/renderLinkInfo'))
|
||||
.processor(require('./processors/checkContentRules'))
|
||||
.processor(require('./processors/splitDescription'))
|
||||
|
||||
// overrides base packageInfo and returns the one for the 'angular/angular' repo.
|
||||
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
||||
@@ -140,6 +141,7 @@ module.exports = new Package('angular-base', [
|
||||
.config(function(postProcessHtml, addImageDimensions, autoLinkCode, filterPipes, filterAmbiguousDirectiveAliases, ignoreHttpInUrls, ignoreGenericWords) {
|
||||
addImageDimensions.basePath = path.resolve(AIO_PATH, 'src');
|
||||
autoLinkCode.customFilters = [ignoreGenericWords, ignoreHttpInUrls, filterPipes, filterAmbiguousDirectiveAliases];
|
||||
autoLinkCode.failOnMissingDocPath = true;
|
||||
postProcessHtml.plugins = [
|
||||
require('./post-processors/autolink-headings'),
|
||||
addImageDimensions,
|
||||
|
||||
+33
-16
@@ -19,12 +19,23 @@ const textContent = require('hast-util-to-string');
|
||||
* @property codeElements an array of strings.
|
||||
* Only text contained in these elements will be linked to.
|
||||
* Usually set to "code" but also "code-example" for angular.io.
|
||||
*
|
||||
* @property ignoredLanguages an array of languages that should not be auto-linked
|
||||
*
|
||||
* @property ignoredLanguages an array of languages that should not be auto-linked
|
||||
*
|
||||
* @property failOnMissingDocPath if set to true then this post-processor will cause the doc-gen
|
||||
* to fail when it attempts to auto-link to a doc that has no `doc.path` property, which implies
|
||||
* that it exists but is not public (nor rendered).
|
||||
*
|
||||
*/
|
||||
module.exports = function autoLinkCode(getDocFromAlias) {
|
||||
autoLinkCodeImpl.docTypes = [];
|
||||
autoLinkCodeImpl.customFilters = [];
|
||||
autoLinkCodeImpl.codeElements = ['code'];
|
||||
autoLinkCodeImpl.ignoredLanguages = ['bash', 'sh', 'shell', 'json', 'markdown'];
|
||||
autoLinkCodeImpl.failOnMissingDocPath = false;
|
||||
|
||||
return autoLinkCodeImpl;
|
||||
|
||||
function autoLinkCodeImpl() {
|
||||
@@ -64,8 +75,10 @@ module.exports = function autoLinkCode(getDocFromAlias) {
|
||||
// * do not have an ignored language
|
||||
// * are not inside links
|
||||
const isCodeElement = autoLinkCodeImpl.codeElements.some(elementType => is(node, elementType));
|
||||
const hasNoAutoLink = node.properties.className && node.properties.className.includes('no-auto-link');
|
||||
const isLanguageSupported = !autoLinkCodeImpl.ignoredLanguages.includes(node.properties.language);
|
||||
const hasNoAutoLink =
|
||||
node.properties.className && node.properties.className.includes('no-auto-link');
|
||||
const isLanguageSupported =
|
||||
!autoLinkCodeImpl.ignoredLanguages.includes(node.properties.language);
|
||||
const isInLink = isInsideLink(ancestors);
|
||||
return isCodeElement && !hasNoAutoLink && isLanguageSupported && !isInLink;
|
||||
}
|
||||
@@ -76,19 +89,19 @@ module.exports = function autoLinkCode(getDocFromAlias) {
|
||||
|
||||
function getNodes(node, file) {
|
||||
return textContent(node)
|
||||
.split(/([A-Za-z0-9_.-]+)/)
|
||||
.filter(word => word.length)
|
||||
.map((word, index, words) => {
|
||||
// remove docs that fail the custom filter tests
|
||||
const filteredDocs = autoLinkCodeImpl.customFilters.reduce(
|
||||
(docs, filter) => filter(docs, words, index), getDocFromAlias(word));
|
||||
.split(/([A-Za-z0-9_.-]+)/)
|
||||
.filter(word => word.length)
|
||||
.map((word, index, words) => {
|
||||
// remove docs that fail the custom filter tests
|
||||
const filteredDocs = autoLinkCodeImpl.customFilters.reduce(
|
||||
(docs, filter) => filter(docs, words, index), getDocFromAlias(word));
|
||||
|
||||
return foundValidDoc(filteredDocs, word, file) ?
|
||||
// Create a link wrapping the text node.
|
||||
createLinkNode(filteredDocs[0], word) :
|
||||
// this is just text so push a new text node
|
||||
{type: 'text', value: word};
|
||||
});
|
||||
return foundValidDoc(filteredDocs, word, file) ?
|
||||
// Create a link wrapping the text node.
|
||||
createLinkNode(filteredDocs[0], word) :
|
||||
// this is just text so push a new text node
|
||||
{type: 'text', value: word};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,12 +125,16 @@ module.exports = function autoLinkCode(getDocFromAlias) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (doc.path === '') {
|
||||
if (!doc.path) {
|
||||
var message = `
|
||||
autoLinkCode: Doc path is empty for "${doc.id}" - link will not be generated for "${keyword}".
|
||||
Please make sure if the doc should be public. If not, it should probably not be referenced in the docs.`;
|
||||
|
||||
file.message(message);
|
||||
if (autoLinkCodeImpl.failOnMissingDocPath) {
|
||||
file.fail(message);
|
||||
} else {
|
||||
file.message(message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ var createTestPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('autoLinkCode post-processor', () => {
|
||||
let processor, autoLinkCode, aliasMap, filterPipes;
|
||||
let processor, autoLinkCode, aliasMap, filterPipes, log;
|
||||
|
||||
beforeEach(() => {
|
||||
const testPackage = createTestPackage('angular-base-package');
|
||||
@@ -15,6 +15,7 @@ describe('autoLinkCode post-processor', () => {
|
||||
processor.docTypes = ['test-doc'];
|
||||
processor.plugins = [autoLinkCode];
|
||||
filterPipes = injector.get('filterPipes');
|
||||
log = injector.get('log');
|
||||
});
|
||||
|
||||
it('should insert an anchor into every code item that matches the id of an API doc', () => {
|
||||
@@ -126,19 +127,9 @@ describe('autoLinkCode post-processor', () => {
|
||||
expect(doc.renderedContent).toEqual('<code>MyClass</code>');
|
||||
});
|
||||
|
||||
it('should ignore code items that match an API doc but have no path set',
|
||||
() => {
|
||||
aliasMap.addDoc(
|
||||
{docType: 'class', id: 'MyClass', aliases: ['MyClass'], path: ''});
|
||||
const doc = {docType: 'test-doc', renderedContent: '<code>MyClass</code>'};
|
||||
processor.$process([doc]);
|
||||
expect(doc.renderedContent).toEqual('<code>MyClass</code>');
|
||||
});
|
||||
|
||||
it('should ignore documents when the `docType` is set to `member` and the keyword doesn\'t include `.`',
|
||||
() => {
|
||||
aliasMap.addDoc(
|
||||
{docType: 'member', id: 'MyEnum', aliases: ['MyEnum'], path: 'a/b/c'});
|
||||
aliasMap.addDoc({docType: 'member', id: 'MyEnum', aliases: ['MyEnum'], path: 'a/b/c'});
|
||||
const doc = {docType: 'test-doc', renderedContent: '<code>MyEnum</code>'};
|
||||
processor.$process([doc]);
|
||||
expect(doc.renderedContent).toEqual('<code>MyEnum</code>');
|
||||
@@ -193,4 +184,25 @@ describe('autoLinkCode post-processor', () => {
|
||||
processor.$process([doc]);
|
||||
expect(doc.renderedContent).toEqual('<code language="bash">MyClass</code>');
|
||||
});
|
||||
|
||||
it('should record a warning if the autolinked doc has no `path` and `failOnMissingDocPath` is false',
|
||||
() => {
|
||||
aliasMap.addDoc({docType: 'class', id: 'MyClass', aliases: ['MyClass']});
|
||||
const doc = {docType: 'test-doc', renderedContent: '<code>MyClass</code>'};
|
||||
autoLinkCode.failOnMissingDocPath = false;
|
||||
processor.$process([doc]);
|
||||
|
||||
expect(log.warn).toHaveBeenCalledWith(`
|
||||
autoLinkCode: Doc path is empty for "MyClass" - link will not be generated for "MyClass".
|
||||
Please make sure if the doc should be public. If not, it should probably not be referenced in the docs. - doc (test-doc) `);
|
||||
});
|
||||
|
||||
it('should fail if the autolinked doc has no `path` and `failOnMissingDocPath` is true', () => {
|
||||
aliasMap.addDoc({docType: 'class', id: 'MyClass', aliases: ['MyClass']});
|
||||
const doc = {docType: 'test-doc', renderedContent: '<code>MyClass</code>'};
|
||||
autoLinkCode.failOnMissingDocPath = true;
|
||||
expect(() => processor.$process([doc])).toThrowError(`
|
||||
autoLinkCode: Doc path is empty for "MyClass" - link will not be generated for "MyClass".
|
||||
Please make sure if the doc should be public. If not, it should probably not be referenced in the docs. - doc (test-doc) `);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
const path = require('canonical-path');
|
||||
const Package = require('dgeni').Package;
|
||||
const basePackage = require('../angular-base-package');
|
||||
const contentPackage = require('../content-package');
|
||||
const {CONTENTS_PATH, TEMPLATES_PATH, requireFolder} = require('../config');
|
||||
|
||||
const errorPackage = new Package('angular-errors', [basePackage, contentPackage]);
|
||||
errorPackage.factory(require('./readers/error'))
|
||||
.processor(require('./processors/processErrorDocs'))
|
||||
.processor(require('./processors/processErrorsContainerDoc'))
|
||||
|
||||
// Where do we find the error documentation files?
|
||||
.config(function(readFilesProcessor, errorFileReader) {
|
||||
readFilesProcessor.fileReaders.push(errorFileReader);
|
||||
readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([
|
||||
{
|
||||
basePath: CONTENTS_PATH,
|
||||
include: CONTENTS_PATH + '/errors/**/*.md',
|
||||
exclude: CONTENTS_PATH + '/errors/index.md',
|
||||
fileReader: 'errorFileReader'
|
||||
},
|
||||
{
|
||||
basePath: CONTENTS_PATH,
|
||||
include: CONTENTS_PATH + '/errors/index.md',
|
||||
fileReader: 'contentFileReader'
|
||||
},
|
||||
]);
|
||||
})
|
||||
|
||||
// Here we compute the `id`, `code`, `aliases`, `path` and `outputPath` for the `error` docs.
|
||||
// * The `id` is the same as the `path` (the source path with the `.md` stripped off).
|
||||
// * The `code` is the id without any containing paths (currently all errors must be on the top
|
||||
// level).
|
||||
// * The `aliases` are used for automatic code linking and search terms.
|
||||
.config(function(computeIdsProcessor, computePathsProcessor) {
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['error'],
|
||||
getId: function(doc) {
|
||||
return doc.fileInfo
|
||||
.relativePath
|
||||
// strip off the extension
|
||||
.replace(/\.\w*$/, '');
|
||||
},
|
||||
getAliases: function(doc) {
|
||||
doc.code = path.basename(doc.id);
|
||||
return [doc.id, doc.code];
|
||||
}
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates = computePathsProcessor.pathTemplates.concat([
|
||||
{
|
||||
docTypes: ['error'],
|
||||
getPath: (doc) => doc.id,
|
||||
outputPathTemplate: '${path}.json',
|
||||
},
|
||||
]);
|
||||
})
|
||||
|
||||
// Configure jsdoc-style tag parsing
|
||||
.config(function(parseTagsProcessor, getInjectables) {
|
||||
// Load up all the tag definitions in the tag-defs folder
|
||||
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat(
|
||||
getInjectables(requireFolder(__dirname, './tag-defs')));
|
||||
})
|
||||
|
||||
// The templates that define how the `error` and `error-container` doc-types are rendered are
|
||||
// found in the `TEMPLATES_PATH/error` directory.
|
||||
.config(function(templateFinder) {
|
||||
templateFinder.templateFolders.unshift(path.resolve(TEMPLATES_PATH, 'error'));
|
||||
})
|
||||
|
||||
// The AIO application expects content files to be provided as JSON files that it requests via
|
||||
// HTTP. So here we tell the `convertToJsonProcessor` to include docs of type `error` in those
|
||||
// that it converts.
|
||||
.config(function(convertToJsonProcessor, postProcessHtml) {
|
||||
convertToJsonProcessor.docTypes.push('error');
|
||||
postProcessHtml.docTypes.push('error');
|
||||
});
|
||||
|
||||
module.exports = errorPackage;
|
||||
@@ -0,0 +1,55 @@
|
||||
module.exports = function processErrorDocs(createDocMessage) {
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process(docs) {
|
||||
const navigationDoc = docs.find(doc => doc.docType === 'navigation-json');
|
||||
const errorsNode = navigationDoc && findErrorsNode(navigationDoc.data['SideNav']);
|
||||
|
||||
if (!errorsNode) {
|
||||
throw new Error(createDocMessage(
|
||||
'Missing `errors` url - This node is needed as a place to insert the generated errors docs.',
|
||||
navigationDoc));
|
||||
}
|
||||
|
||||
docs.forEach(doc => {
|
||||
if (doc.docType === 'error') {
|
||||
// Add to navigation doc
|
||||
const title = `${doc.code}: ${doc.name}`;
|
||||
errorsNode.children.push({url: doc.path, title: title, tooltip: doc.name});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Look for the `errors` navigation node. It is the node whose first child has `url: 'errors'`.
|
||||
* (NOTE: Using the URL instead of the title, because it is more robust.)
|
||||
*
|
||||
* We will "recursively" check all navigation nodes and their children (in breadth-first order),
|
||||
* until we find the `errors` node. Keep a list of nodes lists to check.
|
||||
* (NOTE: Each item in the list is a LIST of nodes.)
|
||||
*/
|
||||
function findErrorsNode(nodes) {
|
||||
const nodesList = [nodes];
|
||||
|
||||
while (nodesList.length > 0) {
|
||||
// Get the first item from the list of nodes lists.
|
||||
const currentNodes = nodesList.shift();
|
||||
const errorsNode = currentNodes.find(isErrorsNode);
|
||||
|
||||
// One of the nodes in `currentNodes` was the `errors` node. Return it.
|
||||
if (errorsNode) return errorsNode;
|
||||
|
||||
// The `errors` node is not in `currentNodes`. Check each node's children (if any).
|
||||
currentNodes.forEach(node => node.children && nodesList.push(node.children));
|
||||
}
|
||||
|
||||
// We checked all navigation nodes and their children and did not find the `errors` node.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isErrorsNode(node) {
|
||||
return node.children && node.children.length && node.children[0].url === 'errors';
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('processErrorDocs processor', () => {
|
||||
let dgeni, injector, processor, createDocMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
dgeni = new Dgeni([testPackage('angular-errors-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('processErrorDocs');
|
||||
createDocMessage = injector.get('createDocMessage');
|
||||
});
|
||||
|
||||
it('should be available on the injector', () => {
|
||||
expect(processor.$process).toBeDefined();
|
||||
});
|
||||
|
||||
it('should run after the correct processor', () => {
|
||||
expect(processor.$runAfter).toEqual(['extra-docs-added']);
|
||||
});
|
||||
|
||||
it('should run before the correct processor', () => {
|
||||
expect(processor.$runBefore).toEqual(['rendering-docs']);
|
||||
});
|
||||
|
||||
it('should add the error to the `errors` node in the navigation doc if there is a top level node with a `errors` url',
|
||||
() => {
|
||||
const errorDoc = {
|
||||
docType: 'error',
|
||||
name: 'error1',
|
||||
code: '888',
|
||||
path: 'errors/error1',
|
||||
};
|
||||
const navigation = {
|
||||
docType: 'navigation-json',
|
||||
data: {
|
||||
SideNav: [
|
||||
{url: 'some/page', title: 'Some Page'},
|
||||
{
|
||||
title: 'Errors',
|
||||
children: [{'title': 'Overview', 'url': 'errors'}],
|
||||
},
|
||||
{url: 'other/page', title: 'Other Page'},
|
||||
],
|
||||
},
|
||||
};
|
||||
processor.$process([errorDoc, navigation]);
|
||||
expect(navigation.data.SideNav[1].title).toEqual('Errors');
|
||||
expect(navigation.data.SideNav[1].children).toEqual([
|
||||
{url: 'errors', title: 'Overview'},
|
||||
{url: 'errors/error1', title: '888: error1', tooltip: 'error1'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should detect the `errors` node if it is nested in another node', () => {
|
||||
const errorDoc = {
|
||||
docType: 'error',
|
||||
name: 'error1',
|
||||
code: '888',
|
||||
path: 'errors/error1',
|
||||
};
|
||||
const navigation = {
|
||||
docType: 'navigation-json',
|
||||
data: {
|
||||
SideNav: [
|
||||
{url: 'some/page', title: 'Some Page'},
|
||||
{
|
||||
title: 'Errors Grandparent',
|
||||
children: [
|
||||
{url: 'some/nested/page', title: 'Some Nested Page'},
|
||||
{
|
||||
title: 'Errors Parent',
|
||||
children: [
|
||||
{url: 'some/more/nested/page', title: 'Some More Nested Page'},
|
||||
{
|
||||
title: 'Errors',
|
||||
children: [{'title': 'Overview', 'url': 'errors'}],
|
||||
},
|
||||
{url: 'other/more/nested/page', title: 'Other More Nested Page'},
|
||||
],
|
||||
},
|
||||
{url: 'other/nested/page', title: 'Other Nested Page'},
|
||||
],
|
||||
},
|
||||
{url: 'other/page', title: 'Other Page'},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
processor.$process([errorDoc, navigation]);
|
||||
|
||||
const errorsContainerNode = navigation.data.SideNav[1].children[1].children[1];
|
||||
expect(errorsContainerNode.title).toEqual('Errors');
|
||||
expect(errorsContainerNode.children).toEqual([
|
||||
{url: 'errors', title: 'Overview'},
|
||||
{url: 'errors/error1', title: '888: error1', tooltip: 'error1'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should complain if there is no child with `errors` url', () => {
|
||||
const errorDoc = {
|
||||
docType: 'error',
|
||||
name: 'error1',
|
||||
code: '888',
|
||||
path: 'errors/error1',
|
||||
};
|
||||
const navigation = {
|
||||
docType: 'navigation-json',
|
||||
data: {
|
||||
SideNav: [
|
||||
{url: 'some/page', title: 'Some Page'}, {
|
||||
title: 'Errors',
|
||||
tooltip: 'Angular Error reference',
|
||||
children: [{'title': 'Overview', 'url': 'not-errors'}]
|
||||
},
|
||||
{url: 'other/page', title: 'Other Page'}
|
||||
]
|
||||
}
|
||||
};
|
||||
expect(() => processor.$process([errorDoc, navigation]))
|
||||
.toThrowError(createDocMessage(
|
||||
'Missing `errors` url - This node is needed as a place to insert the generated errors docs.',
|
||||
navigation));
|
||||
});
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports = function processErrorsContainerDoc() {
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process(docs) {
|
||||
const errorsDoc = docs.find(doc => doc.id === 'errors/index');
|
||||
errorsDoc.id = 'errors-container';
|
||||
errorsDoc.errors = docs.filter(doc => doc.docType === 'error');
|
||||
}
|
||||
};
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
const processorFactory = require('./processErrorsContainerDoc');
|
||||
const Dgeni = require('dgeni');
|
||||
|
||||
describe('processErrorsContainerDoc processor', () => {
|
||||
it('should be available on the injector', () => {
|
||||
const dgeni = new Dgeni([testPackage('angular-errors-package')]);
|
||||
const injector = dgeni.configureInjector();
|
||||
const processor = injector.get('processErrorsContainerDoc');
|
||||
expect(processor.$process).toBeDefined();
|
||||
});
|
||||
|
||||
it('should run after the correct processor', () => {
|
||||
const processor = processorFactory();
|
||||
expect(processor.$runAfter).toEqual(['extra-docs-added']);
|
||||
});
|
||||
|
||||
it('should run before the correct processor', () => {
|
||||
const processor = processorFactory();
|
||||
expect(processor.$runBefore).toEqual(['rendering-docs']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* This file reader will pull the contents from a text file (by default .md)
|
||||
*
|
||||
* The doc will initially have the form:
|
||||
* ```
|
||||
* {
|
||||
* docType: 'error',
|
||||
* content: 'the content of the file',
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
module.exports = function errorFileReader() {
|
||||
return {
|
||||
name: 'errorFileReader',
|
||||
defaultPattern: /\.md$/,
|
||||
getDocs: function(fileInfo) {
|
||||
// We return a single element array because content files only contain one document
|
||||
return [{docType: 'error', content: fileInfo.content}];
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
const Dgeni = require('dgeni');
|
||||
const path = require('canonical-path');
|
||||
const testPackage = require('../../helpers/test-package');
|
||||
|
||||
describe('errorFileReader', () => {
|
||||
let dgeni, injector, fileReader;
|
||||
|
||||
beforeEach(() => {
|
||||
dgeni = new Dgeni([testPackage('angular-errors-package', false)]);
|
||||
injector = dgeni.configureInjector();
|
||||
fileReader = injector.get('errorFileReader');
|
||||
});
|
||||
|
||||
function createFileInfo(file, content, basePath) {
|
||||
return {
|
||||
fileReader: fileReader.name,
|
||||
filePath: file,
|
||||
baseName: path.basename(file, path.extname(file)),
|
||||
extension: path.extname(file).replace(/^\./, ''),
|
||||
basePath: basePath,
|
||||
relativePath: path.relative(basePath, file),
|
||||
content: content
|
||||
};
|
||||
}
|
||||
|
||||
describe('defaultPattern', () => {
|
||||
it('should match .md files', () => {
|
||||
expect(fileReader.defaultPattern.test('abc.md')).toBeTruthy();
|
||||
expect(fileReader.defaultPattern.test('abc.js')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getDocs', () => {
|
||||
it('should return an object containing info about the file and its contents', () => {
|
||||
const fileInfo = createFileInfo(
|
||||
'project/path/modules/someModule/foo/docs/subfolder/bar.md', 'A load of content',
|
||||
'project/path');
|
||||
expect(fileReader.getDocs(fileInfo)).toEqual([
|
||||
{docType: 'error', content: 'A load of content'}
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'category'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'debugging'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'shortDescription'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'videoUrl'};
|
||||
};
|
||||
@@ -9,12 +9,13 @@ const Package = require('dgeni').Package;
|
||||
const gitPackage = require('dgeni-packages/git');
|
||||
const apiPackage = require('../angular-api-package');
|
||||
const contentPackage = require('../angular-content-package');
|
||||
const errorsPackage = require('../angular-errors-package');
|
||||
const cliDocsPackage = require('../cli-docs-package');
|
||||
const { extname, resolve } = require('canonical-path');
|
||||
const { existsSync } = require('fs');
|
||||
const { SRC_PATH } = require('../config');
|
||||
|
||||
module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPackage, cliDocsPackage])
|
||||
module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPackage, cliDocsPackage, errorsPackage])
|
||||
|
||||
// This processor relies upon the versionInfo. See below...
|
||||
.processor(require('./processors/processNavigationMap'))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% for descendant in descendants %}
|
||||
<li>
|
||||
<code>{$ descendant.name $}</code>
|
||||
{$ renderDescendantList(descendant.descendants | filterByPropertyValue('docType', docTypeMatcher), docType, recursed, docTypeMatcher) $}
|
||||
{$ renderDescendantList(descendant.descendants | filterByPropertyValue('docType', docTypeMatcher) | filterByPropertyValue('privateExport', undefined), docType, recursed, docTypeMatcher) $}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{% import "lib/githubLinks.html" as github -%}
|
||||
|
||||
<h1>{$ doc.code $}: {$ doc.shortDescription $}</h1>
|
||||
<div class="github-links">
|
||||
{$ github.githubEditLink(doc, versionInfo) $}
|
||||
</div>
|
||||
|
||||
{% if doc.videoUrl.length %}
|
||||
<div class="error-video-container">
|
||||
<iframe
|
||||
src="{$ doc.videoUrl $}"
|
||||
frameborder="0"
|
||||
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
{% endif%}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<h2>Description</h2>
|
||||
{$ doc.description | marked $}
|
||||
</div>
|
||||
<br>
|
||||
<div class="debugging">
|
||||
<h2>Debugging the error</h2>
|
||||
{$ doc.debugging | marked $}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends 'content.template.html' -%}
|
||||
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
{$ doc.description | marked $}
|
||||
</div>
|
||||
|
||||
<ul class="error-list">
|
||||
{% for error in doc.errors %}
|
||||
<li>
|
||||
<a class="code-anchor" href="{$ error.path $}">
|
||||
<span class="symbol {$ error.category $}"></span>
|
||||
<code class="no-auto-link">{$ error.code $}: {$ error.name $}</code>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
+26
-1
@@ -3616,6 +3616,11 @@ clone@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
|
||||
|
||||
clonedeep@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clonedeep/-/clonedeep-2.0.0.tgz#8ceca0777f477bbf31fe8c871aaf63a390bbc272"
|
||||
integrity sha1-jOygd39He78x/oyHGq9jo5C7wnI=
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
@@ -4677,7 +4682,22 @@ dgeni-packages@^0.28.4:
|
||||
typescript "^3.2.2"
|
||||
urlencode "^1.1.0"
|
||||
|
||||
dgeni@^0.4.11, dgeni@^0.4.9:
|
||||
dgeni@^0.4.13:
|
||||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/dgeni/-/dgeni-0.4.13.tgz#274ad9edfe17bd6c2c1c51e9f30cb20faccdabc0"
|
||||
integrity sha512-gryF1gNdzbQdgAeQxjaFbbKgR0qzyjq93Rc6/3CsBIN88NJ5yvsT4x26BvCKVI6jh2WatgvuGj+akmWvGndTyQ==
|
||||
dependencies:
|
||||
canonical-path "~0.0.2"
|
||||
clonedeep "^2.0.0"
|
||||
dependency-graph "^0.7.0"
|
||||
di "0.0.1"
|
||||
fast-deep-equal "^3.1.3"
|
||||
objectdiff "^1.1.0"
|
||||
optimist "~0.6.1"
|
||||
validate.js "^0.12.0"
|
||||
winston "^2.1.1"
|
||||
|
||||
dgeni@^0.4.9:
|
||||
version "0.4.12"
|
||||
resolved "https://registry.yarnpkg.com/dgeni/-/dgeni-0.4.12.tgz#574a91de25dab0018a9832da83fdb7c2b8c7507b"
|
||||
integrity sha512-AhlRuwduzZhHIpf8DYZ0wWHLvV+9IQ8dQRqzx3ryYEg9trU/gWc2xz3wnJrRpDnS6Hu6F73saj6m1xbckW3k9Q==
|
||||
@@ -5560,6 +5580,11 @@ fast-deep-equal@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
|
||||
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
|
||||
|
||||
fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-glob@^2.2.6:
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
|
||||
|
||||
@@ -205,7 +205,7 @@ function validateCommonConfig(config) {
|
||||
*/
|
||||
function readConfigFile(configPath, returnEmptyObjectOnError) {
|
||||
if (returnEmptyObjectOnError === void 0) { returnEmptyObjectOnError = false; }
|
||||
// If the the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// If the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// version of the given configuration seems to exist, set up `ts-node` if available.
|
||||
if (require.extensions['.ts'] === undefined && fs.existsSync(configPath + ".ts") &&
|
||||
isTsNodeAvailable()) {
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ function validateCommonConfig(config) {
|
||||
*/
|
||||
function readConfigFile(configPath, returnEmptyObjectOnError) {
|
||||
if (returnEmptyObjectOnError === void 0) { returnEmptyObjectOnError = false; }
|
||||
// If the the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// If the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// version of the given configuration seems to exist, set up `ts-node` if available.
|
||||
if (require.extensions['.ts'] === undefined && fs.existsSync(configPath + ".ts") &&
|
||||
isTsNodeAvailable()) {
|
||||
|
||||
@@ -100,7 +100,7 @@ function validateCommonConfig(config: Partial<NgDevConfig>) {
|
||||
* configuration file cannot be read.
|
||||
*/
|
||||
function readConfigFile(configPath: string, returnEmptyObjectOnError = false): object {
|
||||
// If the the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// If the `.ts` extension has not been set up already, and a TypeScript based
|
||||
// version of the given configuration seems to exist, set up `ts-node` if available.
|
||||
if (require.extensions['.ts'] === undefined && existsSync(`${configPath}.ts`) &&
|
||||
isTsNodeAvailable()) {
|
||||
|
||||
Vendored
+10
-5
@@ -174,10 +174,12 @@ export declare type ContentChildren = Query;
|
||||
export declare interface ContentChildrenDecorator {
|
||||
(selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
|
||||
descendants?: boolean;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
read?: any;
|
||||
}): any;
|
||||
new (selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
|
||||
descendants?: boolean;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
read?: any;
|
||||
}): Query;
|
||||
}
|
||||
@@ -734,6 +736,7 @@ export declare type Provider = TypeProvider | ValueProvider | ClassProvider | Co
|
||||
|
||||
export declare interface Query {
|
||||
descendants: boolean;
|
||||
emitDistinctChangesOnly: boolean;
|
||||
first: boolean;
|
||||
isViewQuery: boolean;
|
||||
read: any;
|
||||
@@ -746,12 +749,12 @@ export declare abstract class Query {
|
||||
|
||||
export declare class QueryList<T> implements Iterable<T> {
|
||||
[Symbol.iterator]: () => Iterator<T>;
|
||||
readonly changes: Observable<any>;
|
||||
get changes(): Observable<any>;
|
||||
readonly dirty = true;
|
||||
readonly first: T;
|
||||
readonly last: T;
|
||||
readonly length: number;
|
||||
constructor();
|
||||
constructor(_emitDistinctChangesOnly?: boolean);
|
||||
destroy(): void;
|
||||
filter(fn: (item: T, index: number, array: T[]) => boolean): T[];
|
||||
find(fn: (item: T, index: number, array: T[]) => boolean): T | undefined;
|
||||
@@ -760,7 +763,7 @@ export declare class QueryList<T> implements Iterable<T> {
|
||||
map<U>(fn: (item: T, index: number, array: T[]) => U): U[];
|
||||
notifyOnChanges(): void;
|
||||
reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U;
|
||||
reset(resultsTree: Array<T | any[]>): void;
|
||||
reset(resultsTree: Array<T | any[]>, identityAccessor?: (value: T) => unknown): void;
|
||||
setDirty(): void;
|
||||
some(fn: (value: T, index: number, array: T[]) => boolean): boolean;
|
||||
toArray(): T[];
|
||||
@@ -838,11 +841,11 @@ export declare interface RendererType2 {
|
||||
}
|
||||
|
||||
export declare class ResolvedReflectiveFactory {
|
||||
dependencies: ɵangular_packages_core_core_d[];
|
||||
dependencies: ɵangular_packages_core_core_e[];
|
||||
factory: Function;
|
||||
constructor(
|
||||
factory: Function,
|
||||
dependencies: ɵangular_packages_core_core_d[]);
|
||||
dependencies: ɵangular_packages_core_core_e[]);
|
||||
}
|
||||
|
||||
export declare interface ResolvedReflectiveProvider {
|
||||
@@ -1010,9 +1013,11 @@ export declare type ViewChildren = Query;
|
||||
export declare interface ViewChildrenDecorator {
|
||||
(selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
|
||||
read?: any;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
}): any;
|
||||
new (selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
|
||||
read?: any;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
}): ViewChildren;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 3033,
|
||||
"main-es2015": 447349,
|
||||
"main-es2015": 448090,
|
||||
"polyfills-es2015": 52493
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 3153,
|
||||
"main-es2015": 431137,
|
||||
"main-es2015": 432078,
|
||||
"polyfills-es2015": 52493
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2285,
|
||||
"main-es2015": 240909,
|
||||
"main-es2015": 241738,
|
||||
"polyfills-es2015": 36709,
|
||||
"5-es2015": 745
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ load(":angular_integration_test.bzl", "angular_integration_test")
|
||||
# cases the tests are able to select a random free port.
|
||||
#
|
||||
# Where `ng e2e` is used we pass `ng e2e --port 0` which prompts the cli
|
||||
# to select a random free port for the the e2e test. The protractor.conf is
|
||||
# to select a random free port for the e2e test. The protractor.conf is
|
||||
# automatically updated to use this port.
|
||||
#
|
||||
# Karma automatically finds a free port so no effort is needed there.
|
||||
@@ -61,7 +61,7 @@ INTEGRATION_TESTS = {
|
||||
},
|
||||
"hello_world__systemjs_umd": {
|
||||
# Special case for `hello_world__systemjs_umd` test as we want to pin
|
||||
# `systems` at version 0.20.2 and not link to the the root @npm//systemjs
|
||||
# `systems` at version 0.20.2 and not link to the root @npm//systemjs
|
||||
# which is stuck at 0.18.10 and can't be updated to 0.20.2 without
|
||||
# breaking the legacy saucelabs job.
|
||||
"pinned_npm_packages": ["systemjs"],
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "11.1.0-next.4",
|
||||
"version": "11.1.0",
|
||||
"private": true,
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
"homepage": "https://github.com/angular/angular",
|
||||
|
||||
@@ -331,7 +331,7 @@ def _filter_out_generated_files(files, extension, package_path = None):
|
||||
files_list = files.to_list() if type(files) == _DEPSET_TYPE else files
|
||||
for file in files_list:
|
||||
# If the "package_path" parameter has been specified, filter out files
|
||||
# that do not start with the the specified package path.
|
||||
# that do not start with the specified package path.
|
||||
if package_path and not file.short_path.startswith(package_path):
|
||||
continue
|
||||
|
||||
|
||||
@@ -1773,7 +1773,7 @@ export class HttpClient {
|
||||
|
||||
/**
|
||||
* Constructs a `PATCH` request that interprets the body as an `ArrayBuffer` and
|
||||
* returns the the full event stream.
|
||||
* returns the full event stream.
|
||||
*
|
||||
* @param url The endpoint URL.
|
||||
* @param body The resources to edit.
|
||||
|
||||
@@ -90,7 +90,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstri
|
||||
* You might want to show a set of properties from the same object. If you are waiting
|
||||
* for asynchronous data, the object can be undefined.
|
||||
* In this case, you can use `ngIf` and store the result of the condition in a local
|
||||
* variable as shown in the the following example.
|
||||
* variable as shown in the following example.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfAs'}
|
||||
*
|
||||
|
||||
+3
-1
@@ -63,9 +63,11 @@ describe('Ivy NgModule', () => {
|
||||
}
|
||||
|
||||
const errorCode = ivyEnabled ? 'NG0200: ' : '';
|
||||
const errorLink = ivyEnabled ? '. Find more at https://angular.io/errors/NG0200' : '';
|
||||
expect(() => createInjector(AModule))
|
||||
.toThrowError(`${
|
||||
errorCode}Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule`);
|
||||
errorCode}Circular dependency in DI detected for AModule. Dependency path: AModule > BModule > AModule${
|
||||
errorLink}`);
|
||||
});
|
||||
|
||||
it('merges imports and exports', () => {
|
||||
|
||||
+2
@@ -147,6 +147,8 @@ function toQueryMetadata<TExpression>(obj: AstObject<R3DeclareQueryMetadata, TEx
|
||||
first: obj.has('first') ? obj.getBoolean('first') : false,
|
||||
predicate,
|
||||
descendants: obj.has('descendants') ? obj.getBoolean('descendants') : false,
|
||||
emitDistinctChangesOnly:
|
||||
obj.has('emitDistinctChangesOnly') ? obj.getBoolean('emitDistinctChangesOnly') : true,
|
||||
read: obj.has('read') ? obj.getOpaque('read') : null,
|
||||
static: obj.has('static') ? obj.getBoolean('static') : false,
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
}
|
||||
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
// First we try one of the the following:
|
||||
// First we try one of the following:
|
||||
// 1. The `exports` identifier - referring to the current file/module.
|
||||
// 2. An identifier (e.g. `foo`) that refers to an imported UMD module.
|
||||
// 3. A UMD style export identifier (e.g. the `foo` of `exports.foo`).
|
||||
|
||||
@@ -173,7 +173,7 @@ export class EsmRenderingFormatter implements RenderingFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite the the IVY switch markers to indicate we are in IVY mode.
|
||||
* Rewrite the IVY switch markers to indicate we are in IVY mode.
|
||||
*/
|
||||
rewriteSwitchableDeclarations(
|
||||
outputText: MagicString, sourceFile: ts.SourceFile,
|
||||
|
||||
@@ -40,7 +40,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
// The new folder is at the root of the overall package
|
||||
const entryPoint = bundle.entryPoint;
|
||||
const ngccFolder = this.fs.join(entryPoint.packagePath, NGCC_DIRECTORY);
|
||||
this.copyBundle(bundle, entryPoint.packagePath, ngccFolder);
|
||||
this.copyBundle(bundle, entryPoint.packagePath, ngccFolder, transformedFiles);
|
||||
transformedFiles.forEach(file => this.writeFile(file, entryPoint.packagePath, ngccFolder));
|
||||
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
|
||||
}
|
||||
@@ -67,18 +67,56 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
}
|
||||
|
||||
protected copyBundle(
|
||||
bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) {
|
||||
bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath,
|
||||
transformedFiles: FileToWrite[]) {
|
||||
const doNotCopy = new Set(transformedFiles.map(f => f.path));
|
||||
bundle.src.program.getSourceFiles().forEach(sourceFile => {
|
||||
const relativePath = this.fs.relative(packagePath, absoluteFromSourceFile(sourceFile));
|
||||
const originalPath = absoluteFromSourceFile(sourceFile);
|
||||
if (doNotCopy.has(originalPath)) {
|
||||
return;
|
||||
}
|
||||
const relativePath = this.fs.relative(packagePath, originalPath);
|
||||
const isInsidePackage = isLocalRelativePath(relativePath);
|
||||
if (!sourceFile.isDeclarationFile && isInsidePackage) {
|
||||
const newFilePath = this.fs.resolve(ngccFolder, relativePath);
|
||||
this.fs.ensureDir(this.fs.dirname(newFilePath));
|
||||
this.fs.copyFile(absoluteFromSourceFile(sourceFile), newFilePath);
|
||||
const newPath = this.fs.resolve(ngccFolder, relativePath);
|
||||
this.fs.ensureDir(this.fs.dirname(newPath));
|
||||
this.fs.copyFile(originalPath, newPath);
|
||||
this.copyAndUpdateSourceMap(originalPath, newPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If a source file has an associated source-map, then copy this, while updating its sourceRoot
|
||||
* accordingly.
|
||||
*
|
||||
* For now don't try to parse the source for inline source-maps or external source-map links,
|
||||
* since that is more complex and will slow ngcc down.
|
||||
* Instead just check for a source-map file residing next to the source file, which is by far
|
||||
* the most common case.
|
||||
*
|
||||
* @param originalSrcPath absolute path to the original source file being copied.
|
||||
* @param newSrcPath absolute path to where the source will be written.
|
||||
*/
|
||||
protected copyAndUpdateSourceMap(originalSrcPath: AbsoluteFsPath, newSrcPath: AbsoluteFsPath):
|
||||
void {
|
||||
const sourceMapPath = (originalSrcPath + '.map') as AbsoluteFsPath;
|
||||
if (this.fs.exists(sourceMapPath)) {
|
||||
try {
|
||||
const sourceMap = JSON.parse(this.fs.readFile(sourceMapPath));
|
||||
const newSourceMapPath = (newSrcPath + '.map') as AbsoluteFsPath;
|
||||
const relativePath =
|
||||
this.fs.relative(this.fs.dirname(newSourceMapPath), this.fs.dirname(sourceMapPath));
|
||||
sourceMap['sourceRoot'] = this.fs.join(relativePath, sourceMap['sourceRoot'] || '.');
|
||||
this.fs.ensureDir(this.fs.dirname(newSourceMapPath));
|
||||
this.fs.writeFile(newSourceMapPath, JSON.stringify(sourceMap));
|
||||
} catch (e) {
|
||||
this.logger.warn(`Failed to process source-map at ${sourceMapPath}`);
|
||||
this.logger.warn(e.message ?? e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected writeFile(file: FileToWrite, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath):
|
||||
void {
|
||||
if (isDtsPath(file.path.replace(/\.map$/, ''))) {
|
||||
|
||||
@@ -570,7 +570,7 @@ A.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["", "a", ""]] });`
|
||||
UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); };
|
||||
// TRANSPILED
|
||||
UndecoratedBase.ɵdir = ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) {
|
||||
ɵngcc0.ɵɵstaticViewQuery(_c0, true);
|
||||
ɵngcc0.ɵɵviewQuery(_c0, 3);
|
||||
} if (rf & 2) {
|
||||
let _t;
|
||||
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first);
|
||||
|
||||
@@ -51,6 +51,12 @@ runInEachFileSystem(() => {
|
||||
{name: _('/node_modules/test/esm5.js'), contents: 'export function FooTop() {}'},
|
||||
{name: _('/node_modules/test/esm5.js.map'), contents: 'ORIGINAL MAPPING DATA'},
|
||||
{name: _('/node_modules/test/es2015/index.js'), contents: 'export {FooTop} from "./foo";'},
|
||||
{
|
||||
name: _('/node_modules/test/es2015/index.js.map'),
|
||||
contents:
|
||||
'{"version":3,"file":"index.js","sources":["../src/index.ts"],"mappings":"AAAA"}'
|
||||
},
|
||||
{name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'},
|
||||
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
|
||||
{
|
||||
name: _('/node_modules/test/a/package.json'),
|
||||
@@ -154,6 +160,85 @@ runInEachFileSystem(() => {
|
||||
.toEqual('export {FooTop} from "./foo";');
|
||||
});
|
||||
|
||||
it('should copy any source-map for unmodified files in the program (adding missing sourceRoot)',
|
||||
() => {
|
||||
// Ensure source-mapping for a non-processed source file `index.js`.
|
||||
const sourceMap = {
|
||||
version: 3,
|
||||
file: 'index.js',
|
||||
sources: ['../src/index.ts'],
|
||||
mappings: 'AAAA',
|
||||
};
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/node_modules/test/es2015/index.js.map'),
|
||||
contents: JSON.stringify(sourceMap)
|
||||
},
|
||||
{name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'}
|
||||
]);
|
||||
|
||||
// Simulate that only the `foo.js` file was modified
|
||||
const modifiedFiles = [{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
}];
|
||||
fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']);
|
||||
|
||||
expect(JSON.parse(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))))
|
||||
.toEqual({...sourceMap, sourceRoot: '../../es2015'});
|
||||
});
|
||||
|
||||
it('should copy any source-map for unmodified files in the program (updating sourceRoot)',
|
||||
() => {
|
||||
// Ensure source-mapping for a non-processed source file `index.js`.
|
||||
const sourceMap = {
|
||||
version: 3,
|
||||
file: 'index.js',
|
||||
sourceRoot: '../src',
|
||||
sources: ['index.ts'],
|
||||
mappings: 'AAAA',
|
||||
};
|
||||
loadTestFiles([
|
||||
{
|
||||
name: _('/node_modules/test/es2015/index.js.map'),
|
||||
contents: JSON.stringify(sourceMap)
|
||||
},
|
||||
{name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'}
|
||||
]);
|
||||
|
||||
// Simulate that only the `foo.js` file was modified
|
||||
const modifiedFiles = [{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
}];
|
||||
fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']);
|
||||
|
||||
expect(JSON.parse(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))))
|
||||
.toEqual({...sourceMap, sourceRoot: '../../src'});
|
||||
});
|
||||
|
||||
it('should ignore (with a warning) any invalid source-map for unmodified files in the program',
|
||||
() => {
|
||||
// Ensure source-mapping for a non-processed source file `index.js`.
|
||||
loadTestFiles([
|
||||
{name: _('/node_modules/test/es2015/index.js.map'), contents: 'INVALID JSON STRING'},
|
||||
{name: _('/node_modules/test/src/index.ts'), contents: 'export {FooTop} from "./foo";'}
|
||||
]);
|
||||
|
||||
// Simulate that only the `foo.js` file was modified
|
||||
const modifiedFiles = [{
|
||||
path: _('/node_modules/test/es2015/foo.js'),
|
||||
contents: 'export class FooTop {} // MODIFIED'
|
||||
}];
|
||||
fileWriter.writeBundle(esm2015bundle, modifiedFiles, ['es2015']);
|
||||
|
||||
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/es2015/index.js.map'))).toBe(false);
|
||||
expect(logger.logs.warn).toEqual([
|
||||
[`Failed to process source-map at ${_('/node_modules/test/es2015/index.js.map')}`],
|
||||
['Unexpected token I in JSON at position 0'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update the package.json properties', () => {
|
||||
fileWriter.writeBundle(
|
||||
esm5bundle,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, getSafePropertyAccessString, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, R3ResolvedDependencyType, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
@@ -436,6 +437,7 @@ export function extractQueryMetadata(
|
||||
let read: Expression|null = null;
|
||||
// The default value for descendants is true for every decorator except @ContentChildren.
|
||||
let descendants: boolean = name !== 'ContentChildren';
|
||||
let emitDistinctChangesOnly: boolean = emitDistinctChangesOnlyDefaultValue;
|
||||
if (args.length === 2) {
|
||||
const optionsExpr = unwrapExpression(args[1]);
|
||||
if (!ts.isObjectLiteralExpression(optionsExpr)) {
|
||||
@@ -458,6 +460,17 @@ export function extractQueryMetadata(
|
||||
descendants = descendantsValue;
|
||||
}
|
||||
|
||||
if (options.has('emitDistinctChangesOnly')) {
|
||||
const emitDistinctChangesOnlyExpr = options.get('emitDistinctChangesOnly')!;
|
||||
const emitDistinctChangesOnlyValue = evaluator.evaluate(emitDistinctChangesOnlyExpr);
|
||||
if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
|
||||
throw createValueHasWrongTypeError(
|
||||
emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue,
|
||||
`@${name} options.emitDistinctChangesOnlys must be a boolean`);
|
||||
}
|
||||
emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
|
||||
}
|
||||
|
||||
if (options.has('static')) {
|
||||
const staticValue = evaluator.evaluate(options.get('static')!);
|
||||
if (typeof staticValue !== 'boolean') {
|
||||
@@ -480,6 +493,7 @@ export function extractQueryMetadata(
|
||||
descendants,
|
||||
read,
|
||||
static: isStatic,
|
||||
emitDistinctChangesOnly,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from '../../annotations';
|
||||
import {CycleAnalyzer, ImportGraph} from '../../cycles';
|
||||
import {ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||
import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
|
||||
import {LogicalFileSystem, resolve} from '../../file_system';
|
||||
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
|
||||
@@ -179,7 +179,8 @@ export class NgCompiler {
|
||||
* Get all Angular-related diagnostics for this compilation.
|
||||
*/
|
||||
getDiagnostics(): ts.Diagnostic[] {
|
||||
return [...this.getNonTemplateDiagnostics(), ...this.getTemplateDiagnostics()];
|
||||
return this.addMessageTextDetails(
|
||||
[...this.getNonTemplateDiagnostics(), ...this.getTemplateDiagnostics()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,10 +189,26 @@ export class NgCompiler {
|
||||
* If a `ts.SourceFile` is passed, only diagnostics related to that file are returned.
|
||||
*/
|
||||
getDiagnosticsForFile(file: ts.SourceFile, optimizeFor: OptimizeFor): ts.Diagnostic[] {
|
||||
return [
|
||||
return this.addMessageTextDetails([
|
||||
...this.getNonTemplateDiagnostics().filter(diag => diag.file === file),
|
||||
...this.getTemplateDiagnosticsForFile(file, optimizeFor)
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Angular.io error guide links to diagnostics for this compilation.
|
||||
*/
|
||||
private addMessageTextDetails(diagnostics: ts.Diagnostic[]): ts.Diagnostic[] {
|
||||
return diagnostics.map(diag => {
|
||||
if (diag.code && COMPILER_ERRORS_WITH_GUIDES.has(ngErrorCode(diag.code))) {
|
||||
return {
|
||||
...diag,
|
||||
messageText: diag.messageText +
|
||||
`. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/NG${ngErrorCode(diag.code)}`
|
||||
};
|
||||
}
|
||||
return diag;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
*/
|
||||
|
||||
export {FatalDiagnosticError, isFatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from './src/error';
|
||||
export {ErrorCode, ngErrorCode} from './src/error_code';
|
||||
export {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from './src/error_code';
|
||||
export {replaceTsWithNgInErrors} from './src/util';
|
||||
|
||||
@@ -161,6 +161,27 @@ export enum ErrorCode {
|
||||
INJECTABLE_DUPLICATE_PROV = 9001,
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Base URL for the error details page.
|
||||
* Keep this value in sync with a similar const in
|
||||
* `packages/core/src/render3/error_code.ts`.
|
||||
*/
|
||||
export const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Contains a set of error messages that have detailed guides at angular.io.
|
||||
* Full list of available error guides can be found at https://angular.io/errors
|
||||
*/
|
||||
export const COMPILER_ERRORS_WITH_GUIDES = new Set([
|
||||
ErrorCode.DECORATOR_ARG_NOT_LITERAL,
|
||||
ErrorCode.PARAM_MISSING_TOKEN,
|
||||
ErrorCode.SCHEMA_INVALID_ELEMENT,
|
||||
ErrorCode.SCHEMA_INVALID_ATTRIBUTE,
|
||||
ErrorCode.MISSING_REFERENCE_TARGET,
|
||||
]);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/**
|
||||
* A marker that indicates the start of a segment in a mapping.
|
||||
*
|
||||
* The end of a segment is indicated by the the first segment-marker of another mapping whose start
|
||||
* The end of a segment is indicated by the first segment-marker of another mapping whose start
|
||||
* is greater or equal to this one.
|
||||
*/
|
||||
export interface SegmentMarker {
|
||||
|
||||
@@ -64,7 +64,7 @@ interface EmitResolver {
|
||||
* solves it conceptually the same way, but obviously doesn't need to access an `@internal` API.
|
||||
*
|
||||
* The set that is returned by this function is meant to be filled with import declaration nodes
|
||||
* that have been referenced in a value-position by the transform, such the the installed patch can
|
||||
* that have been referenced in a value-position by the transform, such the installed patch can
|
||||
* ensure that those import declarations are not elided.
|
||||
*
|
||||
* See below. Note that this uses sourcegraph as the TypeScript checker file doesn't display on
|
||||
|
||||
+96
-8
@@ -32,7 +32,7 @@ import * as i0 from "@angular/core";
|
||||
export class ViewQueryComponent {
|
||||
}
|
||||
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, descendants: true }], ngImport: i0, template: `
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
|
||||
@@ -86,7 +86,7 @@ import * as i0 from "@angular/core";
|
||||
export class ViewQueryComponent {
|
||||
}
|
||||
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], descendants: true }], ngImport: i0, template: `
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
|
||||
<div #myRef></div>
|
||||
<div #myRef1></div>
|
||||
`, isInline: true });
|
||||
@@ -166,7 +166,7 @@ import * as i0 from "@angular/core";
|
||||
export class ViewQueryComponent {
|
||||
}
|
||||
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
|
||||
@@ -246,7 +246,7 @@ import * as i0 from "@angular/core";
|
||||
export class ViewQueryComponent {
|
||||
}
|
||||
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], descendants: true, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, descendants: true, read: TemplateRef }], ngImport: i0, template: `
|
||||
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
<div #myRef></div>
|
||||
<div #myRef1></div>
|
||||
@@ -336,7 +336,7 @@ import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective }], ngImport: i0, template: `
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective, emitDistinctChangesOnly: false }], ngImport: i0, template: `
|
||||
<div><ng-content></ng-content></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
|
||||
@@ -413,7 +413,7 @@ import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"] }], ngImport: i0, template: `
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false }], ngImport: i0, template: `
|
||||
<div #myRef></div>
|
||||
<div #myRef1></div>
|
||||
`, isInline: true });
|
||||
@@ -493,7 +493,7 @@ import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
|
||||
<div><ng-content></ng-content></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
|
||||
@@ -596,7 +596,7 @@ import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, read: TemplateRef }], ngImport: i0, template: `
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, read: TemplateRef }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
<div #myRef></div>
|
||||
<div #myRef1></div>
|
||||
@@ -652,3 +652,91 @@ export declare class MyModule {
|
||||
static ɵinj: i0.ɵɵInjectorDef<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: some.directive.js
|
||||
****************************************************************************************************/
|
||||
import { Directive } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SomeDirective {
|
||||
}
|
||||
SomeDirective.ɵfac = function SomeDirective_Factory(t) { return new (t || SomeDirective)(); };
|
||||
SomeDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: SomeDirective, selector: "[someDir]", ngImport: i0 });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeDirective, [{
|
||||
type: Directive,
|
||||
args: [{
|
||||
selector: '[someDir]',
|
||||
}]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: some.directive.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SomeDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDef<SomeDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDefWithMeta<SomeDirective, "[someDir]", never, {}, {}, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: query_with_emit_distinct_changes_only.js
|
||||
****************************************************************************************************/
|
||||
import { Component, ContentChildren, NgModule, ViewChildren } from '@angular/core';
|
||||
import { SomeDirective } from './some.directive';
|
||||
import * as i0 from "@angular/core";
|
||||
export class ContentQueryComponent {
|
||||
}
|
||||
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
|
||||
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRefs", predicate: ["myRef"] }, { propertyName: "oldMyRefs", predicate: ["myRef"], emitDistinctChangesOnly: false }], viewQueries: [{ propertyName: "someDirs", predicate: SomeDirective, descendants: true }, { propertyName: "oldSomeDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
|
||||
<div someDir></div>
|
||||
<div #myRef></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'content-query-component',
|
||||
template: `
|
||||
<div someDir></div>
|
||||
<div #myRef></div>
|
||||
`
|
||||
}]
|
||||
}], null, { myRefs: [{
|
||||
type: ContentChildren,
|
||||
args: ['myRef', { emitDistinctChangesOnly: true }]
|
||||
}], oldMyRefs: [{
|
||||
type: ContentChildren,
|
||||
args: ['myRef', { emitDistinctChangesOnly: false }]
|
||||
}], someDirs: [{
|
||||
type: ViewChildren,
|
||||
args: [SomeDirective, { emitDistinctChangesOnly: true }]
|
||||
}], oldSomeDirs: [{
|
||||
type: ViewChildren,
|
||||
args: [SomeDirective, { emitDistinctChangesOnly: false }]
|
||||
}] }); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule });
|
||||
MyModule.ɵinj = i0.ɵɵdefineInjector({ factory: function MyModule_Factory(t) { return new (t || MyModule)(); } });
|
||||
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(MyModule, { declarations: [ContentQueryComponent] }); })();
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [ContentQueryComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: query_with_emit_distinct_changes_only.d.ts
|
||||
****************************************************************************************************/
|
||||
import { ElementRef, QueryList } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class ContentQueryComponent {
|
||||
myRefs: QueryList<ElementRef>;
|
||||
oldMyRefs: QueryList<ElementRef>;
|
||||
someDirs: QueryList<any>;
|
||||
oldSomeDirs: QueryList<any>;
|
||||
static ɵfac: i0.ɵɵFactoryDef<ContentQueryComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDefWithMeta<ContentQueryComponent, "content-query-component", never, {}, {}, ["myRefs", "oldMyRefs"], never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MyModule, [typeof ContentQueryComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDef<MyModule>;
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -118,6 +118,21 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support query emitDistinctChangesOnly flag",
|
||||
"inputFiles": [
|
||||
"some.directive.ts",
|
||||
"query_with_emit_distinct_changes_only.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid ContentQuery declaration",
|
||||
"files": [
|
||||
"query_with_emit_distinct_changes_only.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
selectors: [["content-query-component"]],
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
// ...
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+4
-4
@@ -5,10 +5,10 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
// ...
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0, TemplateRef);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
const $e0_attrs$ = ["myRef"];
|
||||
// ...
|
||||
ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
// ...
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, __QueryFlags.emitDistinctChangesOnly__);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, __QueryFlags.none__);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.oldMyRefs = $tmp$);
|
||||
}
|
||||
},
|
||||
// ...
|
||||
viewQuery: function ContentQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.emitDistinctChangesOnly__|__QueryFlags.descendants__);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.descendants__);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$);
|
||||
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.oldSomeDirs = $tmp$);
|
||||
}
|
||||
},
|
||||
//...
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import {Component, ContentChildren, ElementRef, NgModule, QueryList, TemplateRef, ViewChildren} from '@angular/core';
|
||||
|
||||
import {SomeDirective} from './some.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'content-query-component',
|
||||
template: `
|
||||
<div someDir></div>
|
||||
<div #myRef></div>
|
||||
`
|
||||
})
|
||||
export class ContentQueryComponent {
|
||||
@ContentChildren('myRef', {emitDistinctChangesOnly: true}) myRefs!: QueryList<ElementRef>;
|
||||
@ContentChildren('myRef', {emitDistinctChangesOnly: false}) oldMyRefs!: QueryList<ElementRef>;
|
||||
|
||||
@ViewChildren(SomeDirective, {emitDistinctChangesOnly: true}) someDirs!: QueryList<any>;
|
||||
@ViewChildren(SomeDirective, {emitDistinctChangesOnly: false}) oldSomeDirs!: QueryList<any>;
|
||||
}
|
||||
@NgModule({declarations: [ContentQueryComponent]})
|
||||
export class MyModule {
|
||||
}
|
||||
+3
-2
@@ -3,8 +3,9 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
selectors: [["content-query-component"]],
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, true);
|
||||
$r3$.ɵɵcontentQuery(
|
||||
dirIndex, SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
selectors: [["view-query-component"]],
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵstaticViewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery($refs$, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__);
|
||||
$r3$.ɵɵviewQuery($refs$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
selectors: [["view-query-component"]],
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
// ...
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, true);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, true);
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, 1);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
+4
-4
@@ -5,10 +5,10 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
// ...
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, true, TemplateRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true, ElementRef);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, true, ElementRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true, TemplateRef);
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, 1, TemplateRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1, ElementRef);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, 1, ElementRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1, TemplateRef);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AttributeMarker, SelectorFlags} from '@angular/compiler/src/core';
|
||||
import {QueryFlags} from '@angular/compiler/src/render3/view/compiler';
|
||||
import {i18nIcuMsg, i18nMsg, i18nMsgWithPostprocess, Placeholder} from './i18n_helpers';
|
||||
|
||||
const EXPECTED_FILE_MACROS: [RegExp, (...args: string[]) => string][] = [
|
||||
@@ -35,6 +36,9 @@ const EXPECTED_FILE_MACROS: [RegExp, (...args: string[]) => string][] = [
|
||||
|
||||
// E.g. `__SelectorFlags.ELEMENT__`
|
||||
flagUnion(/__SelectorFlags\.([^_]+)__/, (_match, member) => getSelectorFlag(member)),
|
||||
|
||||
// E.g. `__QueryFlags.ELEMENT__`
|
||||
flagUnion(/__QueryFlags\.([^_]+)__/, (_match, member) => getQueryFlag(member)),
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -107,6 +111,21 @@ function getSelectorFlag(member: string): number {
|
||||
return marker;
|
||||
}
|
||||
|
||||
const QueryFlagsMap: Record<string, QueryFlags> = {
|
||||
none: QueryFlags.none,
|
||||
descendants: QueryFlags.descendants,
|
||||
isStatic: QueryFlags.isStatic,
|
||||
emitDistinctChangesOnly: QueryFlags.emitDistinctChangesOnly,
|
||||
};
|
||||
|
||||
function getQueryFlag(member: string): number {
|
||||
const marker = QueryFlagsMap[member];
|
||||
if (typeof marker !== 'number') {
|
||||
throw new Error('Unknown SelectorFlag: ' + member);
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
function stringParam() {
|
||||
return /'([^']*?[^\\])'/;
|
||||
}
|
||||
|
||||
@@ -1636,8 +1636,8 @@ describe('compiler compliance', () => {
|
||||
selectors: [["view-query-component"]],
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1695,8 +1695,8 @@ describe('compiler compliance', () => {
|
||||
…
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, true);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, true);
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, 1);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1746,8 +1746,8 @@ describe('compiler compliance', () => {
|
||||
selectors: [["view-query-component"]],
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵstaticViewQuery(SomeDirective, true);
|
||||
$r3$.ɵɵviewQuery($refs$, true);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 3);
|
||||
$r3$.ɵɵviewQuery($refs$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1810,10 +1810,10 @@ describe('compiler compliance', () => {
|
||||
…
|
||||
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, true, TemplateRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true, ElementRef);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, true, ElementRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, true, TemplateRef);
|
||||
$r3$.ɵɵviewQuery($e0_attrs$, 1, TemplateRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1, ElementRef);
|
||||
$r3$.ɵɵviewQuery($e1_attrs$, 1, ElementRef);
|
||||
$r3$.ɵɵviewQuery(SomeDirective, 1, TemplateRef);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1873,8 +1873,8 @@ describe('compiler compliance', () => {
|
||||
selectors: [["content-query-component"]],
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1933,8 +1933,8 @@ describe('compiler compliance', () => {
|
||||
…
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -1992,8 +1992,8 @@ describe('compiler compliance', () => {
|
||||
selectors: [["content-query-component"]],
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, true);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 3);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
@@ -2057,10 +2057,10 @@ describe('compiler compliance', () => {
|
||||
…
|
||||
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1, TemplateRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0, ElementRef);
|
||||
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0, TemplateRef);
|
||||
}
|
||||
if (rf & 2) {
|
||||
let $tmp$;
|
||||
|
||||
@@ -22,14 +22,14 @@ const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
|
||||
|
||||
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);
|
||||
|
||||
const viewQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
|
||||
const viewQueryRegExp = (predicate: string, flags: number, ref?: string): RegExp => {
|
||||
const maybeRef = ref ? `, ${ref}` : ``;
|
||||
return new RegExp(`i0\\.ɵɵviewQuery\\(${predicate}, ${descend}${maybeRef}\\)`);
|
||||
return new RegExp(`i0\\.ɵɵviewQuery\\(${predicate}, ${flags}${maybeRef}\\)`);
|
||||
};
|
||||
|
||||
const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
|
||||
const contentQueryRegExp = (predicate: string, flags: number, ref?: string): RegExp => {
|
||||
const maybeRef = ref ? `, ${ref}` : ``;
|
||||
return new RegExp(`i0\\.ɵɵcontentQuery\\(dirIndex, ${predicate}, ${descend}${maybeRef}\\)`);
|
||||
return new RegExp(`i0\\.ɵɵcontentQuery\\(dirIndex, ${predicate}, ${flags}${maybeRef}\\)`);
|
||||
};
|
||||
|
||||
const setClassMetadataRegExp = (expectedType: string): RegExp =>
|
||||
@@ -3093,10 +3093,10 @@ runInEachFileSystem(os => {
|
||||
expect(jsContents).toMatch(varRegExp('test1'));
|
||||
expect(jsContents).toMatch(varRegExp('test2'));
|
||||
expect(jsContents).toMatch(varRegExp('accessor'));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
|
||||
// match `i0.ɵɵviewQuery(_c2, true, null)`
|
||||
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 1, TemplateRef)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 1, 'TemplateRef'));
|
||||
// match `i0.ɵɵviewQuery(_c2, 1, null)`
|
||||
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 1));
|
||||
});
|
||||
|
||||
it('should generate queries for directives', () => {
|
||||
@@ -3125,14 +3125,14 @@ runInEachFileSystem(os => {
|
||||
expect(jsContents).toMatch(varRegExp('test1'));
|
||||
expect(jsContents).toMatch(varRegExp('test2'));
|
||||
expect(jsContents).toMatch(varRegExp('accessor'));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 1, TemplateRef)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 1, 'TemplateRef'));
|
||||
|
||||
// match `i0.ɵɵviewQuery(_c2, true)`
|
||||
// match `i0.ɵɵviewQuery(_c2, 1)`
|
||||
// Note that while ViewQuery doesn't necessarily make sense on a directive,
|
||||
// because it doesn't have a view, we still need to handle it because a component
|
||||
// could extend the directive.
|
||||
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
|
||||
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 1));
|
||||
});
|
||||
|
||||
it('should handle queries that use forwardRef', () => {
|
||||
@@ -3154,13 +3154,13 @@ runInEachFileSystem(os => {
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, TemplateRef, true, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', true));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, ViewContainerRef, true, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', true));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c0, true, null)`
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, TemplateRef, 1, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', 1));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, ViewContainerRef, 1, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', 1));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, _c0, 1, null)`
|
||||
expect(jsContents).toContain('_c0 = ["parens"];');
|
||||
expect(jsContents).toMatch(contentQueryRegExp('_c0', true));
|
||||
expect(jsContents).toMatch(contentQueryRegExp('_c0', 1));
|
||||
});
|
||||
|
||||
it('should handle queries that use an InjectionToken', () => {
|
||||
@@ -3181,10 +3181,10 @@ runInEachFileSystem(os => {
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
// match `i0.ɵɵviewQuery(TOKEN, true, null)`
|
||||
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', true));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, true, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', true));
|
||||
// match `i0.ɵɵviewQuery(TOKEN, 1, null)`
|
||||
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', 1));
|
||||
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, 1, null)`
|
||||
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', 1));
|
||||
});
|
||||
|
||||
it('should compile expressions that write keys', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user