Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(table): add MatTableDataSource #6747

Merged
merged 2 commits into from
Oct 10, 2017

Conversation

andrewseguin
Copy link
Contributor

@andrewseguin andrewseguin commented Aug 30, 2017

This is a data source that can be used by users that don't want to deal with streams to render a table.

Accepts a data array and optional extras such as MdSort, MdPaginator, and filter string.

Allows user to set up their own data accessor for sorting (params include the data object and sort header id), and the filter accessor which converts their data object into a search string that the filter will compare against.

Closes #6911

@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 30, 2017
// Needs to be set up after the view is initialized since the data source will look at the sort
// and paginator's initial values to know what data should be rendered.
this.dataSource!.sort = this.sort;
this.dataSource!.paginator = this._paginator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks awesome and very user friendly! MdTableDataSource's properties seem to document themselves, but I think using ViewChild and setting the instances in AfterViewInit will trip up some newcomers. IMO this little bit necessitates an example. I'd suggest using an "out of the way" data array like the Basic Example to emphasize how easy this is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally, I'm racking my brain to think of ways we can help devs avoid this but I can't think of anything. I wish I had access to the view ref in the data source and know whether or not they are passing it in at the right time. Maybe I should throw an error if they pass in an empty sort or paginator, saying that perhaps they are assigning it at the wrong time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error would be nice, but I'm afraid it's a valid - though rare - use case to initially pass in a paginator and then try to disable it later during the table's life. If you could get around that, the more descriptive errors are, the better!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an idea of a more magical version. MdSort can inject itself into the data source by injecting the table instance (assumes it is on the table element). MdPaginator can create an input that accepts the data source. This way, the user doesn't need to know about view init.

Check it out here: 097a2ff

@jelbourn I know you are avoiding magic, think this goes too far? Or are we not going far enough... =D

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah that's pretty slick! 🔮 I still think it'd require an example since passing the data source to the paginator is not an immediately obvious thing to do.. I prefer the original; the AfterViewInit part is plenty straight forward once you've done it once. I just don't think everyone will realize that's the correct approach without an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll defer to your judgment that people would get it after seeing an example. I imagine we'll have some canonical StackOverflow question that addresses the issue people would see. Would prefer not to have such tightly coupled magic anyways. If we end up seeing that its an issue, we can always add it into MdSort and MdPaginator later too

@@ -167,6 +72,6 @@

<md-paginator #paginator
[length]="_peopleDatabase.data.length"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be wrong when filtering is applied, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Good catch, forgot to include some affordance for this. I imagine we could either directly change the pagination length in the data source since we have access to it, or offer some variable they could assign to the paginator's [length]. I think I'll go with the former and see how that works

@willshowell willshowell mentioned this pull request Sep 1, 2017
@andrewseguin andrewseguin force-pushed the table-array-datasource branch 3 times, most recently from 8b25d5b to e14d0ec Compare September 1, 2017 21:59
@andrewseguin
Copy link
Contributor Author

Updated for review (includes tests).

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lint

ERROR: /home/travis/build/angular/material2/src/demo-app/table/table-demo.ts[39, 5]: unused expression, expected an assignment or function call
ERROR: /home/travis/build/angular/material2/src/demo-app/table/table-demo.ts[39, 27]: Missing semicolon

// Sorting and/or pagination should be watched if MdSort and/or MdPaginator are provided.
// Otherwise, use an empty observable stream to take their place.
const sortChange = this._sort ? this._sort.mdSortChange : observableOf();
const pageChange = this._paginator ? this._paginator.page : observableOf();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

observableOf() -> empty()?

this._renderChangesSubscription = RxChain.from(this._data)
// Watch for base data or filter changes to provide a filtered set of data.
.call(combineLatest, this._filter)
.call(map, ([data]) => this._filterData(data.slice()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you clone the array on every call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From _updateChangeSubscription's perspective, those calls may change the data in some way (filter, reorder, or splice). When an event triggers, e.g. sort order changed, it wants to make sure data is preserved so it sends a copy to be processed. If sort order changes again, it knows that data array provided on the stream has been protected from mutation.

The exception here is that filterData doesn't actually mutate the data array, but it felt unsymmetrical to "know" that it can be passed the original array safely.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think that the cloning logic more belongs inside the functions that here in the chain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM - not too opinionated on it. Moved cloning to the functions themselves

// If there is a filter string, filter out data that does not contain it.
// Each data object is converted to a string using the function defined by filterTermAccessor.
// May be overriden for customization.
const filteredData = this.filter === '' ? data : data.filter(obj => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When filter is null / undefined? What about when filter is all whitespace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to !this.filter to catch null/undefined. Not sure about whitespace, I think that our users should be doing these things according to their needs (e.g. toLowerCase, trim, etc.) before setting filter

let valueA = this.sortingDataAccessor(a, this.sort!.active);
let valueB = this.sortingDataAccessor(b, this.sort!.active);
return (valueA < valueB ? -1 : 1) * (this.sort!.direction == 'asc' ? 1 : -1);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could avoid the non-nullability assertion operator by storing a reference to this.sort

if (!this.sort || !this.sort.active || this.sort.direction == '') { 
  return data; 
}

const sort = this.sort;
return data.sort((a, b) => {
  let valueA = this.sortingDataAccessor(a, sort.active);
  let valueB = this.sortingDataAccessor(b, sort.active);
  return (valueA < valueB ? -1 : 1) * (sort.direction == 'asc' ? 1 : -1);
});

@andrewseguin
Copy link
Contributor Author

Thanks, fixed lint and changed to use empty(), and changed logic with filter/sort

component.sort.sort(component.sortHeader);
fixture.detectChanges();
expectTableToMatchContent(tableElement, [
['Column ASorted by a descending', 'Column B', 'Column C'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there no space between Column A and Sorted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side-effect of how I set up the expectTableToMatchContent - it converts a cell to its .textContent, which includes visually hidden elements. Sorted headers have a cdk-visually-hidden element that describes the sort.

In our case here, we've got three siblings in the cell (approx.):

<button> Column A </button> 
<div class="sort-arrow"></div> 
<span class="cdk-visually-hidden"> Sorted by a descending </span>

This converts to Column ASorted by a descending

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be an issue when we turn on whitespace collapsing; we should add an &nbsp; between those

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM - added an HTML space before the description label and changed the tests to check for that break

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker and removed pr: needs review labels Sep 6, 2017
@andrewseguin andrewseguin force-pushed the table-array-datasource branch 4 times, most recently from f6ef763 to 53e174d Compare September 27, 2017 22:42
@kara kara changed the title feat(table): add MdTableDataSource feat(table): add MatTableDataSource Oct 2, 2017
@kara
Copy link
Contributor

kara commented Oct 2, 2017

@andrewseguin Check out the linter error? https://travis-ci.org/angular/material2/jobs/282300920

@kara kara removed the action: merge The PR is ready for merge by the caretaker label Oct 2, 2017
@andrewseguin andrewseguin added the action: merge The PR is ready for merge by the caretaker label Oct 3, 2017
@andrewseguin
Copy link
Contributor Author

Fixed lint

@kara
Copy link
Contributor

kara commented Oct 3, 2017

@andrewseguin Rebase?

@kara kara added pr: needs rebase and removed action: merge The PR is ready for merge by the caretaker labels Oct 3, 2017
@andrewseguin
Copy link
Contributor Author

Rebased

@andrewseguin andrewseguin added action: merge The PR is ready for merge by the caretaker and removed pr: needs rebase labels Oct 3, 2017
@andrewseguin andrewseguin merged commit a9600e7 into angular:master Oct 10, 2017
@nishantp9665
Copy link

MatTableDataSource is not imported in @angular/material this error is occurred ...Please give me solution

@nidishkv
Copy link

I am getting the error '@angular/material/material' has no exported member 'MatTableDataSource'.
Using "@angular/material": "2.0.0-beta.12"
"@angular/cdk": "2.0.0-beta.12",

@nishantp9665
Copy link

nishantp9665 commented Nov 18, 2017 via email

@nidishkv
Copy link

Thanks @nishantp12345 . Its working fine when using 5.0.0 rc0 version

@andrewseguin andrewseguin deleted the table-array-datasource branch November 28, 2017 20:39
@angular-automatic-lock-bot
Copy link

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.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MdSort is missing exportAs
8 participants