Angular2 : Avoid DOM render on ngIf second time

I have one Tab control and there are two tabs in it. I have placed two Kendo Grid/Any component in that different tabs.

When I open/select the tab first time it render the related component/html in the DOM. To boost the performance I have placed ngIf on tabs to show only active tab html. So now Dom show only active tab html but now when I traverse to other tab and revisit previous tab it's component/content seems render again. I wants to stop this second time rendering.

Note: If I replace ngIf with hidden then it works as accepted but it cost to performance as so many watches and DOM connected with it.

Actually my main problem is that due to above issue when I navigate to tabs my grid scroll position set to top every time instead it should remain at same state

Below is the some part of the code which I have did.

If condition in tab content html(render only selected tab in DOM)

<div class="tab-heading-outer">
    <div class="tab-heading">
        <ul id="ulOpenedTabs" class="nav nav-tabs main-tabs" role="tablist">
            <li>                 
                <span class="ellipsis"> {{tab.Header}} </span>
            </li>
        </ul>
    </div>
</div>

<div class="tab-content" *ngIf="tab.IsSelected">
    <ng-content>
    <!--Html goes here-->
    </ng-content>
</div>

Upvotes: 9

Views: 5088

Answers (5)

Chris
Chris

Reputation: 442

Can you use [hidden]="condition" instead of *ngIf="condition"?

*ngIf when false will remove the element it's on and all of its children elements from the DOM.

Whereas when [hidden] is true the html is still in the DOM, but it is not visible on the screen to the user at the time until [hidden]="false";

Upvotes: 1

user3550312
user3550312

Reputation: 128

I have created a directive to achieve exactly this goal. It's name is ngxCacheIf and it aims to achieve what you need. The directive act as an ngIf at first, then as a [hidden]. So the content is rendered only once then it is hidden shown.
I used it in replacement of an ngSwitch in my case to keep the previously rendered content in place and save performances.

You would use it this way :

<div class="tab-content" *ngxCacheIf="tab.IsSelected">
    <!--Html goes here-->
</div>

Here is a live example : https://ngx-cache-if.stackblitz.io

Here is the npm, I created it yesterday, so feel free to make any comment, there is also the github coming with it :

https://www.npmjs.com/package/ngx-cache-if

Upvotes: 2

magnattic
magnattic

Reputation: 12998

If you really don't want to remove the component from DOM and re-render it when you open the tab, another solution could be this:

Depending on how your component is implemented, this could solve your performance issues. Especially if you rely heavily on Observables and the async Pipe (as you should), there should be no computation if the template is not checked for changes.

To quote the documentation for detach():

Imagine the data changes constantly, many times per second. For performance reasons, we want to check and update the list every five seconds. We can do that by detaching the component's change detector and doing a local check every five seconds.

You would do the same, only not time-based but based on whether the tab is open or not.

All you have to do is inject the ChangeDetectorRef in the component constructor and hook the change detection up to an @Input() property:

constructor(private changeDetector: ChangeDetectorRef) {}

@Input() set visible(visible: boolean) {
  if(!visible) {
    changeDetector.detach();
  } else {
    changeDetector.reattach();
  }
}

Now you can control whether the component gets new data or not by simply setting [visible]="false" or [visible]="true".

You can find another full example in the official documentation for the ChangeDetectorRef.

Upvotes: 5

David
David

Reputation: 34435

I don't know your implementation of tab, but why don't you use a variable indicating whether the tab has been selected at least once to render it?

<div class="tab-heading-outer">
    <div class="tab-heading">
        <ul id="ulOpenedTabs" class="nav nav-tabs main-tabs" role="tablist">
            <li>                 
                <span class="ellipsis" (click)="onTabSelected(tab)"> {{tab.Header}} </span>
            </li>
        </ul>
    </div>
</div>

<div class="tab-content" *ngIf="renderedTabs[tab.index]"  [hidden]="!tab.ISelected">
    <ng-content>
    <!--Html goes here-->
    </ng-content>
</div>

//component.ts

private renderedTabs : boolean[] = new Array(numberOfTabsHere);

onTabSelected(tab: Tab) 
{
    this.renderedTabs[tab.index] = true;
}

The code above can be simplified if you can modify your implementation of tab to include directly the variable on the Tab object

<div class="tab-heading-outer">
    <div class="tab-heading">
        <ul id="ulOpenedTabs" class="nav nav-tabs main-tabs" role="tablist">
            <li>                 
                <span class="ellipsis" (click)="tab.rendered=true"> {{tab.Header}} </span>
            </li>
        </ul>
    </div>
</div>

<div class="tab-content" *ngIf="tab.rendered" [hidden]="!tab.ISelected">
    <ng-content>
    <!--Html goes here-->
    </ng-content>
</div>

Upvotes: 2

Ramesh Rajendran
Ramesh Rajendran

Reputation: 38683

Note: If I replace ngIf with hidden then it works as accepted but it cost to performance as so many watches and DOM connected with it.

Yes, that would be a perfect solution for your issue. But if you are getting any performance issue, then you should need manually set the scroll potion from previous potion to the grid which is taken (use) by temp variable.

You may need this solution as well : how to move div scroll position based on button click in angular 2

Upvotes: 0

Related Questions