web-dev-qa-db-fra.com

L'utilisation de downgradeModule avec downgradeInjectable dans une application hybride angular/angularjs génère une erreur

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?

8
theOwl

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.

6
Akash Agrawal

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).

4
theOwl

Les réponses dans ce fil m'ont aidé à trouver une solution, mais aucune ne contient le Saint Graal:

  1. La création d'un composant 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.
  2. La création d'un composant 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 .
  3. Dans le problème github, quelqu'un a suggéré de modifier le code source @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)
  4. Pour que l'application fonctionne correctement, j'avais besoin de:
    1. Ne pas avoir de composants contenant des composants AngularJS contenant des composants Angular. AngularJS ne doit contenir que des composants Angular.
    2. Assurez-vous que les composants AngularJS utilisant les services Angular sont créés après un premier composant angulaire, nommé 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.

3
ghusse

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.

0
JGoodgive