Пользователь
0,0
рейтинг
24 ноября 2014 в 10:22

Разработка → AngularJs $parse hacks из песочницы

Предлагаю перевод публикации «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
карма
3,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (6)

  • +6
    Вредные советы
  • +6
    3 примера кода, за который можно спокойно отрубать руки.
  • +9
    eval нового поколения.
  • 0
    Можно короче:

    $scope.$eval('a + b')
    
  • +1
    Спешу заметить, что это несколько лучше, чем «настоящий» eval. При таком подходе явно указывается область видимости и не захватывается контекст вызывающей функции. Но, всё равно, зло.

    В первом случае не обязательно брать «typeof» и сравнивать с «object» (кстати, а если функция?!). В конкретном случае достаточно
    if (foo 
    && foo.bar 
    && foo.bar.baz)
        return foo.bar.baz.name;
    

    Выглядит он не так пугающе, по нему видно, что происходит, да и работает на порядок быстрее.

    Второй сценарий — огромная зияющая дыра. Никогда! Никогда! Никогда не допускайте в клиентском коде возможность принимать код извне. Между сервером и клиентов всегда может быть «мужик посередине». Весь нужный код должен приходить исключительно по тегам <sсript>, хотя и это полностью не решает всех проблем.

    Во всякого рода «Уберштука за 20 минут» или "… 30 строк кода" eval'ы и иже с ними допускаются, но только при том условии, что этот выхлоп никогда не попадёт в серьёзный код и не будет использоваться в продакшене.
  • 0
    Хорошее дополнение к статье.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.