Angular 2 “time ago” pipe

2019-02-01 08:00发布

问题:

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>

回答1:

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


回答2:

I just used this:

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>

Don't reinvent the wheel, unless you plan on learning more about wheels.



回答3:

Based on the excellent answer by @kemsky, I have implemented a variant of the Pipe that fixes a couple of implementation issues:

  • will work properly with angular 2 final and onwards
  • stops polling when destroyed
  • uses a simplistic backoff to decrease polling interval consistent with output

You can find the full pipe + spec code in this gist: https://gist.github.com/JohannesRudolph/8e6de056d9e33353f940d9da9e6ffd82



回答4:

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.



回答5:

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.



回答6:

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



回答7:

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