2016-07-03 6 views
0

У меня есть директива, которая имеет 2 атрибута:
1. титульный объект, переданный с контроллера.
2. Передача функции обмена через контроллер.Состояние гонки с директивой angularjs

scope: { 
    title: '=', 
    change: '&' 
} 


//on the link function I update the object, and call the function. 

link: function(scope) { 
    scope.setValue = function() { 
     scope.title = "Yaniv" 
     scope.change(); 
    }; 
}, 

каким-то образом, Eventhough я написал это в противоположную сторону, кажется, что сначала вызывает функцию, и только после этого он обновляет объект заголовка. Каков наилучший способ преодолеть это? Я уже думал об использовании setTimeout, и он действительно работал над этим. но, интересно, почему эта проблема произошла, и есть ли здесь более чистое решение. прилагается JS скрипка: http://jsfiddle.net/sz82r7pg/

ответ

1

title двухстороннего связывания обновляются в родительской области в конце дайджеста. До тех пор title изменение значения не распространяется из области действия в родительскую область.

setValue вызывается ng-click и работает во время the digest. Это означает, что change следует называть после текущего дайджеста, с setTimeout или $imeout:

scope.setValue = function() { 
       scope.title = "Yaniv" 
       $imeout(() => scope.change()); 
    }; 

Что указывает на неприятный запах и проблемы XY, потому что title обновление уже привязан к переваривается и change является излишним. Это работа для платформы. title изменения следует отслеживать с помощью

$scope.$watch('title', (newValue, oldValue) => { 
    if (newValue === oldValue) return; 
    ... 
}); 

в исходном размере.

+0

Благодарим Вас за ясный ответ. Я не хотел использовать $ watch, поэтому я придумал функцию изменения и добрался до этой проблемы. – Yaniv

+0

@ Yaniv Если у вас есть 'change: '&'', новое значение может быть передано с помощью параметра scope.change (val) ',' title: '=' 'is redundant и наоборот. По сути, это то или другое. Есть ли причина вашего решения? Потому что '$ scope. $ Watch' является идиоматическим, * более чистым решением *. – estus

1

Первое, что нужно отметить, это то, что директива имеет свой собственный объект isolate scope, атрибут title которого связан с названием области управления. Эта привязка данных в основном выполняется с помощью часов, и вы можете знать, что обнаружение изменений с использованием часов происходит не мгновенно, происходит внутри цикла дайджеста, что всегда происходит после внесения изменений, а стек вызовов вызывается (в следующей задаче микро/макрос Я точно не знаю, что, но не имеет значения здесь). Однако вызов функции происходит мгновенно. Так что же происходит следующее:

  • Установить название директивы рамки
  • изменения вызова, который вызывает Foo, название области видимости контроллера является старым один
  • После вызова углового прогонов дайджеста цикла, замечает изменения в области действия директивы и распространяет его на область управления

Это вполне ожидаемое поведение. Самый простой способ в вашем случае не использовать собственное обнаружение изменений, но использовать один встроенный в угловой:

angular.module('zippyModule', []) 
 
.controller("Ctrl3", function ($scope) { 
 
    $scope.title = 'Ori'; 
 
    $scope.$watch("title", function(newValue, oldValue) { 
 
    if (newValue !== oldValue) { 
 
     alert(newValue); 
 
    } 
 
    }); 
 
}) 
 
.directive('zippy', function() { 
 
    return { 
 
    restrict: 'AE', 
 
    scope: { 
 
     title:'=zTitle' 
 
    }, 
 
    template: '<button ng-click="setValue()">What would be the value of title??</button>', 
 
    link: function(scope) { 
 
     scope.setValue = function() { 
 
     scope.title = "Yaniv"; 
 
     }; 
 
    } 
 
    } 
 
});
button { 
 
    font-size:24px; 
 
    margin:40px; 
 
    padding: 10px 40px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script> 
 

 
<div ng-app="zippyModule"> 
 
    <div ng-controller="Ctrl3"> 
 
    <div zippy z-title="title"></div> 
 
    </div> 
 
</div>

Во время написания этого фрагмента я понял, вы используете древнюю версию angularjs в скрипке (1.0.2). Мне пришлось сделать небольшую модификацию для работы с 1.5.x.

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

angular.module('zippyModule', []) 
 
.controller("Ctrl3", function ($scope) { 
 
    $scope.title = 'Ori'; 
 
    $scope.foo = function(newTitle) { 
 
    alert(newTitle); 
 
    }; 
 
}) 
 
.directive('zippy', function() { 
 
    return { 
 
    restrict: 'AE', 
 
    scope: { 
 
     change: '&' 
 
    }, 
 
    template: '<button ng-click="setValue()">What would be the value of title??</button>', 
 
    link: function(scope) { 
 
     scope.setValue = function() { 
 
     scope.change({title:"Yaniv"}); 
 
     }; 
 
    } 
 
    } 
 
});
button { 
 
    font-size:24px; 
 
    margin:40px; 
 
    padding: 10px 40px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script> 
 

 
<div ng-app="zippyModule"> 
 
    <div ng-controller="Ctrl3"> 
 
    <div zippy change="foo(title)"></div> 
 
    </div> 
 
</div>

UPDATE

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

angular.module('zippyModule', []) 
 
.controller("Ctrl3", function ($scope) { 
 
    $scope.title = 'Ori'; 
 
    $scope.fooApiImpl = { 
 
    title: "Ori", 
 
    change: function() { 
 
     alert($scope.fooApiImpl.title); 
 
    } 
 
    }; 
 
}) 
 
.directive('zippy', function() { 
 
    return { 
 
    restrict: 'AE', 
 
    scope: { 
 
     fooApi: '=' 
 
    }, 
 
    template: '<button ng-click="setValue()">What would be the value of title??</button>', 
 
    link: function(scope) { 
 
     scope.setValue = function() { 
 
     scope.fooApi.title = "Yaniv"; 
 
     scope.fooApi.change(); 
 
     }; 
 
    } 
 
    } 
 
});
button { 
 
    font-size:24px; 
 
    margin:40px; 
 
    padding: 10px 40px; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script> 
 

 
<div ng-app="zippyModule"> 
 
    <div ng-controller="Ctrl3"> 
 
    <div zippy foo-api="fooApiImpl"></div> 
 
    </div> 
 
</div>

+0

Спасибо Тамас за добрый ответ. Использование $ watch на контроллере - это то, что я сделал в первую очередь, но для меня это выглядело не очень элегантным. поэтому я подумал, что вызов функции изменения будет еще более удобным для пользователя решением. другой способ - это всего лишь обходной путь, так как я хотел изменить исходный заголовочный объект в родительском контроллере и не отправлять его в качестве параметра. Благодарю. – Yaniv

+0

@ Yaniv Есть способы сделать это, но это признак плохих дизайнерских решений. См. Мое обновление (через минуту) –

+0

Я передам новое значение в качестве параметра функции изменения и удалю изменение scope.title в директиве. поэтому мне не нужно будет полагаться на цикл дайджеста, чтобы закончить. функция изменения назначит новое значение заголовку. – Yaniv