Ng-init + ng-controller: странное поведение в области контроллера

Я новичок в Angular, но действительно наслаждаюсь его подходом. У меня есть файл HTML, где я инициализирую переменную с ng-init в элементе <div>, где я также объявляю контроллер с директивой ng-controller:

<div ng-controller="myCtrl" ng-init='foo="bar"'>

Если я console.log объект $scope из контроллера script, я вижу свойство foo, указанное среди других, но когда я пытаюсь получить к нему доступ из того же script, он дает мне undefined. Я также использую Batarang, и он показывает мне модель для <div> -scope, которая также включает свойство foo.

Я знаю из второго ответа передать переменные контроллеру AngularJS, передовой практике?, чтобы решить эту проблему, переместив мою директиву ng-init во внешнюю <div>, но я хотел бы знать, что действительно происходит здесь за кулисами. Любая помощь очень ценится, спасибо заранее.

ИЗМЕНИТЬ

Порядок директив в элементе div не имеет значения. Проблема все еще существует, даже если ng-init указан до ng-controller

Ответ 1

ОК, я думаю, что понял это

различное поведение ng-init во внешних/внутренних els возникает из-за способа Angular выполняет свою фазу компиляции. компиляция состоит из разных этапов. наиболее важными в этом случае являются:

  • создание экземпляра контроллера
  • Предварительное связывание
  • ссылки
  • postlinking

которые выполняются в этом порядке на основе каждого DOM-кода (т.е. для каждого node, код контроллера, если он присутствует, выполняется перед любыми прелинками, ссылками или postlink f)

ng-init регистрирует a pre -link f на node, указанном, в котором $eval содержание директивы (в моем примере f присваивает значение foo prop). поэтому, когда выполняется код контроллера для того же node, prop еще не существует, что соответствует ответу @Aron

в фазе компиляции Angular пересекает DOM от корня вниз на основе глубины, что означает, что родительские элементы компилируются перед их дочерними элементами. поместив директиву ng-init во внешний el, контроллер дочернего элемента node наследует внешнюю область. это объясняет "внешний el" взлом

hack @Aron указывает на регистрацию наблюдателя на опоре, так что, когда prop наконец-то $eval uated в фазе prelink, обратный вызов f может найти его

Я предлагаю два других возможных хака на основе асинхронных функций JS и Angular (см. этот jsFiddle). один включает использование setTimeout JS native f, тогда как другое больше Angular 'и прибегает к $evalAsync

imho, существует ошибка в реализации Angular директивы ng-init в отношении объявленного намерения. Я взломал код Angular, чтобы поэкспериментировать с разнообразной реализацией. Это не сложно (добавлено 2 строки кода, даже до возможного удаления директивного кода директивы ng-init), и работает, когда применяется к коду в jsFiddle выше, но я не тестировал его в сложных приложениях. Для тех, кто заинтересован, вот что я делаю (refs to v 1.2.0-rc2):

  • в блоке applyDirectivesToNode f объявляю неинициализированный nodeHasInitData локальный var
  • в том же f, после локального directiveName var присваивается значение directive.name prop, я проверяю его на статическую строку "ngInit", которая является нормированным именем Angular, присваивает директиве ng-init когда он объявлен на node
  • Если тест проходит, я устанавливаю nodeHasInitData в true. ничего не делается, если тест не работает (- > nodeHasInitData остается undefined в закрытии)
  • в блоке nodeLinkFn f, перед блоком if, который проверяет наличие контроллеров в node (шаг 1 в списке выше), я добавляю тест на значение nodeHasInitData (Я могу это сделать, потому что nodeLinkFn определяется внутри applyDirectivesToNode)
  • Если тест проходит, я вызываю scope.$eval(attrs.ngInit), что и делает prelink f директивы native ng-init. оба scope и attrs являются собственными параметрами nodeLinkFn, поэтому они доступны. ничего не делается, если тест не работает.
  • таким образом, я переместил 1 инициализацию с шага 2 на новый шаг 0, который передает внутреннюю область el перед выполнением соответствующего кода контроллера.

<суб >  1. Фактически, я реплицировал его, потому что prelink f, определяемый директивой ng-init, все еще существует. Однако это не очень много, и я думаю, что его можно было бы легко избежать, изменив/удалив объект директивы

ИЗМЕНИТЬ

Чтобы избежать репликации, безопасно для иллюстративных целей описанного выше хакера заменить код назначения ngInitDirective Angular var на var ngInitDirective = valueFn({});

Ответ 2

сначала создается контроллер, а затем устанавливается foo = bar.

что это означает, что в коде вашего контроллера foo еще не существует. однако при отладке он уже создан.

вот путь вокруг него, но я думаю, что это действительно взломать. Почему переменная, установленная в ng-init undefined в $scope в AngularJS?

imho angular способ исправить это не помещает данные в представление. Данные принадлежат вашей модели, контроллеру или сервису. Это необходимо для того, чтобы сохранить разделение опасностей.

Ответ 3

Обходной путь заключается в использовании функции для ng-init.

<div ng-app>
    <div ng-controller="MyCtrl" ng-init="init('kittens')">
        Page title is {{ pageTitle }}
    </div>
</div>

Контроллер:

var MyCtrl = function($scope) {   
    $scope.init = function(pageTitle) {
        $scope.pageTitle = pageTitle;
    }
};

Я использую ng-init, чтобы указать тип для моего контроллера, т.е. контроллер используется много раз в приложении, но каждый раз ему нужна одна часть различной информации. Это позволяет мне создавать отдельный контроллер для каждого типа.

Связанные вопросы:

Почему переменная, установленная в ng-init undefined в $scope в AngularJS?

Как установить свойство scope с помощью ng-init?