1. 변화
angularjs로 개발을 하게 되면 생각대로 값에 대한 변경이 이루어지지 않는 경우가 정말 많다. 그러한 이슈들 중 해결방법은 몇가지 있지만 그거 마저도 굉장히 어렵다.
앞서 소개하고 설명했던 공부들중 몇몇이 이에 해당된다. 이번 공부는 $apply와 $watch가 이에 해당된다.
2. $apply
먼저 $apply에 대해 소개를 시작하겠다.
function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
$apply는 위와같이 구현되어져 있다. 따라서 $eval을 반드시 수행하며 이후 에러가 나지않는다면 $digest를 수행하게 된다. 먼저 $eval을 알아보면
$eval([expression], [locals]);
Executes the expression
on the current scope and returns the result. Any exceptions in the expression are propagated (uncaught). This is useful when evaluating AngularJS expressions.
Param | Type | Details |
---|---|---|
expression (optional) | stringfunction() | An AngularJS expression to be executed.
|
locals (optional) | object | Local variables object, useful for overriding values in scope. |
angularjs API를 보면 이렇게 나와있다. 즉 exp를 실행시켜 그에 대한 결과값을 반환하게 되는데 이때 exp는 string형태 또는 함수 형태이다. 함수 형태는 실행시킨후에 리턴값을 반환하는구나 라고 이해할수 있지만 string은 이해하기 힘들 수 있다. 자 예제를 살펴보자.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body ng-app="myApp">
<input type="text" mobi-a="search"/>
<script>
var app = angular.module("myApp", []);
app.directive("mobiA", function() {
return {
controller :['$scope',function(scope){
scope.search = function(){
return '==> hi';
}
}],
link : function(scope,element,attrs){
element.bind('keypress',function(e){
if(e.which==13){
scope.$apply(function(){
var msg = scope.$eval(attrs.mobiA)
console.log(msg());
console.log("===>>>",e.target.value)
})
e.preventDefault();
}
})
}
};
});
</script>
</body>
</html>
콘솔에 위와 같이 찍히는 결과값을 알수 있다. attrs.mobiA는 "search"를 반환하게 되고 이를 $eval("search")형태로 수행하게 된다. 즉 이는 곧 search함수를 리턴하고 이를
msg가 받은후에 msg()형태로 호출하게 된다.
다시 본론으로 돌아와 $apply는 위와 같이 exp를 $eval로 실행후에 결과값을 리턴하며 $digest를 호출하여 digest loop를 돌게 한다. 이는 공부<1>에서 언급한바 있다.
따라서 위와같이 동적으로 생성된 이벤트에 의해 변경된 모델값을 감지하기 위해서는 $apply를 수행하여 $digest를 수행시켜야만 한다.
마지막 예를 보고 다음으로 넘어가자
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
event : <input type="text" ng-model="name" f-d="{{lastName}}"><br>
<br>
Name: {{asd}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lastName = "Doe";
})
.directive("fD",function(){
return function(scope,element,attrs){
element.bind('keyup',function(){
scope.$apply(function(){
scope.asd = attrs.fD;
})
})
}
})
</script>
</body>
</html>
3. $watch
$watch는 이해하기 쉬울것이다. 이것도 역시 결국은 모델값의 변경을 감지하기 위한것인데 watchList를 수색하여 변경된 모델값을 감지시키는 것이다.
$watch(watchExpression, listener, [objectEquality]);
Registers a listener
callback to be executed whenever the watchExpression
changes.
watchExpression
is called on every call to $digest() and should return the value that will be watched. (watchExpression
should not change its value when executed multiple times with the same input because it may be executed multiple times by $digest(). That is, watchExpression
should be idempotent.)listener
is called only when the value from the current watchExpression
and the previous call to watchExpression
are not equal (with the exception of the initial run, see below). Inequality is determined according to reference inequality, strict comparison via the !==
Javascript operator, unless objectEquality == true
(see next point)objectEquality == true
, inequality of the watchExpression
is determined according to the angular.equals
function. To save the value of the object for later comparison, the angular.copy
function is used. This therefore means that watching complex objects will have adverse memory and performance implications.angular.copy
.listener
may change the model, which may trigger other listener
s to fire. This is achieved by rerunning the watchers until no changes are detected. The rerun iteration limit is 10 to prevent an infinite loop deadlock.If you want to be notified whenever $digest is called, you can register a watchExpression
function with no listener
. (Be prepared for multiple calls to your watchExpression
because it will execute multiple times in a single $digest cycle if a change is detected.)
After a watcher is registered with the scope, the listener
fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression
didn't change. To detect this scenario within the listener
fn, you can compare the newVal
and oldVal
. If these two values are identical (===
) then the listener was called due to initialization.
Param | Type | Details |
---|---|---|
watchExpression | function()string | Expression that is evaluated on each $digest cycle. A change in the return value triggers a call to the
|
listener | function(newVal, oldVal, scope) | Callback called whenever the value of
|
objectEquality (optional) | boolean | Compare for object equality using (default: false) |
function() | Returns a deregistration function for this listener. |
위 api를 보면 역시 expression에 대한 설명이 있는데 여기서 exp는 감지되어야 할 대상을 가리키며 감지대상이 변화될때 listener를 호출하게 된다. 이 때 listener는 감지대상의 값 변경에 의한 newValue 와 이전 값 oldValue 그리고 scope를 변수로 받으며 보통 newValue로만 인자를 사용한다.
어느정도 감은 올것이다.
그럼 예제를 보자
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
Name: <input type="text" ng-model="name" f-d="test"><br>
<br>
Name: {{asd}}
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.name = "Doe";
})
.directive("fD",function(){
return function(scope,element,attrs){
scope.$watch('name',function(newValue){
scope.asd = newValue;
})
}
})
</script>
</body>
</html>
scope.asd의 값변경을 $watch를 사용하지 않고 변경시키려 한다면 아무 변화가 없을 것이다.
4. 번외 $parse
function $parse(expression) {
return function() {
return $eval(expression);
}
}
$parse는 위와같은 구조를 갖는다. 앞서 $eval에 대해 공부했으므로 $parse를 반드시 알고갈 필요가 있어 언급한다.
$parse는 위와같은 구조를 갖고 있기 때문에 다음과 같은 결과를 보여준다.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="customersCtrl"> </div>
</body>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $parse) {
$scope.foo = 3;
var parseFn = $parse('foo = 5');
parseFn($scope);
console.log($scope.foo); // returns 5
$scope.foo = 3;
$scope.$eval('foo = 5');
console.log($scope.foo); // returns 5
});
</script>
</html>
위 결과는 $parse와 $scope의 동일한 결과를 보여줌을 알수 있다. 다만 $parse를 언급한 이유는 좀더 유용한 방법이 있기 때문인데
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="customersCtrl"> </div>
</body>
<script>
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $parse) {
$scope.text2 =222;
var getter = $parse('text1');
getter.assign($scope,"11111");
var egetter = $scope.$eval('text2');
console.log($scope); //scope.text2 = 222
});
</script>
</html>
위와 같이 assign을 통해 scope객체 해당 값을 넣어줄수 있기 때문이다. 이는 디렉티브에서 굉장히 유용하게 사용할 수 있다.
자바 스크립트 - 객체 복사 (0) | 2019.03.14 |
---|---|
Anuglar JS 공부<*> - my directive (0) | 2019.02.21 |
Anuglar JS 공부 <5> - 상속 (0) | 2019.02.20 |
Anuglar JS 공부 <4> - module.directive (0) | 2019.02.19 |
Anuglar JS 공부 <3> - 양,단 방향 바인딩 (0) | 2019.02.18 |