I'm using Angular 5 and mat-accordion to show a list of authors. Each author has written multiple books and articles. The author's name appears in the panel-header and the content of the panel shows all of the books, articles, etc.
Because I want to display 100+ authors each with 50+ entries, I don't want to populate the entire accordion and content at once. What I'd like to have happen is that when the user clicks on an author, it kicks off a service that queries the database and then fills the panel content as appropriate. If the panel is closed, the content should remain so re-expanding the panel doesn't kick off another database query.
So when I visit the page, I see the authors Alice, Bob, and Eve. When click on Alice, the app queries the database, gets back Alice's entries, renders the content, then the accordion expands. When I click on Eve, the app should close Alice's panel, query the db, get Eve's entries, render the content, and finally expand the panel.
If I click on Alice again Eve's panel closes, but since the content is already there for Alice, there is no db query or rendering. It just expands. The docs say to use ng-template, but I'm not sure how to do that, and really not sure how to do it so the content remains after the panel is closed. I'm not worried about there being a change to the data that would require getting Alice's data again in case there was a change.
Any examples out there of the best way to handle this?
G. Tranter's answer was correct, I was on the right path. If anyone else ends up on this page, here is what I ended up doing.
ngOnInit(){
this.authorsRetrieved.subscribe( authors => {
this.allAuthors = authors as Array;
this.authorsRetrieved = new Array(
Math.max.apply(Math, this.allTrainers.map(function(t){ return t.trainer_id; }))
);
// as authors are added and deleted, the author_id won't equal the number of
// authors, so get the highest id number, create an array that long
// then fill it with blanks so the keys have some value
this.authorsRetrieved.fill([{}]);
});
showAuthorsWorks(authorID: Number = -1){
if(authorID > this.authorsRetrieved.length){
const tempArray = new Array(authorID - this.authorsRetrieved.length + 1);
tempArray.fill([{}]);
this.authorsRetrieved = this.authorsRetrieved.concat(tempArray);
}
// only make the network call if we have to
// because we filled the id array, we can't just use length
if(typeof(this.authorsRetrieved[authorID][0]['manuscript_id']) === 'undefined'){
this.authorWorksService.getAuthorWorks(authorID).subscribe( works => {
this.worksRetrieved.splice(authorID, 0, works as Array<any>);
});
}
I added a check for the almost impossible situation where the array length is less than the max author_id. You have to create an empty array of N elements, then fill that array. If you don't, the length of the empty array is 0, and you can't push data to an array element that doesn't exist. Even though at the chrome console it says the length is N and the elements are there, just empty.
Thanks again!
If you are referring to the MatExpansionPanelContent directive used with ng-template, all that does is delay loading content until the panel is opened. It doesn't know whether or not it has already been loaded. So if you are using a bound expression for content such as {{lazyContent}} that will be evaluated every time the tab is opened. You need to manage content caching yourself. One easy way to do that is via a getter.
In your component:
_lazyContent: string;
get lazyContent() {
if (!this._lazyContent) {
this._lazyContent = fetchContent();
}
return this._lazyContent;
}
Plus in your HTML:
<mat-expansion-panel>
...
<ng-template matExpansionPanelContent>
{{lazyContent}}
</ng-template>
....
</mat-expansion-panel>
So the ng-template takes care of the lazy loading, and the getter takes care of caching the content.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With