1

A view of my AngularJS app makes heavy use of ng-repeat directive. It is done like this:

<div ng-repeat="branches in company">
    <p>{{branches.name}}</p>
    <p>{{branches.location}}</p>
    <div>
        <select ng-model="branches.officeInformationType">
            <option ng-repeat="offices in branches">{{offices.type}}</option>
        </select>

        <select ng-model="branches.officeInformationMeters">
            <option ng-repeat="offices in branches">{{offices.meters}}</option>
        </select>

        <select ng-model="branches.officeInformationColor">
            <option ng-repeat="offices in branches">{{offices.color}}</option>
        </select>
    </div>
</div>

The fact is, the second ng-repeat and and the others after it (offices in branches) are actually the same everytime, so it wouldn't need to be recalculated for every branch. It would need to be binded to the row it belonges to, for saving it later, so the branches.officeInformation model should still be watched by angular, but I would like to make the whole program more performant.

I am using angular-ui-router and when I change the view between my "Choose your office" view and any other, the lag is tremendous, almost at a minute of wait time when you leave the "Choose your office" page. It renders fast enough, 2 seconds for the whole rendering, but when I leave the page it takes a ton of time to change to the other view.

Any ideas, taking into consideration that the ng-model binding "branches.officeInformation.." is of importance?

EDIT: I have tried remove the nested ng-repeats and for each ng-repeat that I removed, the transition between states got faster and faster. When I removed all the nested ng-repeats the transition became instantaneous, hence why I believe it has to do with the ng-repeats.

The ng-repeats are tracked by $index and where possible I used :: for one time binding.

Thanks.

6
  • 1
    have you tried in the controller to copy your values inside two other arrays and define the two ng-repeat blocks to the new variables instead of the nested ones? In this way they should not render again if branches has been changed Commented May 18, 2017 at 13:11
  • 1
    I don't find anything wrong in this code. But still you can use One-time binding in nested ng-repeat like this : <select ng-model="branches.officeInformationColor"> <option ng-repeat="offices in branches">{{:: offices.color}}</option> </select> Commented May 18, 2017 at 13:12
  • 2
    @PrateekGupta I think you missed the :: before the binding, btw this sets the value to be one time bound, not the ng-repeat to render again when the branches variable changes Commented May 18, 2017 at 13:14
  • @prateekGupta I tried to one-time binding the values, it still takes too much time, and i can't use infinite scrolling because the application user doesn't want it. Considering that it happens only when I change the view (the big lag happens when I go from this view to another, but not viceversa) I would like to know if maybe i can free up some memory or delete some stuff before changing views Commented May 18, 2017 at 13:19
  • It seems odd that it renders quickly, yet is extremely expensive to destroy. Are we sure it's the ng-repeat that causes this? When you remove them does the transition to another view become instantaneous? Commented May 18, 2017 at 19:05

3 Answers 3

1

We can lazy load a dropdown's options right before the user interacts with it.

First, we initialize each dropdown with only the selected option, so you can see it when the dropdown is closed.

Then we attach an ng-focus directive to each dropdown. When our callback fires we can:

  • fully populate the options for that dropdown
  • remove all but the selected option from the previously active dropdown

I wasn't entirely sure of the structure of your data (it looks like some arrays have additional properties on them). So I chose to create "view model" objects that represent the UI. You can adapt this to your own structure.

Controller:

// Set up some test office options (null for no selection)

var allOffices = [null];

for (var i = 0; i < 50; i++) {
    allOffices.push(i);
}

// activeDropdown holds the dropdown that is currently populated with the full list
// of options. All other dropdowns are only populated with the selected option so
// that it shows when the dropdown is closed.

var activeDropdown;

$scope.company = [
    // Branch 1
    [
        // These objects represent each dropdown
        {
            // Just the selected option until the user interacts with it
            options: ["0"],
            selected: "0"
        }, {
            // Just the selected option until the user interacts with it
            options: ["1"],
            selected: "1"
        }, {
            // Just the selected option until the user interacts with it
            options: [null],
            selected: null
        }
    ],
    // Branch 2
    [
        // These objects represent each dropdown
        {
            // Just the selected option until the user interacts with it
            options: ["2"],
            selected: "2"
        }, {
            // Just the selected option until the user interacts with it
            options: ["3"],
            selected: "3"
        }, {
            // Just the selected option until the user interacts with it
            options: [null],
            selected: null
        }
    ]
];

// When the user interacts with a dropdown:
//   - fully populate the array of options for that dropdown 
//   - remove all but the selected option from the previously active dropdown's 
//     options so that it still shows when the dropdown is closed

$scope.loadOffices = function (dropdown) {
    if (activeDropdown === dropdown) {
        return;
    }

    dropdown.options = allOffices;

    if (activeDropdown) {
        activeDropdown.options = [activeDropdown.selected];
    }

    activeDropdown = dropdown;
};

Template:

<div ng-repeat="branch in company">
    <div ng-repeat="dropdown in branch">
        Selected: {{ dropdown.selected }}
        <select ng-focus="loadOffices(dropdown)" ng-model="dropdown.selected">
            <option ng-repeat="o in dropdown.options">{{ o }}</option>
        </select>
    </div>
</div>

Note that ng-focus was the only directive I needed to apply to each dropdown when I tested this. But you may need to add ng-keydown, ng-mouseover, ng-click, or others to get it to work in all scenarios including mobile.

I also noticed a potential styling issue. When you focus on a dropdown, we load all of the options for that dropdown. This may cause the width of the dropdown to change, so if you can set the same width for all of them you should be good.

If the number of options in each dropdown is huge, we may be able to optimize even further by writing some custom directives that interact and allow the actual DOM element options to be shared. But I suspect we won't have to go that far for this example.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks, that sounds like a really good idea, ill be waiting for your code example if you dont mind
@mnemosdev Did this help with your issue?
0

Have you tried 'track by $index' ? it will reduce angular watches overhead.

something like that:

div ng-repeat="branches in company track by $index">
    <p>{{branches.name}}</p>
    <p>{{branches.location}}</p>
    <div>
        <select ng-model="branches.officeInformationType">
            <option ng-repeat="offices in branches track by $index">{{offices.type}}</option>
        </select>

        <select ng-model="branches.officeInformationMeters">
            <option ng-repeat="offices in branches track by $index">{{offices.meters}}</option>
        </select>

        <select ng-model="branches.officeInformationColor">
            <option ng-repeat="offices in branches track by $index">{{offices.color}}</option>
        </select>
    </div>
</div>

3 Comments

yes, they are tracked by $index and where possible I inserted the :: for the one time binding
maybe track by branches.id (or something unique for branch) in nested ng-repeats? But this is blind shot
thanks for the comment, i did try a bunch of solutions in merit to track by but none worked, i guess i rly am binding too many values to be destroyed
0

First and foremost, thanks to those that helped me find the answer.

The problem was that I nested too many ng-repeats with too many event handlers attached to each repeated element. ng-models, ng-changes and ng-clicks were really heavy, but the number of elements was also out of control.

I solved this by using a single select without any nested ng-repeats, this select (and the options) are in a modal view, so a different controller. From that controller I return the select results, having only one select for all the elements in the page. When the data is returned from the modal, I use it from the main controller of the view.

Thanks again.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.