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

the*_*Owl 12 javascript ng-upgrade angular

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

我收到错误:

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

角度js中的自举角度很好

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 ),
] );
Run Code Online (Sandbox Code Playgroud)

然而...

由于bootladpping是在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,
    ) {}

}
Run Code Online (Sandbox Code Playgroud)

降级注射尝试

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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

the*_*Owl 7

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

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

乔治·卡尔帕卡斯(George Kalpakas)的回应:

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

我知道这样做的局限性足以让您决定根本不使用downgradeInjectable()-只是想更清楚地说明您可以做什么和不能做什么。

请注意,当使用带有UpgradeModule的升级注射剂时,等效限制为true:在启动AngularJS之前,不能使用它。但是,通常不会注意到此限制,因为AngularJS通常在Angular模块的ngDoBootstrap()方法中引导,并且AngularJS(与Angular不同)是同步引导程序。

  • 平稳,自从切换到downgradeModule()方法以来,总体来说还不错。upgradeModule()方法对我们不起作用,因为考虑到我们自下而上的升级方法,性能非常糟糕。在非常大的应用程序上使用升级模块时,触发的摘要过多会使应用程序无法使用。自从切换到downgradeModule以来,一切都非常顺利,但是我们一直在解决该问题中提出的问题。 (5认同)

Aka*_*wal 7

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

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

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

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

@Component({
  selector: 'service-bootstrap'
  template: ''
})
export class ServiceBootstrapComponent {}
Run Code Online (Sandbox Code Playgroud)

该组件不执行任何操作.现在,我们在顶级角度模块中声明此组件.

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

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

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

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

<body>
  <service-bootstrap></service-bootstrap>
  <!-- existing body contents -->
</body>
Run Code Online (Sandbox Code Playgroud)

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

  • 万分感谢。就我而言,我使用“ng-content”创建了一个容器,它也充当 AngularJS 应用程序的引导元素。 (2认同)

ghu*_*sse 7

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

  1. service-boostrap除了AngularJS之外,Angular是异步加载的,因此无法在应用程序的代码旁边创建组件。这给出了相同的错误Trying to get the Angular injector before bootstrapping an Angular module.
  2. 创建一个service-bootstrap包装了AngularJS代码的组件的工作原理,但是随后我遇到了Angular组合者内部的变更检测问题,如github上的问题所述。
  3. 在github问题中,有人建议编辑@angular/upgrade源代码以更改falsetrue以强制在区域中创建组件。但是在这种情况下,这似乎会导致性能问题(它似乎在用户事件中多次启动ngZone的代码)
  4. 为了使该应用正常运行,我需要:
    1. ng组件不应包含AngularJS组件,而AngularJS组件应包含Angular组件。我们只需要包含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();
    }
}
Run Code Online (Sandbox Code Playgroud)

将此组件声明为entryComponentAngular模块中的组件,并调用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 }));
Run Code Online (Sandbox Code Playgroud)

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

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

<body>
  <ng1-app></ng1-app>
</body>
Run Code Online (Sandbox Code Playgroud)

使用这种方法,我没有将AngularJS组件嵌套在Angular组件内(这会中断Angular组件中的更改检测),并且仍然确保在访问Angular提供程序之前已加载第一个Angular组件。