Avec angular 5.0, le module de mise à niveau a maintenant la possibilité d’utiliser downgradeModule qui exécute angularjs en dehors de la zone angulaire. En expérimentant cela, j'ai rencontré un problème d'utilisation de downgradeInjectable.
Je reçois l'erreur:
Erreur non capturée: tentative d'obtention de l'injecteur Angular avant de démarrer un module Angular.
Démarrage angulaire en angulaire js fonctionne bien
import 'zone.js/dist/zone.js';
import * as angular from 'angular';
/**
* Angular bootstrapping
*/
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { decorateModuleRef } from 'src/environment';
import { AppModule } from 'src/app/app.module';
import { downgradeModule } from '@angular/upgrade/static';
export const bootstrapFn = ( extraProviders ) => {
const platformRef = platformBrowserDynamic( extraProviders );
return platformRef
.bootstrapModule( AppModule )
.then( decorateModuleRef );
};
angular.module( 'app.bootstrap', [
downgradeModule( bootstrapFn ),
] );
Toutefois...
Comme le démarrage a lieu après l’initialisation d’angularjs, je ne peux plus faire fonctionner l’injectable vers le bas.
Service à déclasser
import { Injectable, Inject, OnInit } from '@angular/core';
@Injectable()
export class MobileService implements OnInit{
constructor(
@Inject( 'angularjsDependency1' ) public angularjsDependency1 : any,
@Inject( 'angularjsDependency2' ) public angularjsDependency2 : any,
) {}
}
Tentative de rétrogradation
import * as angular from 'angular';
import { downgradeInjectable } from '@angular/upgrade/static';
import { MyService } from 'src/services/myService/myService';
export const myServiceDowngraded = angular.module( 'services.mobileService', [
angularjsDependency1,
angularjsDependency2,
] )
.factory(
'mobileService',
downgradeInjectable( MyService ),
).name;
Lorsque "downgradeInjectable (MyService) est exécuté, l'injecteur angulaire n'est pas encore disponible, car angular n'a pas encore été initialisé. Par conséquent, l'erreur:
Erreur non capturée: tentative d'obtention de l'injecteur Angular avant de démarrer un module Angular.
Quelqu'un at-il une idée de la façon dont je pourrais résoudre ce problème?
Remarque: La réponse ci-dessous suit la convention d'appel angulaire 1.x en tant qu'angularjs et de toutes les versions angulaires 2+ en tant qu'angles simples.
En développant la réponse de JGoodgive ci-dessus, en gros, si vous utilisez downgradeModule
, alors le module angulaire est démarré paresseusement par angularjs lorsqu'il doit rendre le premier composant angulaire. Jusque-là, étant donné que le module angulaire n'est pas initialisé, si vous accédez à des services angulaires dans angularjs à l'aide de downgradeInjectable
, ces services ne sont pas disponibles.
La solution de contournement consiste à forcer le démarrage du module angulaire le plus tôt possible. Pour cela, un composant simple est nécessaire:
import {Component} from '@angular/core';
@Component({
selector: 'service-bootstrap'
template: ''
})
export class ServiceBootstrapComponent {}
Ce composant ne fait rien. Maintenant, nous déclarons ce composant dans le module angulaire de niveau supérieur.
@NgModule({
// ...providers, imports etc.
declarations: [
// ... existing declarations
ServiceBootstrapComponent
],
entryComponents: [
// ... existing entry components
ServiceBootstrapComponent
]
})
export class MyAngularModule {}
Ensuite, nous devons également ajouter une version dégradée de ce composant au module angularjs. (J'ai ajouté ceci au module angularjs de niveau supérieur que j'avais)
angular.module('MyAngularJSModule', [
// ...existing imports
])
.directive(
'serviceBootstrap',
downgradeComponent({ component: ServiceBootstrapComponent }) as angular.IDirectiveFactory
)
Enfin, nous ajoutons ce composant dans notre index.html.
<body>
<service-bootstrap></service-bootstrap>
<!-- existing body contents -->
</body>
Lorsque angularjs trouve ce composant dans le balisage, il doit initialiser le module angulaire pour pouvoir restituer ce composant. L'effet secondaire recherché est que les fournisseurs, etc., sont également initialisés et peuvent être utilisés avec downgradeInjectable
, qui peut être utilisé normalement.
Cela m'a été signalé dans un fil angulaire de github.
https://github.com/angular/angular/issues/16491#issuecomment-343021511
La réponse de George Kalpakas:
Juste pour être clair: Vous pouvez utiliser downgradeInjectable () avec downgradeModule (), mais il existe certaines limitations. En particulier, vous ne pouvez pas essayer d’injecter un injectable dégradé tant que l’initialisation de Angular n’est pas terminée. Et Angular est démarré (de manière asynchrone) lors du premier rendu d'un composant dégradé. Ainsi, vous ne pouvez utiliser en toute sécurité qu'un service dégradé à l'intérieur d'un composant dégradé (c'est-à-dire à l'intérieur de composants AngularJS mis à niveau).
Je sais que cela limite suffisamment pour que vous puissiez décider de ne pas utiliser downgradeInjectable () - je voulais simplement préciser davantage ce que vous pouvez et ne pouvez pas faire.
Notez que la limitation équivalente est vraie lorsque vous utilisez un injectable mis à niveau avec UpgradeModule: Vous ne pouvez pas l'utiliser jusqu'à ce que AngularJS ait été initialisé. Cependant, cette limitation passe généralement inaperçue, car AngularJS est généralement amorcé de manière synchrone dans la méthode ngDoBootstrap () du module Angular et AngularJS (contrairement à Angular).
Les réponses dans ce fil m'ont aidé à trouver une solution, mais aucune ne contient le Saint Graal:
service-boostrap
en dehors du code de l'application ne fonctionne pas, car Angular est chargé de manière asynchrone, contrairement à AngularJS. Cela donne la même erreur Trying to get the Angular injector before bootstrapping an Angular module.
service-bootstrap
enveloppant le type de code AngularJS a fonctionné, mais j'ai rencontré des problèmes avec la détection des modifications dans les composants Angular, comme décrit dans ce problème sous github .@angular/upgrade
afin de remplacer false
par true
afin de forcer la création de composants dans la Zone. Mais dans ce cas, cela semble causer des problèmes de performances (il semblait lancer le code de ngZone plusieurs fois sur les événements de l'utilisateur)service-bootstrap
Pour y parvenir, j'ai créé un composant service-bootstrap
légèrement modifié:
import { Component, Output, EventEmitter, AfterViewInit } from "@angular/core";
@Component({
selector: 'service-bootstrap',
template: ``
})
export class ServiceBootstrapComponent implements AfterViewInit{
@Output()
public initialized: EventEmitter<void> = new EventEmitter();
public ngAfterViewInit(){
this.initialized.emit();
}
}
A déclaré ce composant sous la forme entryComponent
dans le module Angular et appelé downgradeComponent
pour l'enregistrer dans AngularJS:
import { downgradeModule, downgradeInjectable, downgradeComponent } from '@angular/upgrade/static';
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(AppModule);
};
const downgradedModule = downgradeModule(bootstrapFn);
const app = angular.module('ngApp', [
downgradedModule,
'app'
]);
app.directive('serviceBootstrap', downgradeComponent({ component: ServiceBootstrapComponent }));
Puis (et la magie se produit ici), j'ai créé un nouveau composant AngularJS:
angular.module("app")
.directive('ng1App', ng1AppDirective);
function ng1AppDirective(){
return {
template: `
<service-bootstrap (initialized)="onInit()"></service-bootstrap>
<section ng-if="initialized">
<!-- Your previous app's code here -->
</section>
`,
controller: ng1AppController,
scope: {},
};
}
ng1AppController.$inject = ['$scope'];
function ng1AppController($scope){
$scope.onInit = onInit;
$scope.initialized = false;
function onInit(){
$scope.initialized = true;
}
}
Ensuite, mon index.html n'a référencé que ce composant
<body>
<ng1-app></ng1-app>
</body>
Avec cette approche, je ne fais pas imbriquer des composants AngularJS dans des composants Angular (ce qui annule la détection de modification dans les composants Angular), et je m'assure néanmoins qu'un premier composant Angular est chargé avant d'accéder à Angular fournisseurs.
J'ai eu le même problème et il a fallu attendre plusieurs heures avant de trouver ceci… .. Ma solution consistait à créer un composant ServiceBootstrapComponent qui n'injecte que tous les services que nous devons rétrograder.
Je rétrograde ensuite ce composant, le marque comme une entrée fr dans @NgModule et l'ajoute à index.html.
Travaille pour moi.