Angular JS display and edit model in textarea

All we need is an easy explanation of the problem, so here it is.

I would like to be able to edit and display complex model in a <textarea> element. Here’s the HTML piece for generating model’s fields dynamically from JSON response:

<p>parent uuid*: </p>
<input ng-model="parentUuid" capitalize type="text" placeholder="String"
    class="form-control" style="width:200px; display: inline-block;"/> <br/>
<p>resource*:</p>
<select ng-model="childResource" ng-change="loadResourceFields(childResource)" 
    class="form-control" style="width:300px; display: inline-block;">
    <option ng-repeat="childResource in restResources">{{childResource}}</option>
</select>
<div ng-repeat="field in childFields">
    <div ng-show={{!field.isEnum}}>
        <p ng-show={{field.isRequired}}>{{field.name}}*: </p>
        <p ng-show={{!field.isRequired}}>{{field.name}}: </p>
        <input type="text" ng-model="createChildResource[field.name]"
            class="form-control" style="width:200px; display: inline-block;" placeholder="{{parseClassName(field.type)}}">
    </div>
    <div ng-show={{field.isEnum}}>
        <p ng-show={{field.isRequired}}>{{field.name}}*: </p>
        <p ng-show={{!field.isRequired}}>{{field.name}}: </p>
        <select ng-model="createChildResource[field.name]" class="form-control" style="width:auto; display: inline-block;">
            <option></option>
            <option ng-repeat="enumValue in field.enumValues" label={{enumValue.name}}>{{enumValue.ordinal}}</option>
        </select>
    </div>
</div>
<div class="preview">
    <p>Preview: </p>
    <textarea style="height:350px; width:550px; overflow:scroll;">{{createChildResource | json}}</textarea >
</div>

The output is the following:

enter image description here

But if I try to add a ngModel to a textarea element to be able to edit this values in place like this:

<div class="preview">
    <p>Preview: </p>
    <textarea ng-model="createChildResource" style="height:350px; width:550px; overflow:scroll;">{{createChildResource | json}}</textarea>
</div>

then the output is the following:

enter image description here

In both cases I can’t edit my model in a textarea element.

How can this be achieved? I’d like to be able to display and edit my model inplace like in this example with a slight difference: editable-textarea="user.description" should be editable-textarea="user".

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I finally understand what you are trying to achieve. On the left you have a bunch of inputs and on the right (bottom), you have a textarea that arranges the inputs as properties of one object and displays them formatted as an object.

Your requirements is to allow user to edit the property values in the textarea and thus update the corresponding property value in the input.

First, as commented, convert the object to a string and then show it inside the textarea.

Next, since you need to react and update the input fields when the textarea is updated, you need to watch the value of the textarea and update the original object (the one that was converted to string).

Using an example here since your code is too complex to understand, let us say that you have the object containerObject as follows:

$scope.containerObject = {
    property_1: "Hello",
    property_2: "World"
};

Your inputs then make use of these properties:

<input ng-model="containerObject.property_1">

<input ng-model="containerObject.property_2">

Now, you wish to display this inside your textarea – you will first convert the object to string and display it as follows:

$scope.getObjectAsText = function () {
    $scope.textAreaModel = JSON.stringify($scope.containerObject);
};

And your textarea markup will look like:

<textarea ng-model="textAreaModel"></textarea>

Each time the value in the input textboxes change, the textarea also gets updated.

Now, for the other way around. When you change the textarea, to have the input textboxes get updated, you need to watch the string model (and NOT the object model):

$scope.$watch('textAreaModel', function () {
    try {
        $scope.containerObject = JSON.parse($scope.textAreaModel);
    } catch(exp) {
        //Exception handler
    };
});

You need to have the try...catch exception handler block because the user may accidentally change the contents such that on converting back to object, the result is not a suitable object (invalid property or invalid syntax).

As long as only the property value is changed, the input values will get updated correctly.

Method 2

You can also wrap it into directive like below, or check the jsfiddler

Html

<div ng-app="app" ng-controller='userCtrl' >
    <textarea obj-edit obj="user" rows='10'></textarea>
    <p ng-bind='user.name'></p>
</div>

javascript

var app = angular.module("app", []);
app.controller('userCtrl', function($scope) {
    $scope.user= {name: 'ron', ocupation: 'coder'};
});
app.directive('objEdit', function() {
    return {
        restrict: 'A',
        scope: {
            obj:'=obj'
        }, 
        link: function(scope, element, attrs) {
            element.text(JSON.stringify(scope.obj, undefined, 2));
            element.change(function(e) {
                console.log(e.currentTarget.value);
                scope.$apply(function() {
                    scope.obj = JSON.parse(e.currentTarget.value);
                });
                console.log(scope.obj);
            })
        }
    }
})

Method 3

Ron’s answer is great.
Here is code that uses ngModel validation

HTML

<form name="text">
   <textarea obj-edit ng-model="ctrl.json" name="json" rows="25" ng-model-options="{ debounce: 300 }"></textarea>
   <div class="alert alert-danger" role="alert" ng-show="text.json.$error.json">
        Error input
   </div>
</form>

Javascript

app.directive('objEdit', function() {
return {
    restrict: 'A',
    require: "ngModel",
    link: function(scope, element, attrs, ctrl) {
        ctrl.$formatters.push(function formatter(value) {
            return JSON.stringify(value, undefined, 2);
        });
        ctrl.$parsers.push(function(value) {
            try {
                var result = JSON.parse(value);
                ctrl.$setValidity('json', true);
                return result;
            } catch (e) {
                ctrl.$setValidity('json', false);
                return undefined;
            }
        });

    }
}});

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply