Solucionado (ver solução)
Solucionado
(ver solução)
8
respostas

[Dúvida] Problema com o mat table do material

Estou estudando e atualmente estou com um projetinho simples onde eu tenho um back-end pronto de cursos e agora quero fazer o front. Ao tentar listar os meus cursos salvos na tela usando o material design table, estou recebendo o seguinte erro:

urses.component.html:4 ERROR Error: Provided data source did not match an array, Observable, or DataSource
    at getTableUnknownDataSourceError (table.mjs:975:12)
    at MatTable._observeRenderChanges (table.mjs:1700:19)
    at MatTable.ngAfterContentChecked (table.mjs:1352:18)
    at callHookInternal (core.mjs:3852:14)
    at callHook (core.mjs:3883:9)
    at callHooks (core.mjs:3834:17)
    at executeInitAndCheckHooks (core.mjs:3784:9)
    at refreshView (core.mjs:12530:21)
    at detectChangesInView (core.mjs:12653:9)
    at detectChangesInEmbeddedViews (core.mjs:12597:13)

O meu component esta da seguinte forma:

import { Component } from '@angular/core';
import { Course } from '../model/course';
import { CoursesService } from '../services/courses.service';
import { Observable, catchError, of } from 'rxjs';

import { MatDialog } from '@angular/material/dialog';
import { ErrorDialogComponent } from 'src/app/shared/components/error-dialog/error-dialog.component';

@Component({
  selector: 'app-courses',
  templateUrl: './courses.component.html',
  styleUrls: ['./courses.component.scss']
})
export class CoursesComponent {

  courses$: Observable<Course[]>; //meu datasource
  displayedColumns = ['name', 'category']

  constructor(public dialog: MatDialog,
    private courseService: CoursesService) {
    this.courses$ = this.courseService.list()
    .pipe(

      catchError(error => {
       this.onError('Erro ao carregar cursos.');
       return of([])
      }),
    );
  }

  onError(errorMsg: string) {
    this.dialog.open(ErrorDialogComponent, {
      data: errorMsg
    });
  }
}

Meu html:

<mat-toolbar color="primary">Cursos Disponíveis</mat-toolbar>

  <mat-card class="example-spacer ">
  <div *ngIf="courses$ | async as courses; else loading">

    <table mat-table [dataSource] = "courses">

      <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef> Nome </th>
        <td mat-cell *matCellDef="let course"> {{course.name}} </td>
      </ng-container>

      <ng-container matColumnDef="category">
        <th mat-header-cell *matHeaderCellDef> Categoria </th>
        <td mat-cell *matCellDef="let course"> {{course.category}} </td>
      </ng-container>

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

  <ng-template #loading>
    <div class="loading-spinner">
      <mat-spinner></mat-spinner>
    </div>
  </ng-template>
  </mat-card>

O service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Course } from '../model/course';
import { delay, first, tap } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CoursesService {

  private readonly API = 'api/cursos';

  constructor(private httpClient: HttpClient) { }

  list() {
    return this.httpClient.get<Course[]>(this.API)
    .pipe(
      first(),
      delay(5000),
      tap(courses => console.log(courses))
    );
  }
}

O que esta errado em relação ao meu datasource que não carrega as minhas informações. Pelo erro aredito que não esta reconhecendo a minha variável como um array.... mas não faço ideia de como resolver

8 respostas

Olá Natali! Tudo bem?

Primeiramente, obrigado por compartilhar sua dúvida conosco. Vamos analisar o problema com o MatTable e tentar encontrar uma solução para que suas informações sejam carregadas corretamente na tabela.

Pelo código fornecido, parece que você está quase no caminho certo. O erro que você está enfrentando é relacionado ao uso do MatTable e seu DataSource. O MatTable espera que o DataSource seja um array, Observable ou uma implementação de DataSource personalizada, mas ele não está reconhecendo a variável courses$ como um array, embora ela seja do tipo Observable<Course[]>.

O problema está no uso incorreto do MatTable, pois você está passando o Observable diretamente como a fonte de dados na linha <table mat-table [dataSource]="courses">. O MatTable não sabe como lidar diretamente com um Observable. Portanto, precisamos fazer uma pequena alteração para corrigir isso.

Para que o MatTable funcione corretamente com um Observable, você pode utilizar a implementação de DataSource do Angular chamada MatTableDataSource. Vamos atualizar seu código para fazer uso dessa implementação.

  1. No seu arquivo "courses.component.ts", faça a seguinte alteração:

    import { MatTableDataSource } from '@angular/material/table';
    // ... outras importações
    
    @Component({
      selector: 'app-courses',
      templateUrl: './courses.component.html',
      styleUrls: ['./courses.component.scss']
    })
    export class CoursesComponent {
      // Remova a declaração do Observable, pois não precisaremos mais dela aqui
    
      dataSource = new MatTableDataSource<Course>(); // Aqui criamos o MatTableDataSource
    
      displayedColumns = ['name', 'category'];
    
      constructor(public dialog: MatDialog, private courseService: CoursesService) {
        this.courseService.list().subscribe(
          (courses) => {
            this.dataSource.data = courses; // Atribuímos os cursos ao MatTableDataSource quando eles são recebidos
          },
          (error) => {
            this.onError('Erro ao carregar cursos.');
          }
        );
      }
    
      // ... resto do código
    }
    
  2. No seu arquivo "courses.component.html", atualize a linha <table mat-table [dataSource]="courses"> para:

    <table mat-table [dataSource]="dataSource"> <!-- Use a dataSource criada no componente -->
      <!-- ... o resto do código permanece igual -->
    </table>
    

Com essas alterações, você está utilizando corretamente o MatTableDataSource, que aceita um array de dados e se integra perfeitamente ao MatTable. Quando a chamada ao serviço this.courseService.list() retorna os cursos, você atribui esses cursos ao MatTableDataSource usando this.dataSource.data = courses.

Espero que isso resolva o problema e carregue as informações corretamente na sua tabela usando o Material Design Table. Se você tiver mais dúvidas ou precisar de mais ajuda, fique à vontade para perguntar.

Espero que tenha te ajudado, bons estudos!

Suposta solução: Olá, Natali!

Pelo erro que você está recebendo, parece que o seu dataSource não está sendo reconhecido como um array. Isso pode acontecer porque o dataSource espera um array, mas você está passando um Observable<!Course[]>.

Uma solução possível é transformar o seu Observable<!Course[]> em um array usando o operador toArray() do RxJS. Você pode fazer isso modificando o seu código da seguinte forma:

import { toArray } from 'rxjs/operators';

// ...

this.courses$ = this.courseService.list()
  .pipe(
    toArray(),
    catchError(error => {
      this.onError('Erro ao carregar cursos.');
      return of([]);
    }),
  );

Dessa forma, o dataSource receberá um array e o erro deve ser resolvido.

Espero ter ajudado e bons estudos!

Obrigada pela ajuda Renan, agora não recebo mais o erro, porém os dados que tenho no banco não são apresentados na tela e no console eu não estou recebendo nenhum erro... oq pode estar acontecendo?

No log eu consigo ver que tenho os dois itens:

mas eles não são

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

o listados:

Boa tarde, Natali, estou com o mesmo problema, você conseguiu resolver?

Acho que descobri o que seria o "erro", esta acontecendo comigo, o backend retorna uma Page, porém não sei consumir esse tipo de dado, quando descobrir eu volto

Olá Natali, tudo bem contigo?

Fico feliz que o erro inicial tenha sido resolvido. Agora, em relação aos dados não estarem sendo apresentados na tela, podemos investigar algumas possíveis causas.

  1. Verifique o template HTML: Certifique-se de que as colunas definidas em displayedColumns no componente CoursesComponent correspondem exatamente aos nomes das propriedades dos objetos que estão sendo retornados pelo serviço. Por exemplo, se o objeto Course tiver propriedades name e category, as colunas no template devem estar corretamente definidas como 'name' e 'category'.

  2. Verifique a estrutura do objeto Course: Certifique-se de que a classe Course no arquivo "course.ts" está definida corretamente e possui as mesmas propriedades que estão sendo referenciadas no template HTML.

  3. Verifique a requisição do serviço: No serviço CoursesService, certifique-se de que a requisição HTTP está sendo feita corretamente e que os dados do backend estão sendo retornados de forma esperada. No console, você já visualizou os dados retornados pela API, o que é um bom sinal.

  4. Atualize a view após receber os dados: Por padrão, o MatTableDataSource não detecta automaticamente as mudanças nos dados. Após receber os dados do serviço, é necessário atualizar a view do componente chamando this.dataSource._updateChangeSubscription();. Adicione essa linha ao final do bloco que atribui os dados ao MatTableDataSource.

Se você realizar essas verificações e ainda enfrentar problemas, por favor, compartilhe novamente o código atualizado do componente CoursesComponent e do serviço CoursesService. Assim, poderemos dar uma olhada mais detalhada e fornecer mais orientações.

Espero que isso ajude a resolver a questão.

Caso precise eu estarei por aqui para ajudar!

Abraços e bons estudos.

solução!

Olá Gabriel, eaí tudo ok por aí?

Parece que você identificou um aspecto importante da situação. Se o backend está retornando uma Page, você precisa ajustar o serviço CoursesService para tratar corretamente essa estrutura de dados.

Normalmente, uma Page retornada pelo backend possui dois campos principais: content e totalElements. O array de objetos que você deseja exibir na tabela geralmente estará dentro do campo content.

Vamos atualizar o serviço para lidar com esse tipo de dado. Aqui está uma sugestão de como fazer isso:

  1. Atualize o serviço CoursesService da seguinte forma:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Course } from '../model/course';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CoursesService {

  private readonly API = 'api/cursos';

  constructor(private httpClient: HttpClient) { }

  list(): Observable<Course[]> {
    return this.httpClient.get<any>(this.API)
      .map((response: any) => response.content); // Mapeia o array de cursos dentro do campo 'content'
  }
}
  1. No componente CoursesComponent, vamos realizar algumas alterações para acomodar essa mudança:
// ... imports e outras declarações

export class CoursesComponent {

  dataSource = new MatTableDataSource<Course>();
  displayedColumns = ['name', 'category'];

  constructor(public dialog: MatDialog, private courseService: CoursesService) {
    this.courseService.list().subscribe(
      (courses) => {
        this.dataSource.data = courses;
        this.dataSource._updateChangeSubscription(); // Atualiza a view após receber os dados
      },
      (error) => {
        this.onError('Erro ao carregar cursos.');
      }
    );
  }

  // ... resto do código
}

Dessa forma, com as modificações no serviço e no componente, você deve conseguir consumir corretamente os dados retornados pelo backend e apresentá-los na tabela usando o MatTable.

Espero que isso resolva o problema tanto para você quanto para a Natali.

Se houver mais alguma dúvida ou necessidade de ajuda adicional, não hesite em informar.

Estarei por aqui se precisar tá! :)

Abraços e bons estudos.

Renan, a minha tabela agora esta funcionando perfeitamente. A minha situação também era que eu recebia page. Com os ajustes agora esta rodando 100%. Obrigada!

Oi Natali, eaí?

Fico feliz que pude ajudar!

Caso precise é só falar e peço desculpas pela demora em obter um retorno anteriormente.

Abraços e boa sorte em sua jornada.

Sucesso!