Skip to content

Commit 5671a2d

Browse files
committed
feat(ngBindOnceDirs.js): implementation of simple bindOnce directives angular#5408
Signed-off-by: Josh Kurz <[email protected]>
1 parent cceb455 commit 5671a2d

File tree

4 files changed

+135
-1
lines changed

4 files changed

+135
-1
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ angularFiles = {
4646
'src/ng/directive/form.js',
4747
'src/ng/directive/input.js',
4848
'src/ng/directive/ngBind.js',
49+
'src/ng/directive/ngBindOnceDirs.js',
4950
'src/ng/directive/ngClass.js',
5051
'src/ng/directive/ngCloak.js',
5152
'src/ng/directive/ngController.js',

src/AngularPublic.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
ngValueDirective,
4949
ngAttributeAliasDirectives,
5050
ngEventDirectives,
51+
ngBindOnceDirectives,
5152
5253
$AnchorScrollProvider,
5354
$AnimateProvider,
@@ -186,7 +187,8 @@ function publishExternalAPI(angular){
186187
ngInclude: ngIncludeFillContentDirective
187188
}).
188189
directive(ngAttributeAliasDirectives).
189-
directive(ngEventDirectives);
190+
directive(ngEventDirectives).
191+
directive(ngBindOnceDirectives);
190192
$provide.provider({
191193
$anchorScroll: $AnchorScrollProvider,
192194
$animate: $AnimateProvider,

src/ng/directive/ngBindOnceDirs.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc directive
5+
* @name ng.directive:ngBindOnce
6+
* @restrict AC
7+
*
8+
* @description
9+
* The `ngBindOnce` attribute tells Angular to only set up an initial binding. After the specified value has been
10+
* set, then the watcher is destroyed.
11+
*
12+
* ngBindOnce works with promise values or any other asynchrounous type of values that are not not set on the initial digest.
13+
*
14+
* @element ANY
15+
* @param {expression} ngBindOnce {@link guide/expression Expression} to evaluate and set once.
16+
*
17+
* @example
18+
* Notice how the text value is not changed when the scope value is changed.
19+
<doc:example>
20+
<doc:source>
21+
<script>
22+
function Ctrl($scope) {
23+
$scope.name = 'Free Bird';
24+
}
25+
</script>
26+
<div ng-controller="Ctrl">
27+
Enter name: <input type="text" ng-model="name"><br>
28+
Hello <span id="bindOnceTest" ng-bind-once-text="name"></span>!
29+
</div>
30+
</doc:source>
31+
<doc:protractor>
32+
it('should check ng-bind-once-text', function() {
33+
var exampleContainer = $('.doc-example-live');
34+
var nameInput = element(by.model('name'));
35+
36+
expect(exampleContainer.findElement(by.id('bindOnceTest')).getText()).toBe('Free Bird);
37+
nameInput.clear();
38+
nameInput.sendKeys('world');
39+
expect(exampleContainer.findElement(by.id('bindOnceTest')).getText()).toBe('Free Bird');
40+
});
41+
</doc:protractor>
42+
</doc:example>
43+
*/
44+
var ngBindOnceDirectives = {};
45+
forEach([{name: 'src', method: 'attr'}, {name: 'text', method: 'text'},
46+
{name: 'href', method: 'attr'}, {name: 'class', method: 'addClass'},
47+
{name: 'html', method: 'html'}, {name: 'alt', method: 'attr'},
48+
{name: 'style', method: 'css'}, {name: 'value', method: 'attr'},
49+
{name: 'id', method: 'attr'}, {name: 'title', method: 'attr'}],
50+
function(v) {
51+
var directiveName = directiveNormalize('ng-bind-once-' + v.name);
52+
ngBindOnceDirectives[directiveName] = function() {
53+
return {
54+
link: function(scope, element, attr) {
55+
var rmWatcher = scope.$watch(attr[directiveName], function(newV,oldV){
56+
if(newV){
57+
if(v.method === 'attr'){
58+
element[v.method](v.name,newV);
59+
} else {
60+
element[v.method](newV);
61+
}
62+
rmWatcher();
63+
}
64+
});
65+
}
66+
};
67+
};
68+
}
69+
);
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'use strict';
2+
3+
describe('bindOnce directives', function() {
4+
var scope, $compile;
5+
6+
beforeEach(inject(function($injector) {
7+
$compile = $injector.get('$compile');
8+
scope = $injector.get('$rootScope');
9+
}));
10+
11+
12+
describe('Creating ngBindOnce* directive', function() {
13+
14+
angular.forEach(['text','src','href','id','class','alt','value','title'], function(v){
15+
16+
var bindOnceNode;
17+
18+
beforeEach(function(){
19+
scope.testValue = 'tester+tester+tester';
20+
bindOnceNode = $compile('<div ng-bind-once-' + v + '="testValue"></div>')(scope);
21+
scope.$apply();
22+
expect(bindOnceNode).not.toBe(undefined);
23+
});
24+
25+
iit('should have the correct values for the bindOnce directive', function() {
26+
if(v === 'text'){
27+
expect(bindOnceNode[v]()).toBe('tester+tester+tester');
28+
} else if(v === 'class'){
29+
expect(bindOnceNode.hasClass('tester+tester+tester')).toBe(true);
30+
} else {
31+
expect(bindOnceNode.attr(v)).toBe('tester+tester+tester');
32+
}
33+
});
34+
35+
iit('should not have any watchers on the scope', function() {
36+
expect(scope.$$watchers.length).toBe(0);
37+
});
38+
});
39+
40+
});
41+
42+
describe('Creating ngBindOnceHtml and ngBindOnceStyle directives', function() {
43+
44+
iit('should set the correct html to the element and destroy the watchers', function() {
45+
scope.testValue = '<p>No Bindings</p>';
46+
var bindOnceHtmlNode = $compile('<div ng-bind-once-html="testValue"></div>')(scope);
47+
scope.$apply();
48+
expect(bindOnceHtmlNode.html()).toBe('<p>No Bindings</p>');
49+
expect(scope.$$watchers.length).toBe(0);
50+
});
51+
52+
iit('should set the correct style to the element and destroy the watchers', function() {
53+
scope.testValue = {width: '100px', height: '200px'};
54+
var bindOnceStyleNode = $compile('<div ng-bind-once-style="testValue"></div>')(scope);
55+
scope.$apply();
56+
expect(bindOnceStyleNode.css('height')).toBe('200px');
57+
expect(bindOnceStyleNode.css('width')).toBe('100px');
58+
expect(scope.$$watchers.length).toBe(0);
59+
});
60+
61+
});
62+
});

0 commit comments

Comments
 (0)