¿Por qué es necesario "declare var angular" al usar Typescript con AngularJS?
Tengo una aplicación funcional de AngularJS 1.7 donde todo mi código está escrito en TypeScript. Pero algo siempre me ha molestado.
En mi archivo app.module.ts, tengo este pedazo de fealdad:
declare var angular;
para que el
this.app=angular.module('app'...
se pueda transpilar y ejecutar.
Cosas que he probado:
1) Reemplazar declare var angular; con
import angular from "angular";
se transpila perfectamente, pero luego el navegador se queja
Uncaught (in promise) TypeError: angular_1.default.module no es una función
2) Reemplazar declare var angular; con
import * as angular from "angular";
también se transpila correctamente, pero el navegador da un error similar:
Uncaught (in promise) TypeError: angular.module no es una función
3) Reemplazar declare var angular; con
import ng from "angular";
y luego usar ng.angular.module o ng.module no se transpila en absoluto.
Estoy seguro de que he probado algunas otras cosas, pero la única manera en que alguna vez he podido hacer que las cosas funcionen es con ese declare var angular;
Todo funciona bien, pero eso me huele muy mal. ¿Por qué tengo que hacer eso? ¿Qué estoy haciendo mal? ¿Hay una manera mejor de hacerlo?
Detalles :
- Visual Studio 2017/2019
- TypeScript 3.3
- SystemJS (no RequireJS)
- AngularJS 1.7.8
- @types/angular 1.6.54
package.json
"devDependencies": {
"@types/angular": "^1.6.54",
...
},
"dependencies": {
"angular": "~1.7.8",
...
}
tsconfig.json
{
"compileOnSave": true,
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [ "node_modules/types/*", "node_modules/*" ],
},
"module": "system",
"target": "es6",
"sourceMap": true,
"lib": [ "es6", "dom" ],
"allowSyntheticDefaultImports": true,
"outFile": "./app/everything.js",
"moduleResolution": "node",
"types": [
"angular",
"jquery"
]
},
"include": ["app/**/*"],
"exclude": ["node_modules", "lib"],
"strict": true
}
app.module.ts
declare var angular;
...
export class App {
app: ng.IModule;
constructor() {
this.app = angular.module('app', [
...
]);
}
public run() {
this.app.run([
...
]);
...
}
index.html
...
<script src="lib/systemjs/system.js"></script>
<script src="lib/angular/angular.min.js" type="text/javascript"></script>
...
<script src="app/everything.js" type="text/javascript"></script>
<script>
System.config({
map: {
"angular": "lib/angular",
...
},
packages: {
"angular": { main: "angular.min.js", defaultExtension: "js" },
...
}
});
System.import("app.module")
.then(function (app) {
let a = new app.App();
a.run();
angular.element(function () {
angular.bootstrap(document, ['app']);
});
});
</script>
Respuesta #1
Tus tipos de Typescript, @types/angular, buscan la variable angular para adjuntar sus tipos.
Normalmente, habrías importado angular así: import angular from "angular" y los tipos se aplicarían a la variable angular que acabas de crear, y todo funcionaría perfectamente.
Sin embargo, ya estás cargando Angular de manera global en tu HTML <script>. En este caso, no quieres import angular from "angular" porque entonces habrás cargado Angular más de una vez, y las cosas podrían fallar.
Al escribir declare var angular simplemente le estás diciendo a Typescript que confíe en ti en que existe, aunque no lo haya visto ser importado en el alcance de este archivo.
Esto significa
- Typescript no se quejará de que Angular esté indefinido.
@types/angularse adjuntará a tu variableangular.
Cuando llegues al tiempo de ejecución, asumiendo que angular realmente existe en window, entonces funcionará.
Este es un problema que será necesario mientras te estés moviendo a empaquetadores como Webpack o SystemJS. Una vez que elimines el Angular global de index.html <script>, puedes importarlo en tus archivos TS y eliminar el declare var.