【问题标题】:AngularJS: How to nest applications within an angular appAngularJS:如何在 Angular 应用程序中嵌套应用程序
【发布时间】:2018-05-01 18:09:09
【问题描述】:

我一直在从事一个更像框架的项目,并且有几个可以安装的应用程序/模块。将其视为基本的应用商店或 google.play 商店。它是一种内网应用程序,所有模块都可以添加到您的用户帐户中。

该框架已经在开发中,但我现在正在考虑应用程序/模块的想法。 (link to a proof of concept in development, can be found here)

应用程序应该有点独立,并且不能突然包含框架中的脚本,这完全可以通过将它们构建在单独的模块中,如下所示:

angular.module('myApp', []);

然而,一个应用程序可以有模板、脚本、css,它可以在单独的服务器上运行,所以我正在寻找获取脚本和 cssfile(s) 并动态加载它们的最佳方法当用户从框架内启动app 时进入应用程序。

  • 目前我正在构建应用程序,就好像它们有一个主模板,例如 www.framework.com/apps/myapp/views/app.html,为了简单起见,我将脚本捆绑到每个应用程序的 1 个脚本文件中,因此还有一个 www.framework.com/apps/myapp/script.js 被包括在内。

该框架包含一个加载应用程序的模板和一个appController。模板包含这块:

<div data-ng-controller="AppController" data-ng-include="app.appTemplate">
    <div>loading...</div>
</div>

这基本上绑定到$scope.app.appTemplate,它会在所有脚本加载时更新,所以首先它显示一个加载模板,然后在页面中包含脚本后,它会将app.appTemplate更新为上面提到的主模板应用。

在加载第一个索引模板时,该模板当前加载了框架中的AppController,因此它使用的是框架的$scope,而不是它自己的脚本。

我仍然必须以某种方式启动应用程序自己的 Angular 模块,让它自己运行,而不需要在框架中运行任何东西来“让它工作”

我仍在研究如何最好地加载依赖的 javascript 文件(可能会使用 requrejs 或其他依赖加载器),但我目前不知道如何在不使用框架的 AppController

编辑

我创建了一个小型演示项目来展示手头的问题,full code is visible at git-hub 目前这个项目做了一些硬编码的事情,我的想法是当我得到正确的概念证明时,我会减少那些硬编码,现在都是关于在框架中加载应用程序。如果可能的话,我可以考虑从哪里获取 URL 和应用程序名称...

【问题讨论】:

    标签: angularjs angularjs-scope


    【解决方案1】:

    您不能在另一个引导模块中引导模块。 Bootstrapping 编译视图并将 rootScope 绑定到它,遍历 DOM 并设置范围绑定并一直执行指令链接函数。如果你这样做两次,你就会遇到问题。

    您可能不得不重新考虑您的架构。我认为与 Angular 相关的“模块”或“应用程序”这个词可能用词不当,会导致你走错路。

    您的应用程序中的每个“用户安装的应用程序”都可能真正由您的应用程序模块中的控制器控制,或者注册到您的应用程序模块引用的模块。所以你不会“启动多个应用程序”,你实际上只是启动一个,引用其他模块,然后使用这些模块中的控制器来控制屏幕上的部分视图。

    你要做的是当一个新的“小部件”被安装时,你向系统注册它的模块文件(.js),其中包含一个名为 WidgetCtrl 的控制器,然后当你的页面加载时,你会在您的应用模块上引用小部件的模块。从那里应该可以使用 ng-controller 和/或 ng-include 动态分配给元素。

    我希望这是有道理的。

    【讨论】:

    • 但是我的依赖数组中不是有 100 个 appmodules 吗?以及那 100 个依赖项,那么它们不是必须在页面加载时加载吗?我正在尝试动态注入,只是因为我可以在需要时插入脚本,而不是在页面加载时。
    • 这就是我们解决它的方法:我们有 1 个framework 模块,它的依赖项中有一个framework.applications 模块。现在,当我们为应用程序动态加载脚本时,它会将控制器绑定到该 framework.applications 模块。 (通过$controllerProvider.register() 函数。所以基本上我们像你说的那样重组了我们的架构,让控制器成为应用程序的逻辑,而不是每个应用程序都有一个模块。
    • 路由呢?模块添加后是否可以将新路由添加到配置中?
    • 是的,路由存储在一个配置中,并在应用加载时注册到 routeprovider。然而,这不是一个故障保存系统,我们了解到路由往往会产生冲突,/:appname/:otherparameter 是一个让我们头疼的问题。因此,在注册应用程序的路由时,我们取消了 :appname 作为参数,并将其注册为 /app4/:otherparameter。
    • 引导后是否动态注册了路由?如果用户使用带有书签的 URL 进入应用程序,它的路由是正确的还是命中了“否则”的路由呢?我们正在尝试构建一个类似的系统,并且在如何将应用程序彼此隔离方面遇到了很多问题。阅读一篇关于您如何构建系统的博客文章会很棒。
    【解决方案2】:

    与当前接受的答案相反,实际上是可能的。

    我正在处理类似的问题,建议的答案在我的情况下是不可接受的。我以前写过包含多个应用程序的页面,但那是几年前的事了,应用程序彼此独立。基本上有两件事要做:

    • 告诉主应用程序忽略一个子元素。
    • 引导子元素。

    有一个ng-non-bindable 属性简单地告诉AngularJS 忽略该元素。这解决了我们的第一个问题。

    但是,当您尝试引导子元素时; AngularJS 会抛出一个错误,告诉你它已经被引导(至少对我来说,1.2.13 版)。以下技巧可以完成工作:

    <div ng-non-bindable data-$injector="">
      <div id="bootstrap-me">
        <script src="/path/to/app.js"></script>
        <div ng-include="'/path/to/app.html'"/>
      </div>
    </div>
    

    这个解决方案并不完美。理想情况下,ng-non-bindable 属性可以将所需的 data-$injector 属性添加到元素。我将制作一个功能,并希望对 AngularJS 提出拉取请求。


    我没有机会提出拉取请求。显然,我应该说,一些内部结构发生了变化,但 ng-non-bindable 仍在使用 Ventzy Kunev 的演示代码在 1.3.13 版本中工作(再次感谢,请参见下面的链接)。

    【讨论】:

    • 这真的很有帮助,正是我所需要的。我什至不必添加data-$injector(Angular 1.3)。如果没有该解决方案,我永远无法在嵌套应用程序中使用指令 - 非常感谢!
    • 这对我来说过去工作得很好,在角度 1.2.16 中。我刚刚尝试了 1.2.26 和 1.3.0-rc4,这两个版本现在都抛出异常:Error: [ng:btstrpd] App Already Bootstrapped with this Element '&amp;lt;div id="viewAppDiv"&amp;gt;'... 其他人有这个问题吗?
    • 这必须是公认的答案。这是 plunkr,它演示了将应用程序嵌入模态 plnkr.co/edit/AVD1BVGb5T5GRH3QwBvl?p=preview 两个应用程序 - “modal-app”和“list-app”。 modal-app 显示模态,但其内容由嵌入列表应用中的控制器管理。由于模态的 html 在模态打开之前不会呈现的模板中,因此列表应用程序的引导在此之后完成 - 请参阅 modal.js 的末尾。在实际应用程序中,我使用“通知程序”服务,在呈现模式后触发事件,然后我的“管理器”类引导所需的应用程序和模块彼此不知道。
    • 嗨 orca,你是否向 AngularJS 提出了拉取请求,如果是的话,自从它被拉取以来事情发生了怎样的变化——尤其是关于 @simon-green 的评论——现在应该是对更新版本的 Angular 进行不同的声明?
    • 从 Angular 1.4.7 开始对我来说工作正常,我真的很喜欢这个解决方案,但有一个问题,你需要先引导内部应用程序。真可惜。
    【解决方案3】:

    如果每个子应用程序都在自己的模块中,您可以使用 angular.bootstrap 动态加载该模块。当特定应用程序的 url 加载时,您可以获取必要的脚本,然后当 promise 解决时,您可以执行以下操作:

    // grab a reference to the element where you'll be loading the sub-app
    var subapp = document.getElementById('subapp-id');
    
    // assuming the script you get back contains an angular module declaration named
    // 'subapp', manually start the sub-app
    angular.bootstrap(angular.element(subapp), ['subapp']);
    

    希望对你有帮助

    【讨论】:

    • 我试过那个路径,你可以在 github repo 中看到它,在framework.appController 中,检查$routeParam 并加载带有承诺的脚本,加载脚本后执行的那个: angular.bootstrap($('#application-container'), [$scope.app.name]); #application-container 存在于应用程序中加载的 html 中,app.name 与模块名称相同。但这显然不能解决问题。应用的模板找不到要加载的控制器。
    • 我希望你想在另一个模块的 ng-view 中引导一个模块不是问题(框架的 ng-app 在 body 标签上运行,而应用程序只是内容区)
    • 啊,好吧,我在你的代码中寻找类似的东西,看看你是否已经走这条路了。我想我错过了,抱歉。有趣的问题,我会继续努力的。
    【解决方案4】:

    与上面 UnicodeSnowman 的回答类似,另一个似乎可以满足我需求的潜在解决方案(我在文档站点上构建了一个实时 Angular 编辑器)是通过使用独立于主&lt;div id="myApp"&gt;.

    This article 非常有助于让它正常工作。

    一般流程

    • 创建您的主应用程序(我选择手动引导,但您可以在这部分使用ng-app
    • 创建一个新的 HTML 结构/应用程序(在我的例子中是演示应用程序):
    • 使用自定义 id 将其附加到演示 div:someCoolDemoContainer
    • Boostrap 新创建的应用
    • 将其移回原始应用程序(用于布局/定位目的)

    代码示例 (未测试;仅显示基本流程)

    <div id="myApp">
        <h1>Demo</h1>
    
        <p>Click the button below to checkout the cool demo!</p>
    
        <button ng-click="showDemo()">Show Demo</button>
    
        <div class='insertion-point'></div>
    </div>
    
    <div id="demos">
    </div>
    
    <script type="text/javascript">
        /*
         * Init/bootstrap our main app
         */
        var appContainer = document.getElementById('myApp');
    
        angular.module('myApp', ['myDependency']);
        angular.bootstrap(appContainer, ['myApp']);
    
        // Do lots of other things like adding controllers/models/etc.
    
        /*
         * Init/bootstrap our demo app when the user clicks a button
         */
        function showDemo() {
          // Append our demo code
          $('#demos').append('<div id="someCoolDemoContainer">Angular app code goes here</div>');
    
          // Bootstrap the new app
          var demoContainer = document.getElementById('someCoolDemoContainer');
          angular.module('someCoolDemo', ['myDependency']);
          angular.module('someCoolDemo').controller('myController', function() { ... });
    
          angular.bootstrap(demoContainer, ['someCoolDemo']);
    
          // Re-insert it back into the DOM where you want it
          $('#myApp').find('.insertion-point').append($('#someCoolDemoContainer'));
        }
    </script>
    

    【讨论】:

    • 关于这一点的快速说明,我刚刚意识到:每次引导时(在这种情况下,每次我们展示演示时),您的主应用程序都会重新运行所有配置函数。因此,正如我在上面所展示的,您需要确保有一个公共模块可供两个应用程序用作依赖项,而不是直接依赖于应用程序的演示模块。
    【解决方案5】:

    我知道这已经很老了,但我正在寻找一种将 AngularJS 应用程序嵌入到 Angular 应用程序中的方法,并使用这篇文章的答案来做到这一点,所以我想我会在这里为任何人发布 plunker否则正在寻找类似的解决方案。

    我发现有两种方法可以做到这一点,都使用 Angular 组件的 ngOnInit 中手动引导 angularjs 应用程序:

    ngOnInit(): void {
      // manually bootstrap the angularjs app
      angular.bootstrap(document.getElementById('ngListApp'), ['list-app']);
    }
    

    在将被引导的元素上设置 ngNonBindable 属性:

    <div ngNonBindable #insert>
      <!-- can insert the angular js template directly here, inside non-bindable element -->
      <div id="ngListApp" ng-controller="ListController as list">
        <input ng-model="inputValue" />
        <ul>
          <li ng-repeat="item in items">{{ item }}</li>
        </ul>
      </div>
    </div>
    

    或者将 angularjs 模板 html 注入到 Angular 组件内的 ngOnInit 事件处理程序中的元素中,这样 Angular 在编译期间就不会尝试解释 AngularJS 代码(尤其是 DOM 中带有大括号的 AngularJS 属性的插值)。

    ngOnInit(): void{
      // insert angularjs template html here
      this.div.nativeElement.innerHTML = this.htmlTemplate;
    
      // then manually bootstrap the angularjs app
      angular.bootstrap(document.getElementById('ngListApp'), ['list-app']);
    }
    

    Plunker 在这里:

    http://plnkr.co/plunks/0qOJ6T8roKQyaSKI

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-27
      • 1970-01-01
      • 2015-02-15
      • 1970-01-01
      • 2016-06-09
      • 1970-01-01
      • 2018-03-12
      相关资源
      最近更新 更多