С большой силой приходит и большая ответственность — техника безопасности в AngularJS

    image

    Несомненно, ангуляр даёт вам силу. Но пользоваться ей нужно с умом. Я постарался сформулировать три простых правила, которые я много раз нарушал и страдал от этого.

    1. Делайте копию объекта, если он может подвергнуться нежелательным изменениям.


    В ангуляре данные по умолчанию едины, и если вы измените их намеренно, случайно или в результате ошибки, под угрозой окажутся все места его использования. Например, у нас есть фабрика пустых сущностей, которые будут использованы для заполнения форм.
    module.factory("emptyEntity", function() {
    	var emptyObject = {
    		name:"",
    		surname:"",
    		address:{
    			city:""
    			street:"",
    		}
    	};
    
        return {
            createEmptyEntity: function(){
                return emptyObject;
            }
        };
    
    });
    

    И далее, в контроллере формы мы создаём в $scope этот пустой объект и используем его как модель формы.
       $scope.model = mapper.createEmptyPetition();
    

    Что будет, если при вводе формы эта модель изменится, а потом вызвать mapper.createEmptyPetition() снова для другой формы? Так, как везде используется один и тот же экземпляр объекта emptyObject, изменения будут отражены в нём, и при следующем вызове mapper.createEmptyPetition() мы получим грязный и использованный объект. Подобных моментов при разработке может возникать великое множество, и нужно осторожно относиться к раздаче ссылок на объекты направо и налево. В данном случае следовало бы сделать вот так — возвращать копию объекта, чтобы её изменения не касались оригинального объекта:
     createEmptyEntity: function(){
         return angular.copy(emptyObject);
     }
    

    2. Не теряйте ссылку на объект/массив, если не хотите потерять синхронизацию данных


    Простой пример.
    У нас есть контроллер, в $scope которого лежит массив, и есть функция для очищения массива:
    module.controller("NewPetitionController", ["$scope", function($scope) {
            $scope.myArray = [1,2,3,4];
    
            $scope.cleanArray = function(){
                 $scope.myArray = [];
           }
        }
    ]);
    

    И где-то во вьюшке вы отдаёте массив в какую-нибудь директиву, например, которая его отрисует.
    <div my-array-viewer array="myArray"></div>
    

    Что будет, если вызвать функцию cleanArray? Директива спокойно продолжит отображать старый добрый полный массив, потому что у неё осталась ссылка на него. А кодом "$scope.myArray = []" мы только создали новый массив и записали ссылку на него в свойство myArray, на что директиве my-array-viewer абсолютно параллельно. Чтобы занулить массив, не потеряв на него ссылку, нужно просто вызвать $scope.myArray.length = 0;
    То же касается объектов. Нельзя просто взять и присвоить переменной новый объект, нужно изменить старый, чтобы остальные части прилоежния, имеющие ссылку на этот объект, не потеряли её.
    module.controller("NewPetitionController", ["$scope", function($scope) {
            $scope.myObj = {foo: "bar"};
    
            $scope.setObj = function(newObj){
                 //$scope.myObj = newObj; //Так делать нельзя, это приведёт к утере ссылки
                 angular.extend($scope.myObj, newObj); //нужно вот так, чтобы изменился исходный объект
           }
        }
    ]);
    

    3. Будьте внимательны с дочерними $scope


    Многие директивы, такие как ng-if, ng-include создают дочерний $scope. Что это значит? У этих директив будет создан новый экземпляр $scope, в свойстве prototype которого будет родительский скоуп — стандартной javascript-наследование. Из этого следует, что изменение простых свойств (string, number, boolean etc.) в дочернем скоупе НЕ БУДЕТ затрагивать родительский скоуп, так как простые свойства при наследовании копируются. В отличие от них, объекты при прототипном наследовании передаются ссылками, поэтому изменение свойств объектов будет отображаться в родительском скоупе.
    Поэтому так делать не следует, это не будет работать:
    <div ng-if="true">
       <a ng-click="showSecondBlock = true">Показать второй блок</a>
    </div>
    
    <div ng-if="showSecondBlock">
    Второй блок отображается!
    </div>
    

    Вместо этого, нужно иметь для таких дел специальный объект в $scope, назовём его viewModel
    app.controller("MainCtrl", function($scope) {
      $scope.viewModel = {};
    });
    

    <div ng-if="true">
       <a ng-click="viewModel.showSecondBlock = true">Показать второй блок</a>
    </div>
    
    <div ng-if="viewModel.showSecondBlock">
    Второй блок отображается!
    </div>
    


    Пишите в комментариях, о какие ещё особенности ангуляр-way вам довелось набить шишек.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 22
    • +5
      Первые два пункта, скорее, относятся даже не к конкретно AngularJS или javascript'у, а к программированию в принципе.
      • +2
        Без сомнений, но в AngularJS как в ярком представителе MVC фреймворка с двусторонним биндингом это очень заметно, потому что тут вся работа производится с данными — массивами, объектами и их свойствами.
      • +2
        По второму пункту было бы неплохо привести пример тех самых «не безопасных директив». Скажем если директива использует скоуп и смотрит за ним, то проблем по идее не должно быть.

        jsfiddle.net/XAZFr/ — это то что я смог сходу придумать… но буду рад увидеть более адекватные примеры.
        • –15
          Никакой особенной силы ангуляр не даёт. Он даёт возможность написать статью с пафосным заголовком, в который можно вставить смешную картинку.
          • +9
            И не говорите, все эти языки, фреймворки, инструменты…
            Почему нельзя по старинке дырочки в бумажке делать?
            • –1
              Самое смешное то, что все эти клоуны, которые считали что ангуляром можно пользоваться, сейчас пользуют реакт. Но минусы свои не заберут, ггг. Русское IT такое русское.
              • +2
                все эти клоуны, которые считали что ангуляром можно пользоваться, сейчас пользуют реакт.

                Какое интересное утверждение подкрепленное лишь фантазией автора. Действительно, русское IT такое русское.
          • +2
            Только недавно была статья про использование директивы Ace, так опять пишут про основы JS и часть документации про изолированные скоупы. К сожалению, самое интересное в посте — картинка. А жаль.
            • +1
              Проблема мэйнстрим-технологий: слишком много шума вокруг, слишком мало людей реально читают документацию. Можете просто посмотреть на количество тупых вопросов связанных с AngularJS которых по хорошему не должно возникать, если человек на нормальном уровне знает JS и хотя бы немного поразбирался с инструментом который использует. Но без развернутого описания, с чем такое поведение связано, ценность статьи снижается.
            • 0
              Вообще, при работе с формой, не следует отправлять на сервер объект, связанный с полями формы. Стоит отправить его копию. Простой пример:

              В форме используется ресурс
              user: {
                email: a@a.ru,
                password: 123
              }
              

              делаете user.$save(), получаете в ответ что-то вроде {data: true} (мало ли что может вернуть бэкенд), user становится
              user: {
                data: true
              }
              

              и поля формы внезапно очищаются.

              Чтобы этого не происходило нужно отделять друг от друга объект формы и ресурса
              • 0
                а если бэкенд изменяет свойства объекта? например, форматирует текст, приводит номер телефона в нужный формат, вставляет айдишники (мало ли что может делать бэкенд). Тогда нужно опять вручную прокидывать изменения в модель?..
                • 0
                  Вообще имеет смысл использовать нормальные объекты для хранения данных внутри приложения. То есть у нас есть объект Mymodel, который умеет из данных приходящих с сервера формировать данные для нашего приложения, у нас есть метод serializer/marshal/toJSON, который подготавливает данные для отправки на сервер.

                  Для своих проектов коллега реализовал небольшой враппер для моделей и коллекций оных по аналогии с backbone. Сейчас обкатываем на проекте.
                  • 0
                    Слава богу, мы не стали городить таких костылей, не смотря на то, что переписали всё с Бекбона. Обычный ангуляровский ресурс отлично справляется и логика модель=объект отлично ложится на приложение
                  • 0
                    Странно, конечно. Но если на бэкенде такая логика, то будет одна модель, да.
                • +1
                  В Angular 1.2 появились alias для контроллеров.

                  <div ng-controller="StoreController as store">{{store.name}}</div>
                  

                  app.controller('StoreController', function() {
                  	this.name = 'this.name';
                  });
                  

                  Пример использования jsfiddle.net/5UW2X/
                  Используя alias, аргумент $scope для контроллера становится не обязательным, но в дочерних контроллерах, что бы получить доступ родительскому контроллеру необходимо обратиться через $scope.[aliasName].some

                  P.S. $scope.$watch возращает функцию, при вызове которой слежение прекратиться
                  var unwatch = $scope.$watch("some", function () {
                  	//only one time callback
                  	unwatch();
                  });
                  

                  • +1
                    Вторая проблема, как и третья, решаются одинаково: $scope — это не модель, а контейнер моделей. Столько копий уже об это сломано.

                    Так что ваш пример, хоть и корректен, но является плохой практикой.
                    Если есть необходимость обнулять массив, то это сигнал, что он должен быть свойством другого объекта (модели)

                    $scope.model.array

                    Раньше еще часто замечали, что в ng-model должна быть минимум одна точка, тогда никогда не словите эту проблему и не будете иметь геморроя с хаками вроде array.length = 0…
                    По опыту: рано или поздно, если нарушать это правило, проблему не только словите, но и пропустите в продакшн, не заметив где-нибудь, и вот тогда уже перестаните это делать.
                    • 0
                      Например, как в этом видео www.youtube.com/watch?v=DTx23w4z6Kc (кстати очень советую остальные видео из этого курса, лучшего введения в Angular я еще не видел).
                    • 0
                      Я вообще-то пользуюсь точкой обычно, но чисто ради интереса — а как очистить объект?
                      • 0
                        В нутри ngResource есть что-то похожее — функция shallowClearAndCopy. Посмотрите ее содержимое.
                      • 0
                        По первому пункту: зачем вообще нужна var emptyObject = {...}? Почему не переместить код инициализации пустого объекта в тело createEmptyEntity() и избавиться от необходимости что-то копировать и помнить об этом?
                        • 0
                          Я намеревался как можно проще проиллюстрировать ситуацию, когда один экземпляр объекта может использоваться много раз.
                        • 0
                          По второму пункту — если не нужна цепочка прототипов можно сделать так:
                          angular.copy($scope.myObj, newObj); 
                          

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.