angular's nicest part extracted as a standalone module for the browser and node.
angular-expressions exposes a .compile()-method which can be used to compile evaluable expressions:
var expressions = require("angular-expressions");
evaluate = expressions.compile("1 + 1");
evaluate(); // returns 2You can also set and get values on a given scope:
evaluate = expressions.compile("name");
scope = { name: "Jenny" };
evaluate(scope); // returns 'Jenny'
evaluate = expressions.compile("ship.pirate.name = 'Störtebeker'");
evaluate(scope); // won't throw an error because angular's expressions are forgiving
console.log(scope.ship.pirate.name); // prints 'Störtebeker'For assigning values, you can also use .assign():
evaluate = expressions.compile("ship.pirate.name");
evaluate.assign(scope, "Störtebeker");
console.log(scope.ship.pirate.name); // prints 'Störtebeker'Check out their readme for further information.
Angular provides a mechanism to define filters on expressions:
expressions.filters.uppercase = (input) => input.toUpperCase();
expr = expressions.compile("'arr' | uppercase");
expr(); // returns 'ARR'Arguments are evaluated against the scope:
expressions.filters.currency = (input, currency, digits) => {
input = input.toFixed(digits);
if (currency === "EUR") {
return input + "€";
} else {
return input + "$";
}
};
expr = expressions.compile("1.2345 | currency:selectedCurrency:2");
expr({
selectedCurrency: "EUR",
}); // returns '1.23€'If you need an isolated filters object, this can be achieved by setting the filters attribute in the options argument.
var isolatedFilters = {
transform: (input) => input.toLowerCase(),
};
var resultOne = expressions.compile("'Foo Bar' | transform", {
filters: isolatedFilters,
});
console.log(resultOne()); // prints 'foo bar'Compiles src and returns a function evaluate(). The compiled function is cached under compile.cache which is stored as an LRUCache which prevents the cache to exceed a max size (256 cache items by default) to speed up further calls.
When options contain values that cannot be identified safely (function, symbol, or object literals, or custom identifier callbacks), that compilation is not stored in the global cache.
Returns a compile function with options bound once. Tags are passed later. Each bound compiler has its own LRU, keyed only by source, so function literals can be cached safely for that instance:
var myCompiler = expressions.compile.withOptions({
literals: { auditFn: () => "ok" },
cacheSize: 256,
});
myCompiler("auditFn()")(); // 'ok'
myCompiler("1 + 2")(); // 3Options are snapshotted. Later mutation of the original options object is ignored. Two bound compilers do not share compiled state.
Compiles also export the AST.
Example output of: compile("tmp + 1").ast
{ type: 'Program',
body:
[ { type: 'ExpressionStatement',
expression:
{ type: 'Identifier',
name: 'tmp',
constant: false,
toWatch: [ [Circular] ] } } ],
constant: false }
NOTE angular $parse do not export ast variable it's done by this library.
An empty object where you may define your custom filters.
The internal Lexer.
The internal Parser.
Evaluates the compiled src and returns the result of the expression. Property look-ups or assignments are executed on a given scope.
Tries to assign the given value to the result of the compiled expression on the given scope and returns the result of the assignment.
There is no dist build because it's not 2005 anymore. Use a module bundler like webpack or browserify. They're both capable of CommonJS and AMD.
Important: Checks that reject certain well-known dangerous values (such as globalThis, eval, Function, or setTimeout) are not a security boundary. They only cover a small, fixed set of built-ins and exist to help you notice incorrect usage of the library (for example, putting the global object or eval into the evaluation scope or locals). Do not treat them as sandboxing or as protection against untrusted expressions or untrusted scope data. Safe use still depends on what you pass into compile() and into the evaluated scope, locals, and filters.
If you are running in Node.JS, we recommend you to use the following flag in your node command so that access to __proto__ is not possible.
This puts an additional security layer which makes vulnerabilities much less likely to affect you.
The flag can be set like this :
node --disable-proto=delete app.jsIf you're using some libraries that still rely on Prototype, this could break your application.
Here is the revised text rewritten specifically for developers using the angular-expressions library in their projects.
angular-expressions strictly blocks the lookup of properties inherited from the prototype chain to prevent Remote Code Execution (RCE) vulnerabilities.
When designing your application templates and evaluation contexts, you must guide your end-users toward safe alternatives rather than attempting to circumvent this protection.
Your users cannot directly invoke inherited methods in their expressions (e.g., "test".toUpperCase() or [3, 1, 2].toSorted()). Instead, expose user-defined filter functions in your configuration:
- Blocked:
"test".toUpperCase()-> Allowed Alternative:"test" | upper - Blocked:
[3, 1, 2].toSorted()-> Allowed Alternative:[3, 1, 2] | sort
When you provide custom filters to the expression evaluator, ensure their return values are secure:
- Primitives: Always safe to return.
- Objects: Should be plain data objects (no inheritance, methods, getters, or setters).
- State Mutation: If a returned object must include methods or setters, verify they cannot mutate any state outside the evaluation context.
🛑 CRITICAL SECURITY RISK: Never write filters that expose constructors or prototypes.
For example, registering filters like prototype: (x) => Object.getPrototypeOf(x) or constructor: (x) => x.constructor allows malicious users to access Object.prototype via ({}) | prototype or Function.prototype via [] | constructor | prototype. This introduces severe RCE vulnerabilities.
If your expressions need to know the type of a variable, return a primitive string descriptor instead of the constructor itself (e.g., return Object.prototype.toString.call(x).slice(8, -1)).
If you pass raw ORM model instances into the evaluation context, field resolution will fail. This happens because ORMs typically define fields as accessor properties on the model's prototype, which angular-expressions blocks.
Before passing data to the expression evaluator, always convert your ORM models into plain-data views:
- Sequelize: Pass
modelInstance.dataValuesinstead ofmodelInstance - Mongoose: Pass
modelInstance.toObject()instead ofmodelInstance
compile() rejects expression source longer than 250 characters. Raise the limit per call with { maxExpressionLength: 750 }. This bounds parser depth and cache memory. It is not a sandbox.
You can change the maxSize of the LRUCache that is used, by running :
const expressions = require("angular-expressions");
expressions.compile.cache.setMaxSize(10000);
For a private cache with bound options (including function-valued literals), use compile.withOptions. Per-call compile(src, { cache: ... }) is not supported.
When an item is cached on the global compile() path, identifier callbacks are not part of the cache key; those compilations are not stored. Prefer withOptions if you use custom isIdentifierStart or isIdentifierContinue.
Do not put dangerous values such as eval, Function, globalThis, or filters that invoke them into the evaluation scope, locals, or filter set. The library may throw when it recognizes some of these values, but that is only a usage hint: it does not list every dangerous built-in and must not be relied on as a sandbox. Treating untrusted input as trusted scope or filter data can still lead to Remote Code Execution (RCE).
Kudos go entirely to the great angular.js team, it's their implementation!
Suggestions and bug-fixes are always appreciated. Don't hesitate to create an issue or pull-request. All contributed code should pass
- the tests in node.js by running
npm test - the tests in all major browsers by running
npm run test-browserand then visitinghttp://localhost:8080/bundle
