2016-01-18 3 views
5

У меня одностраничное приложение AngularJS, состоящее из нескольких модулей, целью которых является предоставление пользователю совместной панели (основного виджета) и других связанных с ними виджетов (других подключенных пользователей, метаданных пэдов и т. Д.).В AngularJS, как запустить код после инициализации всех контроллеров?

Я решил разделить приложение следующим образом:

  • 1 Модуль хостинга службы, ответственный за обнажая методы инициализации для компонента колодки
  • N модулей хостинга пользовательских директив (и их контроллер), соответствующей различные виджеты в приложении
  • 1 модуль, отвечающий за параметры сбора и инициализации компонента подушки

Давайте си mplify this, предположив, что у меня есть только 1 виджет, единственной целью которого является отображение пользователю статуса сообщения: «аутентификация», «аутентификация», «ошибка» или «готовность».

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

Услуга:

angular.module("app.core").factory("padService", padService); 
function padService() { 
    // Callback registration and notification code omitted 
    return { 
     initialize: function (authToken) { ... }, 
     onAuthenticated: function (callback) { ... }, 
     onReady: function (callback) { ... }, 
     onError: function (callback) { ... } 
    }; 
} 

Виджет:

angular.module("app.widget").directive("widget", widget); 
function widget() { 
    return { 
     templateUrl: 'app/widget.html', 
     restrict: 'E', 
     controller: widgetController 
    }; 
} 
function widgetController($scope, padService) { 
    $scope.message = "authenticating"; 
    padService.onAuthenticated(function (user) { 
     $scope.message = "authenticated"; 
     // Do other stuff related to user authentication event 
    }); 
    padService.onReady(function (padInstance) { 
     $scope.message = "ready"; 
     // Do other stuff related to pad readiness event 
    }); 
    padService.onError(function (error) { 
     $scope.message = "error"; 
     // Do other stuff related to error event 
    }); 
} 

Теперь "модуль инициализатор", в своей простейшей форме, собирает аутентификации маркера authToken из фрагмента URL (по аналогии с OAuth2) и просто вызывает padService.initialize(authToken);. Обратите внимание, что это также может быть выделенное всплывающее окно аутентификации, поэтому оно находится в собственном модуле.

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

angular.module("app.initializer").run(run); 
function run($document, $timeout, tokenService, padService) { 
    // This does not work because run() is called before the 
    // controllers are initialized (widget does not get notified) 
    var authToken = tokenService.getTokenFromUrl(); 
    padService.initialize(authToken); 

    $document.ready(function() { 
     // This does not work because angular does not detect 
     // changes made to the widget controller's $scope 
     var authToken = tokenService.getTokenFromUrl(); 
     padService.initialize(authToken); 

     // This does not work in firefox for some reason (but 
     // does in chrome!)... except if I enter debug mode or 
     // set the timeout to a longer value, which makes it 
     // either really difficult to diagnostic or ugly as hell 
     $timeout(function() { 
      var authToken = tokenService.getTokenFromUrl(); 
      padService.initialize(authToken); 
     }, 0); 
    }); 
} 
+0

Имеет ли сервис знания о том, какие контроллеры будут подписываться на свои услуги? Или это имеет значение? Что служба должна знать от своих клиентов, чтобы функционировать? – georgeawg

+0

Вы можете посмотреть, как ui-riouter реализует свой метод 'resolve' https://github.com/angular-ui/ui-router/blob/master/src/resolve.js –

+0

Служба не знает своих подписчиков, она просто вызывает внутренний метод 'notify()', когда происходит событие, которое, в свою очередь, вызывает каждый зарегистрированный обратный вызов с некоторыми аргументами (на самом деле это просто пользовательская реализация '$ on 'и' $ broadcast', чтобы избежать загрязнения глобальных имен событий). Службе не нужно ничего знать о функциях своих подписчиков, но ее необходимо инициализировать с помощью набора параметров (здесь токен аутентификации, предоставляемый модулем 'app.initializer'). –

ответ

2

Контроллеры создаются синхронно (я полагаю), поэтому не должно быть никаких трудностей с выполнением некоторого кода после этого.

Это ошибочное предположение. Рамка AngularJS регулярно создает и уничтожает директивы и их контроллеры в течение срока действия приложения. ng-repeat, ng-if, ng-include и т. Д. Все создают и уничтожают DOM, содержащие директивы. Если ваш «виджет» является частью ng-repeat, его контроллер получает несколько экземпляров, один раз для каждого элемента в списке, который ng-repeat.

Чтобы сохранить данные, которые сохраняются на протяжении всего срока службы приложения, храните его в службе. (Или по $rootScope; не рекомендуется, но опция.) Контроллеры не могут предположить, что они были запущены во время загрузки. Им нужно «догнать» и подписаться на изменения.

Держите постоянные данные в заводской службы и обеспечивает сеттера и поглотителя функции.

angular.module("app").factory("padService", function() { 
    //Store service status here 
    var status = "none-yet"; 

    function setStatus(s) { 
     status = s; 
     return status; 
    }; 

    function getStatus() { 
     return status; 
    }; 

    return { 
     setStatus: setStatus, 
     getStatus: getStatus 
    }; 
}); 

В вашем «виджете», введите услугу, подпишитесь на изменения и «догоняй».

angular.module("app").directive("widget", function() { 
    function widgetController($scope, padService) { 
     //subscribe with $watch 
     $scope.$watch(padService.getStatus, function(newStatus) { 
      //catch-up and react to changes 
      case (newStatus) { 
       "authenticated": 
        // Do stuff related to authenticated state 
        break; 
       "ready": 
        // Do stuff related to pad ready state 
        break; 
       "error": 
        // Do stuff related to error state 
        break; 
       default: 
        // Do something else 
      } 
      $scope.message = newStatus; 
     }; 
    }; 
    return { 
      templateUrl: 'app/widget.html', 
      restrict: 'E', 
      controller: widgetController 
    } 
}); 

Когда директива первого регистрирует слушателя с помощью $watch, рамки AngularJS, выполняет функцию часов (в данном случае padService.getStatus), и выполняет функцию слушателя. Это позволяет директиве «догнать» текущий статус службы.

В каждом цикле дайджест рама AngularJS выполняет padService.getStatus. Если статус изменился, среда выполняет функцию слушателя с новым статусом в качестве первого параметра. Это позволяет директиве реагировать на изменения.

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

+1

Это имеет большой смысл. Главное, что я пропустил, это бит '$ scope. $ Watch' и шаг назад из моего варианта использования, чтобы рассмотреть другие варианты использования директивы. –

0

магазин статус в службе

function padService() { 
    var ctx = this; 
    ctx.status = 'authenticating'; 
    return { 
     initialize: function (authToken) { ... }, 
     onAuthenticated: function (callback) { ... }, 
     onReady: function (callback) { ... }, 
     onError: function (callback) { ... }, 
     getStatus: function() { return ctx.status; } 
    }; 
} 

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

function widgetController($scope, padService) { 
    $scope.message = padService.getStatus(); 
    padService.onAuthenticated(function() { 
    $scope.message = "authenticated"; 
    }); 
    padService.onReady(function() { 
    $scope.message = "ready"; 
    }); 
    padService.onError(function() { 
    $scope.message = "error"; 
    }); 
} 

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

Следующая вещь, которую вы можете сделать, это просто один из способов абонентского вещать изменения, внесенные в status слушателям

+0

Если я правильно понимаю ваш ответ, вы имеете в виду, что я должен выполнить код подписки на событие контроллера на инициализации контроллера, если связанное событие уже было запущено при создании контроллера? Это позволило бы контроллеру «догнать» пропущенные события, но он действительно чувствует себя против принципа DRY ... в то время как все, что я хочу сделать, это обеспечить, чтобы все контроллеры были инициализированы до того, как запущен метод 'initialize()' службы , –

+0

простите меня, я мог бы неверно истолковать ваше заявление. Вы теперь выполняете подписку на подписку на контроллер, верно? Также вы имеете в виду, делая подписку на события на контроллере init против принципа DRY? – jkris

+0

Мой код выполняет подписку на событие на контроллере init, это правда и (надеюсь) не против принципа DRY. Однако в вашем примере вы добавили метод, чтобы получить состояние службы от контроллера (в дополнение к событиям, которые также сообщают об изменении состояния), вот где я вижу «повтор». Если бы я хотел вызвать веб-службу в контроллере, когда служба будет готова (вместо отображения сообщения), мне пришлось бы вызвать этот метод как в обратном вызове подписки на событие, так и в коде инициализации контроллера (в последнем случае, только если состояние готово). –

0

прибавляя для более полного решения

Сервис

padService.$inject = ['$rootScope']; 
function padService($rootScope) { 
    return { 
    status: "authenticating", 
    initialize: function (authToken) { 
     //Update the status 
     $rootScope.$broadcast('STATUS_CHANGED'); 
    }, 
    subscribe: function(scope, callback){ 
     var ctx = this; 
     scope.$on('STATUS_CHANGED', function(){ 
     callback(ctx.status); 
     }); 
    } 
    }; 
} 

Контролер

function widgetController($scope, padService) { 
    $scope.status = padService.status; 
    padService.subscribe($scope, function(status){ 
    $scope.status = status; 
    }); 
}