为什么我的混合Angular应用程序中没有注入菜单选项?

时间:2017-10-13 22:16:40

标签: angularjs angular angular-material

我正在尝试调试我在混合Angular / AngularJS应用程序中重新加载时收到的错误。您可以在stackblitz查看此示例。这种在Angular应用程序中引导AngularJS的方法是based on Victor Savkin work

TypeError: Cannot read property 'xPosition' of undefined
    at new MatMenu (VM4642 material.umd.js:13096)
    at createClass (VM4479 core.umd.js:10963)
    at createDirectiveInstance (VM4479 core.umd.js:10788)
    at createViewNodes (VM4479 core.umd.js:12229)
    at callViewAction (VM4479 core.umd.js:12675)
    at execComponentViewsAction (VM4479 core.umd.js:12584)
    at createViewNodes (VM4479 core.umd.js:12256)
    at createRootView (VM4479 core.umd.js:12124)
    at callWithDebugContext (VM4479 core.umd.js:13507)
    at Object.debugCreateRootView [as createRootView] (VM4479 core.umd.js:12824) "<my-layout class="ng-scope" ng-version="4.3.6">"

通过单步执行代码,我可以看到xPosition所在的对象是injected default options objectMdMenu取决于通过InjectionToken引用的此默认选项对象。但是在我的混合Angular / AngularJS应用程序中,重新加载页面时,此依赖关系将返回undefined。如果从另一个页面切换到该页面,则依赖性正确加载。

下面是dom结构的简化视图。请注意,ng1和ng2组件是隔行扫描的。上述错误是指顶级降级组件,而不是包含<md-menu>的降级组件。

<ng2-root>
  <router-outlet></router-outlet>
  <div ui-view>
    <downgraded-ng2-component>
      <ng1-component>
        <another-downgraded-ng2-component>
          <button md-icon-button [mdMenuTriggerFor]"toolbarMenu"></button>
          <md-menu #toolbarMenu="mdMenu">
            <button md-menu-item>...</button>
          </md-menu>
        </another-downgraded-ng2-component>
      </ng1-component>
    </downgraded-ng2-component>
  </div>
</ng2-root>

1 个答案:

答案 0 :(得分:1)

这里的问题是,不能保证一个模块的提供者将在另一个模块的提供者之前被实例化,除非有一些明确指定Angular需要它们的东西。

在您的情况下,例如,AngularJSModule(其构造函数引导应用程序的AngularJS部分)过渡依赖于MatMenu MAT_MENU_DEFAULT_OPTIONS注入。但是Angular并没有意识到这一点(因为它隐藏在AngularJS引导中)。

例如,如果您要从MAT_MENU_DEFAULT_OPTIONS明确声明对AngularJSModule的依赖性,那么一切都应该有效:

export class AngularJSModule {
  constructor(@Inject(MAT_MENU_DEFAULT_OPTIONS) ignored: object, upgrade: UpgradeModule) {
    ...
  }
}

注意:这仅用于说明目的。我实际上并不建议这样做。

虽然这种懒惰引导AngularJS的方法并不完全正式支持&#34;但是你应该能够通过确保引导AngularJS异步发生(为所有提供者提供时间 - 包括{ {1}} - 要初始化)。有几种方法可以实现这一目标。以下是几个例子:

  1. MAT_MENU_DEFAULT_OPTIONS的构造函数异步调用upgrade.bootstrap()

    AngularJSModule

    Updated demo 1

  2. export class AngularJSModule { constructor(upgrade: UpgradeModule) { Promise.resolve().then(() => { // Bootstrap AngularJS // ... }); } } 的{​​{1}}生命周期挂钩中拨打upgrade.bootstrap()。在这种情况下,我们需要确保只调用一次,因为EmptyComponent可以多次实例化:

    ngOnInit()

    Updated demo 2

  3. 最后请注意,我们通常建议在主EmptyComponent的{​​{1}}方法中调用let angularJsBootstrapped = false; @Component({template: ''}) export class EmptyComponent implements OnInit { constructor(private upgrade: UpgradeModule) {} ngOnInit() { // Ensure AngularJS is only bootstrapped once. if (!angularJsBootstrapped) { this.upgrade.bootstrap(document.body, [module.name]); setUpLocationSync(this.upgrade); angularJsBootstrapped = true; } } } 。但是由于upgrade.bootstrap()没有调用延迟加载的模块(因为它们在技术上不是自举的),所以你需要求助于上面显示的方法之一。