8000 refactor(core): stop producing `ng-reflect` attributes by default · angular/angular@855c290 · GitHub
[go: up one dir, main page]

Skip to content

Commit 855c290

Browse files
committed
refactor(core): stop producing ng-reflect attributes by default
BREAKING CHANGE: This commit deprecates `ng-reflect-*` attributes and updates the runtime to stop producing them by default. Please refactor application and test code to avoid relying on `ng-reflect-*` attributes. To enable a more seamless upgrade to v20, we've added the `provideNgReflectAttributes()` function (can be imported from the `@angular/core` package), which enables the mode in which Angular would be producing those attribites (in dev mode only). You can add the `provideNgReflectAttributes()` function to the list of providers within the bootstrap call.
1 parent 916f768 commit 855c290

File tree

12 files changed

+206
-56
lines changed

12 files changed

+206
-56
lines changed

packages/core/src/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export {makeStateKey, StateKey, TransferState} from './transfer_state';
118118
export {booleanAttribute, numberAttribute} from './util/coercion';
119119
export {REQUEST, REQUEST_CONTEXT, RESPONSE_INIT} from './application/platform_tokens';
120120
export {DOCUMENT} from './document';
121+
export {provideNgReflectAttributes} from './ng_reflect';
121122

122123
import {global} from './util/global';
123124
if (typeof ngDevMode !== 'undefined' && ngDevMode) {

packages/core/src/ng_reflect.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {EnvironmentProviders, InjectionToken, makeEnvironmentProviders} from './di/index';
10+
11+
/** Defines the default value of the `NG_REFLECT_ATTRS_FLAG` flag. */
12+
export const NG_REFLECT_ATTRS_FLAG_DEFAULT = false;
13+
14+
/**
15+
* Defines an internal flag that indicates whether the runtime code should be
16+
* producing `ng-reflect-*` attributes.
17+
*/
18+
export const NG_REFLECT_ATTRS_FLAG = new InjectionToken<boolean>(
19+
typeof ngDevMode === 'undefined' || ngDevMode ? 'NG_REFLECT_FLAG' : '',
20+
{
21+
providedIn: 'root',
22+
factory: () => NG_REFLECT_ATTRS_FLAG_DEFAULT,
23+
},
24+
);
25+
26+
/**
27+
* Enables the logic to produce `ng-reflect-*` attributes on elements with bindings.
28+
*
29+
* Note: this is a dev-mode only setting and it will have no effect in production mode.
30+
* In production mode, the `ng-reflect-*` attributes are *never* produced by Angular.
31+
*
32+
* Important: using and relying on the `ng-reflect-*` attributes is not recommended,
33+
* they are deprecated and only present for backwards compatibility. Angular will stop
34+
* producing them in one of the future versions.
35+
*
36+
* @publicApi
37+
*/
38+
export function provideNgReflectAttributes(): EnvironmentProviders {
39+
const providers =
40+
typeof ngDevMode === 'undefined' || ngDevMode
< B41A /td>41+
? [
42+
{
43+
provide: NG_REFLECT_ATTRS_FLAG,
44+
useValue: true,
45+
},
46+
]
47+
: [];
48+
return makeEnvironmentProviders(providers);
49+
}
50+
51+
export function normalizeDebugBindingName(name: string) {
52+
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
53+
name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
54+
return `ng-reflect-${name}`;
55+
}
56+
57+
const CAMEL_CASE_REGEXP = /([A-Z])/g;
58+
59+
function camelCaseToDashCase(input: string): string {
60+
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
61+
}
62+
63+
export function normalizeDebugBindingValue(value: any): string {
64+
try {
65+
// Limit the size of the value as otherwise the DOM just gets polluted.
66+
return value != null ? value.toString().slice(0, 30) : value;
67+
} catch (e) {
68+
return '[ERROR] Exception while trying to serialize the value';
69+
}
70+
}

packages/core/src/render3/component_ref.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import {elementEndFirstCreatePass, elementStartFirstCreatePass} from './view/ele
7575
import {ViewRef} from './view_ref';
7676
import {createLView, createTView, getInitialLViewFlagsFromDef} from './view/construction';
7777
import {BINDING, Binding, DirectiveWithBindings} from './dynamic_bindings';
78+
import {NG_REFLECT_ATTRS_FLAG, NG_REFLECT_ATTRS_FLAG_DEFAULT} from '../ng_reflect';
7879

7980
export class ComponentFactoryResolver extends AbstractComponentFactoryResolver {
8081
/**
@@ -163,10 +164,16 @@ function createRootLViewEnvironment(rootLViewInjector: Injector): LViewEnvironme
163164
const sanitizer = rootLViewInjector.get(Sanitizer, null);
164165
const changeDetectionScheduler = rootLViewInjector.get(ChangeDetectionScheduler, null);
165166

167+
let ngReflect = false;
168+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
169+
ngReflect = rootLViewInjector.get(NG_REFLECT_ATTRS_FLAG, NG_REFLECT_ATTRS_FLAG_DEFAULT);
170+
}
171+
166172
return {
167173
rendererFactory,
168174
sanitizer,
169175
changeDetectionScheduler,
176+
ngReflect,
170177
};
171178
}
172179

packages/core/src/render3/instructions/shared.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from '../../sanitization/sanitization';
1919
import {assertIndexInRange, assertNotSame} from '../../util/assert';
2020
import {escapeCommentText} from '../../util/dom';
21-
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
21+
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../ng_reflect';
2222
import {stringify} from '../../util/stringify';
2323
import {assertFirstCreatePass, assertLView} from '../assert';
2424
import {attachPatchData} from '../context_discovery';
@@ -43,6 +43,7 @@ import {SanitizerFn} from '../interfaces/sanitization';
4343
import {isComponentDef, isComponentHost} from '../interfaces/type_checks';
4444
import {
4545
CONTEXT,
46+
ENVIRONMENT,
4647
FLAGS,
4748
HEADER_OFFSET,
4849
INJECTOR,
@@ -309,6 +310,10 @@ export function markDirtyIfOnPush(lView: LView, viewIndex: number): void {
309310
}
310311

311312
function setNgReflectProperty(lView: LView, tNode: TNode, attrName: string, value: any) {
313+
const environment = lView[ENVIRONMENT];
314+
if (!environment.ngReflect) {
315+
return;
316+
}
312317
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
313318
const renderer = lView[RENDERER];
314319
attrName = normalizeDebugBindingName(attrName);
@@ -334,7 +339,8 @@ function setNgReflectProperties(
334339
publicName: string,
335340
value: any,
336341
) {
337-
if (!(tNode.type & (TNodeType.AnyRNode | TNodeType.Container))) {
342+
const environment = lView[ENVIRONMENT];
343+
if (!environment.ngReflect || !(tNode.type & (TNodeType.AnyRNode | TNodeType.Container))) {
338344
return;
339345
}
340346

packages/core/src/render3/interfaces/view.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,12 @@ export interface LViewEnvironment {
381381

382382
/** Scheduler for change detection to notify when application state changes. */
383383
changeDetectionScheduler: ChangeDetectionScheduler | null;
384+
385+
/**
386+
* Whether `ng-reflect-*` attributes should be produced in dev mode
387+
* (always disabled in prod mode).
388+
*/
389+
ngReflect: boolean;
384390
}
385391

386392
/** Flags associated with an LView (saved in LView[FLAGS]) */

packages/core/src/util/ng_reflect.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

packages/core/test/acceptance/defer_spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {global} from '../../src/util/global';
4444
import {TimerScheduler} from '@angular/core/src/defer/timer_scheduler';
4545
import {Console} from '../../src/console';
4646
import {formatRuntimeErrorCode, RuntimeErrorCode} from '../../src/errors';
47+
import {provideNgReflectAttributes} from '../../src/ng_reflect';
4748

4849
/**
4950
* Clears all associated directive defs from a given component class.
@@ -213,7 +214,10 @@ function createFixture(template: string) {
213214

214215
// Set `PLATFORM_ID` to a browser platform value to trigger defer loading
215216
// while running tests in Node.
216-
const COMMON_PROVIDERS = [{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID}];
217+
const COMMON_PROVIDERS = [
218+
{provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
219+
provideNgReflectAttributes(),
220+
];
217221

218222
describe('@defer', () => {
219223
beforeEach(() => {

packages/core/test/acceptance/i18n_spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ import {clearTranslations, loadTranslations} from '@angular/localize';
4040
import {By} from '@angular/platform-browser';
4141
import {expect} from '@angular/platform-browser/testing/src/matchers';
4242
import {BehaviorSubject} from 'rxjs';
43+
import {provideNgReflectAttributes} from '../../src/ng_reflect';
4344

4445
describe('runtime i18n', () => {
4546
beforeEach(() => {
4647
TestBed.configureTestingModule({
4748
declarations: [AppComp, DirectiveWithTplRef, UppercasePipe],
49+
providers: [provideNgReflectAttributes()],
4850
// In some of the tests we use made-up tag names for better readability, however
4951
// they'll cause validation errors. Add the `NO_ERRORS_SCHEMA` so that we don't have
5052
// to declare dummy components for each one of them.

packages/core/test/acceptance/template_ref_spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {Component, Injector, TemplateRef, ViewChild, ViewContainerRef} from '../../src/core';
9+
import {
10+
Component,
11+
Injector,
12+
provideNgReflectAttributes,
13+
TemplateRef,
14+
ViewChild,
15+
ViewContainerRef,
16+
} from '../../src/core';
1017
import {TestBed} from '../../testing';
1118

1219
describe('TemplateRef', () => {
@@ -78,7 +85,10 @@ describe('TemplateRef', () => {
7885
constructor(public viewContainerRef: ViewContainerRef) {}
7986
}
8087

81-
TestBed.configureTestingModule({declarations: [MenuContent, App]});
88+
TestBed.configureTestingModule({
89+
declarations: [MenuContent, App],
90+
providers: [provideNgReflectAttributes()],
91+
});
8292
const fixture = TestBed.createComponent(App);
8393
fixture.detectChanges();
8494

0 commit comments

Comments
 (0)
0