angularmomentjsangular-providersangular-standalone-components

Why can't I provide config at (standalone) component level?


I'm working with Angular Material and moment.js. I had the problem that dates were parsed according to my timezone, instead of just, well, what the user entered.

There's already a question about it here and it also has a working answer. But it's rather old, not taking into account standalone components, which do offer the possibility of providing stuff directly at the component level. However, that does not work in this case:

@Component({
  selector: 'app-component-with-material-datepicker',
  templateUrl: './component-with-material-datepicker.component.html',
  styleUrls: ['./component-with-material-datepicker.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    MatDatepickerModule,
    MatMomentDateModule,
  ],
  providers: [
    { provide: ErrorStateMatcher, useClass: InstantErrorStateMatcher }, // works
    MomentDateAdapter,
    { provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }, // no effect
  ],
})
export class ComponentWithMaterialDatePicker {
 // ...

This leaves me no choice but to provide the config at module level. I wish to understand why.


Solution

  • But that provider is an environmentProvider and they need to be placed in the providers array of bootstrapApplication second parameter. So could you check the below stackblitz. Where I add in environment providers and it seems to work fine!

    Important points:

    import

    import { provideMomentDateAdapter } from '@angular/material-moment-adapter';
    

    code

    bootstrapApplication(App, {
      providers: [
        importProvidersFrom(MatNativeDateModule),
        provideMomentDateAdapter(undefined, { useUtc: true }),
        provideAnimations(),
      ],
    });
    

    code

    import { Component, importProvidersFrom } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { MatDatepickerModule } from '@angular/material/datepicker';
    import { MatInputModule } from '@angular/material/input';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
    import moment from 'moment';
    import { MatNativeDateModule } from '@angular/material/core';
    import { provideMomentDateAdapter } from '@angular/material-moment-adapter';
    import { provideAnimations } from '@angular/platform-browser/animations';
    import { CommonModule } from '@angular/common';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [
        MatFormFieldModule,
        MatInputModule,
        MatDatepickerModule,
        FormsModule,
        ReactiveFormsModule,
        CommonModule,
      ],
      template: `
        <mat-form-field>
      <mat-label>Moment.js datepicker</mat-label>
      <input matInput [matDatepicker]="dp" [formControl]="date">
      <mat-hint>MM/DD/YYYY</mat-hint>
      <mat-datepicker-toggle matIconSuffix [for]="dp"></mat-datepicker-toggle>
      <mat-datepicker #dp></mat-datepicker>
    </mat-form-field>
    
    {{date.value | json}}
      `,
    })
    export class App {
      name = 'Angular';
      date = new FormControl(moment([2017, 0, 1]));
    }
    
    bootstrapApplication(App, {
      providers: [
        importProvidersFrom(MatNativeDateModule),
        provideMomentDateAdapter(undefined, { useUtc: true }),
        provideAnimations(),
      ],
    });
    

    stackblitz