Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML template filled in server-side and updated client-side

I have a webpage with dynamic content. Let's say it's a product page. When the user goes directly to example.com/product/123 I want to render my product template on the server and send html to the browser. However, when the user later clicks a link to /product/555 I'd like to use JavaScript to update the template on the client-side.

I'd like to use something like Knockout.js or Angularjs, but I don't see how I can pre-populate those templates with some initial data on the server and still have a functioning template on the client. i.e. If my Angular template is this:

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

When the user goes directly to the URL, I need something that still works as an Angular template, but is filled in with the html for the current product. Obviously this doesn't work:

<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>

It seems like my only option is to send the server-rendered html to the browser along with a separate matching template...?

In that case, I'd like to avoid writing every template twice. Which means I need to either switch to JavaScript for my server language (which I would not be happy about) or choose a template language that compiles to both Java and JavaScript and then find a way to hack it into the Play Framework (which is what I'm currently using.)

Does anyone have any advice?

like image 323
takteek Avatar asked Aug 07 '12 02:08

takteek


3 Answers

If you would really like to have an initial value stored in an area before Angular activates- you can use the ng-bind attribute rather than {{bound strings}}, from your example:

<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>

I'm not sure where this would come in handy, but you'll also want to include the initial data-set as part of a script tag in the document, so that when angular DOES activate it doesn't wipe out the displayed information with nulls.

Edit: (As requested by commenters)

Alternatively, you could make an ng-repeat at the top of the list, have it configured to fill out based on the 'features' list itself. Following that ng-repeat element, have non-ng-repeat elements which have an ng-hide attribute with the setting ng-hide="features", if Angular loads, all the elements from the original server-provided list hide themselves, and the angular list jumps into existence. No hacky modifications to Angular, and no fiddling with the direct ng-bind attribute.

As a side note, you might still want to send a piece of script capable of reading that initial server-element for its data to feed it into angular before angular synchronizes if you want to avoid a blink where angular clears the data while waiting for a request for the same data from the server.

like image 155
Zoey Avatar answered Nov 16 '22 01:11

Zoey


I've only used Knockout, not Angular, but a seemingly very common approach which I use is to have the initial state of your data rendered into the page markup as JSON, and on DOM ready use that to build your initial Javascript view model, then apply the Knockout bindings to build the UI. So the UI gets built client side even for an item such as your product which already exists on the server. This means the very same templates can be invoked both for the initial UI creation and when you add something client side, like a sub-product with its own view model and template. Is this an option for you?

Edit: in case I misunderstood your requirements, the approach I'm talking about is detailed more in this question: KnockoutJS duplicating data overhead

like image 25
Tom W Hall Avatar answered Nov 16 '22 03:11

Tom W Hall


One option in AngularJS might be to use a directive that copies values rendered on the server into the model and have subsequent actions retrieve data via JavaScript.

I've used the method described here in an ASP.NET WebForms application to pre-populate my model via hidden values from the server on the first request. Per the discussion this breaks from the Angular way but it is possible.

Here is example of the 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));
    }
});
like image 32
Gloopy Avatar answered Nov 16 '22 01:11

Gloopy