在angular / angularjs混合应用程序中使用downgradeModule和downgradeInjectable会导致错误

时间:2017-11-08 21:23:49

标签: angular ng-upgrade

使用角度5.0,升级模块现在可以选择使用downgradeModule,它在角度区域之外运行angularjs。在尝试这个时,我遇到了使用downgradeInjectable的问题。

我收到错误:

未捕获错误:在引导Angular模块之前尝试获取Angular注入器。

角度js中的自举角度正常

import requests

def get_weather_json(city, country):
    R = requests.get("http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + country + "&appid=xxx")
    return R.json()

然而...

由于在初始化angularjs之后进行自举,我不能再使降级注射工作。

要降级的服务

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 ),
] );

降级可注射尝试次数

import { Injectable, Inject, OnInit } from '@angular/core';

@Injectable()
export class MobileService implements OnInit{
    constructor(
        @Inject( 'angularjsDependency1' ) public angularjsDependency1 : any,
        @Inject( 'angularjsDependency2' ) public angularjsDependency2 : any,
    ) {}

}

当“downgradeInjectable(MyService)运行时,角度注入器尚未可用,因为角度尚未被引导。因此错误:

未捕获错误:在引导Angular模块之前尝试获取Angular注入器。

有没有人知道如何解决这个问题?

6 个答案:

答案 0 :(得分:7)

注意:下面的答案遵循将angular 1.x称为angularjs而将所有angular 2+版本称为简单角度的惯例。

扩展JGoodgive上面的答案,基本上,如果你正在使用downgradeModule,那么当需要渲染第一个角度分量时,angularjs会懒散地自行训练角度模块。在此之前,由于角度模块未初始化,如果您使用downgradeInjectable访问angularjs内的任何角度服务,则这些服务也不可用。

解决方法是尽早强制启动角度模块。为此,需要一个简单的组件:

import {Component} from '@angular/core';

@Component({
  selector: 'service-bootstrap'
  template: ''
})
export class ServiceBootstrapComponent {}

这个组件没有做任何事情。现在,我们在顶级角度模块中声明此组件。

@NgModule({
  // ...providers, imports etc.
  declarations: [
    // ... existing declarations
    ServiceBootstrapComponent
  ],
  entryComponents: [
    // ... existing entry components
    ServiceBootstrapComponent
  ]
})
export class MyAngularModule {}

接下来,我们还需要将此组件的降级版本添加到angularjs模块。 (我把它添加到了我的顶级angularjs模块中)

angular.module('MyAngularJSModule', [
  // ...existing imports
])
.directive(
  'serviceBootstrap',
  downgradeComponent({ component: ServiceBootstrapComponent }) as angular.IDirectiveFactory
)

最后,我们在index.html中引入了这个组件。

<body>
  <service-bootstrap></service-bootstrap>
  <!-- existing body contents -->
</body>

当angularjs在标记中找到该组件时,它需要初始化角度模块以便能够呈现该组件。这样做的预期副作用是提供者等也被初始化,并且可以与downgradeInjectable一起使用,这可以正常使用。

答案 1 :(得分:6)

这是在一个有角度的github线程中向我指出的。

https://github.com/angular/angular/issues/16491#issuecomment-343021511

  

George Kalpakas的回应:

     

要明确:   您可以将downgradeInjectable()与downgradeModule()一起使用,但存在某些限制。特别是,在Angular被引导之前,你不能尝试注射降级的注射剂。并且Angular在第一次呈现降级的组件时被引导(异步)。因此,您只能在降级的组件内(即在升级的AngularJS组件内部)安全地使用降级服务。

     

我知道这是有限的,你可能决定不使用downgradeInjectable() - 只是想让你更清楚你能做什么和不做什么。

     

请注意,使用带有UpgradeModule的升级注射器时,等效限制为真:在AngularJS被引导之前,您无法使用它。这种限制通常不会引起注意,因为AngularJS通常在Angular模块的ngDoBootstrap()方法和AngularJS(与Angular不同)bootstraps中同步引导。

答案 2 :(得分:4)

此主题中的答案帮助我找到了解决方案,但没有一个包含圣杯:

  1. 在应用程序的代码旁边创建service-boostrap组件是行不通的,因为与AngularJS不同,Angular是异步加载的。这给出了相同的错误Trying to get the Angular injector before bootstrapping an Angular module.
  2. 创建一个service-bootstrap组件来包装AngularJS代码,但是,然后我遇到了this issue on github中所述的Angular构成者内部的更改检测问题。
  3. 在github问题中,有人建议编辑@angular/upgrade源代码,以将false更改为true,以强制在区域中创建组件。但是在这种情况下,这似乎会导致性能问题(它似乎在用户事件上多次启动ngZone的代码)
  4. 为了使应用正常运行,我需要:
    1. 不要让包含AngularJS的ng组件包含Angular组件。我们只需要包含Angular组件的AngularJS。
    2. 确保在名为service-bootstrap的第一个角度组件之后创建使用Angular服务的AngularJS组件。

为此,我创建了一个经过稍微修改的service-bootstrap组件:

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();
    }
}

在Angular模块中将此组件声明为entryComponent,并调用downgradeComponent在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 }));

然后(魔术发生在这里),我创建了一个新的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;
    }
}

然后,我的index.html仅引用了此组件

<body>
  <ng1-app></ng1-app>
</body>

通过这种方法,我没有将AngularJS组件嵌套在Angular组件中(这会破坏Angular组件中的更改检测),但仍然确保在访问Angular提供程序之前先加载了第一个Angular组件。

答案 3 :(得分:0)

我遇到了同样的问题,在发现这个问题之前几个小时就搞砸了。 我的解决方法是创建一个ServiceBootstrapComponent,它只会注入我们需要降级的所有服务。

然后我降级该组件,将其标记为@NgModule中的条目并将其添加到index.html。

适合我。

答案 4 :(得分:0)

我有同样的问题,原因在上面的答案中得到了解释。

我通过使用$injector动态注入降级的角度服务来解决此问题。

步骤

  • 将降级的服务注册到angularjs模块

    angular.module('moduleName', dependencies)    
    angular.factory('service', downgradeInjectable(Service));
    
  • 向您的控制器注入$injector并使用它来获得降级的服务

    const service = this.$injector.get('service');
    service.methos();
    

答案 5 :(得分:0)

我在混合应用程序中遇到了同样的错误。我们正在使用以下版本:

  • AngularJS 1.7.x
  • 角度7.3.x

in this answer所述,我还使用了一个名为<ng2-bootstrap>的虚拟组件来强制Angular加速。然后,我创建了一个AngularJS服务,用于检查Angular是否已被引导:

// tslint:disable: max-line-length
/**
 * This service can be used in cases where Angular fails with following error message:
 *
 * `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`
 *
 * Above error occurs because of how `downgradeModule` works.
 */

/*@ngInject*/
export class Ng2BootstrapDetectionService {
  private bootstrapDone = false;
  constructor(private $q: ng.IQService) {}

  public whenBootstrapDone(): ng.IPromise<void> {
    if (this.bootstrapDone) {
      return this.$q.resolve();
    }

    const deferred = this.$q.defer<void>();

    angular.element(document).ready(() => {
      const intervalId = setInterval(() => {
        const el = document.querySelector('ng2-bootstrap');
        if (el && el.outerHTML.includes('ng-version=')) {
          this.bootstrapDone = true;
          clearInterval(intervalId);
          deferred.resolve();
        }
      }, 500);
    });

    return deferred.promise;
  }
}

Ng2BootstrapDetectionService的用法如下:

import {NotificationService} from 'ng2-app/notification.service';

// This can be used in cases where you get following error:
// `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`

// You will need access to following
// $injector: AngularJS Injector
// Ng2BootstrapDetectionService: our custom service to check bootsrap completion
this.Ng2BootstrapDetectionService
  .whenBootstrapDone()
  .then(() => {
    const notificationService = this.$injector
      .get<NotificationService>('ng2NotificationService');
    notificationService.notify('my message!');
  });

您可以找到有关此解决方案at the end of this blog post的更多详细信息。