tekstrand
tekstrand

Reputation: 1493

How can I combine strings from arrays to create HTML markup with Angular?

I've never seen an API do this before, but I'm working with what I've got. This is part of the response body from the API I'm dealing with

"body": {
  "isRichText":true,
  "messageSegments":[
    {
      "htmlTag":"p",
      "markupType":"Paragraph",
      "text":"",
      "type":"MarkupBegin"
    },
    {
      "text":"This is a ",
      "type":"Text"
    },
    {
      "htmlTag":"b",
      "markupType":"Bold",
      "text":"",
      "type":"MarkupBegin"
    },
    {
      "text":"post",
      "type":"Text"
    },
    {
      "htmlTag":"b"
      ,"markupType":"Bold",
      "text":"",
      "type":"MarkupEnd"
    },
    {
      "text":" from the standard ",
      "type":"Text"
    },
    {
      "htmlTag":"i",
      "markupType":"Italic",
      "text":"",
      "type":"MarkupBegin"
    },
    {
      "text":"chatter",
      "type":"Text"
    },
    {
      "htmlTag":"i",
      "markupType":"Italic",
      "text":"",
      "type":"MarkupEnd"
    },
    {
      "text":" UI with some HTML tags",
      "type":"Text"
    },
    {
      "htmlTag":"p",
      "markupType":"Paragraph",
      "text":"\n",
      "type":"MarkupEnd"
    }
  ]
}

I need to combine each one of those segments in order to create what will end up being one element inside of a paragraph tag(in this case).

HTML

<div ng-repeat="bodyElement in post.body.messageSegments">
  <!-- ng-if maybe? -->{{bodyElement.htmlTag}} {{bodyElement.text}}
</div>

What is the best way to complete this? Is it directly in the js files, or should I attempt in the templates?

Upvotes: 1

Views: 121

Answers (2)

Shaun Scovil
Shaun Scovil

Reputation: 3987

My suggestion would be to create a service to parse the message segments, and a directive to display the results.

Here is a working example: JSFiddle

Service

The service $messageSegment has two methods: parse and parseHttpResponse. The latter can be used in the transformResponse config option of an $http request.

angular.module('myApp')
  .factory('$messageSegment', messageSegmentFactory);

function messageSegmentFactory() {
  var $messageSegment = {};

  $messageSegment.parse = function(arr) {
    var html = '';

    if (!angular.isArray(arr) || !arr.length)
      return html;

    do {
      var segment = arr.shift();
      switch (segment.type) {
        case 'Link':
          html += '<a href="' + segment.url + '">' + segment.text + '</a>';
          break;
        case 'Mention':
          html += '<a href="/users/' + segment.user.id + '">' + segment.text + '</a>';
          break;
        case 'Hashtag':
          html += '<a class="hashtag">' + segment.text + '</a>';
          break;
        case 'MarkupBegin':
          html += '<' + segment.htmlTag + '>';
          break;
        case 'MarkupEnd':
          html += '</' + segment.htmlTag + '>';
          break;
        default:
          html += segment.text;
      }
    } while (arr.length);

    return html;
  };

  $messageSegment.parseHttpResponse = function(data) {
    return $messageSegment.parse(data.body.messageSegments);
  };

  return $messageSegment;
}

Directive

This sfChatter directive observes its url attribute and, whenever that value changes, will make an $http request, parse the response and update its own inner HTML automatically.

angular.module('myApp')
  .directive('sfChatter', sfChatterDirective);

sfChatterDirective.$inject = ['$http', '$messageSegment'];
function sfChatterDirective($http, $messageSegment) {
  return {
    restrict: 'E',
    link: postLink
  };

  function postLink(scope, iElement, iAttrs) {
    iAttrs.$observe('url', function(value) {
      var url = scope.$eval(value);
      $http({
        url: url,
        method: 'GET',
        transformResponse: $messageSegment.parseHttpResponse
      }).then(function(res) {
        iElement.html(res.data);
      });
    });
  }
}

Usage

In your app, you would do something like <sf-chatter url="myUrl">, where myUrl is a scope variable that tells the directive what $http endpoint to hit.

Upvotes: 1

Brian Baker
Brian Baker

Reputation: 996

Here is a little bit of angular magic for you. The controller code (you'll need to inject $sce):

var markup = ''
for(var i = 0; i < $scope.messageSegments.length; i++){
  // console.log(markup)
  if($scope.messageSegments[i].type === 'MarkupBegin'){
    markup = markup + '<'+$scope.messageSegments[i].htmlTag+'>'
  }
  else if($scope.messageSegments[i].type === 'MarkupEnd'){
    markup  = markup + '</'+$scope.messageSegments[i].htmlTag+'>'
  }
  else{
    markup  = markup + $scope.messageSegments[i].text
  }  
}
$scope.markup = markup;
$scope.markup = 
 $sce.trustAsHtml(markup);

And bind in HTML like this:

<div  ng-bind-html="markup"></div>

Here is a plunker.

Upvotes: 0

Related Questions