Closed alanpurple closed 7 years ago
Meanwhile the documentation is updated, I can provide you my own implementation with an HTTP REST API.
Here, I retrieve some Conventions from my API and send them to the table. There's not filter, nor sorting and every data is sent to the view (as I only have a few ones).
export class DelegateConventionDatasource extends DataSource<Convention>{
delegate:Delegate;
subject:BehaviorSubject<Convention[]>;
//I have a custom http component that adds headers on calls
constructor(private authHttpService:AuthHttpService, private userLoginService:UserLoginService){
super();
}
connect(collectionViewer: CollectionViewer): Observable<Convention[]> {
this.subject = new BehaviorSubject<Convention[]>([]);
//My http call relies on a property stored in this service, so I watch changes on it
this.userLoginService.userChange.subscribe((user) => {
this.delegate = user.delegate;
//I prevent http calls when subject is supposed to be disconnected (route to another link and back there)
if(!this.subject.isStopped){
this.retrieveConventions(this.delegate.id).then((response:Response) => {
const conventions: Convention[] = response.json();
this.subject.next(conventions);
});
}
});
return Observable.merge(this.subject);
}
retrieveConventions(delegateId:number|string):Promise<Response>{
return this.authHttpService.get(`${environment.api_url}delegate/${delegateId}/convention`);
}
disconnect(collectionViewer: CollectionViewer): void {
this.subject.complete();
this.subject.observers = [];
}
Here's an example without my logic :
export class DelegateConventionDatasource extends DataSource<Convention>{
subject:BehaviorSubject<Convention[]>;
constructor(private http:Http){
super();
}
connect(collectionViewer: CollectionViewer): Observable<Convention[]> {
this.subject = new BehaviorSubject<Convention[]>([]);
this.http.get('http://someurl.com/conventions').toPromise()
.then((response:Response) => {
const conventions: Convention[] = response.json();
this.subject.next(conventions);
});
return Observable.merge(this.subject);
}
disconnect(collectionViewer: CollectionViewer): void {
this.subject.complete();
this.subject.observers = [];
}
@egavard thanks, I tried what you provided, but
even connect() wasn't called
export class ItemDataSource extends DataSource<Item>{
constructor(private http: Http) {
super();
}
subject: BehaviorSubject<Item[]> = new BehaviorSubject<Item[]>([]);
connect(): Observable<Item[]> {
console.log('connect');
if (!this.subject.isStopped)
this.http.get('/item').toPromise()
.then(res => {
this.subject.next(res.json())
});
return Observable.merge(this.subject);
}
disconnect() {
this.subject.complete();
this.subject.observers = [];
}
}
ngOnInit() {
this.itemDataSource = new ItemDataSource(this.http);
}
<md-table [dataSource]="itemDataSource">
<ng-container cdkColumnDef="name">
<md-header-cell *cdkHeaderCellDef>이름</md-header-cell>
<md-cell *cdkCellDef="let row">{{row.name}}</md-cell>
</ng-container>
<md-header-row *cdkHeaderRowDef="['name']"></md-header-row>
<md-row *cdkRowDef="let row; columns: ['name'];"></md-row>
</md-table>
with no error
Maybe it's because your connect and disconnect methods don't match the needed signature with CollectionViewer ?
Could you try ?
EDIT: That's not causing trouble. The only thing I can see that differs from my sources is that I'm instanciating the datasource in my constructor and not onInit.
@egavard already tried
@egavard thanks, it now works, for some reason, after restart chrome...
@egavard may I ask why you use .toPromise ? instead of standard map and catch
@alanpurple It's just a matter of preferences. I don't like Observable. And in this case, I don't need abort, nor retries.
@andrewseguin We should have an example http-based data source in the docs.
I think in much of http cases, BehaviorSubject is not required
@Injectable()
export class ItemService {
constructor(private http: Http) { }
getItemAll(): Observable<Item[]> {
return this.http.get('/item')
.map(res => res.json())
.catch(err => Observable.throw(err));
}
...
}
export class ItemDataSource extends DataSource<Item>{
constructor(private itemService: ItemService) {
super();
}
connect(): Observable<Item[]> {
return this.itemService.getItemAll();
}
disconnect() {
}
}
this is enough
Did you try to navigate between another component and the one using the table ? Is the http call only made once each time ?
That was the reason I used the BehaviorSubject. It allowed me to mark the subject complete on disconnect, to prevent multiple calls to my API.
@egavard yes. exactly same, once each time
Here's an example we will put up on the docs site soon: https://plnkr.co/edit/mjQbufh7cUynD6qhF5Ap?p=preview
How can i fix the header when scroll? Anyone?
Very hard to apply pagination, sort and filter
Can someone show how to apply http pagination in the table?
Hey @lemang Here is my code. 👨💻
// my http service for data source
getClaims(param?: { page?: number, pageSize?: number }): Observable<MyDtoModel> {
// just creating params
let params = this.url.paramsWithValue(param);
return this.http
.get(`${this.api}/Get`, this.url.requestOptions(null, params)) // my custom header options.
.map(this.dataHelper.extractData)
.catch(this.dataHelper.handleErrors);
}
// component.ts
displayedColumns = ['col1', 'col2', 'col3', 'col4', 'col5'];
dataSource: DataSource<IClaimResources>;
@ViewChild(MdPaginator) paginator: MdPaginator;
@ViewChild(MdSort) sort: MdSort;
constructor( private ClaimService: ClaimService) { }
ngOnInit() {
// refactor is welcome.
this.dataSource = new DelegateClaimsDatasource(this.ClaimService, this.paginator, this.sort);
}
// datasource delegate class
export class DelegateClaimsDatasource extends DataSource<IClaimResources> {
subject: BehaviorSubject<IClaimResources[]>;
pager: IPager; //my server dto models
constructor(private claimService: ClaimService, private paginator: MdPaginator, private sort: MdSort) {
super();
}
private getData(p: MdPaginator, claimService: ClaimService) {
claimService.getClaims({ page: p.pageIndex, pageSize: p.pageSize })
.map((res: IHttpApiMessage) => <DtoModel>res.contentBody)
.do((dto: DtoModel) => this.subject.next(dto.content))
.subscribe((dto: DtoModel) => this.pager = dto.pager, (error: any) => Observable.throw(error));
}
// refactoring is welcome.
connect(): Observable<IClaimResources[]> {
const displayedChanges = [
this.sort.mdSortChange,
this.paginator.page,
];
// on changes
Observable.merge(...displayedChanges).subscribe((d) => {
// if it is sorting, reset page index to 0;
if (d['active'] && d['direction']) this.paginator.pageIndex = 0;
this.getData(this.paginator, this.claimService, this.sort);
});
// init
if (!this.subject.isStopped)
this.getData(this.paginator, this.claimService, this.sort, );
return Observable.merge(this.subject);
}
disconnect(): void {
this.subject.complete();
this.subject.observers = [];
}
}
// component.html
<md-table [dataSource]="dataSource" mdSort >
// md-row.... same as angular docs.
<md-table>
// paginator... My paging calculation is dynamic.
<md-paginator [pageSizeOptions]="[5, 10, 15, 35, 50, 100, 200]"
[length]="dataSource?.pager?.totalItems" [pageIndex]="dataSource?.pager?.currentPage" [pageSize]="dataSource?.pager?.pageSize">
</md-paginator>
@irowbin hi how did you make the checkbox work for each row. When i select a checkbox it selects all the checkboxes.
@nnarayanancs Here https://plnkr.co/edit/EU3BBlViWpPf2NJW4PXx?p=preview
I am getting this error ERROR in SelectionModel is not an NgModule even though I have imported SelectionModel from angular material. I am already using md-table from the material. So I know it is installed. Please advise.
From: Robin Regmi [mailto:notifications@github.com] Sent: Monday, July 31, 2017 9:31 PM To: angular/material2 material2@noreply.github.com Cc: Nisha Narayanan nnarayanan@casestack.com; Mention mention@noreply.github.com Subject: Re: [angular/material2] (suggestion)md-table with http example (#5670)
@nnarayanancshttps://github.com/nnarayanancs Here https://plnkr.co/edit/EU3BBlViWpPf2NJW4PXx?p=preview
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/angular/material2/issues/5670#issuecomment-319264709, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AcyOlWx-_XeOn_MdADCSHnoYrJE9vMJAks5sTqoOgaJpZM4OTzD_.
@irowbin Why are you importing the "startWith" rx operator? It's not used anywhere in the code?
Could you please focus on the original subject here, the http datasource example ?
@DavidTheProgrammer That wasn't my code. I just post here to help for checkbox selection. @egavard Yeah you're right. Focus the actual Subject.
Can any one has working plunker for server side pagination,sorting,filtering? I am trying like below approach:
CategoryService which will provide paged data. CategoryListing.ts file which will handle all data, sorting, paging and filtering. CategoryListing.html to display category data and edit button.
@irowbin : I looked into your plunker but I did not find service file over there as you have , please guide me in that.
Thanks,
I made an small and runnable example project where the backend is remote, the state manager ist ngrx and sort and pagination just triggers the request actions. Even filtering can be implemented this way. https://github.com/Braincompiler/ngrx-material-remote-data-table Hope this helps someone :)
Is there something special that needs to be done for sorting? My table shows up fine initially, but after trying to sort with a mat-sort-header column, I get the following error:
Uncaught Error: Error trying to diff '[object Object]'. Only arrays and iterables are allowed at DefaultIterableDiffer.webpackJsonp.../../../core/@angular/core.es5.js.DefaultIterableDiffer.diff (core.es5.js:6843) at CdkTable.webpackJsonp.../../../cdk/esm5/table.es5.js.CdkTable._renderRowChanges (table.es5.js:633) at SafeSubscriber._next (table.es5.js:605) at SafeSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:238) at SafeSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.next (Subscriber.js:185) at Subscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber._next (Subscriber.js:125) at Subscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89) at TakeUntilSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber._next (Subscriber.js:125) at TakeUntilSubscriber.webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber.next (Subscriber.js:89) at MapSubscriber.webpackJsonp.../../../../rxjs/operators/map.js.MapSubscriber._next (map.js:85)
@sneckelmann Looks like your data source offered an object to the table through the connect
stream. Make sure you only emit arrays of data on the stream.
Thanks for the feedback @andrewseguin . I am returning an Observable of an object array. It works the first time for initial load but sorting causes the mentioned error.
I'll keep trying to hunt down an answer though but seems I may end up on SO.
Thanks again.
@sneckelmann My assumption is that you merged several streams together and the result is assumed to be your data. E.g. you merged dataChanges
and sortChanges
- when dataChanges
emits, you receive your data, but when sortChanges
emits then you receive the sort change event which is what you are returning to the table.
If that's not it, for sure post the data source on StackOverflow, there's a great community there that has been answering a lot of similar questions. Observables are awesome but they have a steeper learning curve.
Here's an example we will put up on the docs site soon: https://plnkr.co/edit/mjQbufh7cUynD6qhF5Ap?p=preview
@andrewseguin - thanks for this. Would you be able to add sorting, filtering, and pagination on this example, too? I think there's a few of us, myself included, that are facing issues with this.
A working http example would be very helpful for me as well.
Tried to run the plunkr above and got:
Event tracked Multipane Show Preview Toolbar undefined undefined editor-1.2.1.js:2 URL visited /edit/mjQbufh7cUynD6qhF5Ap?p=preview unpkg.com/@angular/cdk@2.0.0-beta.12/bundles/cdk.umd.js/bidi:1 GET https://unpkg.com/@angular/cdk@2.0.0-beta.12/bundles/cdk.umd.js/bidi 404 () zone.min.js:1 Error: (SystemJS) XHR error (404) loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi Error: XHR error (404) loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi at new k (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:12526) at XMLHttpRequest.t [as __zone_symbol___onreadystatechange] (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:881) at e.invokeTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:20606) at t.runTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:15783) at XMLHttpRequest.invoke (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:21563) Error loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi as "@angular/cdk/bidi" from https://unpkg.com/@angular/material/bundles/material.umd.js at new k (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:12526) at XMLHttpRequest.t [as __zone_symbol___onreadystatechange] (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:881) at e.invokeTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:20606) at t.runTask (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:15783) at XMLHttpRequest.invoke (https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js:1:21563) Error loading https://unpkg.com/@angular/cdk/bundles/cdk.umd.js/bidi as "@angular/cdk/bidi" from https://unpkg.com/@angular/material/bundles/material.umd.js
etc.
I am working diligently on an example with http, sorting, pagination - I'm almost there but was getting errors where the sort, paginator components were trying to render before dataSource.filteredData was available.
I tried moving everything to ngAfterViewInit to no success as well. I'll update shortly!
@pevans360 @iamwhitebox is my example not helpful? :) https://github.com/Braincompiler/ngrx-material-remote-data-table
Do you miss something?
I started with angular few weeks ago (I have 0 .js experience in general) and this is how it looks so far. Add and Edit are working using mat dialogs. Implementing Delete shouldn't be a problem aswell. Few issues tho:
I have no idea how to update view in table after I close dialog. At the moment when I close dialog I'm updating entire table with http 'getAll' function. Hassling with dataSource looks way out of my beginner league. :)
I still have to migrate from Http to HttpClient and I kinda have some issues with getAll.
When I sort this issues I can upload entire project or if someone can help I can post it now.
PS: Sorry for Croatian on image but u get an idea. Oh and pagination works aswell.
After trying for days to make this work, I'm ripping it out in favour of a static template displaying the information coming back. Is there an option in Angular Material, maybe, to implement a table which just loads from static data? If we could load a strongly typed result set into a table and have that displayed, that would be awesome. Maybe rename the existing mat-table to mat-table-dynamic (or add a dynamic operator to the tag or something, I don't know). For simple/most workloads, that would suffice. The existing mat-table implementation is extremely confusing and needlessly so.
But I love Angular Material and thank you all for the hard work you do and continue to do on this amazing framework!
Okay I'm almost done, but still stuck with the first thing. Here's an Stackoverflow question if someone more experienced could take a look:
I understand that the topic is already closed, but I would like to share a missing "feature": Server-side Filtering.
I will share my full src (Angular 4) based on the @irowbin code (thanks).
tuple.ts model
export interface ITuple<T> {
value: T;
text: any;
}
export class Tuple<T> implements ITuple<T> {
constructor(
public value: T,
public text: any
) { }
}
component.html
<div class="example-header">
<mat-form-field>
<input matInput #inputFilterValue placeholder="Filter">
</mat-form-field>
</div>
<mat-table class="app-nna-table" #table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="productName">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.productName || 'No available'}} </mat-cell>
</ng-container>
<ng-container matColumnDef="description">
<mat-header-cell *matHeaderCellDef mat-sort-header> Description </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.description || 'No available'}} </mat-cell>
</ng-container>
<ng-container matColumnDef="productTypeName">
<mat-header-cell *matHeaderCellDef mat-sort-header> Category </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.productTypeName}} </mat-cell>
</ng-container>
<ng-container matColumnDef="validFromDate">
<mat-header-cell *matHeaderCellDef mat-sort-header> Start </mat-header-cell>
<mat-cell *matCellDef="let element"> {{(element.validFromDate | date) || 'No available'}} </mat-cell>
</ng-container>
<ng-container matColumnDef="validToDate">
<mat-header-cell *matHeaderCellDef mat-sort-header> End </mat-header-cell>
<mat-cell *matCellDef="let element"> {{(element.validToDate | date) || 'No available'}} </mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef mat-sort-header> Actions </mat-header-cell>
<mat-cell *matCellDef="let element">
<div class="app-tablet-actions">
<a href="">
<mat-icon title="Add Product">add_circle</mat-icon>
</a>
<a href="">
<mat-icon>mode_edit</mat-icon>
</a>
<a href="">
<mat-icon>delete</mat-icon>
</a>
</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
<mat-paginator #paginator [length]="dataSource?.pager?.totalCount" [pageIndex]="dataSource?.pager?.requestedPage" [pageSize]="10" [pageSizeOptions]="[5, 10, 25, 50, 100]">
</mat-paginator>
data-source.ts
export class MatTableDataSource<T> extends DataSource<any[]> {
//#region properties
public pager: any[];
//#endregion
constructor(
public resource: RESTfulangular<T>,
public subject: BehaviorSubject<any[]>,
public paginator: MatPaginator,
public sort: MatSort,
public filters: ITuple<any>
) {
super();
}
public connect(): Observable<any[]> {
//#region properties
const displayedChanges = [
this.sort.sortChange,
this.paginator.page,
this.filters.text
];
//#endregion
//#region logic
// on changes
Observable.merge(...displayedChanges).subscribe((d: {}|Element) => {
// if it's sorting, reset page index to 0;
if ((d as any)["active"] && (d as any)["direction"]) {
this.paginator.pageIndex = 0;
}
this.getData(this.resource, this.paginator, this.sort, ((d instanceof KeyboardEvent) ? (d.target as HTMLInputElement).value : null));
});
// init
if (!this.subject.isStopped) {
this.getData(this.resource, this.paginator, this.sort, null);
}
//#endregion
return Observable.merge(this.subject);
}
public disconnect(): void {
this.subject.complete();
this.subject.observers = [];
}
// RESTfulangular is a custom model for ngx-restangular
private getData(resource: RESTfulangular<T>, paginator: MatPaginator, sort: MatSort, filterValue?: string) {
const callbackRequest = (params?: any): void => {
resource.customGET(null, params)
.map((metadata: any) => metadata)
.do((elements: any) => this.subject.next(elements.items))
.subscribe((dto: IProduct[]) => {
this.pager = dto;
}, (err: any) => Observable.throw(err));
};
const urlParams: IUrlParams = {
page: paginator.pageIndex || 1,
count: paginator.pageSize || 10,
orderBy: sort.active,
descending: (sort.direction === "desc") ? true : false,
} as IUrlParams;
const filterParams: ITuple<any> = {} as ITuple<any>;
// can be improved (refactoring accepted)
if (filterValue && this.filters && this.filters.value && this.filters.value.length) {
this.filters.value.forEach((column: string) => {
(filterParams as any)[column] = filterValue;
});
}
callbackRequest((filterParams) ? Object.assign(urlParams, filterParams) : urlParams);
}
}
--
component.ts
export class ProductsComponent implements OnInit, AfterViewInit {
//#region properties
// public dataSource = new MatTableDataSource<Element>(null); => NgMaterial 5
public displayedColumns = ["productName", "description", "productTypeName", "validFromDate", "validToDate", "actions"];
public dataSource: MatTableDataSource<IProduct[]>;
public dataSubject = new BehaviorSubject<any[]>([]);
public dataFilterValueChange: Observable<string> = new Observable<string>();
@ViewChild("inputFilterValue") public inputFilterValue: ElementRef;
@ViewChild(MatPaginator) public paginator: MatPaginator;
@ViewChild(MatSort) public sort: MatSort;
//#endregion
constructor(
@Inject(NOTIFICATION_SERVICE_TOKEN) public toastService: INotificationService,
@Inject(PIM_DATA_SERVICE_TOKEN) public pimDataService: IPimDataService,
) {
//
}
public ngOnInit() {
this.dataFilterValueChange = Observable.fromEvent(this.inputFilterValue.nativeElement, "keyup");
this.refresh();
}
/**
* Set the paginator after the view init since this component will
* be able to query its view for the initialized paginator.
*/
public ngAfterViewInit() {
this.setupTable();
}
public refresh(): void {
const filters: ITuple<any> = { value: ["name"], text: this.dataFilterValueChange };
this.dataSource = new MatTableDataSource(this.pimDataService.getProductsResource(), this.dataSubject, this.paginator, this.sort, filters);
}
private setupTable(): void {
if (this.dataSource) {
(this.dataSource as any).paginator = this.paginator;
(this.dataSource as any).sort = this.sort;
}
}
}
In filters (this.filters) from component.ts, it's decided how many columns will be filtered in the input value.
I hope I can help you.
Best regards.
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.
Bug, feature request, or proposal:
proposal
What is the expected behavior?
something intuitive
What is the current behavior?
no idea how to
It will be very thankful if you provide an example of md-table with http service, not some mock database class