Предлагаю перевод публикации «AngularJs $parse hacks».
В недрах AngularJs прячется одна маленькая и замечательная функция: $parse. Обычно она используется внутри фрэймворка для интерполяции значений, например при двусторонней провязке данных (two way data binding):
<p>Hello, {{ user.name }}</p>
// where user is an object in the scope
Наглядно этот простой пример можно посмотреть здесь.
Чтобы вычислить значение выражения user.name AngularJs вызовет $parse, после чего поместит результат в DOM.
$parse преобразовывает выражения, а не просто достает свойства объектов, например:
<p>{{ 'Hello ' + user.name }}</p>
Пример можно посмотреть здесь.
Вы можете использовать $parse в своем коде путем добавления зависимости в контроллер (или любую другую функцию). Вызов $parse подразумевает два шага: компиляция выражения в template функцию и последующий вызов этой самой функции с контекстом и локальными переменными. Обычно в роли контекста и локальных переменных выступает $scope объект:
function controller($scope, $parse) {
$scope.user = {
name: 'Joe'
};
var template = $parse('Hello + user.name');
var msg = template($scope); // Hello Joe
}
Такое двухшаговое выполнение наблюдается и в других template библиотеках, таких как Handlebars.
В добавок $parse многое прощает, например, если $scope.user объект не существует, выражение нормально преобразуется, но возвращает undefined, которое отображается как пустая строка, пример здесь. Такое поведение функции $parse вылилось в следующие хаки:
Hack 1: безопасный доступ вложенных свойст
Если у нас есть объект со значением свойства, которое может быть null, доступ к вложенным свойствам этого свойства принуждает к всякого рода проверкам:
var foo = {
bar: {
baz: {
name: 'baz'
}
}
};
var name;
if (typeof foo === 'object' &&
typeof foo.bar === 'object' &&
typeof foo.bar.baz === 'object') {
name = foo.bar.baz.name;
}
Конечно можно использовать стороннюю библиотеку вроде l33teral и обвертывать объекты для безопасного доступа:
var leet = require('l33teral');
var fooL = leet(foo);
var name = fooL.tap('bar.baz.name');
Но если вы уже используете AngularJs, то просто используйте $parse:
var name = $parse('bar.baz.name')(foo);
Полный пример. Если свойство не существует, вызов вернет undefined:
$parse('bar.baz2.name')(foo); // returns undefined
Так же можно назначить первый шаг функцию в переменную, во избежание повторного компилирования выражения.
var getName = $parse('bar.baz.name');
...
getName(foo);
Hack 2: отправка логики с бакэнда клиенту
Если требуется динамически что-то запускать (вычислять) на клиенте, можно отправлять логику с сервера в виде строки. В строке помимо методов можно определять и локальные переменные, для этого $parse вызывается с 2-мя аргументами (контекст и локальные переменные):
var ops = {
add: function (a, b) { return a + b; },
mul: function (a, b) { return a * b; }
};
var logic = 'mul(add(a, 1), b)';
var data = {
a: 10,
b: 4
};
var result = $parse(logic)(ops, data); // 44
Полный пример.
Аргумент data может переопределять свойства в контексте аргумента ops, но я рекомендую держать методы отдельно от данных для более понятной реализации.
Hack 3: Spreadsheet за 20 минут
Для демонстрации мощи AngularJs во всей красе я люблю ссылаться на David Graunke's spreadsheet example. Это супер потрясающий, пример который использует $parse для динамического преобразования выражений внутри каждой ячейки. Выражения могут ссылаться на значения других ячеек, которые в свою очередь могут ссылаться на другие ячейки и т.д. Основная логика этого примера заключается в том, что все ячейки находятся в scope, а функция coumpute вызывается каждый раз, когда значение любой ячейки меняется.
function sheetController($scope, $parse) {
$scope.columns = ['A', 'B', 'C', 'D'];
$scope.rows = [1, 2, 3, 4];
$scope.cells = {}; // will be filled with row x column objects
$scope.compute = function(cell) {
return $parse($scope.cells[cell])($scope);
};
}
Spreadsheet в действии.
Автор: VBauer