diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 6e37f2cc52b5..4a7f801b2bea 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -47,6 +47,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
+ uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1
with:
sarif_file: results.sarif
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f3c68183b31..ad466a0985ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+
+# 20.0.5 "agardite-ant" (2025-07-02)
+### cdk
+| Commit | Type | Description |
+| -- | -- | -- |
+| [45cc05641](https://github.com/angular/components/commit/45cc05641f89afb760077cb806af9814a01909e8) | fix | **tree:** remove leaking subscription ([#31457](https://github.com/angular/components/pull/31457)) |
+### material
+| Commit | Type | Description |
+| -- | -- | -- |
+| [7556beaa7](https://github.com/angular/components/commit/7556beaa7b20247d7c01f9e76d7097a0758f5ce7) | fix | **schematics:** typo in prompt question ([#31449](https://github.com/angular/components/pull/31449)) |
+### google-maps
+| Commit | Type | Description |
+| -- | -- | -- |
+| [bdfeb04c3](https://github.com/angular/components/commit/bdfeb04c3a220121d7b98b9ac8147e0cfdcdfeb6) | fix | fix update schematic ([#31448](https://github.com/angular/components/pull/31448)) |
+
+
+
# 20.0.4 "strontium-shack" (2025-06-25)
### cdk
diff --git a/docs/src/app/shared/doc-viewer/doc-viewer.ts b/docs/src/app/shared/doc-viewer/doc-viewer.ts
index 89b3b73cc81b..ed1b7b05f682 100644
--- a/docs/src/app/shared/doc-viewer/doc-viewer.ts
+++ b/docs/src/app/shared/doc-viewer/doc-viewer.ts
@@ -187,13 +187,17 @@ export class DocViewer implements OnDestroy {
const examplePortal = new ComponentPortal(componentClass, this._viewContainerRef);
const exampleViewer = portalHost.attach(examplePortal);
const exampleViewerComponent = exampleViewer.instance;
- if (example !== null && componentClass === ExampleViewer) {
- DocViewer._initExampleViewer(
- exampleViewerComponent as ExampleViewer,
- example,
- file,
- region,
- );
+ if (example !== null) {
+ if (componentClass === ExampleViewer) {
+ DocViewer._initExampleViewer(
+ exampleViewerComponent as ExampleViewer,
+ example,
+ file,
+ region,
+ );
+ } else {
+ (exampleViewerComponent as HeaderLink).example.set(example);
+ }
}
this._portalHosts.push(portalHost);
});
diff --git a/docs/src/app/shared/doc-viewer/header-link.ts b/docs/src/app/shared/doc-viewer/header-link.ts
index a518be234af6..889e078095a8 100644
--- a/docs/src/app/shared/doc-viewer/header-link.ts
+++ b/docs/src/app/shared/doc-viewer/header-link.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
-import {Component, inject} from '@angular/core';
+import {Component, computed, inject, signal} from '@angular/core';
import {Router} from '@angular/router';
import {MatIcon} from '@angular/material/icon';
@@ -18,7 +18,7 @@ import {MatIcon} from '@angular/material/icon';
selector: 'header-link',
template: `
+ [attr.aria-describedby]="example()" [href]="_fragmentUrl()">
link
`,
@@ -29,18 +29,12 @@ export class HeaderLink {
* Id of the anchor element. Note that is uses "example" because we instantiate the
* header link components through the ComponentPortal.
*/
- example: string = '';
+ readonly example = signal('');
/** Base URL that is used to build an absolute fragment URL. */
- private _baseUrl: string;
+ private _baseUrl = inject(Router).url.split('#')[0];
- constructor() {
- const router = inject(Router);
-
- this._baseUrl = router.url.split('#')[0];
- }
-
- _getFragmentUrl(): string {
- return `${this._baseUrl}#${this.example}`;
- }
+ protected readonly _fragmentUrl = computed(() => {
+ return `${this._baseUrl}#${this.example()}`;
+ });
}
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
index 0e7c0a779167..f999763b7602 100644
--- a/docs/tsconfig.json
+++ b/docs/tsconfig.json
@@ -31,6 +31,7 @@
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
- "strictTemplates": true
+ "strictTemplates": true,
+ "typeCheckHostBindings": true
}
}
diff --git a/goldens/cdk/a11y/index.api.md b/goldens/cdk/a11y/index.api.md
index ecd9b7654403..916d9c30d76c 100644
--- a/goldens/cdk/a11y/index.api.md
+++ b/goldens/cdk/a11y/index.api.md
@@ -175,7 +175,7 @@ export class FocusKeyManager extends ListKeyManager {
// @public
export class FocusMonitor implements OnDestroy {
constructor(...args: unknown[]);
- protected _document?: Document | null | undefined;
+ protected _document: Document;
focusVia(element: HTMLElement, origin: FocusOrigin, options?: FocusOptions_2): void;
focusVia(element: ElementRef, origin: FocusOrigin, options?: FocusOptions_2): void;
monitor(element: HTMLElement, checkChildren?: boolean): Observable;
diff --git a/goldens/cdk/text-field/index.api.md b/goldens/cdk/text-field/index.api.md
index cb1f19df72e8..b9ab33d29cf3 100644
--- a/goldens/cdk/text-field/index.api.md
+++ b/goldens/cdk/text-field/index.api.md
@@ -51,7 +51,7 @@ export class CdkAutofill implements OnDestroy, OnInit {
// @public
export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
constructor(...args: unknown[]);
- protected _document?: Document | null | undefined;
+ protected _document: Document;
get enabled(): boolean;
set enabled(value: boolean);
get maxRows(): number;
diff --git a/package.json b/package.json
index 2d5d8f55987a..2b6b137bce71 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"ci-notify-slack-failure": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/circleci/notify-slack-job-failure.mts",
"prepare": "husky"
},
- "version": "20.0.4",
+ "version": "20.0.5",
"dependencies": {
"@angular-devkit/core": "catalog:",
"@angular-devkit/schematics": "catalog:",
diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.ts b/src/cdk/a11y/focus-monitor/focus-monitor.ts
index dc1770436476..948d0b78cf87 100644
--- a/src/cdk/a11y/focus-monitor/focus-monitor.ts
+++ b/src/cdk/a11y/focus-monitor/focus-monitor.ts
@@ -142,7 +142,7 @@ export class FocusMonitor implements OnDestroy {
};
/** Used to reference correct document/window */
- protected _document? = inject(DOCUMENT, {optional: true});
+ protected _document = inject(DOCUMENT);
/** Subject for stopping our InputModalityDetector subscription. */
private readonly _stopInputModalityDetector = new Subject();
@@ -206,7 +206,7 @@ export class FocusMonitor implements OnDestroy {
// If the element is inside the shadow DOM, we need to bind our focus/blur listeners to
// the shadow root, rather than the `document`, because the browser won't emit focus events
// to the `document`, if focus is moving within the same shadow root.
- const rootNode = _getShadowRoot(nativeElement) || this._getDocument();
+ const rootNode = _getShadowRoot(nativeElement) || this._document;
const cachedInfo = this._elementInfo.get(nativeElement);
// Check if we're already monitoring this element.
@@ -280,7 +280,7 @@ export class FocusMonitor implements OnDestroy {
options?: FocusOptions,
): void {
const nativeElement = coerceElement(element);
- const focusedElement = this._getDocument().activeElement;
+ const focusedElement = this._document.activeElement;
// If the element is focused already, calling `focus` again won't trigger the event listener
// which means that the focus classes won't be updated. If that's the case, update the classes
@@ -303,15 +303,9 @@ export class FocusMonitor implements OnDestroy {
this._elementInfo.forEach((_info, element) => this.stopMonitoring(element));
}
- /** Access injected document if available or fallback to global document reference */
- private _getDocument(): Document {
- return this._document || document;
- }
-
/** Use defaultView of injected document if available or fallback to global window reference */
private _getWindow(): Window {
- const doc = this._getDocument();
- return doc.defaultView || window;
+ return this._document.defaultView || window;
}
private _getFocusOrigin(focusEventTarget: HTMLElement | null): FocusOrigin {
diff --git a/src/cdk/dialog/dialog-container.ts b/src/cdk/dialog/dialog-container.ts
index 038a113a028c..67a21c365cd6 100644
--- a/src/cdk/dialog/dialog-container.ts
+++ b/src/cdk/dialog/dialog-container.ts
@@ -82,7 +82,7 @@ export class CdkDialogContainer
private _renderer = inject(Renderer2);
private _platform = inject(Platform);
- protected _document = inject(DOCUMENT, {optional: true})!;
+ protected _document = inject(DOCUMENT);
/** The portal outlet inside of this container into which the dialog content will be loaded. */
@ViewChild(CdkPortalOutlet, {static: true}) _portalOutlet: CdkPortalOutlet;
diff --git a/src/cdk/scrolling/viewport-ruler.ts b/src/cdk/scrolling/viewport-ruler.ts
index bd477c3ede4b..66ea77dd9f79 100644
--- a/src/cdk/scrolling/viewport-ruler.ts
+++ b/src/cdk/scrolling/viewport-ruler.ts
@@ -36,7 +36,7 @@ export class ViewportRuler implements OnDestroy {
private readonly _change = new Subject();
/** Used to reference correct document/window */
- protected _document = inject(DOCUMENT, {optional: true})!;
+ protected _document = inject(DOCUMENT);
constructor(...args: unknown[]);
diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts
index 5abb0be0f97a..f7dd3cc9f267 100644
--- a/src/cdk/text-field/autosize.ts
+++ b/src/cdk/text-field/autosize.ts
@@ -122,7 +122,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
private _cachedScrollTop: number;
/** Used to reference correct document/window */
- protected _document? = inject(DOCUMENT, {optional: true});
+ protected _document = inject(DOCUMENT);
private _hasFocus: boolean;
diff --git a/src/cdk/tree/tree.md b/src/cdk/tree/tree.md
index 30de3315ce20..9f8e0c7163f8 100644
--- a/src/cdk/tree/tree.md
+++ b/src/cdk/tree/tree.md
@@ -55,11 +55,11 @@ In order to use the tree, you must define a tree node template. There are two ty
template defines the look of the tree node, expansion/collapsing control and the structure for
nested children nodes.
-A node definition is specified via any element with `cdkNodeDef`. This directive exports the node
+A node definition is specified via any element with `cdkTreeNodeDef`. This directive exports the node
data to be used in any bindings in the node template.
```html
-
+
{{node.key}}: {{node.value}}
```
@@ -80,7 +80,7 @@ When using nested tree nodes, the node template must contain a `cdkTreeNodeOutle
where the children of the node will be rendered.
```html
-
+
{{node.value}}
@@ -96,7 +96,7 @@ a tree node recursively by setting `[cdkTreeNodeToggleRecursive]` to true.
activation. For icon buttons, ensure that `aria-label` is provided.
```html
-
+
@@ -110,7 +110,7 @@ The `cdkTreeNodePadding` directive can be placed in a flat tree's node template
information of a flat tree node.
```html
-
+
{{node.value}}
```
@@ -125,10 +125,10 @@ The tree may include multiple node templates, where a template is chosen
for a particular data node via the `when` predicate of the template.
```html
-
+
{{node.value}}
-
+
[ A special node {{node.value}} ]
```
@@ -200,11 +200,11 @@ interaction.
```html
```
In this example, `$event` contains the node's data and is equivalent to the implicit data passed in
-the `cdkNodeDef` context.
+the `cdkTreeNodeDef` context.
diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts
index 497ba28d4b86..e6e485e733f5 100644
--- a/src/cdk/tree/tree.ts
+++ b/src/cdk/tree/tree.ts
@@ -1400,6 +1400,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI
.changed.pipe(
map(() => this.isExpanded),
distinctUntilChanged(),
+ takeUntil(this._destroyed),
)
.subscribe(() => this._changeDetectorRef.markForCheck());
this._tree._setNodeTypeIfUnset(this._type);
diff --git a/src/google-maps/schematics/ng-update/index.ts b/src/google-maps/schematics/ng-update/index.ts
index e195902f23b5..ca337a1bd02b 100644
--- a/src/google-maps/schematics/ng-update/index.ts
+++ b/src/google-maps/schematics/ng-update/index.ts
@@ -6,5 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import {Rule} from '@angular-devkit/schematics';
+
/** Entry point for the migration schematics with target of Angular Material v20 */
-export function updateToV20(): void {}
+export function updateToV20(): Rule {
+ return () => {};
+}
diff --git a/src/material/schematics/ng-generate/theme-color/schema.json b/src/material/schematics/ng-generate/theme-color/schema.json
index 6adc7d434fd6..b8e36dc8ba4e 100644
--- a/src/material/schematics/ng-generate/theme-color/schema.json
+++ b/src/material/schematics/ng-generate/theme-color/schema.json
@@ -49,7 +49,7 @@
"type": "boolean",
"default": true,
"description": "Whether to generate output file in scss or CSS",
- "x-prompt": "Do you want to generated file to be a scss file? This is the recommended way of setting up theming in your application. If not, a CSS file will be generated with all the system variables defined. (Leave blank to generate a scss file)"
+ "x-prompt": "Do you want the generated file to be a scss file? This is the recommended way of setting up theming in your application. If not, a CSS file will be generated with all the system variables defined. (Leave blank to generate a scss file)"
}
}
}
diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts
index 7b0bfe914e34..f3b288ef7f9c 100644
--- a/src/material/sidenav/drawer.ts
+++ b/src/material/sidenav/drawer.ts
@@ -182,7 +182,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
private _ngZone = inject(NgZone);
private _renderer = inject(Renderer2);
private readonly _interactivityChecker = inject(InteractivityChecker);
- private _doc = inject(DOCUMENT, {optional: true})!;
+ private _doc = inject(DOCUMENT);
_container? = inject(MAT_DRAWER_CONTAINER, {optional: true});
private _focusTrap: FocusTrap | null = null;
@@ -347,9 +347,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
constructor() {
this.openedChange.pipe(takeUntil(this._destroyed)).subscribe((opened: boolean) => {
if (opened) {
- if (this._doc) {
- this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement;
- }
+ this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement;
this._takeFocus();
} else if (this._isFocusWithinDrawer()) {
this._restoreFocus(this._openedVia || 'program');
diff --git a/src/material/tree/tree.md b/src/material/tree/tree.md
index a9eba1238d0c..e2411fa4cda0 100644
--- a/src/material/tree/tree.md
+++ b/src/material/tree/tree.md
@@ -55,11 +55,11 @@ In order to use the tree, you must define a tree node template. There are two ty
template defines the look of the tree node, expansion/collapsing control and the structure for
nested children nodes.
-A node definition is specified via any element with `matNodeDef`. This directive exports the node
+A node definition is specified via any element with `matTreeNodeDef`. This directive exports the node
data to be used in any bindings in the node template.
```html
-
+
{{node.key}}: {{node.value}}
```
@@ -80,7 +80,7 @@ When using nested tree nodes, the node template must contain a `matTreeNodeOutle
where the children of the node will be rendered.
```html
-
+
{{node.value}}
@@ -96,7 +96,7 @@ a tree node recursively by setting `[matTreeNodeToggleRecursive]` to true.
activation. For icon buttons, ensure that `aria-label` is provided.
```html
-
+
@@ -119,7 +119,7 @@ The `matTreeNodePadding` can be placed in a flat tree's node template to display
information of a flat tree node.
```html
-
+
{{node.value}}
```
@@ -134,10 +134,10 @@ The tree may include multiple node templates, where a template is chosen
for a particular data node via the `when` predicate of the template.
```html
-
+
{{node.value}}
-
+
[ A special node {{node.value}} ]
```
@@ -209,11 +209,11 @@ interaction.
```html
```
In this example, `$event` contains the node's data and is equivalent to the implicit data passed in
-the `matNodeDef` context.
+the `matTreeNodeDef` context.