21

使用 Angular 5.0,升级模块现在可以选择使用 downgradeModule,它在 angular 区域之外运行 angularjs。在对此进行试验时,我遇到了使用 downgradeInjectable 的问题。

我收到错误:

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

在 Angular js 中引导 Angular 工作正常

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

然而...

由于在初始化 angularjs 之后进行引导,我无法再让降级注入工作。

服务要降级

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

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

}

降级注射尝试

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;

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

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

有谁知道我该如何解决这个问题?

4

6 回答 6

16

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

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

为了实现这一点,我创建了一个稍作修改的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();
    }
}

将该组件声明为entryComponent在 Angular 模块中并调用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 组件。

于 2018-07-04T13:42:28.470 回答
11

注意:下面的答案遵循将 angular 1.x 称为 angularjs 并将所有 angular 2+ 版本称为简单 angular 的约定。

扩展上面 JGoodgive 的答案,基本上,如果您使用downgradeModule,则 angular 模块在需要渲染第一个角度组件时由 angularjs 懒惰地引导。在那之前,由于没有初始化 angular 模块,如果您使用 angularjs 访问任何 angular 服务downgradeInjectable,这些服务也不可用。

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

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

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

这个组件不做任何事情。现在,我们在顶级 Angular 模块中声明这个组件。

@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 在标记中找到该组件时,它需要初始化 Angular 模块才能呈现该组件。这样做的预期副作用是提供程序等也被初始化并且可以与 一起使用downgradeInjectable,这可以正常使用。

于 2018-02-07T16:23:22.780 回答
10

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

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

乔治卡尔帕卡斯的回应:

明确一点:您可以将 downgradeInjectable() 与 downgradeModule() 一起使用,但有一定的限制。特别是,在引导 Angular 之前,您不能尝试注入降级的可注入对象。第一次渲染降级的组件时,Angular 会被引导(异步)。因此,您只能在降级组件内(即在升级的 AngularJS 组件内)安全地使用降级服务。

我知道这已经足够限制你可能决定根本不使用 downgradeInjectable() - 只是想更清楚你能做什么和不能做什么。

请注意,当使用带有 UpgradeModule 的升级注入时,等效限制是正确的:在引导 AngularJS 之前,您不能使用它。不过,这个限制通常不会被注意到,因为 AngularJS 通常在 Angular 模块的 ngDoBootstrap() 方法中引导,而 AngularJS(与 Angular 不同)同步引导。

于 2017-11-09T18:43:41.517 回答
2

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

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

脚步

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

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

    const service = this.$injector.get('service');
    service.method();
    
于 2019-05-29T06:56:39.850 回答
0

我有同样的问题,在找到这个之前它已经吸收了几个小时。我的解决方法是创建一个 ServiceBootstrapComponent,它什么都不做,只注入我们需要降级的所有服务。

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

为我工作。

于 2018-01-17T10:22:16.997 回答
0

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

  • AngularJS 1.7.x
  • 角 7.3.x

正如这个答案中提到的,我还使用了一个名为<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!');
  });

您可以在本博文末尾找到有关此解决方案的更多详细信息。

于 2019-06-05T13:41:03.433 回答