mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {animate, style, transition, trigger} from '@angular/animations';
|
|
import {Platform} from '@angular/cdk/platform';
|
|
import {DOCUMENT} from '@angular/common';
|
|
import {
|
|
Component,
|
|
computed,
|
|
inject,
|
|
OnDestroy,
|
|
OnInit,
|
|
signal,
|
|
WritableSignal,
|
|
} from '@angular/core';
|
|
import {Events, MessageBus} from 'protocol';
|
|
import {interval} from 'rxjs';
|
|
|
|
import {FrameManager} from './frame_manager';
|
|
import {ThemeService} from './theme-service';
|
|
import {MatTooltip, MatTooltipModule} from '@angular/material/tooltip';
|
|
import {DevToolsTabsComponent} from './devtools-tabs/devtools-tabs.component';
|
|
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
|
import {Frame} from './application-environment';
|
|
|
|
const DETECT_ANGULAR_ATTEMPTS = 10;
|
|
|
|
enum AngularStatus {
|
|
/**
|
|
* This page may have Angular but we don't know yet. We're still trying to detect it.
|
|
*/
|
|
UNKNOWN,
|
|
|
|
/**
|
|
* We've given up on trying to detect Angular. We tried ${DETECT_ANGULAR_ATTEMPTS} times and
|
|
* failed.
|
|
*/
|
|
DOES_NOT_EXIST,
|
|
|
|
/**
|
|
* Angular was detected somewhere on the page.
|
|
*/
|
|
EXISTS,
|
|
}
|
|
|
|
const LAST_SUPPORTED_VERSION = 9;
|
|
|
|
@Component({
|
|
selector: 'ng-devtools',
|
|
templateUrl: './devtools.component.html',
|
|
styleUrls: ['./devtools.component.scss'],
|
|
animations: [
|
|
trigger('enterAnimation', [
|
|
transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]),
|
|
transition(':leave', [style({opacity: 1}), animate('200ms', style({opacity: 0}))]),
|
|
]),
|
|
],
|
|
standalone: true,
|
|
imports: [DevToolsTabsComponent, MatTooltip, MatProgressSpinnerModule, MatTooltipModule],
|
|
})
|
|
export class DevToolsComponent implements OnInit, OnDestroy {
|
|
AngularStatus = AngularStatus;
|
|
angularStatus: AngularStatus = AngularStatus.UNKNOWN;
|
|
angularVersion: WritableSignal<string | undefined> = signal(undefined);
|
|
angularIsInDevMode = true;
|
|
hydration: boolean = false;
|
|
ivy: WritableSignal<boolean | undefined> = signal(undefined);
|
|
|
|
supportedVersion = computed(() => {
|
|
const version = this.angularVersion();
|
|
if (!version) {
|
|
return false;
|
|
}
|
|
const majorVersion = parseInt(version.toString().split('.')[0], 10);
|
|
|
|
// Check that major version is either greater or equal to the last supported version
|
|
// or that the major version is 0 for the (0.0.0-PLACEHOLDER) dev build case.
|
|
return (majorVersion >= LAST_SUPPORTED_VERSION || majorVersion === 0) && this.ivy();
|
|
});
|
|
|
|
private readonly _firefoxStyleName = 'firefox_styles.css';
|
|
private readonly _chromeStyleName = 'chrome_styles.css';
|
|
private readonly _messageBus = inject<MessageBus<Events>>(MessageBus);
|
|
private readonly _themeService = inject(ThemeService);
|
|
private readonly _platform = inject(Platform);
|
|
private readonly _document = inject(DOCUMENT);
|
|
private readonly _frameManager = inject(FrameManager);
|
|
|
|
private _interval$ = interval(500).subscribe((attempt) => {
|
|
if (attempt === DETECT_ANGULAR_ATTEMPTS) {
|
|
this.angularStatus = AngularStatus.DOES_NOT_EXIST;
|
|
}
|
|
this._messageBus.emit('queryNgAvailability');
|
|
});
|
|
|
|
inspectFrame(frame: Frame) {
|
|
this._frameManager.inspectFrame(frame);
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this._themeService.initializeThemeWatcher();
|
|
|
|
this._messageBus.once('ngAvailability', ({version, devMode, ivy, hydration}) => {
|
|
this.angularStatus = version ? AngularStatus.EXISTS : AngularStatus.DOES_NOT_EXIST;
|
|
this.angularVersion.set(version);
|
|
this.angularIsInDevMode = devMode;
|
|
this.ivy.set(ivy);
|
|
this._interval$.unsubscribe();
|
|
this.hydration = hydration;
|
|
});
|
|
|
|
const browserStyleName = this._platform.FIREFOX
|
|
? this._firefoxStyleName
|
|
: this._chromeStyleName;
|
|
this._loadStyle(browserStyleName);
|
|
}
|
|
|
|
/** Add a style file in header based on fileName */
|
|
private _loadStyle(styleName: string) {
|
|
const head = this._document.getElementsByTagName('head')[0];
|
|
|
|
const style = this._document.createElement('link');
|
|
style.rel = 'stylesheet';
|
|
style.href = `./styles/${styleName}`;
|
|
|
|
head.appendChild(style);
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this._interval$.unsubscribe();
|
|
}
|
|
}
|