feat(ngRef): add directive to publish controller, or element into scope
Thanks to @drpicox for the original implementation: PR #14080 Closes #16511
This commit is contained in:
Vendored
+1
@@ -74,6 +74,7 @@ var angularFiles = {
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngOptions.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRef.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
'src/ng/directive/ngStyle.js',
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name ngRef:noctrl
|
||||
@fullName A controller for the value of `ngRefRead` could not be found on the element.
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ng.ngRef ngRef directive} specifies
|
||||
a value in `ngRefRead` that cannot be resolved to a directive / component controller.
|
||||
|
||||
Causes for this error can be:
|
||||
|
||||
1. Your `ngRefRead` value has a typo.
|
||||
2. You have a typo in the *registered* directive / component name.
|
||||
3. The directive / component does not have a controller.
|
||||
|
||||
Note that `ngRefRead` takes the name of the component / directive, not the name of controller, and
|
||||
also not the combination of directive and 'Controller'. For example, for a directive called 'myDirective',
|
||||
the correct declaration is `<div ng-ref="$ctrl.ref" ng-ref-read="myDirective">`.
|
||||
@@ -0,0 +1,27 @@
|
||||
@ngdoc error
|
||||
@name ngRef:nonassign
|
||||
@fullName Non-Assignable Expression
|
||||
@description
|
||||
|
||||
This error occurs when ngRef defines an expression that is not-assignable.
|
||||
|
||||
In order for ngRef to work, it must be possible to write the reference into the path defined with the expression.
|
||||
|
||||
For example, the following expressions are non-assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="{}"></my-directive>
|
||||
|
||||
<my-directive ng-ref="myFn()"></my-directive>
|
||||
|
||||
<!-- missing attribute value is also invalid -->
|
||||
<my-directive ng-ref></my-directive>
|
||||
|
||||
```
|
||||
|
||||
To resolve this error, use a path expression that is assignable:
|
||||
|
||||
```
|
||||
<my-directive ng-ref="$ctrl.reference"></my-directive>
|
||||
|
||||
```
|
||||
@@ -28,6 +28,7 @@
|
||||
ngInitDirective,
|
||||
ngNonBindableDirective,
|
||||
ngPluralizeDirective,
|
||||
ngRefDirective,
|
||||
ngRepeatDirective,
|
||||
ngShowDirective,
|
||||
ngStyleDirective,
|
||||
@@ -194,6 +195,7 @@ function publishExternalAPI(angular) {
|
||||
ngInit: ngInitDirective,
|
||||
ngNonBindable: ngNonBindableDirective,
|
||||
ngPluralize: ngPluralizeDirective,
|
||||
ngRef: ngRefDirective,
|
||||
ngRepeat: ngRepeatDirective,
|
||||
ngShow: ngShowDirective,
|
||||
ngStyle: ngStyleDirective,
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngRef
|
||||
* @restrict A
|
||||
*
|
||||
* @description
|
||||
* The `ngRef` attribute tells AngularJS to assign the controller of a component (or a directive)
|
||||
* to the given property in the current scope. It is also possible to add the jqlite-wrapped DOM
|
||||
* element to the scope.
|
||||
*
|
||||
* If the element with `ngRef` is destroyed `null` is assigned to the property.
|
||||
*
|
||||
* Note that if you want to assign from a child into the parent scope, you must initialize the
|
||||
* target property on the parent scope, otherwise `ngRef` will assign on the child scope.
|
||||
* This commonly happens when assigning elements or components wrapped in {@link ngIf} or
|
||||
* {@link ngRepeat}. See the second example below.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} ngRef property name - A valid AngularJS expression identifier to which the
|
||||
* controller or jqlite-wrapped DOM element will be bound.
|
||||
* @param {string=} ngRefRead read value - The name of a directive (or component) on this element,
|
||||
* or the special string `$element`. If a name is provided, `ngRef` will
|
||||
* assign the matching controller. If `$element` is provided, the element
|
||||
* itself is assigned (even if a controller is available).
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ### Simple toggle
|
||||
* This example shows how the controller of the component toggle
|
||||
* is reused in the template through the scope to use its logic.
|
||||
* <example name="ng-ref-component" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-toggle ng-ref="myToggle"></my-toggle>
|
||||
* <button ng-click="myToggle.toggle()">Toggle</button>
|
||||
* <div ng-show="myToggle.isOpen()">
|
||||
* You are using a component in the same template to show it.
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myToggle', {
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* });
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should publish the toggle into the scope', function() {
|
||||
* var toggle = element(by.buttonText('Toggle'));
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(false);
|
||||
* toggle.click();
|
||||
* expect(toggle.evaluate('myToggle.isOpen()')).toEqual(true);
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### ngRef inside scopes
|
||||
* This example shows how `ngRef` works with child scopes. The `ngRepeat`-ed `myWrapper` components
|
||||
* are assigned to the scope of `myRoot`, because the `toggles` property has been initialized.
|
||||
* The repeated `myToggle` components are published to the child scopes created by `ngRepeat`.
|
||||
* `ngIf` behaves similarly - the assignment of `myToggle` happens in the `ngIf` child scope,
|
||||
* because the target property has not been initialized on the `myRoot` component controller.
|
||||
*
|
||||
* <example name="ng-ref-scopes" module="myApp">
|
||||
* <file name="index.html">
|
||||
* <my-root></my-root>
|
||||
* </file>
|
||||
* <file name="index.js">
|
||||
* angular.module('myApp', [])
|
||||
* .component('myRoot', {
|
||||
* templateUrl: 'root.html',
|
||||
* controller: function() {
|
||||
* this.wrappers = []; // initialize the array so that the wrappers are assigned into the parent scope
|
||||
* }
|
||||
* })
|
||||
* .component('myToggle', {
|
||||
* template: '<strong>myToggle</strong><button ng-click="$ctrl.toggle()" ng-transclude></button>',
|
||||
* transclude: true,
|
||||
* controller: function ToggleController() {
|
||||
* var opened = false;
|
||||
* this.isOpen = function() { return opened; };
|
||||
* this.toggle = function() { opened = !opened; };
|
||||
* }
|
||||
* })
|
||||
* .component('myWrapper', {
|
||||
* transclude: true,
|
||||
* template: '<strong>myWrapper</strong>' +
|
||||
* '<div>ngRepeatToggle.isOpen(): {{$ctrl.ngRepeatToggle.isOpen() | json}}</div>' +
|
||||
* '<my-toggle ng-ref="$ctrl.ngRepeatToggle"><ng-transclude></ng-transclude></my-toggle>'
|
||||
* });
|
||||
* </file>
|
||||
* <file name="root.html">
|
||||
* <strong>myRoot</strong>
|
||||
* <my-toggle ng-ref="$ctrl.outerToggle">Outer Toggle</my-toggle>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <div><em>wrappers assigned to root</em><br>
|
||||
* <div ng-repeat="wrapper in $ctrl.wrappers">
|
||||
* wrapper.ngRepeatToggle.isOpen(): {{wrapper.ngRepeatToggle.isOpen() | json}}
|
||||
* </div>
|
||||
*
|
||||
* <ul>
|
||||
* <li ng-repeat="(index, value) in [1,2,3]">
|
||||
* <strong>ngRepeat</strong>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* <my-wrapper ng-ref="$ctrl.wrappers[index]">ngRepeat Toggle {{$index + 1}}</my-wrapper>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen()}} // This is always undefined because it's
|
||||
* assigned to the child scope created by ngIf.
|
||||
* </div>
|
||||
* <div ng-if="true">
|
||||
<strong>ngIf</strong>
|
||||
* <my-toggle ng-ref="ngIfToggle">ngIf Toggle</my-toggle>
|
||||
* <div>ngIfToggle.isOpen(): {{ngIfToggle.isOpen() | json}}</div>
|
||||
* <div>outerToggle.isOpen(): {{$ctrl.outerToggle.isOpen() | json}}</div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="styles.css">
|
||||
* ul {
|
||||
* list-style: none;
|
||||
* padding-left: 0;
|
||||
* }
|
||||
*
|
||||
* li[ng-repeat] {
|
||||
* background: lightgreen;
|
||||
* padding: 8px;
|
||||
* margin: 8px;
|
||||
* }
|
||||
*
|
||||
* [ng-if] {
|
||||
* background: lightgrey;
|
||||
* padding: 8px;
|
||||
* }
|
||||
*
|
||||
* my-root {
|
||||
* background: lightgoldenrodyellow;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-wrapper {
|
||||
* background: lightsalmon;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
*
|
||||
* my-toggle {
|
||||
* background: lightblue;
|
||||
* padding: 8px;
|
||||
* display: block;
|
||||
* }
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* 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('(index, value) 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');
|
||||
* });
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
*/
|
||||
|
||||
var ngRefMinErr = minErr('ngRef');
|
||||
|
||||
var ngRefDirective = ['$parse', function($parse) {
|
||||
return {
|
||||
priority: -1, // Needed for compatibility with element transclusion on the same element
|
||||
restrict: 'A',
|
||||
compile: function(tElement, tAttrs) {
|
||||
// Get the expected controller name, converts <data-some-thing> into "someThing"
|
||||
var controllerName = directiveNormalize(nodeName_(tElement));
|
||||
|
||||
// Get the expression for value binding
|
||||
var getter = $parse(tAttrs.ngRef);
|
||||
var setter = getter.assign || function() {
|
||||
throw ngRefMinErr('nonassign', 'Expression in ngRef="{0}" is non-assignable!', tAttrs.ngRef);
|
||||
};
|
||||
|
||||
return function(scope, element, attrs) {
|
||||
var refValue;
|
||||
|
||||
if (attrs.hasOwnProperty('ngRefRead')) {
|
||||
if (attrs.ngRefRead === '$element') {
|
||||
refValue = element;
|
||||
} else {
|
||||
refValue = element.data('$' + attrs.ngRefRead + 'Controller');
|
||||
|
||||
if (!refValue) {
|
||||
throw ngRefMinErr(
|
||||
'noctrl',
|
||||
'The controller for ngRefRead="{0}" could not be found on ngRef="{1}"',
|
||||
attrs.ngRefRead,
|
||||
tAttrs.ngRef
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refValue = element.data('$' + controllerName + 'Controller');
|
||||
}
|
||||
|
||||
refValue = refValue || element;
|
||||
|
||||
setter(scope, refValue);
|
||||
|
||||
// when the element is removed, remove it (nullify it)
|
||||
element.on('$destroy', function() {
|
||||
// only remove it if value has not changed,
|
||||
// because animations (and other procedures) may duplicate elements
|
||||
if (getter(scope) === refValue) {
|
||||
setter(scope, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -313,6 +313,7 @@ beforeEach(function() {
|
||||
|
||||
function generateCompare(isNot) {
|
||||
return function(actual, namespace, code, content) {
|
||||
|
||||
var matcher = new MinErrMatcher(isNot, namespace, code, content, {
|
||||
inputType: 'error',
|
||||
expectedAction: 'equal',
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngRef', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
toEqualJq: function(util) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
// Jquery <= 2.2 objects add a context property that is irrelevant for equality
|
||||
if (actual && actual.hasOwnProperty('context')) {
|
||||
delete actual.context;
|
||||
}
|
||||
|
||||
if (expected && expected.hasOwnProperty('context')) {
|
||||
delete expected.context;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: util.equals(actual, expected)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('on a component', function() {
|
||||
|
||||
var myComponentController, attributeDirectiveController, $rootScope, $compile;
|
||||
|
||||
beforeEach(module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'foo',
|
||||
controller: function() {
|
||||
myComponentController = this;
|
||||
}
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {
|
||||
attributeDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
$compile = _$compile_;
|
||||
}));
|
||||
|
||||
it('should bind in the current scope the controller of a component', function() {
|
||||
$rootScope.$ctrl = 'undamaged';
|
||||
|
||||
$compile('<my-component ng-ref="myComponentRef"></my-component>')($rootScope);
|
||||
expect($rootScope.$ctrl).toBe('undamaged');
|
||||
expect($rootScope.myComponentRef).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should throw if the expression is not assignable', function() {
|
||||
expect(function() {
|
||||
$compile('<my-component ng-ref="\'hello\'"></my-component>')($rootScope);
|
||||
}).toThrowMinErr('ngRef', 'nonassign', 'Expression in ngRef="\'hello\'" is non-assignable!');
|
||||
});
|
||||
|
||||
it('should work with non:normalized entity name', function() {
|
||||
$compile('<my:component ng-ref="myComponent1"></my:component>')($rootScope);
|
||||
expect($rootScope.myComponent1).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with data-non-normalized entity name', function() {
|
||||
$compile('<data-my-component ng-ref="myComponent2"></data-my-component>')($rootScope);
|
||||
expect($rootScope.myComponent2).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with x-non-normalized entity name', function() {
|
||||
$compile('<x-my-component ng-ref="myComponent3"></x-my-component>')($rootScope);
|
||||
expect($rootScope.myComponent3).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with data-non-normalized attribute name', function() {
|
||||
$compile('<my-component data-ng-ref="myComponent1"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponent1).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should work with x-non-normalized attribute name', function() {
|
||||
$compile('<my-component x-ng-ref="myComponent2"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponent2).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should not bind the controller of an attribute directive', function() {
|
||||
$compile('<my-component attribute-directive-1 ng-ref="myComponentRef"></my-component>')($rootScope);
|
||||
expect($rootScope.myComponentRef).toBe(myComponentController);
|
||||
});
|
||||
|
||||
it('should not leak to parent scopes', function() {
|
||||
var template =
|
||||
'<div ng-if="true">' +
|
||||
'<my-component ng-ref="myComponent"></my-component>' +
|
||||
'</div>';
|
||||
$compile(template)($rootScope);
|
||||
expect($rootScope.myComponent).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should nullify the variable once the component is destroyed', function() {
|
||||
var template = '<div><my-component ng-ref="myComponent"></my-component></div>';
|
||||
|
||||
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 = '<my-component ng-ref="myComponent"></my-component>';
|
||||
$rootScope.$ctrl = {};
|
||||
var parent = $compile('<div></div>')($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 binding to a nested property', function() {
|
||||
$rootScope.obj = {};
|
||||
|
||||
$compile('<my-component ng-ref="obj.myComponent"></my-component>')($rootScope);
|
||||
expect($rootScope.obj.myComponent).toBe(myComponentController);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should bind the jqlite wrapped DOM element if there is no component', inject(function($compile, $rootScope) {
|
||||
|
||||
var el = $compile('<span ng-ref="mySpan">my text</span>')($rootScope);
|
||||
|
||||
expect($rootScope.mySpan).toEqualJq(el);
|
||||
expect($rootScope.mySpan[0].textContent).toBe('my text');
|
||||
}));
|
||||
|
||||
it('should nullify the expression value if the DOM element is destroyed', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div><span ng-ref="mySpan">my text</span></div>')($rootScope);
|
||||
element.children().remove();
|
||||
expect($rootScope.mySpan).toBe(null);
|
||||
}));
|
||||
|
||||
it('should bind the controller of an element directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
$compile('<my-directive ng-ref="myDirective"></my-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myDirective).toBe(myDirectiveController);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngRefRead', function() {
|
||||
|
||||
it('should bind the element instead of the controller of a component if ngRefRead="$element" is set', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
var el = $compile('<my-component ng-ref="myEl" ng-ref-read="$element"></my-component>')($rootScope);
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind the element instead an element-directive controller if ngRefRead="$element" is set', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<my-directive ng-ref="myEl" ng-ref-read="$element"></my-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind an attribute-directive controller if ngRefRead="controllerName" is set', function() {
|
||||
var attrDirective1Controller;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('elementDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective1', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {
|
||||
attrDirective1Controller = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$compileProvider.directive('attributeDirective2', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<element-directive' +
|
||||
'attribute-directive-1' +
|
||||
'attribute-directive-2' +
|
||||
'ng-ref="myController"' +
|
||||
'ng-ref-read="$element"></element-directive>')($rootScope);
|
||||
|
||||
expect($rootScope.myController).toBe(attrDirective1Controller);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw if no controller is found for the ngRefRead value', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('elementDirective', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: 'my text',
|
||||
controller: function() {}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
|
||||
expect(function() {
|
||||
$compile('<element-directive ' +
|
||||
'ng-ref="myController"' +
|
||||
'ng-ref-read="attribute"></element-directive>')($rootScope);
|
||||
}).toThrowMinErr('ngRef', 'noctrl', 'The controller for ngRefRead="attribute" could not be found on ngRef="myController"');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
it('should bind the jqlite element if the controller is on an attribute-directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: 'my text',
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<div my-directive ng-ref="myEl"></div>')($rootScope);
|
||||
|
||||
expect(myDirectiveController).toBeDefined();
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should bind the jqlite element if the controller is on an class-directive', function() {
|
||||
var myDirectiveController;
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('myDirective', function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
template: 'my text',
|
||||
controller: function() {
|
||||
myDirectiveController = this;
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var el = $compile('<div class="my-directive" ng-ref="myEl"></div>')($rootScope);
|
||||
|
||||
expect(myDirectiveController).toBeDefined();
|
||||
expect($rootScope.myEl).toEqualJq(el);
|
||||
expect($rootScope.myEl[0].textContent).toBe('my text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('transclusion', function() {
|
||||
|
||||
it('should work with simple transclusion', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myComponent', {
|
||||
transclude: true,
|
||||
template: '<ng-transclude></ng-transclude>',
|
||||
controller: function() {
|
||||
this.text = 'SUCCESS';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template = '<my-component ng-ref="myComponent">{{myComponent.text}}</my-component>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude components', function() {
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myComponent', {
|
||||
transclude: 'element',
|
||||
controller: function($animate, $element, $transclude) {
|
||||
this.text = 'SUCCESS';
|
||||
this.$postLink = function() {
|
||||
$transclude(function(clone, newScope) {
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-component ng-ref="myComponent">' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with ngIf and transclusion on same element', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: '<ng-transclude></ng-transclude>',
|
||||
transclude: true,
|
||||
controller: function($scope) {
|
||||
this.text = 'SUCCESS';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-component ng-if="present" ng-ref="myComponent" >' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
|
||||
$rootScope.$apply('present = false');
|
||||
expect(element.text()).toBe('');
|
||||
$rootScope.$apply('present = true');
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
$rootScope.$apply('present = false');
|
||||
expect(element.text()).toBe('');
|
||||
$rootScope.$apply('present = true');
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude & destroy components', function() {
|
||||
var myComponentController;
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.component('myTranscludingComponent', {
|
||||
transclude: 'element',
|
||||
controller: function($animate, $element, $transclude) {
|
||||
myComponentController = this;
|
||||
|
||||
var currentClone, currentScope;
|
||||
this.transclude = function(text) {
|
||||
this.text = text;
|
||||
$transclude(function(clone, newScope) {
|
||||
currentClone = clone;
|
||||
currentScope = newScope;
|
||||
$animate.enter(clone, $element.parent(), $element);
|
||||
});
|
||||
};
|
||||
this.destroy = function() {
|
||||
currentClone.remove();
|
||||
currentScope.$destroy();
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-transcluding-component ng-ref="myComponent">' +
|
||||
'{{myComponent.text}}' +
|
||||
'</my-transcluding-component>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
|
||||
myComponentController.transclude('transcludedOk');
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('transcludedOk');
|
||||
|
||||
myComponentController.destroy();
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be compatible with element transclude directives', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider
|
||||
.directive('myDirective', function($animate) {
|
||||
return {
|
||||
transclude: 'element',
|
||||
controller: function() {
|
||||
this.text = 'SUCCESS';
|
||||
},
|
||||
link: function(scope, element, attrs, ctrl, $transclude) {
|
||||
$transclude(function(clone, newScope) {
|
||||
$animate.enter(clone, element.parent(), element);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
var template =
|
||||
'<div>' +
|
||||
'<my-directive ng-ref="myDirective">' +
|
||||
'{{myDirective.text}}' +
|
||||
'</my-directive>' +
|
||||
'</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.text()).toBe('SUCCESS');
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should work with components with templates via $http', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('httpComponent', {
|
||||
templateUrl: 'template.html',
|
||||
controller: function() {
|
||||
this.me = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $httpBackend, $rootScope) {
|
||||
var template = '<div><http-component ng-ref="controller"></http-component></div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$httpBackend.expect('GET', 'template.html').respond('ok');
|
||||
$rootScope.$apply();
|
||||
expect($rootScope.controller).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
expect($rootScope.controller.me).toBe(true);
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should work with ngRepeat-ed components', function() {
|
||||
var controllers = [];
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component('myComponent', {
|
||||
template: 'foo',
|
||||
controller: function() {
|
||||
controllers.push(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
inject(function($compile, $rootScope) {
|
||||
$rootScope.elements = [0,1,2,3,4];
|
||||
$rootScope.controllers = []; // Initialize the array because ngRepeat creates a child scope
|
||||
|
||||
var template = '<div><my-component ng-repeat="(key, el) in elements" ng-ref="controllers[key]"></my-component></div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$apply();
|
||||
|
||||
expect($rootScope.controllers).toEqual(controllers);
|
||||
|
||||
$rootScope.$apply('elements = []');
|
||||
|
||||
expect($rootScope.controllers).toEqual([null, null, null, null, null]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user