Статьи

Нет необходимости смотреть AngularJS “Controller As”

В предыдущем сообщении в блоге я обсуждал, как использовать расширение Angular для улучшения качества кода и возможности его повторного использования . В моем примере я использовал новый контроллер в качестве синтаксиса. Я вижу много дискуссий в Интернете об этом подходе. Мне это нравится, потому что он позволяет вам обращаться с вашими контроллерами как с чистыми объектами JavaScript, а не как с прославленными пакетами захвата, которые ничего не делают, кроме манипулирования $ scope . Самая большая жалоба, которую я прочитал, заключается в том, что вам все равно приходится брать зависимость от $ scope, когда вы хотите наблюдать за свойствами. Или ты?

Из уроков, полученных при работе над большим Angular-проектом (под «большим» я подразумеваю команду из 25+ разработчиков, распределенных по всему миру, с базой кода, содержащей более 80 000 строк кода TypeScript с сотнями контроллеров, фильтров и сервисов) одна ошибка Я сделал рано, слишком сильно зависел от объема. Например, я мог бы предположить, что страница сведений унаследовала область главной страницы. Эта зависимость от иерархии затрудняла рефакторинг страниц, поэтому я быстро понял, что обмен свойствами должен осуществляться через компоненты и службы, а не основываться на подразумеваемой области действия.

Часы похожи. Вы должны спросить себя, что вы ищете, а затем решить, стоит ли использовать реальные $ watch. $ Watch вносит значительные накладные расходы и запускает каждый цикл дайджеста. Он будет вызываться несколько раз, когда другой код изменяет модель данных, поэтому вы хотите максимально сохранить свои часы. Как ты можешь это сделать?

Один пример, который я описал в своем видео Angular Debugging and Performance . Сценарий представляет собой постраничный список. У меня есть один миллион элементов в списке, и я показываю по несколько на страницу. Вы, вероятно, знаете из прошлого опыта с подкачкой страниц, что нужно рассчитать несколько переменных, например, сколько всего страниц существует и где «страница» вписывается в диапазон проиндексированных элементов, которые представляют эту страницу. Для обработки подкачки я создал контроллер, который отслеживает полный список, а затем предоставляет «список отображения» с текущей страницей. Вот базовое определение с кодом для генерации миллиона записей:

function Controller() {
    var idx = 0;
    this.list = [];
    this.displayList = [];
    while (idx < 1000000) {
        this.list.push(Math.random());
        idx += 1;
    }
    this.refreshPages();
}

Контроллер отслеживает несколько переменных, и все они пересчитываются в функцию refreshPages, которая должна запускаться при каждом изменении текущей страницы. Вот начальное определение контроллера и этого метода:

angular.extend(Controller.prototype, {
    pageSize: 20,
    _currentPage: 1,
    nextPage: 2,
    previousPage: 0,
    currentIndex: 0,
    totalPages: 0,
    refreshPages: function () {
        var curIdx = this.currentIndex;
        this.totalPages = Math.ceil(this.list.length / this.pageSize);
        this.currentIndex = this.pageSize * (this._currentPage - 1);
        this.previousPage = this._currentPage - 1;
        this.nextPage = this._currentPage + 1;
        if (curIdx !== this.currentIndex || this.displayList.length === 0) {
            while (this.displayList.length > 0) {
                this.displayList.pop();
            }
            for (curIdx = this.currentIndex;
                 curIdx < this.list.length && curIdx < this.currentIndex + this.pageSize;
                 curIdx += 1) {
                this.displayList.push(this.list[curIdx]);
            }
        }
    }
});

Вы можете заметить, что текущая страница определена как частная собственность. Почему бы не выставить его, а затем использовать $ watch? Ответ прост. Я контролирую свою модель, поэтому мне не нужно смотреть ее, чтобы знать, когда она мутирует. Вместо этого я могу воспользоваться преимуществами чистого JavaScript и представить текущую страницу как свойство, которое обновляет необходимые переменные при каждом изменении. После того, как это произойдет, я быстро очищаю предоставленный массив для страницы и заново заполняю его на основе вновь вычисленных переменных. Вот решение, которое не включает добавление ненужных часов, но вместо этого использует Object.defineProperty :

Object.defineProperty(Controller.prototype, "currentPage", {
    enumerable: true,
    configurable: true,
    get: function () { return this._currentPage; },
    set: function (val) {
        this._currentPage = val;
        this.refreshPages();
    }
});

Теперь свойство определяется на контроллере с использованием чистого JavaScript и может быть связано как любое другое свойство. Вот разметка для кнопки для перехода на предыдущую страницу. Он просто уменьшает свойство currentPage и отключается, когда вы находитесь на первой странице.

<button data-ng-click="ctrl.currentPage = ctrl.currentPage-1"
          data-ng-disabled="ctrl.currentPage == 1">
    &lt;
</button>

Если бы я захотел еще больше оптимизировать, я мог бы добавить дополнительное условие, чтобы убедиться, что значение действительно отличается перед обновлением страниц. В результате получается огромный список, эффективно разбитый на страницы с использованием синтаксиса «контроллер как». Вы можете увидеть работающий пример здесь: длинный список в AngularJS . Полный исходный код находится здесь: длинный постраничный список в исходном коде AngualrJS .

Суть: я предпочитаю синтаксис «контроллер как», потому что он позволяет мне определять контроллеры как объекты чистого JavaScript с минимальными зависимостями. Мне не нужно явно заботиться о $ scope и привязке данных, и вместо этого я могу рассматривать сам контроллер как модель представления и знать, что открытые свойства доступны для привязки.

Вместо использования $ watch я использую встроенные функции JavaScript для управления моей моделью. Это дает дополнительное преимущество, заключающееся в упрощении тестирования компонента (в этом примере вы можете протестировать контроллер без какой-либо зависимости от Angular). Если меня беспокоит изменение значения с другого контроллера, я настраиваю службу связи, а не добавляю часы и снова сохраняю контроль над изменениями без необходимости вызывать каждый цикл дайджеста.

$ посмотрите мое последнее видео, чтобы узнать больше об отладке и производительности AngularJS .