user2611144
user2611144

Reputation: 413

Angular 2 "time ago" pipe

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>

Upvotes: 35

Views: 40463

Answers (8)

kemsky
kemsky

Reputation: 15279

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;
        });
    };
}

Upvotes: 25

Hien Nguyen
Hien Nguyen

Reputation: 18973

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;
        }
    }

Upvotes: 0

David Prieto
David Prieto

Reputation: 2299

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>

Upvotes: 37

TlmaK0
TlmaK0

Reputation: 3906

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

Upvotes: 1

kenny
kenny

Reputation: 1776

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;
        }
    }
}

Upvotes: 0

Bharath Kalakoti
Bharath Kalakoti

Reputation: 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

Upvotes: -1

Julien
Julien

Reputation: 3873

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.

Upvotes: 0

Thierry Templier
Thierry Templier

Reputation: 202326

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.

Upvotes: 0

Related Questions