-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
fix(material/tabs): ensure the ink bar realigns when the tab header changes dimensions #24885
Conversation
const itemsTracked = new Set<MatPaginatedTabHeaderItem>(); | ||
const itemContainerItemChanged = new Subject<void>(); | ||
|
||
const observer = new ResizeObserver(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be feature detected since it could run in a server environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Angular have their own mechanism for this or can I just do a window.ResizeObserver
check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can get it from this._platform.isBrowser
, although I think I'd prefer to use typeof ResizeObserver === 'function'
since it would avoid errors on browsers that don't support ResizeObserver
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a check, thanks.
const itemsTracked = new Set<MatPaginatedTabHeaderItem>(); | ||
const itemContainerItemChanged = new Subject<void>(); | ||
|
||
const observer = new ResizeObserver(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can get it from this._platform.isBrowser
, although I think I'd prefer to use typeof ResizeObserver === 'function'
since it would avoid errors on browsers that don't support ResizeObserver
.
5390d87
to
2e251a3
Compare
2e251a3
to
b8a35e6
Compare
I've cleaned it up a bit. If you have suggestions for the test, I would love to hear them. We can also do some other kind of test or just not a write at all if you're worried about it introducing flakiness if the component is expected to not be trouble in the future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the CI didn't run for some reason 😕
startWith(this._items), | ||
map(() => { | ||
for (const item of this._items.toArray()) { | ||
this._ngZone.run(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still hasn't been changed to runOutSizeAngular
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I forgot to push.
? new ResizeObserver(() => { | ||
itemContainerItemChanged.next(); | ||
}) | ||
: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that we should be stubbing out an observable. You can use the EMPTY
observable from rxjs instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not. We're stubbing out the RO function. I could also return null
and check for null
down below. I don't think EMPTY works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see. I find this side-effect-ey use of map
confusing. Here's how I would've implemented it:
- Keep the
merge(dirChange, resize, this._items.changes)
above as is. - Have a utility that looks like this:
private _itemsResized(): Observable<void> {
if (typeof ResizeObserver !== 'function') {
return EMPTY;
}
return this._items.changes.pipe(
startWith(this._items),
switchMap(
(items: QueryList<MatPaginatedTabHeaderItem>) =>
new Observable((observer: Observer<void>) =>
this._ngZone.runOutsideAngular(() => {
const resizeObserver = new ResizeObserver(() => observer.next());
items.forEach(item => resizeObserver.observe(item.elementRef.nativeElement));
return () => resizeObserver.disconnect();
}),
),
),
// Skip the first emit since the resize observer emits when an item is observed.
skip(1),
);
}
- Change the
merge
call above tomerge(dirChange, resize, this._items.changes, this._itemsResized())
.
This approach fixes the following issues:
- You generally don't want side effects from your
map
operator. - The
ResizeObserver
wasn't being disconnected when the subscription is dropped. - It avoids the extra call when the elements are observed for the first time.
- The
ResizeObserver
itself wasn't being created outside theNgZone
.
Note that this is slightly more wasteful when it comes to recreating resize observers, but I think that it's not a big deal in this case since tabs don't tend to change very often. If it becomes a problem, we can start keeping track of which items are being observed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I adopted a similar implementation. I did add an "startWith" at the end to fire a single emission which is what the old code was doing. This is still important for the initial layout IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can reintroduce the first render by removing the skip(1)
. I added it, because the first emit seems redundant to me. It fires when the elements are observed for the first time, but the initial render should already be correct since we do it right after the elements are inserted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing skip
will fire it on the RO layout which causes the rest to fail because as it turns out, the fakeAsyncZone
doesn't actually capture the ResizeObserver
tick (even though it technically runs before the paint
, it's not before the call stack is cleared). This explains why that test passed and was not flaky.
We could remove the test and not bother or perhaps try to a stability check hack to fix that if you don't think the first synchronous emission is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that explains why the test was passing. I'd rather avoid the test than have it fire on init, because it has the potential to cause layout thrashing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I removed the test for now to keep it simple especially since MDC is coming "soon". I talked with some internal folks and these links should be OK to share:
Before: https://screencast.googleplex.com/cast/NTA3MjkzNjU0MjE0MjQ2NHw0NTU1YzBmNi1lOA
After: https://screencast.googleplex.com/cast/NjI3MjI0NjU2Njg3OTIzMnw0MjBiMDcxZS0zNQ
This shows the stuff working in lieu of some tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we get rid of the startWith(undefined)
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be gone. I hadn't realize that pushing would immediately send it for re-review, unlike some other tools. :)
3dfef13
to
83478a0
Compare
5bf036c
to
91bb1ef
Compare
So the change LGTM, but for some reason the CI didn't run. Can you try cherry-picking the commit to a new branch and opening another PR? |
The CI system says it's missing some labels. Do I need to add some labels on the new PR? Or this one perhaps? |
The label checks are separate. Usually it queues up another 11 jobs that don't show up here for some reason. This has happened for other people and CircleCI haven't been able to figure out why. There was a theory that it might be because you aren't watching the fork that you're opening the PR from. |
Opened #24932. I'll adjust the description etc if the CI runs. |
(It also tells me that the CI workflow is pending approval and links me to https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks -- claiming a maintainer has to approve my CI run on this PR) |
Did you try clicking the "Watch" button on your fork? Also the "Approve and run" button is only for deploying the dev app, it doesn't affect any of the other checks. You can check what other PRs look like, e.g. #24931 |
I swapped the watch to "All Activity" just now. |
Then I really don't know why it didn't run 😕. Anyway, I ran it locally and the only problem is this lint failure:
|
…tems have changed in dimensions
91bb1ef
to
a568bf5
Compare
I tried fix it locally. Want me to run something else? Should I abandon #24932? |
Yeah, you can abandon the other one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caretaker note: for some reason the CI didn't run on this PR. I've verified that everything passes on my machine.
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@angular/cdk](https://github.com/angular/components) | dependencies | patch | [`13.3.7` -> `13.3.8`](https://renovatebot.com/diffs/npm/@angular%2fcdk/13.3.7/13.3.8) | | [@angular/material](https://github.com/angular/components) | dependencies | patch | [`13.3.7` -> `13.3.8`](https://renovatebot.com/diffs/npm/@angular%2fmaterial/13.3.7/13.3.8) | --- ### Release Notes <details> <summary>angular/components</summary> ### [`v13.3.8`](https://github.com/angular/components/blob/HEAD/CHANGELOG.md#​1338-lead-lamp-2022-05-25) [Compare Source](angular/components@13.3.7...13.3.8) ##### material | Commit | Type | Description | | -- | -- | -- | | [8611a742b](angular/components@8611a74) | fix | **tabs:** ensure the ink bar realigns when the tab header items have changed in dimensions ([#​24885](angular/components#24885)) | ##### material-experimental | Commit | Type | Description | | -- | -- | -- | | [7386fe865](angular/components@7386fe8) | fix | **mdc-checkbox:** Use cursor:pointer for label ([#​24927](angular/components#24927)) | ##### multiple | Commit | Type | Description | | -- | -- | -- | | [a7ee8a80b](angular/components@a7ee8a8) | fix | fix focus and hover styles for mdc-checkbox and mdc-radio ([#​24930](angular/components#24930)) | | [b8fddd60c](angular/components@b8fddd6) | fix | fix style imports and deps for mdc-checkbox and mdc-radio ([#​24972](angular/components#24972)) | #### Special Thanks Joey Perrott, Miles Malerba, Vaughan Hilts and Wagner Maciel <!-- CHANGELOG SPLIT MARKER --> </details> --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Co-authored-by: cabr2-bot <[email protected]> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1381 Reviewed-by: crapStone <[email protected]> Co-authored-by: Calciumdibromid Bot <[email protected]> Co-committed-by: Calciumdibromid Bot <[email protected]>
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This should fix #6130 and also #4648?. It uses
ResizeObserver
to ensure we get notifications about the dimensions of the tabs when they change. The layout change will fire synchronously.It may also fix #3048.