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.