<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.1/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/2.5.7/core.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/core.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.26/zone.min.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/http.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/common.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/router.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/compiler.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/platform-browser.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/platform-browser-dynamic.umd.js"></script>
<script src="https://unpkg.com/@angular/[email protected]/bundles/forms.umd.js"></script>

<my-app></my-app>
// app.js

const { Component, VERSION } = ng.core;
interface Item { id: number; name: string; color: string; }
@Component({
  selector: 'my-app',
  template: `
   <div>
<hr/>
<h3>Without trackBy :( we can't keep focus in the active input box when items (bullets) change as all items are recarated on collection change</h3>
  <ul>
    <li *ngFor="let item of items; let i = index">
      <strong [style.color]="item.color">{{item.id}} | {{item.name}}</strong>
      <input size="100%" [value]="values[i]" (input)="values[i] = $event.target.value" >
    </li>
  </ul>
<hr/>
</div>
<div>
<h3>With trackBy :) focus stays in the active input box even as items (bullets) change. Elements are re-rendered with standard change detection as angular can track elements with their id </h3>
<ul>
  <li *ngFor="let itemTrackBy of items; trackBy: trackByFn; let i = index">
    <strong [style.color]="itemTrackBy.color" >{{itemTrackBy.id}} | {{itemTrackBy.name}}</strong>
    <input size="100%" [value]="values[i]" (input)="values[i] = $event.target.value">
  </li>
</ul>
<hr/>
<a target="_blank" href="https://medium.com/simars/improve-ngfor-usability-and-performance-with-trackby-97f32ab92f1c?source=friends_link&sk=7350839886e93f03b360aedcf1577d37">Click Here to read full explanation</a>
</div>

  `
})
class AppComponent  {

  values = ['', '', '']; // values bound to input boxes
  items: Item[]; // item to *ngFor without trackby
  itemsMock: Item[][]; // Array Mock item array to toggle between
  toggle: boolean;
  intervalTimer: any;

  constructor() {
    this.itemsMock = [
      [
        {id: 1, name: 'One', color: 'red'},
        {id: 2, name: 'Two', color: 'blue'},
        {id: 3, name: 'Three', color: 'green'}
      ],
      [
        {id: 1, name: 'Uno', color: 'blue'},
        {id: 2, name: 'Dos', color: 'green'},
        {id: 3, name: 'Tres', color: 'red'}
      ]
    ];

    this.items = this.itemsMock[0];

    this.toggle = false;
    this.intervalTimer = setInterval(() => {
      this.toggleItems();
    }, 5000);
  }

  toggleItems() {
    this.toggle = !this.toggle;
    this.items = this.toggle ? this.itemsMock[1] : this.itemsMock[0];
  }

  trackByFn(index, item ) {
    console.log( 'TrackBy:', item.id, 'at index', index );
    return( item.id );
  }

  ngOnDestroy(): void {
    clearInterval(this.intervalTimer);
  }

}

// main.js
const { BrowserModule } = ng.platformBrowser;
const { NgModule } = ng.core;
const { CommonModule } = ng.common;
const { FormsModule } =  ng.forms

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    CommonModule,
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: []
})
class AppModule {}

const { platformBrowserDynamic } = ng.platformBrowserDynamic; 

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.