출처: https://benohead.com/angularjs-sharing-data-controllers/
한글 번역본
너는 너의 것들을 decoupled 상태로 유지하려고 해야 하고 너의 directives, controllers 와 services들은 self contained(자체적으로 담겨있어야함) 에도 불구하고, 너는 controller들 사이에 data를 공유해야 될 때가 있다.
두 가지 메인 시나리오가 있다.
1. Parent와 child controller 간에 데이터 공유
2. two siblings 와 같은 연결성이 없는 controller간에 데이터 공유
Parent와 child간에 데이터 공유
아래와 같이 parent, child controller를 가지고 있다고 해보자.
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl as vm">
</div>
</div>
js에서는 아래처럼 정의 될 것이다.
var app = angular.module('sharing', []);
app.controller('ParentCtrl', function($scope) {
});
app.controller('ChildCtrl', function($scope) {
});
parent controller에 user name을 정의한다.
app.controller('ParentCtrl', function($scope) {
$scope.user = {
name: "Henri"
};
});
Note that you shouldn’t define your variable as a primitive in the scope since this will cause shadow property to appear in the child scope hiding the original property on the parent scope (caused by the JavaScript prototype inheritance). So you should define an object in the parent scope and defined the shared variable as a property of this object.
There are now three ways to access this shared variable:
- using $parent in HTML code
- using $parent in child controller
- using controller inheritance
Using $parent in HTML code
You can directly access variables in the parent scope by using $parent:
Hello {{$parent.user.name}}
Using $parent in child controller
Of course, you could also reference the shared variable using $parent in the controller and expose it in the scope of the child controller:
app.controller('ChildCtrl', function($scope) {
$scope.parentUser = $scope.$parent.user;
});
Then, you can use this scope variable in your HTML code:
Hello {{parentUser.name}}
Using controller inheritance
But since the scope of a child controller inherits from the scope of the parent controller, the easiest way to access the shared variable is actually not to do anything. All you need to do is:
Hello {{user.name}}
연관성이 없는 controller 간에 데이터 공유
parent-child 관계가 아닌 2 controller 간에 데이터를 공유하기 위해서는 $parent를 사용할 수도 없고 prototype inheritance를 사용하지도 못한다.
그런 경우 다음 세가지를 사용할 수 있다.
- Factory 나 Service에 공유 데이터를 넣는다.
- Root Scope에 공유 데이터를 넣는다.
- data 변경에 대해 다른 controller에게 알려 준다.
Factory 나 Service에 공유 데이터를 넣는 법
AngularJS facories(and services)는 methods(business logic) 과 properties(data)를 둘 다 가지고 있을 수 있고 다른 components(예: 너의 controller들) 에 inject 할 수 있다.
이것이 factory안에 공유 variable을 정의할 수 있도록 해주고, 양쪽 contoller에 이것을 inject 하면 이 factory data는 양쪽 controller 안에 scope variable로 bind 된다.
The first step is to define a factory holding a shared value:
app.factory('Holder', function() {
return {
value: 0
};
});
Then you inject this factory in your two controllers:
app.controller('ChildCtrl', function($scope, Holder) {
$scope.Holder = Holder;
$scope.increment = function() {
$scope.Holder.value++;
};
});
app.controller('ChildCtrl2', function($scope, Holder) {
$scope.Holder = Holder;
$scope.increment = function() {
$scope.Holder.value++;
};
});
In both controllers, we bind the Holder factory to a scope variable and define a function which can be called from the UI and updates the vale of the shared variable:
<div>
<h2>First controller</h2>
<button>+</button>{{Holder.value}}
</div>
<div>
<h2>Second controller</h2>
<button>+</button>{{Holder.value}}
</div>
No matter which “+” button you press, both values will be incremented (or rather the shared value will be incremented and reflected in both scopes).
Holding the shared data in the root scope
Of course, instead of using a factory or a service, you can also directly hold the shared data in the root scope and reference it from any controller. Although this actually works fine, it has a few disadvantages:
- Whatever is present in the root scope is inherited by all scopes
- You need to use some naming conventions to prevent multiple modules or libraries from overwriting each other’s data
In general, it’s much cleaner to encapsulate the shared data in dedicated factories or services which are injected in the components which require access to this share data than making these data global variables in the root scope.
event를 사용해서 data 변경을 다른 controller에게 알리는 방법
factory data를 통해 양쪽 Scope에 bind 하지 않기를 원하는 경우 (예: one scope의 변경사항을 다른 scope에게 알려주기만을 원하는 경우), controller 간의 event notification을 사용해서 data를 sync할 수 있다. event를 handle하기 위해 angularjs 에서는 3가지 방법을 제공한다:
- $emit은 event를 발생시키고 현재 scope 과 모든 parent scope에 전달한다.
- $broadcast는 event를 발생시키고 현재 scope과 모든 child scope에 전달한다.
- $on 은 the scope의 변경사항을 listen 하는데 이용한다.
Using $emit
Since $emit 은 event를 scope 계층 상단으로 전달하기에, 두가지 사용법이 있다.
- parent controller들에게 event를 전달
- root scope을 통해 연관 되지 않은 controller에 효율적으로 event를 전달
첫번 째 시나리오, child scope에서 emit을 사용:
$scope.$emit("namechanged", $scope.name);
parent controller scope에서 listen:
$scope.$on("namechanged", function(event, name) {
$scope.name = name;
});
두번째 시나리오, rootscope에서 emit:
$rootScope.$emit("namechanged", $scope.name);
그리고 rootscope에서 listen:
$rootScope.$on("namechanged", function(event, name) {
$scope.name = name;
});
In this case there is effectively no further propagation of the event since the root scope has no parent scope. It is thus the preferred way to propagate events to unrelated scopes (and should be preferred to $broadcast in such scenarios).
There is one thing you need to consider when registering to events on the root scope: in order to avoid leaks when controllers are created and destroyed multiple times, you need to unregister the event listeners. A function to unregistered is returned by $emit. You just need to register this function as a handler for the $destroy event in your controller, replacing the code above by:
var destroyHandler = $rootScope.$on("namechanged", function(event, name) {
$scope.name = name;
});
$scope.$on('$destroy', destroyHandler);
Using $broadcast
Theoretically, you could also use $broadcast to cover two scenarios:
- Propagating events to child controllers
- Propagating events to unrelated controllers through the root scope
Effectively, the second use case doesn’t make much sense since you would basically trigger an event on the root scope and propagate it to all child scopes which is much less efficient than propagating and listening to events on the root scope only.
In the first scenario, you broadcast an event on the parent controller scope:
$scope.$broadcast("namechanged", $scope.name);
And listen to this event on the child controller scope:
$scope.$on("namechanged", function(event, name) {
$scope.name = name;
});
Similarities and differences between $emit and $broadcast
Both $emit and $broadcast dispatch an event through the scope hierarchy notifying the registered listeners. In both cases, the event life cycle starts at the scope on which the function was called. Both functions will pass all exceptions thrown by the listeners to $exceptionHandler.
An obvious difference is that $emit propagates upwards and $broadcast downwards (in the scope hierarchy). Another difference is that when you use $emit, the event will stop propagating if one of the listeners cancels it while the event cannot be canceled when propagated with $broadcast.
Demo
You can see the code from this post in action on plunker:
haystack
which starts at the last occurrence ofneedle
and goes until the end ofhaystack
.