Skip to content

Commit efe78d5

Browse files
AleksanderBodurrialxhub
authored andcommitted
fix(devtools): allow DevTools to fail gracefully for unsupported versions of Angular. (#55233)
Angular DevTools depends on many modern Angular features in order to function. As a result, at present the last officially supported version is v12. Angular DevTools may function for some Angular 9, 10 and 11 applications, but they are not officially supported. This commit fixes an issue where DevTools would not inject a backend script into an Angular application if it detected it was below version 12. This backend script is important because it's used to inform the DevTools panel that the inspected application is in fact Angular, but that it is not on a supported version. Angular 9, 10 and 11 applications that successfully have Angular DevTools initialize will now have a red highlight and tooltip on their version number, informing the user that they are using Angular DevTools on a version of Angular that is no longer supported. Angular DevTools for applications that are below version 9 will continue to display the "Angular Devtools supports Angular versions 12 and above" message. PR Close #55233
1 parent 832fafc commit efe78d5

8 files changed

Lines changed: 72 additions & 36 deletions

File tree

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,27 @@
3030
{{ tab }}
3131
</a>
3232
}
33-
@if (angularVersion) {
33+
@if (angularVersion()) {
3434
<section id="app-angular-version">
3535
Angular version:
36-
<span id="version-number">
37-
{{ angularVersion }}
38-
</span>
36+
37+
@if (majorAngularVersion() > 12 || majorAngularVersion() == 0) {
38+
<span id="version-number">
39+
{{ angularVersion() }}
40+
</span>
41+
} @else {
42+
<span
43+
id="version-number"
44+
matTooltip="
45+
Angular Devtools supports Angular versions 12 and above. Some DevTools features may be available in
46+
older versions of Angular, but it is not officially supported.
47+
"
48+
class="unsupported-version"
49+
>
50+
{{ angularVersion() }} (unsupported)
51+
</span>
52+
}
53+
3954
@if (latestSHA) {
4055
| DevTools SHA: {{ latestSHA }}
4156
}

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.scss‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ ng-injector-tree.hidden {
6767
-webkit-user-select: text;
6868
-ms-user-select: text;
6969
user-select: text;
70+
71+
&.unsupported-version {
72+
color: red;
73+
}
7074
}
7175

7276
mat-icon {
@@ -89,6 +93,10 @@ mat-icon {
8993
:host-context(.dark-theme) {
9094
#version-number {
9195
color: #5caace;
96+
97+
&.unsupported-version {
98+
color: red;
99+
}
92100
}
93101

94102
.inspector-active {

‎devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import {
1010
AfterViewInit,
1111
Component,
12+
computed,
1213
EventEmitter,
1314
inject,
15+
input,
1416
Input,
1517
OnInit,
1618
Output,
@@ -59,7 +61,6 @@ type Tabs = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree';
5961
providers: [TabUpdate],
6062
})
6163
export class DevToolsTabsComponent implements OnInit, AfterViewInit {
62-
@Input() angularVersion: string | undefined = undefined;
6364
@Input() isHydrationEnabled = false;
6465

6566
@Output() frameSelected = new EventEmitter<Frame>();
@@ -80,6 +81,15 @@ export class DevToolsTabsComponent implements OnInit, AfterViewInit {
8081

8182
TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID;
8283

84+
angularVersion = input<string | undefined>(undefined);
85+
majorAngularVersion = computed(() => {
86+
const version = this.angularVersion();
87+
if (!version) {
88+
return -1;
89+
}
90+
return parseInt(version.toString().split('.')[0], 10);
91+
});
92+
8393
constructor(
8494
public tabUpdate: TabUpdate,
8595
public themeService: ThemeService,

‎devtools/projects/ng-devtools/src/lib/devtools.component.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
@switch (angularStatus) {
33
@case (AngularStatus.EXISTS) {
44
@if (angularIsInDevMode) {
5-
@if (supportedVersion) {
5+
@if (supportedVersion()) {
66
<div class="devtools-wrapper noselect" [@enterAnimation]>
7-
<ng-devtools-tabs (frameSelected)="inspectFrame($event)" [isHydrationEnabled]="hydration" [angularVersion]="angularVersion"></ng-devtools-tabs>
7+
<ng-devtools-tabs (frameSelected)="inspectFrame($event)" [isHydrationEnabled]="hydration" [angularVersion]="angularVersion()"></ng-devtools-tabs>
88
</div>
99
} @else {
1010
<p class="text-message">

‎devtools/projects/ng-devtools/src/lib/devtools.component.ts‎

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
import {animate, style, transition, trigger} from '@angular/animations';
1010
import {Platform} from '@angular/cdk/platform';
1111
import {DOCUMENT} from '@angular/common';
12-
import {Component, inject, OnDestroy, OnInit} from '@angular/core';
12+
import {
13+
Component,
14+
computed,
15+
inject,
16+
OnDestroy,
17+
OnInit,
18+
signal,
19+
WritableSignal,
20+
} from '@angular/core';
1321
import {Events, MessageBus} from 'protocol';
1422
import {interval} from 'rxjs';
1523

@@ -40,6 +48,8 @@ enum AngularStatus {
4048
EXISTS,
4149
}
4250

51+
const LAST_SUPPORTED_VERSION = 9;
52+
4353
@Component({
4454
selector: 'ng-devtools',
4555
templateUrl: './devtools.component.html',
@@ -56,10 +66,22 @@ enum AngularStatus {
5666
export class DevToolsComponent implements OnInit, OnDestroy {
5767
AngularStatus = AngularStatus;
5868
angularStatus: AngularStatus = AngularStatus.UNKNOWN;
59-
angularVersion: string | boolean | undefined = undefined;
69+
angularVersion: WritableSignal<string | undefined> = signal(undefined);
6070
angularIsInDevMode = true;
6171
hydration: boolean = false;
62-
ivy!: boolean;
72+
ivy: WritableSignal<boolean | undefined> = signal(undefined);
73+
74+
supportedVersion = computed(() => {
75+
const version = this.angularVersion();
76+
if (!version) {
77+
return false;
78+
}
79+
const majorVersion = parseInt(version.toString().split('.')[0], 10);
80+
81+
// Check that major version is either greater or equal to the last supported version
82+
// or that the major version is 0 for the (0.0.0-PLACEHOLDER) dev build case.
83+
return (majorVersion >= LAST_SUPPORTED_VERSION || majorVersion === 0) && this.ivy();
84+
});
6385

6486
private readonly _firefoxStyleName = 'firefox_styles.css';
6587
private readonly _chromeStyleName = 'chrome_styles.css';
@@ -85,9 +107,9 @@ export class DevToolsComponent implements OnInit, OnDestroy {
85107

86108
this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => {
87109
this.angularStatus = version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST;
88-
this.angularVersion = version;
110+
this.angularVersion.set(version);
89111
this.angularIsInDevMode = devMode;
90-
this.ivy = ivy;
112+
this.ivy.set(ivy);
91113
this._interval$.unsubscribe();
92114
this.hydration = hydration;
93115
});
@@ -98,17 +120,6 @@ export class DevToolsComponent implements OnInit, OnDestroy {
98120
this._loadStyle(browserStyleName);
99121
}
100122

101-
get majorAngularVersion(): number {
102-
if (!this.angularVersion) {
103-
return -1;
104-
}
105-
return parseInt(this.angularVersion.toString().split('.')[0], 10);
106-
}
107-
108-
get supportedVersion(): boolean {
109-
return (this.majorAngularVersion >= 9 || this.majorAngularVersion === 0) && this.ivy;
110-
}
111-
112123
/** Add a style file in header based on fileName */
113124
private _loadStyle(styleName: string) {
114125
const head = this._document.getElementsByTagName('head')[0];

‎devtools/projects/ng-devtools/src/lib/devtools_spec.ts‎

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

9-
import {Component} from '@angular/core';
9+
import {Component, signal} from '@angular/core';
1010
import {ComponentFixture, TestBed} from '@angular/core/testing';
1111
import {FrameManager} from './frame_manager';
1212
import {DevToolsComponent} from './devtools.component';
@@ -44,8 +44,8 @@ describe('DevtoolsComponent', () => {
4444
it('should render ng devtools tabs when Angular Status is EXISTS and is in dev mode and is supported version', () => {
4545
component.angularStatus = component.AngularStatus.EXISTS;
4646
component.angularIsInDevMode = true;
47-
component.angularVersion = '0.0.0';
48-
component.ivy = true;
47+
component.angularVersion = signal('0.0.0');
48+
component.ivy = signal(true);
4949
fixture.detectChanges();
5050
expect(fixture.nativeElement.querySelector('ng-devtools-tabs')).toBeTruthy();
5151
});
@@ -62,7 +62,7 @@ describe('DevtoolsComponent', () => {
6262
it('should render version support message when Angular Status is EXISTS and angular version is not supported', () => {
6363
component.angularStatus = component.AngularStatus.EXISTS;
6464
component.angularIsInDevMode = true;
65-
component.angularVersion = '1.0.0';
65+
component.angularVersion = signal('1.0.0');
6666
fixture.detectChanges();
6767
expect(fixture.nativeElement.querySelector('.devtools').textContent).toContain(
6868
'Angular Devtools only supports Angular versions 12 and above',

‎devtools/projects/protocol/src/lib/messages.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export interface Events {
238238
shutdown: () => void;
239239
queryNgAvailability: () => void;
240240
ngAvailability: (config: {
241-
version: string | undefined | boolean;
241+
version: string | undefined;
242242
devMode: boolean;
243243
ivy: boolean;
244244
hydration: boolean;

‎devtools/projects/shell-browser/src/app/content-script.ts‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@ detectAngularMessageBus.on('detectAngular', (detectionResult) => {
4646
return;
4747
}
4848

49-
if (detectionResult.isDebugMode !== true) {
50-
return;
51-
}
52-
53-
if (detectionResult.isSupportedAngularVersion !== true) {
54-
return;
55-
}
56-
5749
// Defensive check against non html page. Realistically this should never happen.
5850
if (document.contentType !== 'text/html') {
5951
return;

0 commit comments

Comments
 (0)