4

I would like to make moment to be injectable through out my app.
I just started learning ng2 and couldn't find this type of usage in the docs.
Here is what I have in my app.module.ts:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {AppComponent} from './app.component';
import * as moment from 'moment';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [{provide: 'moment', useValue: moment}],
  bootstrap: [AppComponent]
})
export class AppModule {
}

and here is the component:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.sass']
})


export class AppComponent {
  title = 'app works!';

  constructor(private moment) {
    this.title += this.moment;
  }
}

there is this error:

Uncaught Error: Can't resolve all parameters for AppComponent:

How should this be done correctly?

UPDATED MODULE

const moment = new OpaqueToken('moment');

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [{provide: moment, useValue: moment}],
  bootstrap: [AppComponent]
})

export class AppModule {
}

UPDATED COMPONENT

import { Component } from '@angular/core';
import * as moment from 'moment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.sass']
})


export class AppComponent {
  title = 'app works!';

  constructor(private moment: moment) {
    this.title += this.moment()
  }
}

There is an error on this line constructor(private moment: moment) which tells that: Cannot find name 'moment'.

vlio20
  • 8,955
  • 18
  • 95
  • 180

6 Answers6

4

Moment by itself is not an injectable for Angular2. However it can be wrapped inside one.

Plunker Demo

moment.service.ts

import { Injectable } from '@angular/core';
import * as m from 'moment';
@Injectable()
export class MomentService {
    moment = m;
}

app.module.ts

import { MomentService } from './moment.service';

@NgModule({
    providers: [MomentService]
    ...

app.component.ts

import { MomentService } from './moment.service';

export class AppComponent {
    constructor(private ms: MomentService){
        console.log('Moment:' + this.ms.moment("20111031", "YYYYMMDD").toString());
    }
}

Not perfect, but works.

John Siu
  • 5,056
  • 2
  • 26
  • 47
  • It's a nice way to handle this. But I am hoping there is a way to do it without a wrapper service. The same way we did in ng1 with constant. – vlio20 Oct 08 '16 at 06:35
  • @vlio20 This is actually the Angular 2 way to do global variable, through injectable service public properties/variables. Check out my [ng2-simple-global](https://github.com/J-Siu/ng2-simple-global) service, there is a plunker link in the readme.md. `moment` here is the *global variable*. – John Siu Oct 08 '16 at 07:02
3

You need to use OpaqueToken which will allowed you to create a string based token. I'd just suggest you to change the name of moment to avoid thr collision with moment variable of library.

// You can place this is some file, so that you can export it.
export const Moment = new OpaqueToken('moment');

and then you could use

providers: [{provide: MomentStatic, useClass: moment}],

You can go through this article for more details

While using dependency include it in a Component constructor.

constructor(private moment: MomentStatic)
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • I am getting the same error. I am probably missing the injection part. Can you please add it to your answer (the injection of moment in the constructor)? – vlio20 Sep 29 '16 at 22:05
  • That's but obivious in A2 – Pankaj Parkar Sep 30 '16 at 04:42
  • it's just what I did, but there are few questions and problems. I have imported moment (in the component file) like this: `import * as moment from 'moment';` but the I am getting this error: `Cannot find name 'moment'.` at this line: `constructor(private moment: moment)` (see updated question to see the full component (under update) – vlio20 Sep 30 '16 at 05:37
  • have you added `map` for moment in system.config.js – Pankaj Parkar Sep 30 '16 at 06:14
  • I am using the angular-cli. It is not required to make any changes to the systemjs configuration. moment is available globally. – vlio20 Sep 30 '16 at 06:18
  • I guess its conflicting with moment variable of moment.js.. try updated answer – Pankaj Parkar Sep 30 '16 at 06:29
  • Probably better to use `MomentStatic` instead of `Moment`. But even then, in the component file, from where should I import the `Moment` type (for this line: `constructor(private moment: Moment)`)? – vlio20 Sep 30 '16 at 06:34
  • That's why I said place opaque token in some file & export it when you want to, otherwise you need to define opaque token everytime when you want it – Pankaj Parkar Sep 30 '16 at 06:37
  • The problem is that `export const Moment = new OpaqueToken('moment');` is not a type. I can't use it at this line: `constructor(private moment: MomentStatic)` – vlio20 Sep 30 '16 at 06:44
  • Any progress with this one? – vlio20 Sep 30 '16 at 22:51
  • Oh.sorry will look at it in my morning – Pankaj Parkar Sep 30 '16 at 22:51
  • @vlio20 I changed `useValue` to `useClass` – Pankaj Parkar Oct 01 '16 at 11:06
  • The problem is the injection. You can't specify that the type is const. (you should also update the const Moment to const MomentStatic) – vlio20 Oct 01 '16 at 11:07
0

Moment doesn't have to be injected, it is a library that you can "just use". It's enough to import it in your typescript file now you can use moment's features.

Alexander Ciesielski
  • 10,506
  • 5
  • 45
  • 66
0

If you are loading moment.js so it is available globally how about wrapping it with a service which you can then inject throughout your app?

import {Moment} from '../../node_modules/moment';

import { Injectable } from '@angular/core';

declare var moment: any;

@Injectable()
export class MomentService {
    constructor() { }

    get(): Moment {
        return moment;
    }
}

I this way you get the TS IntelliSense when you are coding (at least I do in VSCode) and you can handle mocking moment for testing easily too.

JayChase
  • 11,174
  • 2
  • 43
  • 52
0

The best workaround that I have come to find is to create a wrapper service, but also expose some of the most common methods directly on the service itself using getters:

import { Injectable } from '@angular/core';
import * as moment from 'moment';

/**
 * A wrapper for the moment library
 */
@Injectable()
export class MomentService {
  /**
   * Creates and returns a new moment object with the current date/time
   */
  public moment() { return moment(); }

  // expose moment properties directly on the service
  public get utc() { return moment.utc; }
  public get version() { return moment.version; }
  public get unix() { return moment.unix; }
  public get isMoment() { return moment.isMoment; }
  public get isDate() { return moment.isDate; }
  public get isDuration() { return moment.isDuration; }
  public get now() { return moment.now; }
}
Merott
  • 7,189
  • 6
  • 40
  • 52
0

Not sure if this helps now but you need something similar (not tested) to the following to make it work in your service.

import { Inject } from '@angular/core';

constructor(@Inject(moment) private moment) {
    this.title += this.moment()
  }

The key bit is the @Inject

Kamalpreet
  • 1,043
  • 9
  • 11