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(material-experimental/mdc-tabs): add option to fit ink bar to content #17507

Merged
merged 3 commits into from
Oct 29, 2019

Conversation

andrewseguin
Copy link
Contributor

See material components documentation for an example of the ink bar fitting to the tab label content

https://material-components.github.io/material-components-web-catalog/#/component/tabs

@andrewseguin andrewseguin added P2 The issue is important to a large percentage of users, with a workaround pr: merge safe labels Oct 25, 2019
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Oct 25, 2019
private _createIndicator(document: Document) {
this._indicator = document.createElement('span');
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this class seems a little strange, it's easy to confuse with the concept of a Foundation in MDC. (I know it wasn't created in this PR, but just something I noticed now).

Why does this class do so much direct DOM manipulation as opposed to being its own component with its own template?

Copy link
Member

@crisbeto crisbeto Oct 25, 2019

Choose a reason for hiding this comment

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

I called this class a foundation because it's a wrapper around MDC's MDCSlidingTabIndicatorFoundation.

The reason why it needs to do so much manual DOM manipulation is because we use it both for the tab group and the tab nav bar. The nav bar uses a directive for the links so we don't have control over the template.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see. Obviously outside the scope of this PR, but maybe we could encapsulate the behavior in a component and then just use ViewContainerRef to create it and attach it to the element with the directive? Not sure if that's actually feasible, just brainstorming...

Copy link
Member

Choose a reason for hiding this comment

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

I think the boilerplate-wise it would end up being about the same amount of code. Ideally we'd turn the tab link into a component, but that would be a breaking change.

* to match the width of the content rather than the tab host element.
*/
get fitToContent(): boolean { return this._fitToContent; }
set fitToContent(fitToContent: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need the getter here and the setter could be turned into a function. Since this is a private class we don't need to deal with getters and setters.

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 just tried that approach but I'm not sure I feel as comfortable with it. There are two conditions I need to meet:

  • Let clients see what the current value is
  • Let clients set a new value, and the foundation reacts to it

To satisfy the first condition, there needs to be a public value fitToContent. However, it would not be clear to clients that in order to change this value, they should not write directly to it, but instead use a setFitToContent function.

If clients didn't need to read the value, I'd agree that a setter would work, but in this case I think the getter/setter pattern is ideal

Copy link
Member

Choose a reason for hiding this comment

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

Kristiyan is saying that this component (ink bar) is not part of the public API, and as such doesn't need a getter/setter (which we only need for Angular inputs) compared to using functions

Copy link
Contributor Author

@andrewseguin andrewseguin Oct 28, 2019

Choose a reason for hiding this comment

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

EDIT: Talked with Jeremy, I did miss that y'all are saying to do "getFitToContent" and "setFitToContent" rather than the ES2015 getter/setter. I'll make the change

OLD REPLY:

Sorry, I see that my explanation may be vague regarding this.

When fitInkBarToContent is provided as an input to the MatTabGroup and MatTabNav, they store the value into the MatInkBarFoundation. This will be the source of truth for it's value (not cached in the tab group or nav). When the value is set, the foundation needs to render again, hence the need for a setter (this can be either a native setter or setFitToContent).

When our users want to access the property fitInkBarToContent, we give them the value stored in MatInkBarFoundation. There are two ways we can do this, expose the property fitToContent, or provide a getFitToContent function. If we expose the property, it's not clear from the MatTabGroup/MatTabNav that it should only use that property for getting, not for setting. In the future, we may make a change and not realize the setter must be used, not the property.

If this doesn't make sense, I may be missing what you are proposing. Let's chat in the next team meeting

Copy link
Member

Choose a reason for hiding this comment

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

Andrew and I chatted, he just didn't follow that we meant do e.g. getFitToContent and setFitToContent instead

private _createIndicator(document: Document) {
this._indicator = document.createElement('span');
Copy link
Member

Choose a reason for hiding this comment

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

I think the boilerplate-wise it would end up being about the same amount of code. Ideally we'd turn the tab link into a component, but that would be a breaking change.

* to match the width of the content rather than the tab host element.
*/
get fitToContent(): boolean { return this._fitToContent; }
set fitToContent(fitToContent: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

Kristiyan is saying that this component (ink bar) is not part of the public API, and as such doesn't need a getter/setter (which we only need for Angular inputs) compared to using functions

@@ -128,16 +141,36 @@ export class MatInkBarFoundation {
this._destroyed = true;
}

/** Creates and appends the indicator element. */
Copy link
Member

Choose a reason for hiding this comment

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

This should also explain what an "indicator" 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.

Since indicator and ink bar are synonymous, I renamed our use of the indicator to be consistently called the "ink bar". Alternatively we can rename everything to "indicator" (and also rename classes like MatInkBar to MatTabIndicator).

Where we do mention indicator (in comments referring to MDC's indicator), I made it clearer that we named it ink bar.

get fitInkBarToContent(): boolean { return this._foundation.fitToContent; }
set fitInkBarToContent(v: boolean) { this._foundation.fitToContent = coerceBooleanProperty(v); }

constructor(public elementRef: ElementRef, @Inject(DOCUMENT) private _document: any) {
Copy link
Member

Choose a reason for hiding this comment

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

This should define this._document at the class level as type Document, and then set the injected document: any in the constructor

(we use this pattern elsewhere in the library)

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

* to match the width of the content rather than the tab host element.
*/
get fitToContent(): boolean { return this._fitToContent; }
set fitToContent(fitToContent: boolean) {
Copy link
Member

Choose a reason for hiding this comment

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

Andrew and I chatted, he just didn't follow that we meant do e.g. getFitToContent and setFitToContent instead

@jelbourn jelbourn added pr: lgtm action: merge The PR is ready for merge by the caretaker labels Oct 28, 2019
@andrewseguin andrewseguin added the target: patch This PR is targeted for the next patch release label Oct 29, 2019
@andrewseguin andrewseguin merged commit 77d51ca into angular:master Oct 29, 2019
@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 Nov 29, 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 P2 The issue is important to a large percentage of users, with a workaround target: patch This PR is targeted for the next patch release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants