JavaScript
Angular
23 July 2014

С большой силой приходит и большая ответственность — техника безопасности в 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 вам довелось набить шишек.

+8
18k 117
Comments 22
Top of the day