【问题标题】:Using angularjs with turbolinks将 angularjs 与 turbolinks 一起使用
【发布时间】:2013-01-25 17:11:16
【问题描述】:

我正在尝试在我的应用程序中使用 Angularjs 框架和 turbolinks。页面更改后,它不会初始化新的事件监听器。有什么办法让它工作吗?提前致谢!

【问题讨论】:

  • 在客户端使用带有 MVC 框架的 turbolinks 是不是一个好主意。 turbolinks 不是为了将 Javascript 减少到最低限度,以便仅在 Ruby 中编码以服务客户端页面以及运行服务器代码吗?

标签: ruby-on-rails angularjs turbolinks


【解决方案1】:

AngularJS 与 Turbolinks

TurbolinksAnguluarJS 都可以用来使 Web 应用程序响应更快,因为响应用户交互,网页上会发生一些事情无需重新加载和重新呈现整个页面。

它们在以下方面有所不同:

  • AngularJS 可帮助您构建富客户端应用程序,您可以在其中编写大量运行在客户端计算机上的 JavaScript 代码。此代码使站点与用户交互。它使用 JSON API 与服务器端后端(即 Rails 应用程序)进行通信。

  • 另一方面,
  • Turbolinks 有助于使网站具有交互性,而无需您编写 JavaScript 代码。它允许您坚持使用在服务器端运行的 Ruby/Rails 代码,并且仍然“神奇地”使用 AJAX 替换并因此仅重新呈现已更改的页面部分。

Turbolinks 的强大之处在于允许您使用这种强大的 AJAX 机制而无需手动执行任何操作,只需编写 Ruby/Rails 代码,随着您的应用程序的增长,您可能会想要集成 JavaScript 框架,例如 AngularJS .

特别是在这个中间阶段,您希望将 AngularJS 连续集成到您的应用程序中,一次一个组件,将 Angular JS 和 Turbolinks 一起运行是非常有意义的。

如何同时使用 AngularJS 和 Turbolinks

使用回调手动引导 Angular

在您的 Angular 代码中,您有一行定义您的应用程序模块,如下所示:

# app/assets/javascripts/angular-app.js.coffee
# without turbolinks
@app = angular.module 'MyApplication', ['ngResource']

此代码在页面加载时运行。但是由于 Turbolinks 只是替换了页面的一部分并阻止了整个页面的加载,因此您必须确保 angular 应用程序已正确初始化(“引导”),即使在 Turbolinks 完成此类部分重新加载之后也是如此。因此,将上面的模块声明替换为以下代码:

# app/assets/javascripts/angular-app.js.coffee
# with turbolinks
@app = angular.module 'MyApplication', ['ngResource']

$(document).on 'turbolinks:load', ->
  angular.bootstrap document.body, ['MyApplication']

不要自动引导

您经常在教程中看到如何通过在 HTML 代码中使用 ng-app 属性自动引导 Angular 应用程序。

<!-- app/views/layouts/my_layout.html.erb -->
<!-- without turbolinks -->
<html ng-app="YourApplication">
  ...

但是将这种机制与上面显示的手动引导一起使用会导致应用程序引导两次,因此会导致应用程序停止。

因此,只需删除此ng-app 属性:

<!-- app/views/layouts/my_layout.html.erb -->
<!-- with turbolinks -->
<html>
  ...

进一步阅读

【讨论】:

  • A 尝试了引导技术。但是,我遇到了一个问题,现在在我的指令上创建了多个事件。例如,如果 turbolinks 加载一个视图:ng-click="save()" 然后重新加载视图...我的 save() 方法现在触发了 3 次。有什么想法吗?
  • 我能够通过将 Angular 移动到我们管理员的 &lt;head&gt; 部分来解决多个事件问题和可能的其他错误场景(因此,防止了我的脚本的多个实例化)。这不是面向公众的体验,所以我不太担心在 html head 部分加载 JS 的陷阱。
  • 这个方法对我来说非常适合 angularjs 1.0.6。但是在升级到 angularjs 1.2.0.rc3 之后,我在通过 turbolinks 导航到新页面后在控制台中得到了这个:Uncaught Error: [ng:btstrpd] App Already Bootstrapped with this Element 'document'。显然一个元素只能是bootstrapped once。将angular.bootstrap(document, ['YourApplication']) 更改为angular.bootstrap("body", ['YourApplication']) 似乎没有任何问题。
  • 我必须使用“body”,但还必须从$(document).on 行中删除“ready”,并添加了$ -&gt;
  • 很好的答案!如果使用 Turbolinks 5,则必须将事件名称更改为 $(document).on "turbolinks:load", -&gt; …。谢谢!
【解决方案2】:

Turbolinks 试图优化页面的渲染,会与 AngularJS 的正常引导冲突。

如果您在应用的某些地方使用 Turbolinks,而某些部分使用 Angular。我提出这个优雅的解决方案:

每个指向 angularapp 页面的链接(使用 ng-app="appname")都应该有这个属性:

<a href="/myapp" data-no-turbolink>Say NO to Turbolinks</a>.

第二个 - 在 Stackoverflow 上提到的是通过处理 page:load 事件显式地重新加载/引导每个 ng-app。我认为这是侵入性的,更不用说您可能会加载页面上没有的内容,从而浪费资源。

我个人使用过上述解决方案。

希望对你有帮助

【讨论】:

  • 它在非常大的应用程序中很有用,可以最大限度地减少冲突
  • 感谢反馈:)
【解决方案3】:

如果出现错误

未捕获的错误:[ng:btstrpd] 应用程序已通过此引导 元素“文档”

升级到 Angular 1.2.x 后,您可以使用下面的方法来解决问题。

angular.module('App').run(function($compile, $rootScope, $document) {
  return $document.on('page:load', function() {
    var body, compiled;
    body = angular.element('body');
    compiled = $compile(body.html())($rootScope);
    return body.html(compiled);
  });
});

在之前的帖子中,@nates 建议将 angular.bootstrap(document, ['YourApplication']) 更改为 angular.bootstrap("body", ['YourApplication']),但这会导致未编译内容的闪现。

【讨论】:

    【解决方案4】:

    将以下事件处理程序添加到您的应用程序。

    咖啡脚本:

    bootstrapAngular = ->
      $('[ng-app]').each ->
        module = $(this).attr('ng-app')
        angular.bootstrap(this, [module])
    
    $(document).on('page:load', bootstrapAngular)
    

    Javascript:

    function bootstrapAngular() {
      $('[ng-app]').each(function() {
        var module = $(this).attr('ng-app');
        angular.bootstrap(this, [module]);
      });
    };
    $(document).on('page:load', bootstrapAngular);
    

    这将导致在 Turbolinks 加载每个页面后启动 Angular 应用程序。

    感谢https://gist.github.com/ayamomiji/4736614

    【讨论】:

    • 真的很难采用 Coffeescript!这个的非咖啡版本对我来说更干净。
    • 这对我有用,除非您直接访问该页面。糟透了!
    • 是的,更清楚地说,这仅涵盖 Turbolinks 加载的页面。您还需要包含$(document).on('ready', bootstrapAngular)
    • 不,我打败了你。你应该在收听准备和页面:加载,但你不应该使用 ng-app。当 Angular 看到 ng-app 时,它会尝试加载你的模块,但为时过早。使用另一个自定义属性,例如“my-app”而不是“ng-app”来避免这个问题。我的问题是它试图在之前的代码实际加载到我的模块中之前加载我的模块。
    • 这比公认的解决方案效果更好。你会因为不使用 javascript 而投反对票,而对正确的解决方案投赞成票。
    【解决方案5】:

    Turbolinks 对于客户端 MVC 框架不太有意义。 Turbolinks 用于从服务器响应中去除除正文之外的所有内容。使用客户端 MVC,您应该只将 JSON 传递给客户端,而不是 HTML。

    无论如何,turbolinks 都会创建自己的回调。

    page:load
    page:fetch
    page:restore
    page:change
    

    【讨论】:

    • 我只在我的应用程序的几个页面上使用了 Angular js。而且我认为有任何方法可以在不放弃 turbolinks 的情况下做到这一点
    • 我已经尝试挂钩回调,它会导致 window.history 和 location 出现问题。我很难清楚地表达它们,但我可以告诉你,实现并不简单,禁用 turbolinks 可以解决问题,参考:stackoverflow.com/questions/30804236/…
    【解决方案6】:

    jquery.turbolinks 插件可以通过 ng-app 指令触发模块的引导。如果您尝试手动引导模块,jquery.turbolinks 可能会导致 ng:btstrpd 错误。我发现的一个警告是 jquery.turbolinks 依赖于 page:load 事件,该事件可以在任何新的页面特定 &lt;script&gt; 标记完成运行之前触发。如果您在 application.js 之外包含模块定义,这可能会导致 $injector:nomod 错误。如果您确实希望在仅包含在某些页面上的单独 javascript 文件中定义您的模块,您可以通过 data-no-turbolink 在指向这些特定页面的任何链接上 disable turbolinks

    【讨论】:

    • 我应该在发布之前仔细阅读此内容,这至少部分回答了我在帖子中的问题。当我有一个使用前端框架的应用程序时,还有什么其他有用的信息可以了解 Turbolinks 的工作原理吗?
    【解决方案7】:

    根据我看到的 cmets,如果我有一个现有的应用程序,那么以 Angular 与 Turbolinks 冲突的方式(例如我允许 Angular 处理某些路由的地方)同时使用两者的唯一有效场景是我正在尝试移植到 Angular。

    就个人而言,如果我要从头开始执行此操作,我认为最好的解决方案是决定应该处理路由并坚持下去。如果是 Angular,那么摆脱 Turbolinks -> 如果您有接近单页应用程序的东西,它不会为您做太多事情。如果您允许 Rails 处理路由,那么只需使用 Angular 来组织在提供模板时服务器无法处理的客户端行为。

    我在这里错过了一个场景吗?即使在大型应用程序中,尝试在不同框架之间拆分路由职责对我来说似乎并不优雅......除了刷新页面或导航到新路由之外,是否存在其他一些 Turbolinks 会干扰 Angular 的场景?

    【讨论】:

      【解决方案8】:

      同时使用 Turbolinks 和 AngularJS

      +1 到@fiedl 以获得很好的答案。但我更喜欢使用page:changepage:load,因为这提供了一些灵活性:DOM 可以从turbolinks 以外的源接收page:load 事件,所以你可能不希望有相同的回调触发。

      注意 page:change然后 page:load 应该将您的回调行为限制为仅由 turbolinks 发起的事件。

      function boostrapAngularJS () {
          angular.bootstrap(document.body, ['My Application']);
          addCallbackToPageChange();
      }
      function addCallbackToPageChange() {
          angular.element(document).one('page:change', function () {
              angular.element(this).one('page:load', boostrapAngularJS);
          });
      }
      addCallbackToPageChange();
      

      (这将允许/要求您在 html 中保留 ng-app 声明,与使用 AngularJS 时一样。)

      【讨论】:

        【解决方案9】:

        Turbolinks 自动获取页面,交换其&lt;body&gt;,并合并其&lt;head&gt;,所有这些都不会产生整个页面加载的成本。

        https://github.com/turbolinks/turbolinks#turbolinks

        因此,我们可以在 &lt;body&gt; 元素上执行,而不是在 &lt;html&gt; 元素上附加 ng-app 指令。

        <html>
          <body ng-app=“yourApplicationModuleName">
          </body>
        </html>
        

        【讨论】:

          猜你喜欢
          • 2013-10-03
          • 1970-01-01
          • 2014-10-12
          • 2014-10-06
          • 2014-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多