【问题标题】:HTML template filled in server-side and updated client-side服务端填写 HTML 模板,客户端更新
【发布时间】:2012-08-07 02:57:16
【问题描述】:

我有一个包含动态内容的网页。假设它是一个产品页面。当用户直接转到example.com/product/123 时,我想在服务器上呈现我的产品模板并将 html 发送到浏览器。但是,当用户稍后单击指向/product/555 的链接时,我想使用 JavaScript 在客户端更新模板。

我想使用 Knockout.js 或 Angularjs 之类的东西,但我不知道如何在服务器上使用一些初始数据预先填充这些模板,并且在客户端仍然有一个正常工作的模板。即如果我的 Angular 模板是这样的:

<ul>
    <li ng-repeat="feature in features">
      {{feature.title}}
      <p>{{feature.description}}</p>
    </li>
</ul>

当用户直接访问 URL 时,我需要一些仍可用作 Angular 模板的东西,但会填充当前产品的 html。显然这是行不通的:

<ul>
    <li ng-repeat="feature in features">Hello
      <p>This feature was rendered server-side</p>
    </li>
    <li>Asdf <p>These are stuck here now since angular won't replace them when
       it updates.... </p></li>
</ul>

看来我唯一的选择是将服务器渲染的 html 连同一个单独的匹配模板一起发送到浏览器...?

在这种情况下,我想避免每个模板都写两次。这意味着我需要为我的服务器语言切换到 JavaScript(我不会对此感到高兴),或者选择一种同时编译为 Java 和 JavaScript 的模板语言,然后找到一种方法将其破解到 Play 框架中(这就是我目前正在使用。)

有人有什么建议吗?

【问题讨论】:

    标签: playframework knockout.js angularjs dust.js


    【解决方案1】:

    如果您真的想在 Angular 激活之前将初始值存储在某个区域中 - 您可以使用 ng-bind 属性而不是 {{bound strings}},来自您的示例:

    <ul>
        <li ng-repeat="feature in features">
            <div ng-bind="feature.title">Hello</div>
            <p ng-bind="feature.description">This feature was rendered server-side but can be updated once angular activates</p>
        </li>
    </ul>
    

    我不确定这会在哪里派上用场,但您还需要将初始数据集作为脚本标签的一部分包含在文档中,这样当 angular DOES 激活时它不会消失显示的信息为空。

    编辑:(应评论者的要求)

    或者,您可以在列表顶部创建一个 ng-repeat,将其配置为根据“功能”列表本身进行填写。在该 ng-repeat 元素之后,具有具有设置 ng-hide="features" 的 ng-hide 属性的非 ng-repeat 元素,如果加载了 Angular,则原始服务器提供的列表中的所有元素都会隐藏自己,并且角度列表开始存在。没有对 Angular 进行任何修改,也没有摆弄直接 ng-bind 属性。

    作为旁注,如果您想避免 Angular 清除数据时出现闪烁,您可能仍希望发送一段脚本,该脚本能够读取该初始服务器元素的数据以在 Angular 同步之前将其输入 Angular等待来自服务器的相同数据的请求。

    【讨论】:

    • 我赞成这个,因为它似乎是迄今为止最有希望的答案。我仍然看不到如何处理服务器呈现多个列表项然后 Angular 稍后替换它们的情况。我想我总是可以分叉 Angular 并添加该功能....
    • @takteek 哦,好主意!在列表顶部创建一个 ng-repeat,将其配置为根据“功能”列表本身进行填写。在该 ng-repeat 元素之后,具有具有设置 ng-hide="features" 的 ng-hide 属性的非 ng-repeat 元素,如果加载了 Angular,则原始服务器提供的列表中的所有元素都会隐藏自己,并且角度列表开始存在。没有对 Angular 进行任何修改,也没有摆弄直接 ng-bind 属性。
    • 太棒了,我想这就是我要去的。它仍然不完美,但它似乎是我要做的最好的修改角度或淘汰赛。老实说,我很惊讶没有一个框架可以解决这个问题。
    • 至少有少数(非常早期的)应用程序框架正试图解决这个问题。 Derby 有一个工作演示; Flatiron 已经表达了完全“同构”客户端/服务器代码的愿望,看起来很有希望。您还可以查看Meteor,它非常喜欢在客户端呈现,但对 Google 的 ajax 抓取 API 有特定的服务器端支持。 [未来的读者:这整个领域正在快速发生变化,所以这个评论可能已经过时了。]
    • @DessixMachina - @takteek 的问题正是我想要的,我认为您使用ng-hide 的建议非常有希望。您能否将该建议放在答案正文中?
    【解决方案2】:

    我只使用了 Knockout,而不是 Angular,但我使用的一种看似非常常见的方法是将数据的初始状态作为 JSON 呈现到页面标记中,并在 DOM 上准备好使用它来构建您的初始 Javascript查看模型,然后应用 Knockout 绑定来构建 UI。因此,即使对于已经存在于服务器上的产品(例如您的产品),UI 也会构建客户端。这意味着可以在初始 UI 创建和添加客户端时调用相同的模板,例如具有自己的视图模型和模板的子产品。这是您的选择吗?

    编辑:如果我误解了您的要求,我正在谈论的方法在这个问题中更详细:KnockoutJS duplicating data overhead

    【讨论】:

    • 不幸的是,没有。服务器端呈现的原因是 URL 是搜索引擎可抓取的。
    • 为了让您的网站可被搜索引擎抓取,您可以按照 Google 的相关说明进行操作:developers.google.com/webmasters/ajax-crawling/docs/…
    • @DessixMachina 在受支持的浏览器上,我使用历史 API,因此我的 URL 不会有散列片段。
    • @takteek 看看我上面的答案 - 它应该足以满足您的需求,因为它甚至在 javascript 激活之前就包含了 HTML。
    【解决方案3】:

    AngularJS 中的一个选项可能是使用指令将服务器上呈现的值复制到模型中,并让后续操作通过 JavaScript 检索数据。

    我在 ASP.NET WebForms 应用程序中使用了here 中描述的方法,在第一次请求时通过来自服务器的隐藏值预填充我的模型。根据讨论,这与 Angular 方式不同,但这是可能的。

    以下是 html 示例:

    <input type="hidden" ng-model="modelToCopyTo" copy-to-model value='"this was set server side"' />
    

    JavaScript:

    var directiveModule = angular.module('customDirectives', []);
    
    directiveModule.directive('copyToModel', function ($parse) {
        return function (scope, element, attrs) {
            $parse(attrs.ngModel).assign(scope, JSON.parse(attrs.value));
        }
    });
    

    【讨论】:

    • 这是一个有趣的想法,但我不确定它如何解决我的示例中使用重复元素的问题。如果服务器使用 3 个&lt;li&gt;s 填充列表,我看不出如何告诉 Angular 在稍后更新视图模型时替换这 3 个元素。
    • 隐藏输入的值可以是 $scope.features 的 JSON,类似于 Tom Hall 的建议,一旦它被复制到模型中,
    • 应该会被填充。我们没有意识到您希望它完全呈现给搜索引擎爬行,所以这并没有真正的帮助.. 我使用这种方法的主要原因是希望避免空表单和延迟,同时进行 ajax 调用以获取初始数据。 . 因为它已经到达后端,所以最好在没有额外往返的情况下填充数据。
    【解决方案4】:

    我不知道这样做有什么好的技术,但这是我暂时在我正在构建的 Rails 应用程序中确定的东西。

    您首先使用 ng-init 使用种子数据初始化您的模板。

    <ul ng-init="features = <%= features.to_json %>">
        <li ng-repeat="feature in features">
          {{feature.title}}
          <p>{{feature.description}}</p>
        </li>
    </ul>
    

    然后你渲染种子数据两次。一次来自服务器,一次 Angular 引导您的应用程序。当应用程序启动时,Angular 将隐藏初始种子数据,只留下角度化的模板。

    在引导之前使用ng-cloak 隐藏角度模板非常重要。

    <ul ng-hide="true">
      <% features.each do |feature| %>
        <li>
          <%= feature.title %>
          <p><%= feature.description =></p>
        </li>
      <% end %>
    </ul>
    <ul ng-hide="false" ng-cloak ng-init="features = <%= features.to_json %>">
        <li ng-repeat="feature in features">
          {{feature.title}}
          <p>{{feature.description}}</p>
        </li>
    </ul>
    

    它不适用于大型模板,您正在复制标记,但至少在 Angular 引导您的应用程序时不会出现这种闪烁。

    理想情况下,我希望能够在服务器上和客户端上重复使用相同的模板。像小胡子这样的东西浮现在脑海中。显然,诀窍是实现 angular 的指令和流控制。不容易。

    【讨论】:

    • 在 ng-repeat 中使用 ng-include 时,您的解决方案如何?
    猜你喜欢
    相关资源
    最近更新 更多
    热门标签