42

I am fairly new to AngularJS.

I am trying to bind an object to a textarea.

HTML:

<textarea rows="5" cols="10" ng-model="menuItem.preset"></textarea>

Model:

{
    "kind": "title",
    "label": "ADD_TITLE",
    "iconSrc": "textTitle.png",
    "experimentInclude": "",
    "experimentExclude": "three",
    "preset": {
        "compType": "richTitle",
        "styleId": "txtNew"
    }
}

Result:

json shown as object

How can I show the JSON stringified (and later save it as an object again)?

Guy
  • 12,488
  • 16
  • 79
  • 119

9 Answers9

32

You need a custom directive that parses the input to an object, and displays the object as a string, respectively:

Something like:

angular.module('yourApp').directive('jsonText', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attr, ngModel) {            
          function into(input) {
            return JSON.parse(input);
          }
          function out(data) {
            return JSON.stringify(data);
          }
          ngModel.$parsers.push(into);
          ngModel.$formatters.push(out);

        }
    };
});
<textarea json-text rows="5" cols="10" ng-model="menuItem.preset"></textarea>

Fiddle: http://jsfiddle.net/HzYQn/

holographic-principle
  • 19,688
  • 10
  • 46
  • 62
  • @Jason that fiddle is not my solution (maybe you forgot to save it) but here's one here: http://jsfiddle.net/HzYQn/ – holographic-principle Jul 28 '13 at 12:21
  • 7
    For pretty-printing use `return JSON.stringify(data, undefined, 2);` – Amir Nissim Feb 02 '14 at 15:21
  • 4
    for 2 way data binding I had to add `scope.$watch(attr.ngModel, function(newValue) {element[0].value = out(newValue); }, true);` – Amir Nissim Mar 25 '14 at 14:14
  • This throws an error that shows up in the console, though. – efeder Sep 05 '14 at 18:32
  • @efeder Does it happen only on jsfiddle or outside of its sanbox as well? – holographic-principle Sep 05 '14 at 19:31
  • I implemented it on my site, and it throws an error, which shows up in the developer console. I wrapped the JSON parse/stringify in try catch blocks, and if an error is thrown, I set a scope variable so that the view can display that the json is invalid. But I'm still not sure what to return from the into/out functions in that case. – efeder Sep 08 '14 at 13:15
25

I’ve just researched what I believe to be the most “proper” way of doing this, as I needed it for my https://github.com/vorburger/MUI.js... So here is a Plonker with my solution. It’s based on & is essentially a special case (i.e. an application of) the related Q How to do two-way filtering in angular.js? The added twist is that model updates should also change the textbox.. that's what the $watch / $setViewValue / $render thing does.

var app = angular.module('app', []);

app.directive('jsonText', function() {
  return {
    restrict: 'A', // only activate on element attribute
    require: 'ngModel', // get a hold of NgModelController
    link: function(scope, element, attrs, ngModelCtrl) {

      var lastValid;

      // push() if faster than unshift(), and avail. in IE8 and earlier (unshift isn't)
      ngModelCtrl.$parsers.push(fromUser);
      ngModelCtrl.$formatters.push(toUser);

      // clear any invalid changes on blur
      element.bind('blur', function() {
        element.val(toUser(scope.$eval(attrs.ngModel)));
      });

      // $watch(attrs.ngModel) wouldn't work if this directive created a new scope;
      // see https://stackoverflow.com/questions/14693052/watch-ngmodel-from-inside-directive-using-isolate-scope how to do it then
      scope.$watch(attrs.ngModel, function(newValue, oldValue) {
        lastValid = lastValid || newValue;

        if (newValue != oldValue) {
          ngModelCtrl.$setViewValue(toUser(newValue));

          // TODO avoid this causing the focus of the input to be lost..
          ngModelCtrl.$render();
        }
      }, true); // MUST use objectEquality (true) here, for some reason..

      function fromUser(text) {
        // Beware: trim() is not available in old browsers
        if (!text || text.trim() === '') {
          return {};
        } else {
          try {
            lastValid = angular.fromJson(text);
            ngModelCtrl.$setValidity('invalidJson', true);
          } catch (e) {
            ngModelCtrl.$setValidity('invalidJson', false);
          }
          return lastValid;
        }
      }

      function toUser(object) {
        // better than JSON.stringify(), because it formats + filters $$hashKey etc.
        return angular.toJson(object, true);
      }
    }
  };
});


app.controller('Ctrl', ['$scope',
  function($scope) {
    $scope.model = {};
    $scope.model.data = {
      "kind": "title",
      "label": "ADD_TITLE",
      "iconSrc": "textTitle.png",
      "experimentInclude": "",
      "experimentExclude": "three",
      "preset": {
        "compType": "richTitle",
        "styleId": "txtNew"
      }
    };
  }
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div ng-app="app" class="container">
  <div ng-controller="Ctrl" class="row">
    <textarea json-text ng-model='model.data' rows="15"></textarea>
    <p>{{ model.data }}</p>
  </div>
</div>
Community
  • 1
  • 1
vorburger
  • 3,439
  • 32
  • 38
13

try with json filter

<textarea rows="5" cols="10" >
   {{ menuItem.preset | json }}
</textarea>
Oscar López
  • 381
  • 2
  • 8
5

Here is our JSON directive with validity checks:

app.directive('jsonInput', function () {
  'use strict';
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, elem, attr, ctrl) {
        ctrl.$parsers.unshift(function(input) {
          try {
            var obj = JSON.parse(input);
            ctrl.$setValidity('jsonInput', true);
            return obj;
          } catch (e) {
            ctrl.$setValidity('jsonInput', false);
            return null;
          }
        });
        ctrl.$formatters.unshift(function(data) {
          if (data == null) {
            ctrl.$setValidity('jsonInput', false);
            return "";
          }
          try {
            var str = JSON.stringify(data);
            ctrl.$setValidity('jsonInput', true);
            return str;
          } catch (e) {
          ctrl.$setValidity('codeme', false);
              return "";
          }
        });
    }
  };

});

When the user enters invalid JSON, the model is null. When the model contains circular references or is null, the user will see an empty string ("") and the input is invalid.

Enjoy.

Vincent
  • 2,342
  • 1
  • 17
  • 22
  • 1
    Changes in 1.3 break this code. Here is a fix for this: http://stackoverflow.com/questions/26564986/use-a-formatter-on-a-object-typed-ngmodel-value-with-angular-1-2-worked-but-no – s.Daniel Mar 04 '15 at 20:56
3

also you can define toString method on your model:

  $scope.menuItem.preset.toString = function(){
        return JSON.stringify(this);
    }

http://jsfiddle.net/ceJ4w/19/

and then to synchronise back use watch

http://jsfiddle.net/ceJ4w/20/

but it looks more like dirty hack than solution

Stepan Suvorov
  • 25,118
  • 26
  • 108
  • 176
1

Is using the ng-change event too messy?

<textarea ng-model="textmainboardJSON" ng-change="updateMainboard()"

http://jsfiddle.net/gsw9Q/3/

Steve Black
  • 609
  • 5
  • 9
0

You can do that in two steps :

Ven
  • 19,015
  • 2
  • 41
  • 61
0

Try this. It worked in my case

<textarea rows="5" cols="10" >{{menuItem.preset}}</textarea>

Rohidas Kadam
  • 428
  • 5
  • 12
0

I have simple way to bind arrays data to text area in angularjs. When there are two arrays and you need to show them parallel. Further you can get it on jsfiddle as well.

http://jsfiddle.net/Ahsan_Aftab/bc7hd258/18/

<!DOCTYPE html>
    <html>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js">
    <body>

    <div ng-app="myApp" ng-controller="myCtrl">


            <textarea ng-model="TextAreacodeGroups" style="width:52%;height:200px;" columns="25" id="code_groups">

        {{CodeGroupsFun()}}

       </textarea>

    </div>

<script>

    var app = angular.module('myApp', []);

    app.controller('myCtrl', function($scope) {
        var combined;
        var array1=['01','02','03','04'];
        var array2= ['Students','Teachers','Managers','Operators'];

     $scope.CodeGroupsFun = function () {

     $scope.TextAreacodeGroups =
      combined = array1.map(function (e, i) {
       return '\n' + array1[i] + '     -     ' + array2[i];
                    });
                };
    });

</script>    
    </body>
    </html>
Ahsan Aftab
  • 181
  • 2
  • 6