cssangulartypescriptangular-material

Material Date Picker is out of container when changing month/year


I have an application in Angular 18 with a page containing a material date picker.

When I popup the Date Picker it's fine but when I change the month with left/right arrow or the year, data are outside the container of component

enter image description here

This is my configuration

 "dependencies": {
    "@angular/animations": "^18.1.3",
    "@angular/cdk": "^18.0.0",
    "@angular/common": "^18.1.3",
    "@angular/compiler": "^18.1.3",
    "@angular/core": "^18.1.3",
    "@angular/fire": "^18.0.1",
    "@angular/forms": "^18.1.3",
    "@angular/material": "^18.0.0",
    "@angular/material-moment-adapter": "^18.0.0",
    "@angular/platform-browser": "^18.1.3",
    "@angular/platform-browser-dynamic": "^18.1.3",
    "@angular/router": "^18.1.3",
    "@ng-bootstrap/ng-bootstrap": "^17.0.0",
    "@ngx-translate/core": "^15.0.0",
    "@ngx-translate/http-loader": "^8.0.0",
    "firebase": "^10.11.1",
    "flag-icons": "^7.2.1",
    "moment": "^2.30.1",
    "ng-bootstrap": "^1.6.3",
    "ngx-mat-select-search": "^7.0.6",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.3"
  },

Style declaration in angular.json

   "styles": [
          "src/styles.scss",
          "@angular/material/prebuilt-themes/indigo-pink.css"
        ],

styles.css

/* You can add global styles to this file, and also import other style files */

html,
body {
    height: 100%;
}

body {
    margin: 0;
    font-family: Roboto, "Helvetica Neue", sans-serif;
}



.menu-button-container {
    /* Existing styles */
    height: 100vh;
    /* Set container height to 100% viewport height */
    display: flex;
    /* Enable Flexbox layout */
    justify-content: center;
    /* Center buttons horizontally */
    align-items: center;
    /* Center buttons vertically */
    gap: 20px;
    /* Space buttons horizontally */
    flex-wrap: wrap;
    /* Enable line wrapping */
    align-content: flex-start;
    /* Align rows at the top */
}

.menu-button {
    height: 150px !important; 
    /* Increase button height */
    width: 200px !important;
    /* Increase button width */
    font-size: 18px !important;
    /* Increase font size as desired */
}

.menu-button-content {
    display: flex;
    /* Enable Flexbox */
    flex-direction: column;
    /* Arrange items vertically */
    align-items: center;
    /* Center items horizontally */
    gap: 5px;
    /* Add space between icon and text */
}

#text-page {
    font-family: Georgia, serif;
    font-size: 19px;
    letter-spacing: -1px;
    word-spacing: 0px;
    color: #000000;
    font-weight: normal;
    text-decoration: none;
    font-style: normal;
    font-variant: normal;
    text-transform: none;
    text-align: center;
}

My component with date-time-picker

<app-titre-page [titre]="'admin.people.ajouter.titre'" [introduction]="'admin.people.ajouter.introduction'"></app-titre-page>

<form [formGroup]="addPeopleForm" (ngSubmit)="validerCreationPeople()">
  <div class="flex-container">
    <div class="bloc-identite">
      <div class="bloc-numero">
        <mat-form-field class="full-width">
          <mat-hint>{{ 'admin.people.ajouter.champNumero' | translate }}</mat-hint>
          <input matInput placeholder="{{ 'admin.people.ajouter.champNumero' | translate }}" formControlName="numero" required>
          <mat-error *ngIf="addPeopleForm.get('numero')?.invalid && addPeopleForm.get('numero')?.touched">
            <ng-container *ngIf="addPeopleForm.get('numero')?.errors?.['required']">
              {{ "admin.people.ajouter.champNumeroObligatoire" | translate }}
            </ng-container>
            <ng-container *ngIf="addPeopleForm.get('numero')?.errors?.['notNumeric']">
              {{ "admin.people.ajouter.champNumeroNumerique" | translate }}
            </ng-container>
          </mat-error>
        </mat-form-field>
      </div>

      <div class="bloc-nom-prenom">
        <mat-form-field class="full-width">
          <input matInput placeholder="{{ 'admin.people.ajouter.champNom' | translate }}" formControlName="nom" required>
          <mat-error *ngIf="addPeopleForm.get('nom')?.invalid && addPeopleForm.get('nom')?.touched">
            {{ "admin.people.ajouter.champNomObligatoire" | translate }}
          </mat-error>
        </mat-form-field>

        <mat-form-field class="full-width">
          <input matInput placeholder="{{ 'admin.people.ajouter.champPrenom' | translate }}" formControlName="prenom" required>
          <mat-error *ngIf="addPeopleForm.get('prenom')?.invalid && addPeopleForm.get('prenom')?.touched">
            {{ "admin.people.ajouter.champPrenomObligatoire" | translate }}
          </mat-error>
        </mat-form-field>
      </div>

      <div class="bloc-genre">
        <mat-form-field class="full-width mat-form-field">
          <mat-select formControlName="genre" required placeholder="{{ 'commun.genre' | translate }}">
            <mat-option value="M">{{ "commun.masculin" | translate }}</mat-option>
            <mat-option value="F">{{ "commun.feminin" | translate }}</mat-option>
          </mat-select>
          <mat-error *ngIf="addPeopleForm.get('genre')?.invalid && addPeopleForm.get('genre')?.touched">
            {{ "admin.people.ajouter.champGenreObligatoire" | translate }}
          </mat-error>
        </mat-form-field>
      </div>

      <mat-form-field class="full-width mat-form-field">
        <input matInput [matDatepicker]="picker" placeholder="{{ 'admin.people.ajouter.champNaissance' | translate }}" formControlName="dateNaissance">
        <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
        <mat-datepicker #picker useUtc="false"></mat-datepicker>
      </mat-form-field>
    </div>

    <div class="bloc-adresse">
      <mat-form-field class="full-width">
        <input matInput placeholder="{{ 'admin.people.ajouter.champAdresse' | translate }}" formControlName="adresse" [matAutocomplete]="auto">
        <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selectionnerAdresse($event.option.value)">
          <mat-option *ngFor="let result of resultatsRechercheAdresse" [value]="result">
            {{ result.label }}
          </mat-option>
        </mat-autocomplete>
      </mat-form-field>

      <mat-form-field class="full-width">
        <input matInput placeholder="{{ 'admin.people.ajouter.champTelephone' | translate }}" formControlName="numeroTelephone">
        <mat-error *ngIf="addPeopleForm.get('numeroTelephone')?.invalid && addPeopleForm.get('numeroTelephone')?.touched">
          {{ "admin.people.ajouter.champTelephoneIncorrect" | translate }}
        </mat-error>
      </mat-form-field>

      <mat-form-field class="full-width">
        <input matInput placeholder="{{ 'admin.people.ajouter.champEmail' | translate }}" formControlName="email">
        <mat-error *ngIf="addPeopleForm.get('email')?.invalid && addPeopleForm.get('email')?.touched">
          {{ "admin.people.ajouter.champEmailIncorrect" | translate }}
        </mat-error>
      </mat-form-field>
    </div>
  </div>

  <div class="button-container">
    <button mat-raised-button color="primary" type="submit" [disabled]="addPeopleForm.invalid">
      {{ "commun.enregistrer" | translate }}
    </button>

    <button mat-button color="warn" type="button" (click)="annulerCreationPeople()">
      {{ "commun.annuler" | translate }}
    </button>
  </div>
</form>

CSS :

.full-width {
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
  box-sizing: border-box;
}

.flex-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  margin: 0 -16px;
}

.bloc-identite, .bloc-adresse {
  width: 100%;
  padding: 0 16px;
  box-sizing: border-box;
}

@media (min-width: 768px) {
  .bloc-identite, .bloc-adresse {
    width: calc(50% - 32px); /* Chaque bloc occupe 50% de la largeur avec des marges */
  }
}

.button-container {
  display: flex;
  justify-content: space-between;
  padding: 16px;
  box-sizing: border-box;
}

@media (max-width: 767px) {
  .button-container {
    flex-direction: column;
    align-items: stretch;
  }

  .button-container button {
    margin-bottom: 8px;
  }

  .button-container button:last-child {
    margin-bottom: 0;
  }
}

Typescript :

import { Component,   OnInit } from '@angular/core';
import { FormBuilder, FormGroup,  Validators } from '@angular/forms';
import { numericValidator } from '../../../app-validators';
import { PeopleService } from '../../../service/people.service';
import { Router } from '@angular/router';
import { debounceTime, distinctUntilChanged } from 'rxjs';
import { DataGouvService } from '../../../service/data-gouv.service';
import { People } from '../../../model/people';
import { TranslateService } from '@ngx-translate/core';

@Component({
  selector: 'app-edit-people',
  templateUrl: './edit-people.component.html',
  styleUrl: './edit-people.component.scss'
})
export class EditPeopleComponent implements OnInit {

  addPeopleForm!: FormGroup;
  resultatsRechercheAdresse: any[] = [];
  peopleAEditer: People | undefined;

  constructor(
    private fb: FormBuilder,
    private peopleService: PeopleService,
    private router: Router,
    private dataGouvService: DataGouvService,
    private traductionService: TranslateService,
  ) { 
    const navigation = this.router.getCurrentNavigation();
    if (navigation?.extras.state) {
      this.peopleAEditer = navigation.extras.state['people'];
    }
  }

  ngOnInit(): void {
    this.addPeopleForm = this.fb.group({
      nom: [this.peopleAEditer?.nom || '', [Validators.required, Validators.minLength(3)]],
      prenom: [this.peopleAEditer?.prenom || '', [Validators.required, Validators.minLength(3)]],
      numero: [this.peopleAEditer?.numero || '', [Validators.required, numericValidator()]],
      genre: [this.peopleAEditer?.genre || '', [Validators.required]],
      dateNaissance: [this.peopleAEditer?.dateNaissance || '', []],
      numeroTelephone: [this.peopleAEditer?.numeroTelephone || '', [Validators.pattern(this.traductionService.instant('admin.people.ajouter.champTelephoneFormat'))]],
      email: [this.peopleAEditer?.email || '', [Validators.email]],
      adresse: [this.peopleAEditer?.adresse, []]
    });

     // Abonnez-vous aux changements de valeur du champ de recherche avec debounceTime
     this.addPeopleForm.get('adresse')?.valueChanges
     .pipe(
       debounceTime(300), // Attendre 300 millisecondes après la dernière frappe
       distinctUntilChanged() // Ne déclencher que si la valeur a changé
     )
     .subscribe(value => {
       this.rechercherAdresses(value);
     });
     
  }

  annulerCreationPeople() {
    this.router.navigate(['/admin/people']);
  }

  validerCreationPeople() {
    if (this.addPeopleForm.valid) {
      if (this.peopleAEditer) {
        this.peopleService.mettreAJourPeople(this.addPeopleForm,this.peopleAEditer.uid ).then(() => {
          // Une fois la promesse résolue, naviguez vers la route /admin/people
          this.router.navigate(['/admin/people']);
        });
      } else {
        this.peopleService.creerPeople(this.addPeopleForm).then(() => {
          // Une fois la promesse résolue, naviguez vers la route /admin/people
          this.router.navigate(['/admin/people']);
        });
      }
    }
  }

  // Méthode appelée lorsque l'utilisateur sélectionne une suggestion
  selectionnerAdresse(address: any) {
    this.addPeopleForm.patchValue({
      adresse: address.label
    });
    this.resultatsRechercheAdresse = []; // Réinitialise la liste des résultats après la sélection
  }

  rechercherAdresses(query: string) {
    // Vérifie que query est une chaîne de caractères
    if (typeof query === 'string' && query.trim() !== '') {
      this.dataGouvService.searchAddress(query).subscribe(results => {
        this.resultatsRechercheAdresse = results;
      });
    } else {
      this.resultatsRechercheAdresse = []; // Réinitialise la liste des résultats si la requête est vide ou non valide
    }
  }
}

I think it's a CSS conflict but I cant undertand which one. Do you have any idea ?


Solution

  • Below are the changes to be made.

    1. First start using the new m3 prebuilt styles instead of old m2 styles (Optional).

           "styles": [
             "src/styles.scss",
             "@angular/material/prebuilt-themes/azure-blue.css"
           ],
      
    2. Then make sure you add the environment providers at the app.module level, instead of the shared module, this was the main reason for the breakage.

       @NgModule({
         declarations: [
           AppRoutingModule,
           BrowserModule,
           AdministrationModule,
           SharedModule,
         ],
         providers: [ 
           provideHttpClient(withInterceptorsFromDi()),
           provideAnimationsAsync(),
           provideNativeDateAdapter(),
         ]
       })
       export class AppModule { }
      
    3. Then finally I removed the duplicate translate module import and removed the environment providers since we already shifted them to the root app.module.

    One more thing, is that you are lazy loading, but you are adding the module at the app.module.ts level, this effectively cancels the lazy loading so remove the lazy loaded modules from the app module.

    imports: [
        AppRoutingModule,
        BrowserModule,
        // AdministrationModule, // <- remove this!
        SharedModule  
      ],
      providers: [ ]
    })
    export class AppModule { }
    

    Github Repo