Kamran Ahmed
Kamran Ahmed

Reputation: 12428

Angular JS : Using filter/directive to modify the generated output

In my app, I have got different tasks, each having a due date thay may be like following:

Jun 6, 2014
Apr 12, 2014 @ 1pm
Daily @ 1pm

In my view, I am using ng-repeat to show the tasks and their related information. My ng-repeat looks like below:

  <ul class="tasks">
   <li class="single-task">
       <span class="task-title">{{task.title}}</span>
       <span class="task-duedate">{{task.dueDate}}</span>
   </li>

What I want, is to show the dates differently i.e. instead of showing them the way that they are stored, I want the date to be rendered inside the <span class="task-duedate"></span> like the following:

<span class="task-duedate">
    <span class="date-daymon">Apr 12</span>
    <span class="date-year">2014</span>
    <span class="date-time">1 pm</span>
</span>

So, I have creatd and applied a filter called NormalizeDateTime

<span class="task-duedate">{{task.dueDate | NormalizeDateTime }}</span>

that generates the desired html and returns it.

app.filter('NormalizeDate', function(){
return function( input ) {
        ...
        ...
        return '<span..>' + date + '</span><span..>' + year + '</span><span..>' + time + '</span>';
    };
});

Now the problem I am having is angular, instead of making the browser render the returned html, is showing the date part returned by the filter as it is i.e. in the form of plain text. Now the question is, how may I make it render the text returned by the NormalizeDateTime filter as html?

P.S I have also tried triple braces {{{task.dueDate | NormalizeDateTime}}}, as we do in HandlebarsJS, but that doesn't work either.

UPDATE I have tried to achieve the same using a custom directive:

<span class="task-content group cursor-text group left">
    <span class="task-text cursor-text">{{task.title}}</span>
    <span class="task-trackedtime right">{{task.trackedTime}}</span>
    <span class="task-datetime right" duedate="{{task.dueDate}}"></span>
</span>

Below is how my directive looks like:

app.directive('duedate', [function () {
    return {
        restrict: 'A',
        // transclude: true,
        replace: true,
        template: '<span class="task-datetime right">{{dat}}</span>',
        link: function (scope, iElement, iAttrs) {
            // scope.dat = 'Testing Date';
            console.log(iAttrs.duedate);
        }
    };
}])

But I'm unable to access iAttrs.duedate inside the link of my directive. Why is it so?

Upvotes: 1

Views: 605

Answers (5)

aet
aet

Reputation: 7292

Here ya go! http://jsfiddle.net/Uv6CP/

The trick with getting interpolated values in attributes to work is that you have to call iAttrs.$observe to get the interpolated value.

myApp.directive('duedate', function() {
    return {
        restrict: 'A',
        replace: true,
        template: '<div><span class="date-daymon">{{day}}</span><br><span class="date-year">{{year}}</span><br><span class="date-time">{{time}}</span><br><hr></div>',
        link: function (scope, iElement, iAttrs) {
            iAttrs.$observe('duedate', function(val) {
                console.log("duedate = " + val);
                var parts = val.split('@');
                if (parts.length > 1) {
                    scope.time = parts[1];
                } else {
                    scope.time = "";
                }
                var moreParts = parts[0].split(',');
                if (moreParts.length > 1) {
                    scope.year = moreParts[1];
                } else {
                    scope.year = "";
                }
                scope.day = moreParts[0];
            });
        }
    };
});

Upvotes: 1

Gruff Bunny
Gruff Bunny

Reputation: 27976

DOM manipulation should only be done in a directive, so using a filter is not appropriate.

You don't include an example of a task so I'll assume that it looks something like:

{ 
    id: 1, 
    dueDate: new Date( 2014, 5, 13, 13, 30, 0 ), 
    daily: false 
}

A task should be passed to the directive rather than the dueDate because the directive needs to know when to show the word Daily, which it can't do given just a date.

Here's a directive that should hopefully do what you want:

.directive('dueDate', function($filter) {

    return {
        restrict: 'E',
        scope: {
            task: '='
        },
        replace: true,
        template: '<span class="task-duedate"> \
                      <span class="date-daymon">{{daymon}}</span> \
                      <span class="date-year">{{year}}</span> \
                      <span class="date-time">@ {{time}}</span> \
                  </span>',
        link: function(scope){

            if( scope.task.daily ){
                scope.daymon = 'Daily';
            }
            else {
                scope.daymon = $filter('date')(scope.task.dueDate, 'MMM d');
                scope.year = $filter('date')(scope.task.dueDate, 'yyyy');
            }

            scope.time = $filter('date')(scope.task.dueDate, 'h a');
        }
    };
})

Usage:

<div ng-repeat='task in tasks'>
   <due-date task='task'></due-date>
</div>

Fiddle

Upvotes: 1

Catalin MUNTEANU
Catalin MUNTEANU

Reputation: 5634

You should use the ng-bind-html directive documented here

For your code it should look like:

<span ng-bind-html='task.dueDate | NormalizeDateTime'></span>

You controller should also have 'ngSanitize' as dependency, else you will have an exception.

Upvotes: 1

Michael Rose
Michael Rose

Reputation: 7820

I also had problems with the filter not being able to generate HTML and reverted back to a directive, too.

I would suggest a small change to your directive, i.e. add a scope definition as seen below:

app.directive('duedate', [function () {
    return {
        restrict: 'A',
        replace: true,
        scope: {
            dat: '@duedate'
        },
        template: '<span class="task-datetime right">{{dat}}</span>',
        link: function (scope, iElement, iAttrs) {
            // scope.dat = 'Testing Date';
            console.log(scope.dat);
        }
    };
}])

By adding scope: { dat: '@duedate' } you specify that the directive's scope dat property should be set to the interpreted value of the duedate attribute.

Upvotes: 2

Chandermani
Chandermani

Reputation: 42669

Not sure but try something in the lines of using ngBindHtml directive in your span

<span class="task-duedate" ng-bind-html='task.dueDate | NormalizeDateTime'></span>

and see if it works. Remember to include the ngSanitize module.

See documentation http://docs.angularjs.org/api/ng/directive/ngBindHtml

Upvotes: 1

Related Questions