Angular + Material - Come aggiornare un'origine dati (mat-table)


120

Sto usando un mat-table per elencare il contenuto delle lingue scelte dagli utenti. Possono anche aggiungere nuove lingue utilizzando il pannello di dialogo. Dopo hanno aggiunto una lingua e sono tornati indietro. Voglio che la mia origine dati venga aggiornata per mostrare le modifiche apportate.

Inizializzo il datastore ottenendo i dati dell'utente da un servizio e passandoli a un'origine dati nel metodo di aggiornamento.

Language.component.ts

import { Component, OnInit } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';

@Component({
  selector: 'app-language',
  templateUrl: './language.component.html',
  styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {

  displayedColumns = ['name', 'native', 'code', 'level'];
  teachDS: any;
  user: any;

  constructor(private authService: AuthService, private dialog: MatDialog) { }

  ngOnInit() {
    this.refresh();
  }

  add() {
    this.dialog.open(LanguageAddComponent, {
      data: { user: this.user },
    }).afterClosed().subscribe(result => {
      this.refresh();
    });
  }

  refresh() {
    this.authService.getAuthenticatedUser().subscribe((res) => {
      this.user = res;
      this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);   
    });
  }
}

lingua-data-source.ts

import {MatPaginator, MatSort} from '@angular/material';
import {DataSource} from '@angular/cdk/collections';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';

export class LanguageDataSource extends DataSource<any> {

  constructor(private languages) {
    super();
  }

  connect(): Observable<any> {
    return Observable.of(this.languages);
  }

  disconnect() {
    // No-op
  }

}

Quindi ho provato a chiamare un metodo di aggiornamento in cui ottengo di nuovo l'utente dal back-end e quindi reinizializzo l'origine dati. Tuttavia questo non funziona, non si stanno verificando modifiche.


1
Se desideri attivare la modifica "dall'origine dati", dai un'occhiata a stackoverflow.com/questions/47897694/…
Yennefer

In questo caso è possibile utilizzare emettitori di eventi. stackoverflow.com/a/44858648/8300620
Rohit Parte

Risposte:


58

Attiva un rilevamento delle modifiche utilizzando ChangeDetectorRefnel refresh()metodo subito dopo aver ricevuto i nuovi dati, inserisci ChangeDetectorRef nel costruttore e usa detectChanges in questo modo:

import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { LanguageModel, LANGUAGE_DATA } from '../../../../models/language.model';
import { LanguageAddComponent } from './language-add/language-add.component';
import { AuthService } from '../../../../services/auth.service';
import { LanguageDataSource } from './language-data-source';
import { LevelbarComponent } from '../../../../directives/levelbar/levelbar.component';
import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { MatSnackBar, MatDialog } from '@angular/material';

@Component({
  selector: 'app-language',
  templateUrl: './language.component.html',
  styleUrls: ['./language.component.scss']
})
export class LanguageComponent implements OnInit {
  displayedColumns = ['name', 'native', 'code', 'level'];
  teachDS: any;

  user: any;

  constructor(private authService: AuthService, private dialog: MatDialog,
              private changeDetectorRefs: ChangeDetectorRef) { }

  ngOnInit() {
    this.refresh();
  }

  add() {
    this.dialog.open(LanguageAddComponent, {
      data: { user: this.user },
    }).afterClosed().subscribe(result => {
      this.refresh();
    });
  }

  refresh() {
    this.authService.getAuthenticatedUser().subscribe((res) => {
      this.user = res;
      this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);
      this.changeDetectorRefs.detectChanges();
    });
  }
}

9
Sembra funzionare, è questo il modo corretto per farlo? Sembra un po 'hacky ...
Kay

Quali sono gli altri modi? Potreste fornire esempi nella vostra soluzione per una risposta completa?
Kay


88

Non so se ChangeDetectorRefera richiesto quando è stata creata la domanda, ma ora basta:

import { MatTableDataSource } from '@angular/material/table';

// ...

dataSource = new MatTableDataSource<MyDataType>();

refresh() {
  this.myService.doSomething().subscribe((data: MyDataType[]) => {
    this.dataSource.data = data;
  }
}

Esempio:
StackBlitz


4
Sebbene questa soluzione in effetti funzioni, rovina l'impaginatore del materiale se l'elemento viene aggiunto da qualche parte diverso dalla prima pagina dei risultati. Capisco che questo esuli dallo scopo di questa domanda, ma poiché i due sono correlati, ti capita di avere una soluzione rapida a quel problema che potresti aggiungere alla tua risposta?
Knight

5
@Knight Penso che tu debba assegnare il Paginator alla MatTableDataSource.paginatorproprietà dopo che la vista del Paginator è stata inizializzata. Vedi la descrizione della paginatorproprietà qui: material.angular.io/components/table/api#MatTableDataSource
Martin Schneider,

Buon riferimento. Non l'avevo notato prima nei documenti. Grazie!
Knight

2
@ MA-Maddin puoi essere più specifico su come farlo? esempio?
Nathanphan,

@ Nathanphan in ritardo, ma ha aggiunto un esempio;)
Martin Schneider,

46

Quindi per me, nessuno ha dato la buona risposta al problema che ho incontrato che è quasi lo stesso di @Kay. Per me si tratta di smistamento, ordinamento tabella non si verificano cambiamenti nel tappetino. Scopo questa risposta poiché è l'unico argomento che trovo cercando su Google. Sto usando Angular 6.

Come detto qui :

Poiché la tabella si ottimizza per le prestazioni, non verificherà automaticamente le modifiche all'array di dati. Invece, quando gli oggetti vengono aggiunti, rimossi o spostati sull'array di dati, è possibile attivare un aggiornamento delle righe renderizzate della tabella chiamando il suo metodo renderRows ().

Quindi devi solo chiamare renderRows () nel tuo metodo refresh () per far apparire le tue modifiche.

Vedi qui per l'integrazione.


1
Se l'origine dati della tabella viene modificata sul client, questa risposta è probabilmente ciò che stai cercando. Funziona alla grande!
Alvin Saldanha

Questa è la risposta corretta a partire dal materiale angolare 8
Tom

Grazie. Ma da quale oggetto devo chiamare "renderRows ()"? È in "this.datasource"?
WitnessTruth

19

Dal momento che stai usando MatPaginator, devi solo fare qualsiasi modifica al paginator, questo innesca il ricaricamento dei dati.

Trucco semplice:

this.paginator._changePageSize(this.paginator.pageSize); 

Questo aggiorna la dimensione della pagina alla dimensione della pagina corrente, quindi in pratica non cambia nulla, tranne che _emitPageEvent()viene chiamata anche la funzione private , attivando il ricaricamento della tabella.


Ho provato il tuo codice e non funziona (non ha effetto). Tuttavia nextPage e quindi previousPage di nuovo funzionano, ma non sono una soluzione.
Ahmed Hasn.

_changePageSize()non è pubblico giusto? è sicuro da usare? Altro su stackoverflow.com/questions/59093781/…
Jones

9
this.dataSource = new MatTableDataSource<Element>(this.elements);

Aggiungi questa riga sotto la tua azione di aggiunta o eliminazione della riga specifica.

refresh() {
  this.authService.getAuthenticatedUser().subscribe((res) => {
    this.user = new MatTableDataSource<Element>(res);   
  });
}

cos'è questo. elementi
parvat

8

Il modo migliore per farlo è aggiungere un osservabile aggiuntivo all'implementazione dell'origine dati.

Nel metodo connect dovresti già usare Observable.mergeper iscriverti a un array di osservabili che includono paginator.page, sort.sortChange, ecc. Puoi aggiungere un nuovo oggetto a questo e chiamarlo dopo quando devi causare un aggiornamento.

qualcosa come questo:

export class LanguageDataSource extends DataSource<any> {

    recordChange$ = new Subject();

    constructor(private languages) {
      super();
    }

    connect(): Observable<any> {

      const changes = [
        this.recordChange$
      ];

      return Observable.merge(...changes)
        .switchMap(() => return Observable.of(this.languages));
    }

    disconnect() {
      // No-op
    }
}

E poi puoi chiamare recordChange$.next()per avviare un aggiornamento.

Naturalmente avvolgerei la chiamata in un metodo refresh () e la chiamerei fuori dall'istanza dell'origine dati con il componente e altre tecniche appropriate.


Questo metodo può essere il modo giusto. Per me funziona bene
Manu

come lo implemento quando voglio estendere MatTableDataSource? Quando provo il tuo esempio di codice ottengo l'erroreProperty 'connect' in type 'customDataSource<T>' is not assignable to the same property in base type 'MatTableDataSource<T>'. Type '() => Observable<any>' is not assignable to type '() => BehaviorSubject<T[]>'. Type 'Observable<any>' is not assignable to type 'BehaviorSubject<T[]>'. Property '_value' is missing in type 'Observable<any>'.
Maurice

1
@Maurice il tipo MatTableDataSource implementa il metodo di connessione con un diverso tipo di ritorno. Usa BehaviorSubject <t []>, il che significa che devi solo modificare l'esempio per restituirlo invece di un Observable. Dovresti comunque essere in grado di utilizzare DataSource, ma se devi usare MatTableDataSource, restituisci un BehaviorSubject che è iscritto all'osservabile, supponendo che tu ne abbia uno da cui iniziare. Spero che aiuti. È possibile fare riferimento alla fonte per MatTableDataSource per la sintassi esatta del nuovo tipo di origine dati: github.com/angular/material2/blob/master/src/lib/table/…
jogi

7

Puoi semplicemente usare la funzione di connessione dell'origine dati

this.datasource.connect().next(data);

così. "data" è il nuovo valore per il datatable


Aveva del potenziale ma non sembra funzionare. Se dopo si accede a this.datasource.data, non è aggiornato.
Rui Marques,

4

Puoi aggiornare facilmente i dati della tabella usando "concat":

per esempio:

language.component.ts

teachDS: any[] = [];

language.component.html

<table mat-table [dataSource]="teachDS" class="list">

E, quando aggiorni i dati (language.component.ts):

addItem() {
    // newItem is the object added to the list using a form or other way
    this.teachDS = this.teachDS.concat([newItem]);
 }

Quando si utilizza "concat" angular rileva i cambiamenti dell'oggetto (this.teachDS) e non è necessario utilizzare un'altra cosa.

PD: Funziona con gli angoli 6 e 7, non ho provato un'altra versione.


2
Sì, per me funziona, è un problema relativo al riferimento e al valore var, il rilevamento delle modifiche non vede le nuove modifiche, per questo è necessario aggiornarlo.
Mayra Rodriguez

Ciò potrebbe funzionare se dataSource è solo un array ma non quando dataSource è un oggetto MatTableDataSource.
Rui Marques


3

Bene, mi sono imbattuto in un problema simile in cui ho aggiunto qualcosa all'origine dati e non si ricarica.

Il modo più semplice che ho trovato è stato semplicemente riassegnare i dati

let dataSource = ['a','b','c']
dataSource.push('d')
let cloned = dataSource.slice()
// OR IN ES6 // let cloned = [...dataSource]
dataSource = cloned

Perfetto!! Funziona !! Grazie :)
Nicolas

3

In Angular 9, il segreto è this.dataSource.data = this.dataSource.data;

Esempio:

import { MatTableDataSource } from '@angular/material/table';

dataSource: MatTableDataSource<MyObject>;

refresh(): void {
    this.applySomeModif();
    // Do what you want with dataSource

    this.dataSource.data = this.dataSource.data;
}

applySomeModif(): void {
    // add some data
    this.dataSource.data.push(new MyObject());
    // delete index number 4
    this.dataSource.data.splice(4, 0);
}

2

Ho ottenuto una buona soluzione utilizzando due risorse:

aggiornare sia dataSource che paginator:

this.dataSource.data = this.users;
this.dataSource.connect().next(this.users);
this.paginator._changePageSize(this.paginator.pageSize);

dove ad esempio dataSource è definito qui:

    users: User[];
    ...
    dataSource = new MatTableDataSource(this.users);
    ...
    this.dataSource.paginator = this.paginator;
    ...

1
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';

export class LanguageComponent implemnts OnInit {
  displayedColumns = ['name', 'native', 'code', 'leavel'];
  user: any;
  private update = new Subject<void>();
  update$ = this.update.asObservable();

  constructor(private authService: AuthService, private dialog: MatDialog) {}

   ngOnInit() {
     this.update$.subscribe(() => { this.refresh()});
   }

   setUpdate() {
     this.update.next();
   }

   add() {
     this.dialog.open(LanguageAddComponent, {
     data: { user: this.user },
   }).afterClosed().subscribe(result => {
     this.setUpdate();
   });
 }

 refresh() {
   this.authService.getAuthenticatedUser().subscribe((res) => {
     this.user = res;
     this.teachDS = new LanguageDataSource(this.user.profile.languages.teach);   
    });
  }
}

8
Si prega di aggiungere una spiegazione alla risposta, il semplice inserimento del codice non è molto utile e può comportare la rimozione della risposta.
TJ Wolschon

1

nel mio caso (Angular 6+), ho ereditato da MatTableDataSourceper creare MyDataSource. Senza chiamare dopothis.data = someArray

this.entitiesSubject.next(this.data as T[])

dati dove non vengono visualizzati

classe MyDataSource

export class MyDataSource<T extends WhateverYouWant> extends MatTableDataSource<T> {

    private entitiesSubject = new BehaviorSubject<T[]>([]);


    loadDataSourceData(someArray: T[]){
        this.data = someArray //whenever it comes from an API asyncronously or not
        this.entitiesSubject.next(this.data as T[])// Otherwise data not displayed
    }

    public connect(): BehaviorSubject<T[]> {
        return this.entitiesSubject
    }

}//end Class 

1

Ci sono due modi per farlo perché Angular Material è incoerente e questo è molto scarsamente documentato. La tabella dei materiali angolari non si aggiornerà quando arriverà una nuova riga. Sorprendentemente si dice che sia perché ha problemi di prestazioni. Ma sembra più un problema di progettazione, non possono cambiare. Ci si dovrebbe aspettare che la tabella si aggiorni quando si verifica una nuova riga. Se questo comportamento non deve essere abilitato per impostazione predefinita, dovrebbe esserci un interruttore per disattivarlo.

Ad ogni modo, non possiamo cambiare il materiale angolare. Ma fondamentalmente possiamo usare un metodo scarsamente documentato per farlo:

Uno: se usi un array direttamente come sorgente:

call table.renderRows()

dove table è ViewChild del mat-table

Secondo: se usi l'ordinamento e altre funzionalità

table.renderRows () sorprendentemente non funzionerà. Perché mat-table è incoerente qui. È necessario utilizzare un hack per dire alla fonte modificata. Lo fai con questo metodo:

this.dataSource.data = yourDataSource;

dove dataSource è il wrapper MatTableDataSource utilizzato per l'ordinamento e altre funzionalità.


0

Questo ha funzionato per me:

refreshTableSorce() {
    this.dataSource = new MatTableDataSource<Element>(this.newSource);
}

Non è una soluzione ideale in quanto ricrea il sorgente per la tabella e l'utilizzo di questo con streamlined / socket non è efficiente.
Maihan Nijat

0

Penso che l' MatTableDataSourceoggetto sia in qualche modo collegato all'array di dati a cui passiMatTableDataSource costruttore.

Per esempio:

dataTable: string[];
tableDS: MatTableDataSource<string>;

ngOnInit(){
   // here your pass dataTable to the dataSource
   this.tableDS = new MatTableDataSource(this.dataTable); 
}

Quindi, quando devi modificare i dati; modificare nell'elenco originale dataTablee quindi riflettere la modifica sulla tabella chiamando il _updateChangeSubscription()metodo sutableDS .

Per esempio:

this.dataTable.push('testing');
this.tableDS._updateChangeSubscription();

Funziona con me attraverso Angular 6.


4
Quel metodo è preceduto da un trattino basso _e lo chiami?
Stephane

0

Questo funziona per me:

dataSource = new MatTableDataSource<Dict>([]);
    public search() {
        let url = `${Constants.API.COMMON}/dicts?page=${this.page.number}&` + 
        (this.name == '' ? '' : `name_like=${this.name}`);
    this._http.get<Dict>(url).subscribe((data)=> {
    // this.dataSource = data['_embedded'].dicts;
    this.dataSource.data =  data['_embedded'].dicts;
    this.page = data['page'];
    this.resetSelection();
  });
}

Quindi dovresti dichiarare la tua istanza di origine dati come MatTableDataSource


0

Ho fatto altre ricerche e ho trovato questo posto per darmi ciò di cui avevo bisogno - sembra pulito e si riferisce all'aggiornamento dei dati quando aggiornato dal server: https://blog.angular-university.io/angular-material-data-table/

La maggior parte dei crediti alla pagina sopra. Di seguito è riportato un esempio di come un mat-selector può essere utilizzato per aggiornare una tabella mat associata a un'origine dati al cambio di selezione. Sto usando Angular 7. Ci scusiamo per essere esteso, cercando di essere completo ma conciso - ho strappato il maggior numero possibile di parti non necessarie. Con questo sperando di aiutare qualcun altro ad andare avanti più velocemente!

organization.model.ts:

export class Organization {
    id: number;
    name: String;
}

organization.service.ts:

import { Observable, empty } from 'rxjs';
import { of } from 'rxjs';

import { Organization } from './organization.model';

export class OrganizationService {
  getConstantOrganizations(filter: String): Observable<Organization[]> {
    if (filter === "All") {
      let Organizations: Organization[] = [
        { id: 1234, name: 'Some data' }
      ];
      return of(Organizations);
     } else {
       let Organizations: Organization[] = [
         { id: 5678, name: 'Some other data' }
       ];
     return of(Organizations);
  }

  // ...just a sample, other filterings would go here - and of course data instead fetched from server.
}

organizationdatasource.model.ts:

import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { catchError, finalize } from "rxjs/operators";

import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';

export class OrganizationDataSource extends DataSource<Organization> {
  private organizationsSubject = new BehaviorSubject<Organization[]>([]);

  private loadingSubject = new BehaviorSubject<boolean>(false);

  public loading$ = this.loadingSubject.asObservable();

  constructor(private organizationService: OrganizationService, ) {
    super();
  }

  loadOrganizations(filter: String) {
    this.loadingSubject.next(true);

    return this.organizationService.getOrganizations(filter).pipe(
      catchError(() => of([])),
      finalize(() => this.loadingSubject.next(false))
    ).subscribe(organization => this.organizationsSubject.next(organization));
  }

  connect(collectionViewer: CollectionViewer): Observable<Organization[]> {
    return this.organizationsSubject.asObservable();
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.organizationsSubject.complete();
    this.loadingSubject.complete();
  }
}

organizations.component.html:

<div class="spinner-container" *ngIf="organizationDataSource.loading$ | async">
    <mat-spinner></mat-spinner>
</div>

<div>
  <form [formGroup]="formGroup">
    <mat-form-field fxAuto>
      <div fxLayout="row">
        <mat-select formControlName="organizationSelectionControl" (selectionChange)="updateOrganizationSelection()">
          <mat-option *ngFor="let organizationSelectionAlternative of organizationSelectionAlternatives"
            [value]="organizationSelectionAlternative">
            {{organizationSelectionAlternative.name}}
          </mat-option>
        </mat-select>
      </div>
    </mat-form-field>
  </form>
</div>

<mat-table fxLayout="column" [dataSource]="organizationDataSource">
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
    <mat-cell *matCellDef="let organization">{{organization.name}}</mat-cell>
  </ng-container>

  <ng-container matColumnDef="number">
    <mat-header-cell *matHeaderCellDef>Number</mat-header-cell>
    <mat-cell *matCellDef="let organization">{{organization.number}}</mat-cell>
  </ng-container>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>

organizations.component.scss:

.spinner-container {
    height: 360px;
    width: 390px;
    position: fixed;
}

organization.component.ts:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Observable } from 'rxjs';

import { OrganizationService } from './organization.service';
import { Organization } from './organization.model';
import { OrganizationDataSource } from './organizationdatasource.model';

@Component({
  selector: 'organizations',
  templateUrl: './organizations.component.html',
  styleUrls: ['./organizations.component.scss']
})
export class OrganizationsComponent implements OnInit {
  public displayedColumns: string[];
  public organizationDataSource: OrganizationDataSource;
  public formGroup: FormGroup;

  public organizationSelectionAlternatives = [{
    id: 1,
    name: 'All'
  }, {
    id: 2,
    name: 'With organization update requests'
  }, {
    id: 3,
    name: 'With contact update requests'
  }, {
    id: 4,
    name: 'With order requests'
  }]

  constructor(
    private formBuilder: FormBuilder,
    private organizationService: OrganizationService) { }

  ngOnInit() {
    this.formGroup = this.formBuilder.group({
      'organizationSelectionControl': []
    })

    const toSelect = this.organizationSelectionAlternatives.find(c => c.id == 1);
    this.formGroup.get('organizationSelectionControl').setValue(toSelect);

    this.organizationDataSource = new OrganizationDataSource(this.organizationService);
    this.displayedColumns = ['name', 'number' ];
    this.updateOrganizationSelection();
  }

  updateOrganizationSelection() {
    this.organizationDataSource.loadOrganizations(this.formGroup.get('organizationSelectionControl').value.name);
  }
}

0

Dopo aver letto la tabella dei materiali non si aggiornava l'aggiornamento dei dati post # 11638 Bug Report ho trovato che la migliore (letta, la soluzione più semplice) era come suggerito dal commentatore finale "shhdharmen" con un suggerimento di utilizzare un EventEmitter.

Ciò comporta alcune semplici modifiche alla classe di origine dati generata

cioè) aggiungi una nuova variabile privata alla tua classe di origine dati

import { EventEmitter } from '@angular/core';
...
private tableDataUpdated = new EventEmitter<any>();

e dove inserisco nuovi dati nell'array interno (this.data), emetto un evento.

public addRow(row:myRowInterface) {
    this.data.push(row);
    this.tableDataUpdated.emit();
}

e infine, cambia l'array 'dataMutation' nel metodo 'connect' - come segue

const dataMutations = [
    this.tableDataUpdated,
    this.paginator.page,
    this.sort.sortChange
];

0

// questo è il dataSource
this.guests = [];

this.guests.push ({id: 1, name: 'Ricardo'});

// aggiorna il dataSource this.guests = Array.from (this.guest);


0
npm install @matheo/datasource

Ho rilasciato una libreria destinata ad essere il Material DataSource ufficiale in futuro, supportando qualsiasi tipo di flussi di input (ordinamento, impaginazione, filtri) e alcune configurazioni con debug per vedere come funziona mentre si codifica.

import { MatDataSourceModule } from '@matheo/datasource';

Puoi trovare la demo StackBlitz e ulteriori informazioni qui:
https://medium.com/@matheo/reactive-datasource-for-angular-1d869b0155f6

Sarei felice di sentire la tua opinione e, se necessario, di supportare i tuoi casi d'uso.
Buona programmazione!


0

Avevo provato ChangeDetectorRef, Subject e BehaviourSubject ma quello che funziona per me

dataSource = [];
this.dataSource = [];
 setTimeout(() =>{
     this.dataSource = this.tableData[data];
 },200)

cosa sta succedendo qui? Mi sento come se fossero stati commessi errori di denominazione delle variabili.
ChumiestBucket
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.