From e38a602011348adf7460c792aa09010a5aa4c3eb Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 15 Jun 2020 15:39:12 +0200 Subject: [PATCH 1/2] build: add size test for radio button without group --- goldens/size-test.yaml | 1 + .../size-test/material/radio/BUILD.bazel | 7 ++++++ .../size-test/material/radio/without-group.ts | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 integration/size-test/material/radio/BUILD.bazel create mode 100644 integration/size-test/material/radio/without-group.ts diff --git a/goldens/size-test.yaml b/goldens/size-test.yaml index 201351912a40..0c0305764916 100644 --- a/goldens/size-test.yaml +++ b/goldens/size-test.yaml @@ -1 +1,2 @@ material/list/nav-list: 130473 +material/radio/without-group: 126655 diff --git a/integration/size-test/material/radio/BUILD.bazel b/integration/size-test/material/radio/BUILD.bazel new file mode 100644 index 000000000000..12e1478d275f --- /dev/null +++ b/integration/size-test/material/radio/BUILD.bazel @@ -0,0 +1,7 @@ +load("//integration/size-test:index.bzl", "size_test") + +size_test( + name = "without-group", + file = "without-group.ts", + deps = ["//src/material/radio"], +) diff --git a/integration/size-test/material/radio/without-group.ts b/integration/size-test/material/radio/without-group.ts new file mode 100644 index 000000000000..fb1e148f002c --- /dev/null +++ b/integration/size-test/material/radio/without-group.ts @@ -0,0 +1,23 @@ +import {Component, NgModule} from '@angular/core'; +import {MatRadioModule} from '@angular/material/radio'; +import {platformBrowser} from '@angular/platform-browser'; + +/** + * Basic component using `MatRadioButton`. Doesn't use a `MatRadioGroup`, so the class + * should be tree-shaken away properly. + */ +@Component({ + template: ` + + `, +}) +export class TestComponent {} + +@NgModule({ + imports: [MatRadioModule], + declarations: [TestComponent], + bootstrap: [TestComponent], +}) +export class AppModule {} + +platformBrowser().bootstrapModule(AppModule); From 2c25515b412f25096e585b69a13c9eb4ce81c9c6 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 15 Jun 2020 15:57:14 +0200 Subject: [PATCH 2/2] refactor: use lightweight token for injecting radio group The Angular Material `MatRadioButton` component currently optionally injects the `MatRadioGroup`. This causes the `MatRadioGroup` implementation to be always retained in application bundles. This is problematic as the use of radio groups is not always required. i.e. it's valid to use a standalone radio button. In those cases, we do not want to retain the radio group implementation unnecessarily. This has always been an issue, but the issue became more significant in Ivy where component factory and definitions are attached directly to the `MatRadioGroup` class (resulting in a more significant size increase). We fix this by using a lightweight token for injecting the parent radio group optionally. This solves the retention issue if a standalone radio button is used. Related to: #19576. --- goldens/size-test.yaml | 2 +- src/material-experimental/mdc-radio/radio.ts | 16 ++++++++++++-- src/material/radio/radio.ts | 22 ++++++++++++++------ tools/public_api_guard/material/radio.d.ts | 4 +++- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/goldens/size-test.yaml b/goldens/size-test.yaml index 0c0305764916..adfbd99cb03d 100644 --- a/goldens/size-test.yaml +++ b/goldens/size-test.yaml @@ -1,2 +1,2 @@ material/list/nav-list: 130473 -material/radio/without-group: 126655 +material/radio/without-group: 121448 diff --git a/src/material-experimental/mdc-radio/radio.ts b/src/material-experimental/mdc-radio/radio.ts index 9857e4fc18bb..9ddc9986979b 100644 --- a/src/material-experimental/mdc-radio/radio.ts +++ b/src/material-experimental/mdc-radio/radio.ts @@ -16,6 +16,7 @@ import { ElementRef, forwardRef, Inject, + InjectionToken, OnDestroy, Optional, QueryList, @@ -50,6 +51,14 @@ export const MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR: any = { multi: true }; +/** + * Injection token that can be used to inject instances of `MatRadioGroup`. It serves as + * alternative token to the actual `MatRadioGroup` class which could cause unnecessary + * retention of the class and its component metadata. + */ +export const MAT_RADIO_GROUP = + new InjectionToken<_MatRadioGroupBase<_MatRadioButtonBase>>('MatRadioGroup'); + /** Configuration for the ripple animation. */ const RIPPLE_ANIMATION_CONFIG: RippleAnimationConfig = { enterDuration: numbers.DEACTIVATION_TIMEOUT_MS, @@ -62,7 +71,10 @@ const RIPPLE_ANIMATION_CONFIG: RippleAnimationConfig = { @Directive({ selector: 'mat-radio-group', exportAs: 'matRadioGroup', - providers: [MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR], + providers: [ + MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR, + {provide: MAT_RADIO_GROUP, useExisting: MatRadioGroup}, + ], host: { 'role': 'radiogroup', 'class': 'mat-mdc-radio-group', @@ -117,7 +129,7 @@ export class MatRadioButton extends _MatRadioButtonBase implements AfterViewInit _radioFoundation = new MDCRadioFoundation(this._radioAdapter); _classes: {[key: string]: boolean} = {}; - constructor(@Optional() radioGroup: MatRadioGroup, + constructor(@Optional() @Inject(MAT_RADIO_GROUP) radioGroup: MatRadioGroup, elementRef: ElementRef, _changeDetector: ChangeDetectorRef, _focusMonitor: FocusMonitor, diff --git a/src/material/radio/radio.ts b/src/material/radio/radio.ts index 588bb310b273..6944c5d93529 100644 --- a/src/material/radio/radio.ts +++ b/src/material/radio/radio.ts @@ -83,6 +83,14 @@ export class MatRadioChange { public value: any) {} } +/** + * Injection token that can be used to inject instances of `MatRadioGroup`. It serves as + * alternative token to the actual `MatRadioGroup` class which could cause unnecessary + * retention of the class and its component metadata. + */ +export const MAT_RADIO_GROUP = + new InjectionToken<_MatRadioGroupBase<_MatRadioButtonBase>>('MatRadioGroup'); + /** * Base class with all of the `MatRadioGroup` functionality. * @docs-private @@ -311,7 +319,10 @@ export abstract class _MatRadioGroupBase implemen @Directive({ selector: 'mat-radio-group', exportAs: 'matRadioGroup', - providers: [MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR], + providers: [ + MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR, + {provide: MAT_RADIO_GROUP, useExisting: MatRadioGroup}, + ], host: { 'role': 'radiogroup', 'class': 'mat-radio-group', @@ -475,14 +486,13 @@ export abstract class _MatRadioButtonBase extends _MatRadioButtonMixinBase imple /** The native `` element */ @ViewChild('input') _inputElement: ElementRef; - constructor(@Optional() radioGroup: _MatRadioGroupBase<_MatRadioButtonBase>, + constructor(radioGroup: _MatRadioGroupBase<_MatRadioButtonBase>, elementRef: ElementRef, protected _changeDetector: ChangeDetectorRef, private _focusMonitor: FocusMonitor, private _radioDispatcher: UniqueSelectionDispatcher, - @Optional() @Inject(ANIMATION_MODULE_TYPE) public _animationMode?: string, - @Optional() @Inject(MAT_RADIO_DEFAULT_OPTIONS) - private _providerOverride?: MatRadioDefaultOptions) { + public _animationMode?: string, + private _providerOverride?: MatRadioDefaultOptions) { super(elementRef); // Assertions. Ideally these should be stripped out by the compiler. @@ -626,7 +636,7 @@ export abstract class _MatRadioButtonBase extends _MatRadioButtonMixinBase imple changeDetection: ChangeDetectionStrategy.OnPush, }) export class MatRadioButton extends _MatRadioButtonBase { - constructor(@Optional() radioGroup: MatRadioGroup, + constructor(@Optional() @Inject(MAT_RADIO_GROUP) radioGroup: MatRadioGroup, elementRef: ElementRef, changeDetector: ChangeDetectorRef, focusMonitor: FocusMonitor, diff --git a/tools/public_api_guard/material/radio.d.ts b/tools/public_api_guard/material/radio.d.ts index 1636353cca20..711056836582 100644 --- a/tools/public_api_guard/material/radio.d.ts +++ b/tools/public_api_guard/material/radio.d.ts @@ -37,7 +37,7 @@ export declare abstract class _MatRadioButtonBase extends _MatRadioButtonMixinBa static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_required: BooleanInput; static ɵdir: i0.ɵɵDirectiveDefWithMeta<_MatRadioButtonBase, never, never, { "id": "id"; "name": "name"; "ariaLabel": "aria-label"; "ariaLabelledby": "aria-labelledby"; "ariaDescribedby": "aria-describedby"; "checked": "checked"; "value": "value"; "labelPosition": "labelPosition"; "disabled": "disabled"; "required": "required"; "color": "color"; }, { "change": "change"; }, never>; - static ɵfac: i0.ɵɵFactoryDef<_MatRadioButtonBase, [{ optional: true; }, null, null, null, null, { optional: true; }, { optional: true; }]>; + static ɵfac: i0.ɵɵFactoryDef<_MatRadioButtonBase, never>; } export declare abstract class _MatRadioGroupBase implements AfterContentInit, ControlValueAccessor { @@ -78,6 +78,8 @@ export declare const MAT_RADIO_DEFAULT_OPTIONS: InjectionToken>; + export declare const MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR: any; export declare class MatRadioButton extends _MatRadioButtonBase {