Skip to content

Commit 28a5925

Browse files
pkozlowski-opensourceatscott
authored andcommitted
fix(common): use === operator to match NgSwitch cases (#51504)
This change adjust the equality comparator used by NgSwitch - now it defaults to === from previously used ==. This change is based on the following reasoning: - align behaviour with the built-in switch block); - improve performance (avoid type coercion); - enable better type-checking. BREAKING CHANGE: the NgSwitch directive now defaults to the === equality operator, migrating from the previously used ==. NgSwitch expressions and / or individual condition values need adjusting to this stricter equality check. The added warning message should help pinpointing NgSwitch usages where adjustements are needed. Fixes #33873 PR Close #51504
1 parent e25006b commit 28a5925

File tree

5 files changed

+78
-2
lines changed

5 files changed

+78
-2
lines changed

goldens/public-api/common/errors.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
// @public
88
export const enum RuntimeErrorCode {
9+
// (undocumented)
10+
EQUALITY_NG_SWITCH_DIFFERENCE = 2001,
911
// (undocumented)
1012
INVALID_INPUT = 2952,
1113
// (undocumented)

packages/common/src/directives/ng_switch.ts

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

9-
import {Directive, DoCheck, Host, Input, Optional, TemplateRef, ViewContainerRef, ɵRuntimeError as RuntimeError} from '@angular/core';
9+
import {Directive, DoCheck, Host, Input, Optional, TemplateRef, ViewContainerRef, ɵformatRuntimeError as formatRuntimeError, ɵRuntimeError as RuntimeError} from '@angular/core';
1010

1111
import {RuntimeErrorCode} from '../errors';
1212

13+
import {NG_SWITCH_USE_STRICT_EQUALS} from './ng_switch_equality';
14+
1315
export class SwitchView {
1416
private _created = false;
1517

@@ -133,7 +135,18 @@ export class NgSwitch {
133135

134136
/** @internal */
135137
_matchCase(value: any): boolean {
136-
const matched = value == this._ngSwitch;
138+
const matched =
139+
NG_SWITCH_USE_STRICT_EQUALS ? value === this._ngSwitch : value == this._ngSwitch;
140+
if ((typeof ngDevMode === 'undefined' || ngDevMode) && matched !== (value == this._ngSwitch)) {
141+
console.warn(formatRuntimeError(
142+
RuntimeErrorCode.EQUALITY_NG_SWITCH_DIFFERENCE,
143+
'As of Angular v17 the NgSwitch directive uses strict equality comparison === instead of == to match different cases. ' +
144+
`Previously the case value "${
145+
stringifyValue(value)}" matched switch expression value "${
146+
stringifyValue(
147+
this._ngSwitch)}", but this is no longer the case with the stricter equality check.` +
148+
'Your comparison results return different results using === vs. == and you should adjust your ngSwitch expression and / or values to conform with the strict equality requirements.'));
149+
}
137150
this._lastCasesMatched = this._lastCasesMatched || matched;
138151
this._lastCaseCheckIndex++;
139152
if (this._lastCaseCheckIndex === this._caseCount) {
@@ -256,3 +269,7 @@ function throwNgSwitchProviderNotFoundError(attrName: string, directiveName: str
256269
directiveName}" directive) must be located inside an element with the "ngSwitch" attribute ` +
257270
`(matching "NgSwitch" directive)`);
258271
}
272+
273+
function stringifyValue(value: unknown): string {
274+
return typeof value === 'string' ? `'${value}'` : String(value);
275+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.io/license
7+
*/
8+
9+
10+
/**
11+
* A constant indicating a type of comparison that NgSwitch uses to match cases. Extracted to a
12+
* separate file to facilitate G3 patches.
13+
*/
14+
export const NG_SWITCH_USE_STRICT_EQUALS = true;

packages/common/src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
export const enum RuntimeErrorCode {
1414
// NgSwitch errors
1515
PARENT_NG_SWITCH_NOT_FOUND = 2000,
16+
EQUALITY_NG_SWITCH_DIFFERENCE = 2001,
1617

1718
// Pipe errors
1819
INVALID_PIPE_ARGUMENT = 2100,

packages/common/test/directives/ng_switch_spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,48 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
9191
getComponent().switchValue = 'b';
9292
detectChangesAndExpectText('when b1;when b2;');
9393
});
94+
95+
it('should use === to match cases', () => {
96+
const template = '<ul [ngSwitch]="switchValue">' +
97+
'<li *ngSwitchCase="1">when one</li>' +
98+
'<li *ngSwitchDefault>when default</li>' +
99+
'</ul>';
100+
101+
fixture = createTestComponent(template);
102+
detectChangesAndExpectText('when default');
103+
104+
getComponent().switchValue = 1;
105+
detectChangesAndExpectText('when one');
106+
107+
getComponent().switchValue = '1';
108+
detectChangesAndExpectText('when default');
109+
});
110+
111+
it('should warn if === and == give different results', () => {
112+
const template = '<ul [ngSwitch]="switchValue">' +
113+
'<li *ngSwitchCase="1">when one</li>' +
114+
'<li *ngSwitchDefault>when default</li>' +
115+
'</ul>';
116+
117+
const consoleWarnSpy = spyOn(console, 'warn');
118+
119+
fixture = createTestComponent(template);
120+
getComponent().switchValue = '1';
121+
detectChangesAndExpectText('when default');
122+
123+
expect(consoleWarnSpy.calls.count()).toBe(1);
124+
expect(consoleWarnSpy.calls.argsFor(0)[0])
125+
.toBe(
126+
'NG02001: As of Angular v17 the NgSwitch directive uses strict equality comparison === instead of == to match different cases. ' +
127+
`Previously the case value "1" matched switch expression value "'1'", but this is no longer the case with the stricter equality check.` +
128+
'Your comparison results return different results using === vs. == and you should adjust your ngSwitch expression and / or values to conform with the strict equality requirements.');
129+
130+
131+
getComponent().switchValue = 1;
132+
detectChangesAndExpectText('when one');
133+
expect(consoleWarnSpy.calls.count())
134+
.toBe(1); // no calls to warn when both equality operators agree
135+
});
94136
});
95137

96138
describe('when values changes', () => {

0 commit comments

Comments
 (0)