diff --git a/docs/content/guide/expression.ngdoc b/docs/content/guide/expression.ngdoc index b5d0d4c7a9f5..2dfd828ffc99 100644 --- a/docs/content/guide/expression.ngdoc +++ b/docs/content/guide/expression.ngdoc @@ -113,6 +113,9 @@ This restriction is intentional. It prevents accidental access to the global sta Instead use services like `$window` and `$location` in functions called from expressions. Such services provide mockable access to globals. +It is possible to access the context object using the identifier `this` and the locals object using the +identifier `$locals`. +
diff --git a/src/ng/parse.js b/src/ng/parse.js index 9bcd1161cb16..1fb199a3cf2a 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -325,6 +325,7 @@ AST.ArrayExpression = 'ArrayExpression'; AST.Property = 'Property'; AST.ObjectExpression = 'ObjectExpression'; AST.ThisExpression = 'ThisExpression'; +AST.LocalsExpression = 'LocalsExpression'; // Internal use only AST.NGValueParameter = 'NGValueParameter'; @@ -625,7 +626,8 @@ AST.prototype = { 'false': { type: AST.Literal, value: false }, 'null': { type: AST.Literal, value: null }, 'undefined': {type: AST.Literal, value: undefined }, - 'this': {type: AST.ThisExpression } + 'this': {type: AST.ThisExpression }, + '$locals': {type: AST.LocalsExpression } } }; @@ -745,6 +747,10 @@ function findConstantAndWatchExpressions(ast, $filter) { ast.constant = false; ast.toWatch = []; break; + case AST.LocalsExpression: + ast.constant = false; + ast.toWatch = []; + break; } } @@ -1114,6 +1120,10 @@ ASTCompiler.prototype = { this.assign(intoId, 's'); recursionFn('s'); break; + case AST.LocalsExpression: + this.assign(intoId, 'l'); + recursionFn('l'); + break; case AST.NGValueParameter: this.assign(intoId, 'v'); recursionFn('v'); @@ -1441,6 +1451,10 @@ ASTInterpreter.prototype = { return function(scope) { return context ? {value: scope} : scope; }; + case AST.LocalsExpression: + return function(scope, locals) { + return context ? {value: locals} : locals; + }; case AST.NGValueParameter: return function(scope, locals, assign, inputs) { return context ? {value: assign} : assign; diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 21434af89a0c..0a7507628419 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -550,8 +550,23 @@ describe('parser', function() { }); - it('should not confuse `this`, `undefined`, `true`, `false`, `null` when used as identfiers', function() { - forEach(['this', 'undefined', 'true', 'false', 'null'], function(identifier) { + it('should understand the `$locals` expression', function() { + expect(createAst('$locals')).toEqual( + { + type: 'Program', + body: [ + { + type: 'ExpressionStatement', + expression: { type: 'LocalsExpression' } + } + ] + } + ); + }); + + + it('should not confuse `this`, `$locals`, `undefined`, `true`, `false`, `null` when used as identfiers', function() { + forEach(['this', '$locals', 'undefined', 'true', 'false', 'null'], function(identifier) { expect(createAst('foo.' + identifier)).toEqual( { type: 'Program', @@ -3589,6 +3604,30 @@ describe('parser', function() { $rootScope.null = {a: 42}; expect($rootScope.$eval('this.null.a')).toBe(42); })); + + it('should allow accessing $locals', inject(function($rootScope) { + $rootScope.foo = 'foo'; + $rootScope.bar = 'bar'; + $rootScope.$locals = 'foo'; + var locals = {foo: 42}; + expect($rootScope.$eval('$locals')).toBeUndefined(); + expect($rootScope.$eval('$locals.foo')).toBeUndefined(); + expect($rootScope.$eval('this.$locals')).toBe('foo'); + expect(function() { + $rootScope.$eval('$locals = {}'); + }).toThrow(); + expect(function() { + $rootScope.$eval('$locals.bar = 23'); + }).toThrow(); + expect($rootScope.$eval('$locals', locals)).toBe(locals); + expect($rootScope.$eval('$locals.foo', locals)).toBe(42); + expect($rootScope.$eval('this.$locals', locals)).toBe('foo'); + expect(function() { + $rootScope.$eval('$locals = {}', locals); + }).toThrow(); + expect($rootScope.$eval('$locals.bar = 23', locals)).toEqual(23); + expect(locals.bar).toBe(23); + })); }); }); });