服务器端填充的HTML模板并在客户端更新

13
我有一个包含动态内容的网页,比如说是一个产品页面。当用户直接访问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 Framework 中(这是我目前使用的框架)。
有人有什么建议吗?
4个回答

7
如果你希望在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激活它时,它不会用null清除显示的信息。
编辑:(按评论者要求)
或者,您可以在列表顶部创建一个ng-repeat,将其配置为根据“features”列表本身填充。在该ng-repeat元素之后,具有ng-hide属性的非ng-repeat元素,设置为ng-hide =“features”,如果Angular加载,则来自原始服务器提供的列表的所有元素都会隐藏,而Angular列表跳入存在。没有对Angular进行hacky修改,也没有对直接ng-bind属性进行调整。
顺便说一下,如果你想避免等待来自服务器的相同数据请求时,Angular清除数据的闪烁,你可能仍然需要发送一个能够读取初始服务器元素的脚本片段,并在Angular同步之前将其馈送到Angular中。

我会点赞这个回答,因为它似乎是目前为止最有希望的答案。但我仍然不知道如何处理服务器呈现多个列表项,然后Angular稍后替换它们的情况。我想我可以始终分叉Angular并添加该功能... - takteek
8
哦,好主意!在列表顶部设置一个ng-repeat,并将其配置为根据“features”列表填充。在这个ng-repeat元素之后,有一些没有ng-repeat的元素,这些元素具有ng-hide属性,并设置ng-hide =“features”,如果Angular加载,所有来自原始服务器提供的列表的元素都会隐藏自己,并且angular列表跳入存在。不需要对Angular进行任何hacky修改,也不需要调整直接的ng-bind属性。 - Zoey
1
太好了,我想我会选这个方案。虽然它还不是完美的,但似乎除非修改Angular或Knockout,否则这是最好的选择。说实话,我很惊讶没有一个框架解决了这个问题。 - takteek
1
至少有几个(非常早期阶段的)应用程序框架正在尝试解决这个问题。[Derby](http://derbyjs.com)有一个工作演示; [Flatiron](http://flatironjs.org)已经表达了完全“同构”客户端/服务器代码的愿望,看起来很有前途。您还可以查看[Meteor](http://meteor.com),它强烈倾向于在客户端呈现,但具有Google的ajax爬网API的特定服务器端支持。【未来读者:整个领域正在_快速_变化,因此本评论可能已过时。】 - medmunds
@DessixMachina - @takteek的问题正是我在寻找的,我认为你使用ng-hide的建议非常有前途。你能否把这个建议放到你的回答正文中呢? - Ian Clark

2
我只用过 Knockout,没用过 Angular,但我常用的一个方法是将你的数据的初始状态呈现为 JSON 嵌入页面标记中,在 DOM 加载完成后使用该数据构建初始的 Javascript 视图模型,然后应用 Knockout 的绑定来构建 UI。因此,即使像产品这样已经存在于服务器上的项目,UI 也将在客户端构建。这意味着相同的模板既可用于初始 UI 创建,也可用于添加一些客户端数据,例如具有自己视图模型和模板的子产品。这对你是否可行呢?
编辑:如果我误解了您的要求,我所说的方法在这个问题中有更详细的阐述:KnockoutJS duplicating data overhead

很遗憾,不行。服务器端渲染的原因是为了使URL可以被搜索引擎爬取。 - takteek
要让您的网站可以被搜索引擎爬取,您可以按照谷歌关于此主题的说明进行操作:https://developers.google.com/webmasters/ajax-crawling/docs/getting-started - Zoey
@DessixMachina 在支持的浏览器上,我使用历史记录 API,因此我的 URL 不会有哈希片段。 - takteek
@takteek 请看一下我的上面的回答-它应该足以满足您的需求,因为它包括了在javascript激活之前的HTML。 - Zoey

1

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

我曾经在ASP.NET WebForms应用程序中使用此处描述的方法,通过来自服务器的隐藏值预填充我的模型。根据讨论,这与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个<li>填充列表,我不知道如何告诉Angular在视图模型稍后更新时替换这些3个元素。 - takteek
隐藏输入的值可以是类似于Tom Hall建议的$scope.features的JSON,一旦复制到模型中,<li>就应该填充。我们没有意识到您希望它完全呈现以供搜索引擎爬行,因此这并不会真正有所帮助...我使用这种方法的主要原因是想避免空表单和延迟,同时进行ajax调用以获取初始数据...既然已经访问了后端,不妨在不需要额外往返的情况下填充数据。 - Gloopy

0

我不知道有没有一个好的技巧来做这件事,但这是我在构建一个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 将隐藏初始种子数据,只留下 Angular 化的模板。

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

<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引导应用程序时不会出现闪烁。

理想情况下,我希望能够在服务器上和客户端上重用相同的模板。类似于mustache的东西浮现在脑海中。显然,技巧在于实现Angular的指令和流程控制。这不是一项容易的工作。


当ng-include被用在ng-repeat内时,请问您如何处理解决方案? - toregua

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接