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

Я использую 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>).

Спасибо!

7 ответов

Решение

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

Для любого элемента 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.
});

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

Когда речь идет о вызове кода 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

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

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

// 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);
}); 

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

<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;
    });
};

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

// 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 

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

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) скрипт в демонстрационном плунжере.

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

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

В моем случае я использовал комбинацию глобальных переменных, доступных в браузере (например, окна) и обработки событий. Мой код имеет интеллектуальный механизм генерации форм, который требует вывода 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();потенциально может сломать приложение, если мы изменим границы. Но если ваше приложение знает об объеме и может полагаться на то, что оно не меняется часто, то, что было предложено ранее, является жизнеспособным подходом.

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

Другие вопросы по тегам