+ *
+ *
+ * angular.module('myApp', []);
+ *
+ *
+ * function ToggleController() {
+ * var opened = false;
+ * this.isOpen = function() { return opened; };
+ * this.toggle = function() { opened = !opened; };
+ * }
+ *
+ * angular.module('myApp').component('myToggle', {
+ * template: '',
+ * transclude: true,
+ * controller: ToggleController
+ * });
+ *
+ *
+ * var OuterToggle = function() {
+ * this.toggle = function() {
+ * element(by.buttonText('Outer Toggle')).click();
+ * };
+ * this.isOpen = function() {
+ * return element.all(by.binding('outerToggle.isOpen()')).first().getText();
+ * };
+ * };
+ * var NgRepeatToggle = function(i) {
+ * var parent = element.all(by.repeater('i in [1,2,3]')).get(i - 1);
+ * this.toggle = function() {
+ * element(by.buttonText('ngRepeat Toggle ' + i)).click();
+ * };
+ * this.isOpen = function() {
+ * return parent.element(by.binding('ngRepeatToggle.isOpen() | json')).getText();
+ * };
+ * this.isOuterOpen = function() {
+ * return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
+ * };
+ * };
+ * var NgRepeatToggles = function() {
+ * var toggles = [1,2,3].map(function(i) { return new NgRepeatToggle(i); });
+ * this.forEach = function(fn) {
+ * toggles.forEach(fn);
+ * };
+ * this.isOuterOpen = function(i) {
+ * return toggles[i - 1].isOuterOpen();
+ * };
+ * };
+ * var NgIfToggle = function() {
+ * var parent = element(by.css('[ng-if]'));
+ * this.toggle = function() {
+ * element(by.buttonText('ngIf Toggle')).click();
+ * };
+ * this.isOpen = function() {
+ * return by.binding('ngIfToggle.isOpen() | json').getText();
+ * };
+ * this.isOuterOpen = function() {
+ * return parent.element(by.binding('outerToggle.isOpen() | json')).getText();
+ * };
+ * };
+ *
+ * it('should toggle the outer toggle', function() {
+ * var outerToggle = new OuterToggle();
+ * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
+ * outerToggle.toggle();
+ * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
+ * });
+ *
+ * it('should toggle all outer toggles', function() {
+ * var outerToggle = new OuterToggle();
+ * var repeatToggles = new NgRepeatToggles();
+ * var ifToggle = new NgIfToggle();
+ * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): false');
+ * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): false');
+ * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): false');
+ * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): false');
+ * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
+ * outerToggle.toggle();
+ * expect(outerToggle.isOpen()).toEqual('outerToggle.isOpen(): true');
+ * expect(repeatToggles.isOuterOpen(1)).toEqual('outerToggle.isOpen(): true');
+ * expect(repeatToggles.isOuterOpen(2)).toEqual('outerToggle.isOpen(): true');
+ * expect(repeatToggles.isOuterOpen(3)).toEqual('outerToggle.isOpen(): true');
+ * expect(ifToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): true');
+ * });
+ *
+ * it('should toggle each repeat iteration separately', function() {
+ * var repeatToggles = new NgRepeatToggles();
+ *
+ * repeatToggles.forEach(function(repeatToggle) {
+ * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): false');
+ * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
+ * repeatToggle.toggle();
+ * expect(repeatToggle.isOpen()).toEqual('ngRepeatToggle.isOpen(): true');
+ * expect(repeatToggle.isOuterOpen()).toEqual('outerToggle.isOpen(): false');
+ * });
+ * });
+ *
+ *
+ *
+ */
+var ngRefDirective = ['$parse',function($parse) {
+ return {
+ priority: -1,
+ restrict: 'A',
+ compile: function(tElement, tAttrs) {
+ // gets the expected controller name, converts into "someThing"
+ var controllerName = directiveNormalize(nodeName_(tElement));
+
+ // get the expression for value binding
+ var getter = $parse(tAttrs.ngRef);
+ var setter = getter.assign;
+
+ return function(scope, element) {
+ // gets the controller of the current component or the current DOM element
+ var controller = element.data('$' + controllerName + 'Controller');
+ var value = controller || element[0];
+ setter(scope, value);
+
+ // when the element is removed, remove it (nullify it)
+ element.on('$destroy', function() {
+ // only remove it if value has not changed,
+ // carefully because animations (and other procedures) may duplicate elements
+ if (getter(scope) === value) {
+ setter(scope, null);
+ }
+ });
+ };
+ }
+ };
+}];
diff --git a/test/ng/directive/ngRefSpec.js b/test/ng/directive/ngRefSpec.js
new file mode 100644
index 000000000000..1984750a66b7
--- /dev/null
+++ b/test/ng/directive/ngRefSpec.js
@@ -0,0 +1,330 @@
+'use strict';
+
+describe('ngRef', function() {
+
+ describe('given a component', function() {
+
+ var myComponentController, $rootScope, $compile;
+
+ beforeEach(module(function($compileProvider) {
+ $compileProvider.component('myComponent', {
+ template: 'foo',
+ controller: function() {
+ myComponentController = this;
+ }
+ });
+ }));
+
+ beforeEach(module(function($exceptionHandlerProvider) {
+ $exceptionHandlerProvider.mode('log');
+ }));
+
+ beforeEach(inject(function(_$compile_, _$rootScope_) {
+ $rootScope = _$rootScope_;
+ $compile = _$compile_;
+ }));
+
+ afterEach(inject(function($exceptionHandler) {
+ if ($exceptionHandler.errors.length) {
+ dump(jasmine.getEnv().currentSpec.getFullName());
+ dump('$exceptionHandler has errors');
+ dump($exceptionHandler.errors);
+ expect($exceptionHandler.errors).toBe([]);
+ }
+ }));
+
+ it('should bind in the current scope the controller of a component', function() {
+ $rootScope.$ctrl = 'undamaged';
+
+ $compile('')($rootScope);
+ expect($rootScope.$ctrl).toBe('undamaged');
+ expect($rootScope.myComponent).toBe(myComponentController);
+ });
+
+ it('should work with non:normalized entity name', function() {
+ $compile('')($rootScope);
+ expect($rootScope.myComponent1).toBe(myComponentController);
+ });
+
+ it('should work with data-non-normalized entity name', function() {
+ $compile('')($rootScope);
+ expect($rootScope.myComponent2).toBe(myComponentController);
+ });
+
+ it('should work with x-non-normalized entity name', function() {
+ $compile('')($rootScope);
+ expect($rootScope.myComponent3).toBe(myComponentController);
+ });
+
+ it('should work with data-non-normalized attribute name', function() {
+ $compile('')($rootScope);
+ expect($rootScope.myComponent1).toBe(myComponentController);
+ });
+
+ it('should work with x-non-normalized attribute name', function() {
+ $compile('')($rootScope);
+ expect($rootScope.myComponent2).toBe(myComponentController);
+ });
+
+ it('should not leak to parent scopes', function() {
+ var template =
+ '
' +
+ '' +
+ '
';
+ $compile(template)($rootScope);
+ expect($rootScope.myComponent).toBe(undefined);
+ });
+
+ it('should nullify the variable once the component is destroyed', function() {
+ var template = '
';
+
+ var element = $compile(template)($rootScope);
+ expect($rootScope.myComponent).toBe(myComponentController);
+
+ var componentElement = element.children();
+ var isolateScope = componentElement.isolateScope();
+ componentElement.remove();
+ isolateScope.$destroy();
+ expect($rootScope.myComponent).toBe(null);
+ });
+
+ it('should be compatible with entering/leaving components', inject(function($animate) {
+ var template = '';
+ $rootScope.$ctrl = {};
+ var parent = $compile('')($rootScope);
+
+ var leaving = $compile(template)($rootScope);
+ var leavingController = myComponentController;
+
+ $animate.enter(leaving, parent);
+ expect($rootScope.myComponent).toBe(leavingController);
+
+ var entering = $compile(template)($rootScope);
+ var enteringController = myComponentController;
+
+ $animate.enter(entering, parent);
+ $animate.leave(leaving, parent);
+ expect($rootScope.myComponent).toBe(enteringController);
+ }));
+
+ it('should allow bind to a parent controller', function() {
+ $rootScope.$ctrl = {};
+
+ $compile('')($rootScope);
+ expect($rootScope.$ctrl.myComponent).toBe(myComponentController);
+ });
+
+ });
+
+ it('should bind the dom element if no component', inject(function($compile, $rootScope) {
+ $compile('my text')($rootScope);
+ expect($rootScope.mySpan.textContent).toBe('my text');
+ }));
+
+ it('should nullify the dom element value if it is destroyed', inject(function($compile, $rootScope) {
+ var element = $compile('