Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 9e1e89a

Browse files
committed
feat(ngAs): new as directive to attach children component controllers to the current controller
1 parent 11f2731 commit 9e1e89a

File tree

4 files changed

+450
-0
lines changed

4 files changed

+450
-0
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ var angularFiles = {
5858
'src/ng/directive/attrs.js',
5959
'src/ng/directive/form.js',
6060
'src/ng/directive/input.js',
61+
'src/ng/directive/ngAs.js',
6162
'src/ng/directive/ngBind.js',
6263
'src/ng/directive/ngChange.js',
6364
'src/ng/directive/ngClass.js',

src/AngularPublic.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
scriptDirective,
1313
selectDirective,
1414
optionDirective,
15+
ngAsDirective,
1516
ngBindDirective,
1617
ngBindHtmlDirective,
1718
ngBindTemplateDirective,
@@ -178,6 +179,7 @@ function publishExternalAPI(angular) {
178179
script: scriptDirective,
179180
select: selectDirective,
180181
option: optionDirective,
182+
ngAs: ngAsDirective,
181183
ngBind: ngBindDirective,
182184
ngBindHtml: ngBindHtmlDirective,
183185
ngBindTemplate: ngBindTemplateDirective,

src/ng/directive/ngAs.js

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc directive
5+
* @name ngAs
6+
* @restrict A
7+
*
8+
* @description
9+
* The `ngAs` attribute tells Angular to assign element component controller
10+
* to a given property.
11+
*
12+
* Using this directive you can use the controller of existing components
13+
* in your template (children components).
14+
*
15+
* If the children component is destroyed
16+
* a `null` is assigned to the property.
17+
*
18+
* Note that this is the reverse of `require:`:
19+
* with `require:` is the children who references the parent
20+
* but with `ngAs`is the parent who references the children.
21+
* It is very useful when you want to reuse the same component
22+
* in different situations,
23+
* and they do not need to know which exact parent they have.
24+
*
25+
*
26+
* @element ANY
27+
* @param {expression} ngAs {@link guide/expression Expression} to assign the controller.
28+
*
29+
*
30+
* @example
31+
* ### Use inside the scope
32+
* This example shows how the controller of the component toggle
33+
* is reused in the template through the scope to use its logic.
34+
* <example name="ngAsDirectiveComponentExample" module="ngAsExample">
35+
* <file name="index.html">
36+
* <toggle ng-as="myToggle"></toggle>
37+
* <button ng-click="myToggle.toggle()">Toggle</button>
38+
* <div ng-show="myToggle.isOpen()">You are using a children component to show it.</div>
39+
* </file>
40+
* <file name="script.js">
41+
* angular.module('ngAsExample', [])
42+
* .component('toggle', {
43+
* controller: function() {
44+
* var opened = false;
45+
* this.isOpen = function() { return opened; };
46+
* this.toggle = function() { opened = !opened; };
47+
* }
48+
* });
49+
* </file>
50+
* <file name="protractor.js" type="protractor">
51+
* it('should publish the toggle into the scope', function() {
52+
* var toggle = element(by.buttonText('Toggle'));
53+
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
54+
* toggle.click();
55+
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
56+
* });
57+
* </file>
58+
* </example>
59+
*
60+
* @example
61+
* ### Parent interacts with child via member
62+
* This example shows how the parent controller can have access
63+
* to children component controllers.
64+
* <example name="ngAsDirectiveComponentCounterExample" module="ngAsVoteExample">
65+
* <file name="index.html">
66+
* <competition></competition>
67+
* </file>
68+
* <file name="script.js">
69+
* angular.module('ngAsVoteExample', [])
70+
* .component('voteTaker', {
71+
* controller: function() {
72+
* this.vote = null;
73+
* this.agree = function() { this.vote = 'agree'; };
74+
* this.disagree = function() { this.vote = 'disagree'; };
75+
* this.clear = function() { this.vote = null; };
76+
* },
77+
* template:
78+
* '<button ng-disabled="$ctrl.vote" ng-click="$ctrl.agree()">Agree</button>' +
79+
* '<button ng-disabled="$ctrl.vote" ng-click="$ctrl.disagree()">Disagree</button>'
80+
* })
81+
* .component('competition', {
82+
* controller: function() {
83+
* this.redVoteTaker = null;
84+
* this.blueVoteTaker = null;
85+
* this.match = function() {
86+
* return this.redVoteTaker.vote === this.blueVoteTaker.vote && this.redVoteTaker.vote;
87+
* };
88+
* this.next = function() {
89+
* this.sentence++;
90+
* this.redVoteTaker.clear();
91+
* this.blueVoteTaker.clear();
92+
* };
93+
* },
94+
* template:
95+
* '<p>Red team: <vote-taker ng-as="$ctrl.redVoteTaker"></vote-taker></p>' +
96+
* '<p>Blue team: <vote-taker ng-as="$ctrl.blueVoteTaker"></vote-taker></p>' +
97+
* '<p ng-show="$ctrl.match()">There is a match!</p>' +
98+
* '<button ng-click="$ctrl.next()">Next</button>'
99+
* });
100+
* </file>
101+
* <file name="protractor.js" type="protractor">
102+
* var agrees = element.all(by.buttonText('Agree'));
103+
* var matchMessage = element(by.css('[ng-show]'));
104+
* var next = element(by.buttonText('Next'));
105+
*
106+
* it('should show match message if both agree', function() {
107+
* expect(matchMessage.isDisplayed()).toBeFalsy();
108+
* agrees.click();
109+
* expect(matchMessage.isDisplayed()).toBeTruthy();
110+
* });
111+
*
112+
* it('should hide match message after next is clicked', function() {
113+
* agrees.click();
114+
* next.click();
115+
* expect(matchMessage.isDisplayed()).toBeFalsy();
116+
* });
117+
* </file>
118+
* </example>
119+
*/
120+
var ngAsDirective = ['$parse', function($parse) {
121+
return {
122+
priority: -1,
123+
restrict: 'A',
124+
compile: function(tElement, tAttrs) {
125+
// gets the expected controller name, converts <data-some-thing> into "someThing"
126+
var controllerName = directiveNormalize(nodeName_(tElement));
127+
128+
// get the setter for the as attribute
129+
var getter = $parse(tAttrs.ngAs);
130+
var setter = getter.assign;
131+
132+
return function(scope, element) {
133+
// gets the controller of the current element (see jqLiteController for details)
134+
var controller = element.data('$' + controllerName + 'Controller');
135+
setter(scope, controller);
136+
137+
// when the element is removed, remove it from the scope assignment (nullify it)
138+
element.on('$destroy', function() {
139+
// only remove it if controller has not changed,
140+
// because it can happen that animations (and other procedures) may duplicate elements
141+
if (getter(scope) === controller) {
142+
setter(scope, null);
143+
}
144+
});
145+
};
146+
}
147+
};
148+
}];

0 commit comments

Comments
 (0)