AngularJS에서 서비스 속성에 바인딩하는 방법에 대한 모범 사례를 찾고 있습니다.
AngularJS를 사용하여 만든 서비스의 속성에 바인딩하는 방법을 이해하기 위해 여러 예제를 살펴 보았습니다.
아래에는 서비스의 속성에 바인딩하는 방법에 대한 두 가지 예가 있습니다. 그들은 둘 다 작동합니다. 첫 번째 예는 기본 바인딩을 사용하고 두 번째 예는 $ scope. $ watch를 사용하여 서비스 속성에 바인딩합니다.
서비스의 속성에 바인딩 할 때 이러한 예제 중 하나를 선호합니까, 아니면 알지 못하는 다른 옵션이 권장됩니까?
이 예제의 전제는 서비스가 5 초마다“lastUpdated”및“calls”속성을 업데이트해야한다는 것입니다. 서비스 속성이 업데이트되면 뷰에 이러한 변경 사항이 반영되어야합니다. 이 두 예제는 모두 성공적으로 작동합니다. 더 좋은 방법이 있는지 궁금합니다.
기본 바인딩
다음 코드를보고 실행할 수 있습니다. http://plnkr.co/edit/d3c16z
<html>
<body ng-app="ServiceNotification" >
<div ng-controller="TimerCtrl1" style="border-style:dotted">
TimerCtrl1 <br/>
Last Updated: {{timerData.lastUpdated}}<br/>
Last Updated: {{timerData.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.timerData = Timer.data;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
서비스 속성에 대한 바인딩을 해결하는 다른 방법은 컨트롤러에서 $ scope. $ watch를 사용하는 것입니다.
$ scope. $ watch
다음 코드를보고 실행할 수 있습니다. http://plnkr.co/edit/dSBlC9
<html>
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data.lastUpdated; },
function (value) {
console.log("In $watch - lastUpdated:" + value);
$scope.lastUpdated = value;
}
);
$scope.$watch(function () { return Timer.data.calls; },
function (value) {
console.log("In $watch - calls:" + value);
$scope.calls = value;
}
);
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
</html>
서비스에서 $ rootscope. $ broadcast를 사용할 수 있고 컨트롤러에서 $ root. $ on을 사용할 수 있다는 것을 알고 있지만 첫 번째 브로드 캐스트에서 $ broadcast / $를 사용하도록 만든 다른 예에서는 브로드 캐스트되는 추가 호출이 컨트롤러에서 트리거됩니다. $ rootscope. $ broadcast 문제를 해결하는 방법을 알고 있다면 답변을 제공하십시오.
그러나 앞에서 언급 한 내용을 다시 설명하기 위해 서비스 속성에 바인딩하는 방법에 대한 모범 사례를 알고 싶습니다.
최신 정보
이 질문은 원래 2013 년 4 월에 요청 및 답변되었습니다. 2014 년 5 월, Gil Birman은 새로운 답변을 제공했는데, 정답으로 바뀌 었습니다. Gil Birman의 답변에는 투표가 거의 없기 때문에이 질문을 읽는 사람들은 더 많은 투표로 다른 답변에 찬성하여 그의 답변을 무시할 것입니다. 최고의 답변이 무엇인지 결정하기 전에 Gil Birman의 답변을 적극 권장합니다.
답변
두 번째 접근 방식의 장단점을 고려하십시오 .
-
{{lastUpdated}}
대신{{timerData.lastUpdated}}
쉽게{{timer.lastUpdated}}
읽을 수있는 0 대신에 0 은 더 읽기 쉽다고 주장하지만 (논쟁하지 말고 …이 점에 중립 등급을 부여하므로 직접 결정하십시오) -
+1 컨트롤러가 마크 업을위한 일종의 API 역할을하는 것이 편리 할 수 있습니다. 따라서 데이터 모델의 구조가 변경되는 경우 이론적으로 html 부분을 건드리지 않고 컨트롤러의 API 매핑 을 업데이트 할 수 있습니다 .
-
-1 그러나 이론은 항상 실천되지 않고 나는 보통 자신을 마크 업 수정하지 찾을 수 및 변경이 요구 될 때, 컨트롤러 로직을 어쨌든 . 따라서 API를 작성하려는 추가 노력으로 인해 이점이 무효화됩니다.
-
-1 또한이 방법은 그리 건조하지 않습니다.
-
-1 데이터를
ng-model
코드 에 바인딩$scope.scalar_values
하려면 컨트롤러에서 패키지를 다시 패키지하여 새 REST 호출 을 수행해야하므로 DRY가 줄어 듭니다 . -
-0.1 추가 감시자를 만들면 성능이 약간 저하됩니다. 또한 데이터 속성이 특정 컨트롤러에서 감시 할 필요가없는 모델에 연결되어 있으면 심층 감시자에게 추가 오버 헤드가 발생합니다.
-
-1 여러 컨트롤러에 동일한 데이터 모델이 필요한 경우 어떻게합니까? 즉, 모든 모델 변경 시마다 업데이트 할 여러 API가 있습니다.
$scope.timerData = Timer.data;
지금 막 강렬한 유혹을 느끼기 시작하고 있습니다 … 마지막 요점을 조금 더 깊이 살펴 보도록하겠습니다. 백엔드 (서버)의 모델? 아니면 프론트 엔드에서만 만들어지고 존재하는 모델입니까? 어느 경우이든, 본질적으로 데이터 맵핑 API 는 프론트 엔드 서비스 계층 (각 팩토리 또는 서비스)에 속한다 . (첫 번째 예-선호 사항)에는 서비스 계층 에 이러한 API 가 없으므로 필요하지 않기 때문에 간단합니다.
결론적 으로 모든 것을 분리 할 필요는 없습니다. 그리고 마크 업을 데이터 모델에서 완전히 분리하는 한, 단점이 장점보다 중요합니다.
컨트롤러는 일반적으로 로 흩어지지 않아야합니다 $scope = injectable.data.scalar
. 오히려, 그들은 함께 뿌려해야한다 $scope = injectable.data
의, promise.then(..)
의, 그리고 $scope.complexClickAction = function() {..}
의
데이터 디커플링 따라서보기 – 캡슐화하는 유일한 장소 달성하기위한 다른 방법으로 정말 모델에서보기 분리하는 것이 합리적 이다 지시문과를 . 그러나 거기에도 또는 함수 $watch
에 스칼라 값을 두지 마십시오 . 시간을 절약하거나 코드를 유지 관리하거나 읽을 수있게하지는 않습니다. 각도에서의 강력한 테스트는 일반적으로 결과 DOM 을 테스트하기 때문에 테스트를 더 쉽게 할 수 없습니다 . 오히려 지시문에서 데이터 API 를 객체 형태로 요구하고에 의해 생성 된 er 만 사용 하는 것이 좋습니다.controller
link
$watch
ng-bind
예
http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Bad:<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
Good:<br/>
Last Updated: {{data.lastUpdated}}<br/>
Last Updated: {{data.calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.data = Timer.data;
$scope.lastUpdated = Timer.data.lastUpdated;
$scope.calls = Timer.data.calls;
};
app.factory("Timer", function ($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 500);
};
updateTimer();
return {
data: data
};
});
</script>
</body>
업데이트 : 마지막 으로이 질문으로 돌아와 두 가지 접근 방식이 “잘못된”것이라고 생각하지 않는다고 덧붙였습니다. 원래 나는 Josh David Miller의 대답이 틀렸다고 썼지 만 회고하면 그의 견해, 특히 우려의 분리에 관한 그의 견해는 완전히 유효합니다.
우려를 따로 (그러나 접선 적으로 관련) 분리하면, 방어 카피 에는 고려해야 할 또 다른 이유가 있습니다. 이 질문은 주로 서비스에서 직접 데이터를 읽는 것에 관한 것입니다. 그러나 팀의 개발자가 뷰가 데이터를 표시하기 전에 컨트롤러가 데이터를 사소한 방식으로 변환해야한다고 결정하면 어떻게 될까요? (컨트롤러가 데이터를 변환해야하는지 여부에 대해서는 또 다른 논의가 있습니다.) 오브젝트의 사본을 먼저 작성하지 않으면 동일한 데이터를 사용하는 다른보기 구성 요소에서 무의식적으로 회귀가 발생할 수 있습니다.
이 질문에서 실제로 강조하는 것은 전형적인 앵귤러 응용 프로그램 (그리고 실제로는 모든 JavaScript 응용 프로그램)의 구조적 단점, 우려의 엄격한 결합 및 객체 변경 가능성입니다. 최근 React 와 불변의 데이터 구조를 사용하여 설계 응용 프로그램에 매료되었습니다 . 그렇게하면 다음 두 가지 문제가 훌륭하게 해결됩니다.
-
우려 분리 : 구성 요소는 소품을 통해 모든 데이터를 사용하며 전역 단일 톤 (예 : 각도 서비스)에 거의 의존하지 않으며 뷰 계층에서 그 위에 일어난 일에 대해서는 전혀 알지 못합니다 .
-
변경 성 : 모든 소품은 변경이 불가능하여 의도 하지 않은 데이터 변이의 위험을 제거합니다.
Angular 2.0은 이제 React에서 많은 돈을 빌려 위의 두 가지 포인트를 달성 할 수 있습니다.
답변
내 관점에서 볼 $watch
때 가장 좋은 방법입니다.
실제로 예제를 약간 단순화 할 수 있습니다.
function TimerCtrl1($scope, Timer) {
$scope.$watch( function () { return Timer.data; }, function (data) {
$scope.lastUpdated = data.lastUpdated;
$scope.calls = data.calls;
}, true);
}
그게 당신이 필요한 전부입니다.
속성이 동시에 업데이트되므로 하나의 시계 만 필요합니다. 또한 그것들은 하나의 작은 물체에서 나왔기 때문에 Timer.data
속성을 보도록 변경했습니다 . 전달 된 마지막 매개 변수 $watch
는 참조가 동일한 지 확인하지 않고 깊은 평등을 검사 하도록 지시합니다.
약간의 맥락을 제공하기 위해 서비스 방법을 범위에 직접 배치하는 것보다이 방법을 선호하는 이유는 우려를 적절히 분리하는 것입니다. 서비스를 운영하기 위해 서비스에 대해 알 필요가 없습니다. 컨트롤러의 역할은 모든 것을 하나로 묶는 것입니다. 업무는 서비스에서 데이터를 가져와 필요한 방식으로 처리 한 다음 필요한 특정 정보를 제공하는 것입니다. 그러나 나는 그 임무가 단지 서비스를 바로보기에 전달하는 것이라고 생각하지 않습니다. 그렇지 않으면 컨트롤러가 무엇을하고 있습니까? AngularJS 개발자들은 템플릿에 “논리”를 포함시키지 않기로 선택했을 때와 같은 추론을 따랐습니다 (예 : if
진술).
공평하게, 여기에 여러 관점이있을 수 있으며 다른 답변을 기대합니다.
답변
파티에 늦었지만 미래의 Google 직원에게는 제공된 답변을 사용하지 마십시오.
JavaScript는 “숫자, 문자열 등”값에 대해 얕은 사본 만 전달하는 반면 참조로 오브젝트를 전달하는 메커니즘을 가지고 있습니다.
위의 예에서 서비스의 속성을 바인딩하는 대신 서비스를 범위에 노출시키지 않는 이유는 무엇입니까?
$scope.hello = HelloService;
이 간단한 접근 방식을 통해 각도는 양방향 바인딩과 필요한 모든 마법 같은 작업을 수행 할 수 있습니다. 감시자 또는 불필요한 마크 업으로 컨트롤러를 해킹하지 마십시오.
뷰가 실수로 서비스 속성을 덮어 쓰는 것이 걱정되는 경우 defineProperty
이를 사용 하여 읽기, 열거 가능, 구성 가능 또는 게터 및 세터를 정의하십시오. 서비스를보다 견고하게 만들어 많은 제어권을 얻을 수 있습니다.
마지막 팁 : 컨트롤러보다 서비스에서 작업하는 데 시간을 보낸다면 잘못하고 있습니다 :(.
제공 한 특정 데모 코드에서 다음을 수행하는 것이 좋습니다.
function TimerCtrl1($scope, Timer) {
$scope.timer = Timer;
}
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...
편집하다:
위에서 언급했듯이 다음을 사용하여 서비스 속성의 동작을 제어 할 수 있습니다 defineProperty
예:
// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
get: function() { return self.data.variable; },
set: function(newValue) {
self.data.variable = newValue;
// let's update the database too to reflect changes in data-model !
self.updateDatabaseWithNewData(data);
},
enumerable: true,
configurable: true
});
이제 컨트롤러에서
$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';
우리의 서비스는 propertyWithSetter
어떻게 든 가치를 변화시키고 새로운 가치를 데이터베이스에 게시 할 것입니다!
또는 원하는 방식으로 접근 할 수 있습니다.
에 대한 MDN 설명서 를 참조하십시오 defineProperty
.
답변
나는이 질문에 맥락 적 구성 요소가 있다고 생각합니다.
단순히 서비스에서 데이터를 가져 와서 그 정보를 볼 수 있다면 서비스 속성에 직접 바인딩하는 것이 좋습니다. 단순히 서비스 속성을 모델 속성에 매핑하여 내 관점에서 소비 하는 많은 상용구 코드 를 작성 하고 싶지 않습니다 .
또한 각도에서의 성능은 두 가지를 기반으로합니다. 첫 번째는 페이지에 몇 개의 바인딩이 있는지입니다. 두 번째는 getter 함수의 가격입니다. Misko는 여기 에 대해 이야기합니다 .
서비스 자체에 적용된 데이터 마사지와 달리 서비스 데이터에 대해 인스턴스 특정 로직을 수행해야하고 그 결과가 뷰에 노출 된 데이터 모델에 영향을 미치는 경우 $ watcher가 적절하다고 말할 수 있습니다. 기능이 엄청나게 비싸지 않는 한. 비싼 함수의 경우 결과를 로컬 (컨트롤러) 변수에 캐싱하고 $ watcher 함수 외부에서 복잡한 작업을 수행 한 다음 범위를 그 결과에 바인딩하는 것이 좋습니다.
경고로 $ scope에서 직접 속성을 걸면 안됩니다 . $scope
변수는 모델이 아닙니다. 모델에 대한 참조가 있습니다.
내 생각에는 단순히 서비스에서 아래로 정보를 발산하는 “모범 사례”:
function TimerCtrl1($scope, Timer) {
$scope.model = {timerData: Timer.data};
};
그리고 당신의 견해에는가 포함 {{model.timerData.lastupdated}}
됩니다.
답변
위의 예제를 바탕으로 컨트롤러 변수를 서비스 변수에 투명하게 바인딩하는 방법으로 던질 것이라고 생각했습니다.
아래 예에서 Controller $scope.count
변수 에 대한 변경 사항 은 Service count
변수 에 자동으로 반영됩니다 .
프로덕션 환경에서는 실제로이 바인딩을 사용하여 서비스의 ID를 업데이트 한 다음 비동기 적으로 데이터를 가져오고 서비스 변수를 업데이트합니다. 추가 바인딩은 서비스가 자체적으로 업데이트 될 때 컨트롤러가 자동으로 업데이트됨을 의미합니다.
아래 코드는 http://jsfiddle.net/xuUHS/163/ 에서 작동하는 것으로 볼 수 있습니다
전망:
<div ng-controller="ServiceCtrl">
<p> This is my countService variable : {{count}}</p>
<input type="number" ng-model="count">
<p> This is my updated after click variable : {{countS}}</p>
<button ng-click="clickC()" >Controller ++ </button>
<button ng-click="chkC()" >Check Controller Count</button>
</br>
<button ng-click="clickS()" >Service ++ </button>
<button ng-click="chkS()" >Check Service Count</button>
</div>
서비스 / 컨트롤러 :
var app = angular.module('myApp', []);
app.service('testService', function(){
var count = 10;
function incrementCount() {
count++;
return count;
};
function getCount() { return count; }
return {
get count() { return count },
set count(val) {
count = val;
},
getCount: getCount,
incrementCount: incrementCount
}
});
function ServiceCtrl($scope, testService)
{
Object.defineProperty($scope, 'count', {
get: function() { return testService.count; },
set: function(val) { testService.count = val; },
});
$scope.clickC = function () {
$scope.count++;
};
$scope.chkC = function () {
alert($scope.count);
};
$scope.clickS = function () {
++testService.count;
};
$scope.chkS = function () {
alert(testService.count);
};
}
답변
서비스 자체 의 속성 대신 서비스 자체에 바인딩 하는 것이 더 좋은 방법이라고 생각 합니다.
이유는 다음과 같습니다.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">
<div ng-controller="BindToServiceCtrl as ctrl">
ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
<br />
ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
<br />
<br />
<!-- This is empty since $scope.arrOne never changes -->
arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
<br />
<!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
<!-- Both of them point the memory space modified by the `push` function below -->
arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
</div>
<script type="text/javascript">
var app = angular.module("BindToService", []);
app.controller("BindToServiceCtrl", function ($scope, ArrService) {
$scope.ArrService = ArrService;
$scope.arrOne = ArrService.arrOne;
$scope.arrTwo = ArrService.arrTwo;
});
app.service("ArrService", function ($interval) {
var that = this,
i = 0;
this.arrOne = [];
that.arrTwo = [];
$interval(function () {
// This will change arrOne (the pointer).
// However, $scope.arrOne is still same as the original arrOne.
that.arrOne = that.arrOne.concat([i]);
// This line changes the memory block pointed by arrTwo.
// And arrTwo (the pointer) itself never changes.
that.arrTwo.push(i);
i += 1;
}, 1000);
});
</script>
</body>
이 플 런커 에서 재생할 수 있습니다 .
답변
차라리 감시자를 최대한 줄이려고합니다. 내 이유는 내 경험을 바탕으로하고 있으며 이론적으로 주장 할 수 있습니다.
감시자를 사용하는 문제는 범위의 모든 속성을 사용하여 원하는 구성 요소 또는 서비스의 메서드를 호출 할 수 있다는 것입니다.
현실 세계 프로젝트에서, 곧 당신이 될 겁니다 비 tracable 방법의 체인 호출되는 및 값 특별히 온 보딩 프로세스가 비극적 만드는 변경되는 (더 나은 하드 트레이스했다)가.