-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(tabs): adds ability for lazy loaded tabs #6832
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
Conversation
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.
Need unit tests for this change
src/lib/tabs/tab-body.ts
Outdated
@@ -146,7 +146,8 @@ export class MdTabBody implements OnInit, AfterViewChecked { | |||
*/ | |||
ngAfterViewChecked() { | |||
if (this._isCenterPosition(this._position) && !this._portalHost.hasAttached()) { | |||
this._portalHost.attach(this._content); | |||
// Nested templates via mdTabContent templates causes expression change error | |||
Promise.resolve().then(() => this._portalHost.attach(this._content)); |
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.
Can we attach it in ngDoCheck
instead? It would be better to avoid the extra change detection pass.
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.
Note: After this change I had to update the unit test that checked tab body contents.
src/lib/tabs/tab.html
Outdated
<ng-template><ng-content></ng-content></ng-template> | ||
<ng-template #bodyTemplate> | ||
<ng-content></ng-content> | ||
<ng-template *ngIf="templateBody && templateBody.template" |
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.
Why use ngTemplateOutlet
here instead of just creating a TemplatePortal
directly from the TemplateRef
from mdTabContent
?
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 need the original outlet for the projection, once projected then your able to read the template out and attach. I could replace the original outlet once projected but I'd have to attach/detach/attach. Is that what your suggesting?
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 really follow what you mean.
I was thinking something like
export class MdTab {
@ContentChild(MdTabContent, {read: TemplateRef}) _explicitContent: TemplateRef<any>;
@ViewChild(TemplateRef) _implicitContent: TemplateRef<any>;
}
Then use _explicitContent
if it is defined, otherwise use _implicitContent
(with the same code path we currently have for _content
).
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 are right, querying works even though its not projected. Updated per feedback.
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.
Can you also update the documentation in this PR?
src/lib/tabs/tab-body.ts
Outdated
if (this._isCenterPosition(this._position) && !this._portalHost.hasAttached()) { | ||
// Nested templates via mdTabContent templates causes expression change error |
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.
Make this comment something like
// It is important to attach the view during `DoCheck`; if an embedded view is created during
// change detection, it will either cause a changed-after-checked error or never be checked at all.
src/lib/tabs/tab-content.ts
Outdated
@@ -0,0 +1,6 @@ | |||
import {Directive, TemplateRef} from '@angular/core'; | |||
|
|||
@Directive({ selector: '[mdTabContent]' }) |
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.
Omit spaces in braces
src/lib/tabs/tab-group.spec.ts
Outdated
TestBed.compileComponents(); | ||
})); | ||
|
||
it('should lazy load the second tab', (done) => { |
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.
Why do you need done
here instead of using async
?
@jelbourn ready for another review. |
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.
LGTM
@amcdnl please rebase when you have a chance |
…bs-template # Conflicts: # src/lib/tabs/index.ts
@josephperrott - Merged :) |
Look like this actually is causing a failure in a specific test case. Here is a minimal repro spec: import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {async, ComponentFixture, TestBed, flushMicrotasks, fakeAsync} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MdTabsModule} from './public_api';
describe('MdTabs', () => {
let fixture: ComponentFixture<TestTabs>;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CommonModule, MdTabsModule, BrowserAnimationsModule],
declarations: [TestTabs]
});
TestBed.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestTabs);
element = fixture.nativeElement;
fixture.detectChanges();
});
it('will pass after click.', () => {
const firstTab = element.querySelectorAll('.mat-tab-label')[0] as HTMLElement;
firstTab.click();
fixture.detectChanges();
expect(element.querySelectorAll('.mat-tab-body')[0].querySelectorAll('.items').length).toBe(1);
});
it('won\'t pass without click.', () => {
expect(element.querySelectorAll('.mat-tab-body')[0].querySelectorAll('.items').length).toBe(1);
});
});
@Component({
template: `
<md-tab-group>
<md-tab label="tab 1">
<p class="items"> content for tab 1</p>
</md-tab>
<md-tab label="tab 2">
<p class="items"> content for tab 2</p>
</md-tab>
</md-tab-group>
`
})
class TestTabs {} I am fairly confident this is because on initial load, the DoCheck lifecycle hits before the view is initialized which causes it not attach the portalHost on the first round of change detection. |
…bs-template # Conflicts: # src/demo-app/demo-app/demo-module.ts # src/lib/tabs/public_api.ts # src/lib/tabs/tab.ts
Waiting on @josephperrott changes on #7266 to be merged before rebasing. |
Downgrading this to P2 since it can go in a minor release |
@amcdnl rebase whenever you have a chance :) |
# Conflicts: # src/demo-app/tabs/tabs-demo.html # src/lib/tabs/public_api.ts # src/lib/tabs/tab-body.ts # src/lib/tabs/tab-group.spec.ts # src/lib/tabs/tab-group.ts # src/lib/tabs/tab.ts # src/lib/tabs/tabs-module.ts # src/lib/tabs/tabs.md
@josephperrott I'm going to close this PR and open a new one given all the changes. See: #8921 |
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 adds the ability for users to lazy load the contents of a tab.
The API for this is adding a new
<ng-template mdTabContent>
in the content of your tab with the tab's contents inside this tag.This addresses: #1854 and #5222
References: angular/angular#18691