0

When using server-side templating and client-side angularjs, I'm not able to get angularjs to recognize values I've templated in on the server.

For example (or on jsfiddle):

<div ng-app>
<div ng-controller="Ctrl">
    <textarea ng-model="data" placeholder="Enter a name here">Templated in</textarea>
    {{data}}
</div>
</div>

Angularjs will always replace the value in the text area with the value of $scope.data (which is null). What I want is for the value of $scope.data to take on "Templated in", on app bootstrap, then to continue normally from there.

How can I template in from the server a value, then have angularjs model bind that value once on the client?

heneryville
  • 2,853
  • 1
  • 23
  • 30

2 Answers2

4

Use ng-init

<textarea ng-model="data" placeholder="Enter a name here"
 ng-init="data='Templated in'"></textarea>

See also AngularJS - Value attribute on an input text box is ignored when there is a ng-model used? and
rails + angularjs loading values into textfields on edit

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
2

I came across this answer while trying to figure this out for myself, but never liked that I had to move information from the part of the field where the HTML spec wants it. That just didn't feel like the right approach to me.

For a while, I ended up taking advantage of the fact that Angular sits on top of jQuery (or the embedded jqLite subset if the full jQuery isn't available) to solve this without moving the content, or even touching ng-init. Specifically, you can access the contents of the textarea using (the Angular/jqLite version of) standard jQuery methods during the controller initialization phase.

So I just did

var doc = angular.element(document.documentElement);

$scope.data = doc.find('textarea').eq(0).val();

in my controller, exactly where I would initialize any other scope variable. (Alternately, here's the modified jsFiddle...)

With the full jQuery library available, the code is even simpler, since you have access to full jQuery selectors at that point and can jump straight to $('textarea').eq(0).val() (or even add an id to the field and select on that: $('#data-textarea').val()).

The nice thing about this approach is that it will work for any form elements, though since most of those are <input> tags, and jqLite doesn't support selectors, finding the exact one you want can be a tad tricky. I would simply include the full jQuery library at that point and take advantage of the selectors, but that's me.

This approach also has the major drawback of placing DOM-aware code in the controller, which completely breaks both Angular conventions (The Angular Way (TM)) and the MVC/MVVM programming paradigm. Not the best solution.

UPDATE: So I eventually realized I would need a more long-term solution, one which didn't violate so many best practices. The answer actually comes from the most essential element of Angular, without which none of the rest of it would work (you could argue that for other components, but it's simply more true for this one): Directives. More specifically:

app.directive('input', ['$parse', function ($parse) {
    return {
        restrict: 'E',
        require: '?ngModel',
        link: function (scope, element, attrs) {
            if(attrs.value) {
                $parse(attrs.ngModel).assign(attrs.value, scope);
            }
        }
    };
}]);

This has a number of advantages. First, it doesn't break Angular conventions that help reinforce MVC/MVVM. Second, it doesn't even touch jqLite/jQuery, nor the underlying DOM functions. Third, it has the desired effect of preserving HTML conventions for defining default values, allowing (or at least simplifying) the use of Angular with other existing technologies, such as server-side templating engines.

Why doesn't Angular do this by default? Well, I don't know the actual answer without a bit more research, but a likely answer is that HTML conventions favor static page content, and Angular is designed for dynamic page content. That means breaking out of HTML conventions in many places, not letting it limit what is possible with Angular apps. Since controllers are expected to carry the responsibility of initializing models (and in most cases such an expectation is the correct one), the Angular team would have incentive to ignore the contents of a value attribute (and its analogs among the other form tags).

Of course, with this approach, for any applicable elements that already exist before the controller is initialized, the controller init can override the value attribute. This behavior is not consistent, however, across the entire application, because new elements will trigger the directive evaluation, but not the controller init phase. (Partials with their own controllers hold this behavior as well - elements in place prior to the partial's controller init can be overridden by said init, but other elements added after that won't re-trigger the init.)

There are a number of other ways to write such a directive, and it can be extended to do any number of other things as well, but hopefully this approach helps someone else.

Dan Hunsaker
  • 309
  • 2
  • 6
  • While this would work, I try very hard to avoid having any kind of jQuery or DOM-aware code in my controller. – heneryville Jun 21 '13 at 23:04
  • Fair enough. I tried to make this approach also work with ng-init, but haven't succeeded just yet. – Dan Hunsaker Jun 21 '13 at 23:07
  • did you make sure that there is also an ng-model on the same tag? I'm pretty sure it's required. – heneryville Jun 21 '13 at 23:28
  • Yeah, it would be needed at least for the two-way bind once the model is initialized, so I was definitely trying with ng-model in place. I suspect ng-model is being processed before ng-init gets a chance (which seems counterintuitive, to me), and so by the time ng-init gets run, the textarea is already blank. There are still some other things I'd like to try and rule out to know for sure, though. – Dan Hunsaker Jun 23 '13 at 01:50