Позвоните в AngularJS из старого кода

180

Я использую AngularJS для создания элементов управления HTML, которые взаимодействуют с устаревшим приложением Flex. Все обратные вызовы из приложения Flex должны быть прикреплены к окну DOM.

Например (в AS3)

ExternalInterface.call("save", data);

Позвоню

window.save = function(data){
    // want to update a service 
    // or dispatch an event here...
}

Из функции изменения размера JS я бы хотел отправить событие, которое может услышать контроллер. Кажется, что создание сервиса - это путь. Можете ли вы обновить сервис за пределами AngularJS? Может ли контроллер прослушивать события из службы? В одном эксперименте (нажмите на скрипку) я сделал, кажется, что я могу получить доступ к сервису, но обновление данных сервиса не отражается в представлении (в примере <option>следует добавить к <select>).

Спасибо!

Kreek
источник
1
Обратите внимание, что в jsfiddle над инжектором получается без нацеливания на элемент в приложении с помощью var injector = angular.injector(['ng', 'MyApp']);. Это даст вам совершенно новый контекст и дубликат myService. Это означает, что вы получите два экземпляра сервиса и модели и будете добавлять данные в неправильное место. Вместо этого вы должны использовать целевой элемент в приложении, используя angular.element('#ng-app').injector(['ng', 'MyApp']). На этом этапе вы можете использовать $ apply для переноса изменений модели.
Тхань Нгуен

Ответы:

293

Взаимодействие от внешнего к угловому аналогично отладке углового приложения или интеграции со сторонней библиотекой.

Для любого элемента DOM вы можете сделать это:

  • angular.element(domElement).scope() чтобы получить текущую область видимости для элемента
  • angular.element(domElement).injector() чтобы получить текущий инжектор приложения
  • angular.element(domElement).controller() овладеть ng-controller экземпляр.

С инжектора вы можете получить любой сервис в угловом приложении. Аналогичным образом из области вы можете вызывать любые методы, которые были опубликованы в ней.

Имейте в виду, что любые изменения в угловой модели или любые вызовы методов в области должны быть обернуты $apply()следующим образом:

$scope.$apply(function(){
  // perform any model changes or method invocations here on angular app.
});
Миско Хевери
источник
1
это работает, но я бы хотел, чтобы был какой-то способ напрямую перейти от модуля к его объему - это возможно? Необходимость вернуться назад для выбора [ng-app]корневого узла кажется обратной, когда у меня уже есть ссылка на Модуль ...
mindplay.dk
5
Я не могу заставить это работать: я звоню angular.element(document.getElementById(divName)).scope(), но я не могу вызвать какие-либо функции из него, он просто возвращает "undefined" в консоли.
Эмиль
1
Даже когда я сталкиваюсь с той же проблемой, что и описанная выше @Emil, она возвращает undefined. Любая помощь ?
Бибин
5
element (). scope () не будет работать, если отладочные данные отключены, что является рекомендацией для производства. Разве это не делает его бесполезным в этом сценарии? Это будет только для тестирования / отладки.
К. Норберт
4
Вы не захотите делать это в Angular 1.3. Команда Angular не намеревалась вызывать «.scope ()» для элементов в производственном коде. Он должен был стать инструментом отладки. Итак, начиная с Angular 1.3, вы можете отключить это. Angular перестанет прикреплять область к элементу, используя функцию .data в jQuery. Это ускорит ваше приложение. Кроме того, передача функций в функции кэширования jquery приведет к утечкам памяти. Таким образом, вы должны обязательно отключить это, чтобы ускорить ваше приложение. На сайте Angular есть производственное руководство, которое вы должны использовать, чтобы узнать больше.
морозное
86

Миско дал правильный ответ (очевидно), но некоторые из нас, новичков, могут нуждаться в его упрощении в дальнейшем.

Когда речь идет о вызове кода AngularJS из устаревших приложений, думайте о коде AngularJS как о «микро-приложении», существующем в защищенном контейнере в вашем устаревшем приложении. Вы не можете делать вызовы напрямую (по очень веской причине), но вы можете делать удаленные вызовы с помощью объекта $ scope.

Чтобы использовать объект $ scope, вам нужно получить дескриптор $ scope. К счастью, это очень легко сделать.

Вы можете использовать идентификатор любого HTML-элемента в HTML-приложении AngularJS, чтобы получить дескриптор области приложения AngularJS.

В качестве примера, скажем, мы хотим вызвать несколько функций в нашем контроллере AngularJS, таких как sayHi () и sayBye (). В AngularJS HTML (просмотр) у нас есть div с идентификатором «MySuperAwesomeApp». Вы можете использовать следующий код в сочетании с jQuery, чтобы получить дескриптор $ scope:

var microappscope = angular.element($("#MySuperAwesomeApp")).scope();

Теперь вы можете вызывать функции кода AngularJS с помощью дескриптора области:

// we are in legacy code land here...

microappscope.sayHi();

microappscope.sayBye();

Чтобы сделать вещи более удобными, вы можете использовать функцию для захвата дескриптора области в любое время, когда вы захотите получить к нему доступ:

function microappscope(){

    return angular.element($("#MySuperAwesomeApp")).scope();

}

Тогда ваши звонки будут выглядеть так:

microappscope().sayHi();

microappscope().sayBye();

Вы можете увидеть рабочий пример здесь:

http://jsfiddle.net/peterdrinnan/2nPnB/16/

Я также показал это в слайд-шоу для группы Ottawa AngularJS (просто перейдите к последним 2 слайдам)

http://www.slideshare.net/peterdrinnan/angular-for-legacyapps

Питер Дриннан
источник
4
Обратите внимание, что ответы, содержащие только ссылки, не приветствуются, поэтому ответы SO должны быть конечной точкой поиска решения (в отличие от еще одной остановки ссылок, которая, как правило, со временем устаревает). Пожалуйста, рассмотрите возможность добавления отдельного краткого обзора здесь, сохраняя ссылку в качестве ссылки.
Клеопатра
Приятное дополнительное уточнение. Спасибо.
Джо
1
Прекрасное объяснение! позволил мне обойти проверку формы, выполнив это:<input type="button" onclick="angular.element(this).scope().edit.delete();" value="delete">
Purefan
24

Величайшее объяснение концепции, которую я нашел, находится здесь: https://groups.google.com/forum/#!msg/angular/kqFrwiysgpA/eB9mNbQzcHwJ

Чтобы сохранить вас нажав:

// get Angular scope from the known DOM element
e = document.getElementById('myAngularApp');
scope = angular.element(e).scope();
// update the model with a wrap in $apply(fn) which will refresh the view for us
scope.$apply(function() {
    scope.controllerMethod(val);
}); 
Мудрец
источник
14
Вышеописанное работает, когда приложение и контроллер сосуществуют в одном элементе. Для более сложных приложений, которые используют директиву ng-view для шаблона, вы должны получить первый элемент в представлении, а не элемент DOM всего приложения. Мне пришлось осматривать элементы с помощью document.getElementsByClassName ('ng-scope'); список узлов, чтобы определить правильный элемент DOM области видимости для захвата.
goosemanjack
Я знаю, что это действительно старая тема, но я думаю, что сталкиваюсь с этой проблемой. Есть ли у кого-нибудь код, который показывает, как пройти список, чтобы выяснить элемент DOM, чтобы захватить?
JerryKur
Игнорируйте мой вопрос. Я смог получить эту работу, просто используя document.getElementById (директива any-Control-That-Has-An-NG-Directive '). Scope ().
JerryKur
если вы используете ng-view и разделяете ваши представления на свои файлы. Вы можете поместить и idв верхний элемент HTML, а затем сделать document.getElementById()этот идентификатор. Это дает вам доступ к области действия этого контроллера. методы / свойства и т. д. просто добавьте тонкости в комментарии гусеманджека.
ftravers
13

В дополнение к другим ответам. Если вы не хотите обращаться к методу в контроллере, но хотите получить прямой доступ к сервису, вы можете сделать что-то вроде этого:

// Angular code* :
var myService = function(){
    this.my_number = 9;
}
angular.module('myApp').service('myService', myService);


// External Legacy Code:
var external_access_to_my_service = angular.element('body').injector().get('myService');
var my_number = external_access_to_my_service.my_number 
Алек Хьюитт
источник
13

Благодаря предыдущему посту я могу обновить свою модель с помощью асинхронного события.

<div id="control-panel" ng-controller="Filters">
    <ul>
        <li ng-repeat="filter in filters">
        <button type="submit" value="" class="filter_btn">{{filter.name}}</button>
        </li>
    </ul>
</div>

Я объявляю свою модель

function Filters($scope) {
    $scope.filters = [];
}

И я обновляю свою модель вне моей области

ws.onmessage = function (evt) {
    dictt = JSON.parse(evt.data);
    angular.element(document.getElementById('control-panel')).scope().$apply(function(scope){
        scope.filters = dictt.filters;
    });
};
Гийом Винсент
источник
6

Более безопасный и эффективный способ, особенно когда отладочные данные отключены, - это использование общей переменной для хранения функции обратного вызова. Ваш угловой контроллер реализует эту функцию, чтобы вернуть свои внутренние данные внешнему коду.

var sharedVar = {}
myModule.constant('mySharedVar', sharedVar)
mymodule.controller('MyCtrl', [ '$scope','mySharedVar', function( $scope, mySharedVar) {

var scopeToReturn = $scope;

$scope.$on('$destroy', function() {
        scopeToReturn = null;
    });

mySharedVar.accessScope = function() {
    return scopeToReturn;
}
}]);

Обобщается как директива многократного использования:

Я создал директиву exposeScope, которая работает аналогичным образом, но ее использование проще:

<div ng-controller="myController" expose-scope="aVariableNameForThisScope">
   <span expose-scope='anotherVariableNameForTheSameScope" />
</div>

Это сохраняет текущую область (которая дается функции связи директивы) в глобальном объекте «области видимости», который является держателем для всех областей. Значение, предоставленное атрибуту директивы, используется в качестве имени свойства области в этом глобальном объекте.

Смотрите демо здесь . Как я показал в демонстрации, вы можете запускать события jQuery, когда область сохраняется и удаляется из глобального объекта «области видимости».

<script type="text/javascript" >
    $('div').on('scopeLinked', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }.on('scopeDestroyed', function(e, scopeName, scope, allScopes) {
      // access the scope variable or the given name or the global scopes object
    }

</script>

Обратите внимание, что я не тестировал on ('scopeDestroyed'), когда фактический элемент удаляется из DOM. Если это не работает, может помочь запуск события на самом документе вместо элемента. (см. app.js) скрипт в демонстрационном плунжере.

Кагатай Калан
источник
3

Я знаю, что это старый вопрос, но я недавно искал варианты сделать это, поэтому я решил поместить свои выводы здесь на тот случай, если они кому-нибудь пригодятся.

В большинстве случаев, если существует потребность во внешнем унаследованном коде для взаимодействия с состоянием пользовательского интерфейса или внутренней работой приложения, служба может быть полезна для отвлечения этих изменений. Если внешний код взаимодействует напрямую с вашим угловым контроллером, компонентом или директивой, вы сильно связываете свое приложение с унаследованным кодом, что является плохой новостью.

В моем случае я использовал комбинацию глобальных переменных, доступных в браузере (например, окна) и обработки событий. Мой код имеет интеллектуальный механизм генерации форм, который требует вывода JSON из CMS для инициализации форм. Вот что я сделал:

function FormSchemaService(DOM) {
    var conf = DOM.conf;

    // This event is the point of integration from Legacy Code 
    DOM.addEventListener('register-schema', function (e) {

       registerSchema(DOM.conf); 
    }, false);

    // service logic continues ....

Форма службы формы создается с использованием углового инжектора, как и ожидалось:

angular.module('myApp.services').
service('FormSchemaService', ['$window' , FormSchemaService ])

И в моих контроллерах: function () {'use strict';

angular.module('myApp').controller('MyController', MyController);

MyEncapsulatorController.$inject = ['$scope', 'FormSchemaService'];

function MyController($scope, formSchemaService) {
    // using the already configured formSchemaService
    formSchemaService.buildForm(); 

Пока что это чисто угловое и JavaScript-сервис-ориентированное программирование. Но унаследованная интеграция приходит сюда:

<script type="text/javascript">

   (function(app){
        var conf = app.conf = {
       'fields': {
          'field1: { // field configuration }
        }
     } ; 

     app.dispatchEvent(new Event('register-schema'));

 })(window);
</script>

Очевидно, что у каждого подхода есть свои достоинства и недостатки. Преимущества и использование этого подхода зависят от вашего пользовательского интерфейса. Предложенные ранее подходы не работают в моем случае, так как моя схема формы и унаследованный код не контролируют и не знают угловых областей. Следовательно, настройка моего приложения на основеangular.element('element-X').scope(); может потенциально сломать приложение, если мы изменим границы. Но если ваше приложение знает об объеме и может полагаться на то, что оно не меняется часто, то, что было предложено ранее, является жизнеспособным подходом.

Надеюсь это поможет. Любые отзывы также приветствуются.

Shakus
источник