35

I am trying to create a 'time ago' pipe for my Angular 2 application.

It should transform a date to a string such as '5 minutes ago' or '60 seconds ago'. It works nicely so far, but it doesn't update after the first calculation. If the given date is for example 5 seconds ago, it displays '5 seconds ago' but never changes after that.

I have already tried setting the pipes 'pure' value to false but that didn't help.

Here is my code:

import {Pipe, PipeTransform} from 'angular2/core';

@Pipe({
  name: 'messageTime',
  pure: false
})
export class MessageTimePipe implements PipeTransform {
  transform(value: Date, []): string {
    var result: string;

    // current time
    let now = new Date().getTime();

    // time since message was sent in seconds
    let delta = (now - value.getTime()) / 1000;

    // format string
    if (delta < 10) {
      result = 'jetzt';
    } else if (delta < 60) { // sent in last minute
      result = 'vor ' + Math.floor(delta) + ' Sekunden';
    } else if (delta < 3600) { // sent in last hour
      result = 'vor ' + Math.floor(delta / 60) + ' Minuten';
    } else if (delta < 86400) { // sent on last day
      result = 'vor ' + Math.floor(delta / 3600) + ' Stunden';
    } else { // sent more than one day ago
      result = 'vor ' + Math.floor(delta / 86400) + ' Tagen';
    }

    return result;
  }
}

I'm using the filter like this:

TypeScript:

import {Component, Input} from 'angular2/core';
import {MessageTimePipe} from '../../pipes/message-time.pipe';

@Component({
  selector: 'message-item',
  pipes: [MessageTimePipe],
  templateUrl: 'build/components/message-item/message-item.component.html'
})
export class MessageItemComponent {
  @Input()
  message: JSON;

  date: Date;

  ngOnInit() {

   this.date = new Date(2016, 3, 16, 12, 49, 10);
  }
}

HTML:

<p class="time">
  {{ date | messageTime }}
</p>
user2611144
  • 413
  • 1
  • 4
  • 9

8 Answers8

36

The following library does equivalent job in English and could be forked to change the language or support different ones:

https://www.npmjs.com/package/time-ago-pipe

npm install time-ago-pipe --save

Then in the @NgModule you want to use it in:

import {TimeAgoPipe} from 'time-ago-pipe'

@NgModule({
    imports: [... etc ...],
    declarations: [AppComponent, ...etc..., TimeAgoPipe],
    bootstrap: [AppComponent]
})

And in the template:

<span>{{your_date | timeAgo}}</span>
KyleK
  • 4,643
  • 17
  • 33
David Prieto
  • 2,239
  • 4
  • 32
  • 51
25

Finally got it working, quite challenging and requires interval tweaking:)

import {Pipe, ChangeDetectorRef} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {AsyncPipe} from 'angular2/common';

@Pipe({
    name: 'messageTime',
    pure: false
})
export class MessageTimePipe extends AsyncPipe
{
    value:Date;
    timer:Observable<string>;

    constructor(ref:ChangeDetectorRef)
    {
        super(ref);
    }

    transform(obj:any, args?:any[]):any
    {
        if (obj instanceof Date)
        {
            this.value = obj;

            if(!this.timer)
            {
                this.timer = this.getObservable();
            }

            return super.transform(this.timer, args);
        }

        return super.transform(obj, args);
    }

    private getObservable()
    {
        return Observable.interval(1000).startWith(0).map(()=>
        {
            var result:string;
            // current time
            let now = new Date().getTime();

            // time since message was sent in seconds
            let delta = (now - this.value.getTime()) / 1000;

            // format string
            if (delta < 10)
            {
                result = 'jetzt';
            }
            else if (delta < 60)
            { // sent in last minute
                result = 'vor ' + Math.floor(delta) + ' Sekunden';
            }
            else if (delta < 3600)
            { // sent in last hour
                result = 'vor ' + Math.floor(delta / 60) + ' Minuten';
            }
            else if (delta < 86400)
            { // sent on last day
                result = 'vor ' + Math.floor(delta / 3600) + ' Stunden';
            }
            else
            { // sent more than one day ago
                result = 'vor ' + Math.floor(delta / 86400) + ' Tagen';
            }
            return result;
        });
    };
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
kemsky
  • 14,727
  • 3
  • 32
  • 51
  • Works perfectly, thanks! I think it's nicer to have all the code in the pipe and don't have to use a timeout function in the component. But is it also more efficient? I don't quite understand the code completely. – user2611144 Apr 17 '16 at 11:39
  • i'm extending `async` pipe which is capable of tracking observables and updating the view. Perhaps it would be better to implement such pipe from scratch using `async` [source](https://github.com/angular/angular/blob/60727c4d2ba1e4b0b9455c767d0ef152bcedc7c2/modules/angular2/src/common/pipes/async_pipe.ts) as example, but i don't have enough time. – kemsky Apr 17 '16 at 12:43
  • 1
    Does it work with angular 2 stable version? I'm getting an error in this line: `export class MessageTimePipe extends AsyncPipe` => `Type 'any' is not a constructor function type` – Dee Oct 23 '16 at 18:33
  • @kemsky you may want to add `distinctUntilChanged` operator into the observable pipe, so the observable only emits new values when it changed. – Alisson Reinaldo Silva Jun 27 '20 at 23:56
1

Use ngx-moment https://github.com/urish/ngx-moment, a pipe integration with Momentjs, with i18n support

TlmaK0
  • 3,578
  • 2
  • 31
  • 51
  • 1
    Unfortunately, `Moment.Js` is a legacy project in maintenance mode. See [Project Status](https://momentjs.com/docs/#/-project-status/) – Alex Klaus Dec 13 '20 at 23:09
1

The accepted answer can not work with angular 7+.

I followed this answer and customize for Vietnamese.

https://stackoverflow.com/a/61341940/4964569

I share for whom concern.

import {Pipe, PipeTransform} from '@angular/core';

    @Pipe({
        name: 'dateAgo',
        pure: true
    })
    export class TimePipe implements PipeTransform {
    
        transform(value: any, args?: any): any {
            if (value) {
                const seconds = Math.floor((+new Date() - +new Date(value)) / 1000);
                if (seconds < 29) // less than 30 seconds ago will show as 'Just now'
                    return 'vừa mới đăng';
                const intervals = {
                    'năm': 31536000,
                    'tháng': 2592000,
                    'tuần': 604800,
                    'ngày': 86400,
                    'giờ': 3600,
                    'phút': 60,
                    'giây': 1
                };
                let counter;
                for (const i in intervals) {
                    counter = Math.floor(seconds / intervals[i]);
                    if (counter > 0){
                      return counter + ' ' + i + ' trước'; // singular (1 day ago)
                    }
                }
            }
            return value;
        }
    }
Hien Nguyen
  • 24,551
  • 7
  • 52
  • 62
0

I think that it's not related to your pipe but to the way Angular2 detects changes. It detects changes based on references, i.e. if bound references change and not if elements in them are updated.

See the following sample:

@Component({
  selector: 'my-app',
  template: `
    <div>{{val | pdate}}</div>
  `,
  pipes: [ DatePipe ]
})
export class AppComponent {
  constructor() {
    this.val = new Date();

    setTimeout(() => {
      this.val = new Date(); // Updates view
    }, 1000);

    setTimeout(() => {
      this.val.setTime((new Date().getTime()); // Doesn't update view
    }, 2000);
  }
}

See thisx plunkr: https://plnkr.co/edit/kJdi1wx0iu9tDx8yTmRx?p=preview.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thanks, I already thought that it had to do something with change detection. But how can I get my code working then? Repeatedly update the value every second? That would work probably, but I would prefer a more elegant solution. – user2611144 Apr 16 '16 at 13:16
0

You need to update the 'date' reference to trigger Angular2's change detection, with a setTimeout(()=>this.date=new Date(), period) for instance, as Thierry pointed out.

Do you really need an update every second? Updating every 60 seconds might be good enough depending on your use case, and could display 'just now' or 'less than a minute ago' for the first 60 seconds.

But if you really want the seconds, you only need to update every seconds for the first 60 seconds. The setTimeout(1000) can then become setTimeout(60000), to minimizes overhead.

Julien
  • 3,613
  • 2
  • 23
  • 25
0

A moment.js solution. Working/tested in Angular 6:

import { ChangeDetectorRef, NgZone, Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

moment.locale("de"); // set your language

@Pipe({
    name:'timeago',
    pure:false
})
export class TimeagoPipe implements PipeTransform {    
    private timer: number;
    constructor(private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) {}
    transform(value:string) {
        this.removeTimer();
        let d = new Date(value);
        let now = new Date();
        let seconds = Math.round(Math.abs((now.getTime() - d.getTime())/1000));
        let timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) *1000;
        this.timer = this.ngZone.runOutsideAngular(() => {
            if (typeof window !== 'undefined') {
                return window.setTimeout(() => {
                    this.ngZone.run(() => this.changeDetectorRef.markForCheck());
                }, timeToUpdate);
            }
            return null;
        });

    return moment(d).fromNow();

    }
    ngOnDestroy(): void {
        this.removeTimer();
    }
    private removeTimer() {
        if (this.timer) {
            window.clearTimeout(this.timer);
            this.timer = null;
        }
    }
    private getSecondsUntilUpdate(seconds:number) {
        let min = 60;
        let hr = min * 60;
        let day = hr * 24;
        if (seconds < min) { // less than 1 min, update every 2 secs
            return 2;
        } else if (seconds < hr) { // less than an hour, update every 30 secs
            return 30;
        } else if (seconds < day) { // less then a day, update every 5 mins
            return 300;
        } else { // update every hour
            return 3600;
        }
    }
}
kenny
  • 1,628
  • 20
  • 14
-1

Get Time-Ago in AngularJs2

You need to run this command npm install time-ago-pipe --save to install the time-ago npm package package in your angular application ex : PS D:\D\TimeAgo> npm install time-ago-pipe --save. After run this command the package will added. Image shows how to add import {TimeAgoPipe} from 'time-ago-pipe'; in app.module and put TimeAgoPipe in Declarations[Pass input from componento/p

Community
  • 1
  • 1
  • 2
    That looks like the same [answer David Prieto gave over a year ago](https://stackoverflow.com/a/44591769/2225619)... besides he properly formatted his (https://stackoverflow.com/help/formatting) – Capricorn Jul 31 '18 at 15:17