How can I filter a list of AngularJS components which are expensive to re-draw?

Take the following AngularJS code:

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Document</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.8/angular.js"></script>
    <script src="main.js"></script>
</head>
<body ng-app="app" ng-controller="RootController as vm">
    <input type="text" ng-model="vm.search" /> <br />
    <component ng-repeat="i in vm.indices | filter:vm.search" index="i"></component>
</body>
</html>

main.js

const app = angular.module("app", []);

class RootController
{
    constructor()
    {
        this.indices = [...Array(1000).keys()];
    }
}

class ComponentController
{
    constructor()
    {
    }

    getMessage()
    {
        var startTime = Date.now();
        while (Date.now() < startTime + 1) {}
        return this.index;
    }
}

app.controller("RootController", RootController);
app.component("component",
    {
        controller: ComponentController,
        controllerAs: "vm",
        template: `
            {{vm.getMessage()}}
        `,
        bindings: {
            index: "<"
        }
    }
);

It has the following properties:

  1. We have a list of JS values (the “indices” array).
  2. We ng-repeat over this list to produce a list of components. Each value in it is passed as a parameter to the component.
  3. The component’s rendering depends on that index, but once it’s been done once it never has to be done again (it’s deterministic given the index).
  4. The indices will be filtered but never changed.
  5. Rendering a component is expensive.

It seems that when I type something in to filter the list, AngularJS has to re-render every component every time, and it’s slow. In theory it should just be hiding and showing components, only calling getMessage() once per component, on page load. How can I get this behavior?

I tried a :: one-time binding, which speeds it up as I type a search query, but when I erase the query it’s slow again since I suppose it still has to call getMessage() to re-draw the components it had gotten rid of.

Source: AngularJS