在 Angularjs 中处理模型事件



我是Angularjs的新手,想要保证我没有实现反模式。

我有一个Global controller,可以让模型通过factory.该模型向全局控制器范围内的大约dozen directives提供数据。

位于全局控制器中的指令之一是谷歌地图,需要在我从服务器获取经度/经度数据后进行初始化。我使用全局控制器中.success()函数中的$rootScope.$broadcast("$site:loaded")宣布模型的到来,然后使用指令中的$rootScope.$on("$site:loaded")侦听它。

我是否实现了反模式或这是 A.O.K.?起初,我认为我已经可以利用一种方法来"知道"模型何时到达,类似于Backbone的onModelChanged事件。

关于这个或你在我的代码中看到的任何其他东西的任何提示?(如果它看起来不错,我会接受这样说的答案,并解释一下为什么它很好,如果可以的话)。

下面是从基本模板开始的代码:

<!-- Global controller implemented as global. -->
<!DOCTYPE html>
<!--[if lte IE 8]><html class="ie8"><![endif]-->
<!--[if !IE]><!--><html ng-app="juniper" ng-controller="Global as glb" ng-model="glb.site"><!--<![endif]-->
    <head lang="en-US">
        <base href="/ng-test/">
        <meta charset="utf-8" />
        <title></title>
        {% include "DriverSideSiteBundle:Juniper:stylesheets.html.twig" %}
        {% include "DriverSideSiteBundle:Juniper:javascript.html.twig" %}
        {% block head %}
        {% endblock %}
    </head>
    <body vocab="http://schema.org/" itemscope itemtype="http://schema.org/AutomotiveBusiness">
        {#
            These twig files contain <jn-header>
            The scaffolding HTML changes depending on whether the nav is above, below or not
            part of the header
        #}
        {% include 'DriverSideSiteBundle:Juniper:foundation/header.html.twig' with template %}
        <a href="/ng-test/somplace/">create angular 404 page to handle broken links.</a>
        {#
            These twig files contain <jn-sidebar> and <ng-view>
            The scaffolding HTML changes depending on where the nav is and whether there are sidebars
        #}
        {% if template.content.columns == 2 %}
            {% include 'DriverSideSiteBundle:Juniper:foundation/two-column.content.html.twig' with template %}
        {% endif %}
        {% if template.content == 1 %}
            {% include 'DriverSideSiteBundle:Juniper:foundation/one-column.html.twig' with template %}
        {% endif %}
        {# Todo: minify and concatenate into one request. #}
        {% javascripts
            "@DriverSideSiteBundle/Resources/public/vendor/modernizr/modernizr.js"
            "@DriverSideSiteBundle/Resources/public/vendor/angular/angular.js"
            "@DriverSideSiteBundle/Resources/public/vendor/angular-route/angular-route.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/app.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/site.factory.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/global/global.controller.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/homepage/homepage.controller.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/site-description/site-description.controller.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/site-description/site-description.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/my-garage/my-garage.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/services/services.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/coupons/coupons.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/contact-info/contact-info.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/payments/payments.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/certifications/certifications.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/amenities/amenities.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/jn-map/jn-map.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/jn-nav/jn-nav.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/store-hours/store-hours.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/motorev-admin/motorev-admin.directive.js"
        %}
            {# "@DriverSideSiteBundle/Resources/public/juniper/js/libs/underscore.module.js" #}          
            {# "@DriverSideSiteBundle/Resources/public/juniper/js/header/header.directive.js"
            "@DriverSideSiteBundle/Resources/public/juniper/js/footer/footer.directive.js" #}
            <script src="{{ asset_url }}"></script>
        {% endjavascripts %}
        {# Depending on the layout, include this or that version of the angular templates during initial request #}
        {% javascripts '@DriverSideSiteBundle/Resources/public/juniper/js/homepage/homepage.html' output='bundles/driversidesite/juniper/homepage/homepage.html'%}
      {% endjavascripts %}
        {% block angularTemplates %}
        {% endblock %}
    <script type="text/javascript">
        // Trigger foundation's js
        jQuery(document).ready(function () {
            $(document).foundation();
        });
    </script>
    <!-- Replace key with dynamic key -->
    <script type="text/javascript"
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDyEYqzoex1QGMLK1YXDye8vIs0o-lQbLQ">
    </script>
    </body>
    </html>

Site工厂被注入到全局控制器中,该控制器调用站点的getSite(:siteId)方法。此方法发出$http请求。返回的数据设置为Site工厂;我在某处看到了这种做法,觉得不错。

/**
 *  global.controller.js
 */
(function () {
    'use strict';
    var juniper = angular.module('juniper');
    juniper.controller('Global', [ '$rootScope', 'Site', function($rootScope, Site){
        var self = this;
        // Assign the site model to this controller's `site` attr
        this.site = Site;
        // Use the site model's get method to retrieve the site info
        var promise = this.site.getSite(1409); // Todo: Include the Id dynamically

        promise
            .success( $.proxy(function(data, status, headers, config){
                // Bind to the Site obj
                angular.extend(this, data.data);
                // Broadcast when the data arrives so that other directives and controllers can do something when it arrives 
                $rootScope.$broadcast("$site:loaded");
            }, Site)) // Pass in the Site object for the data to bind to
            .error( function(data, status, headers, config) {
                var error = 'error';
                error += '/n data: ' + data;
                error += '/n status: ' + status;
                error += '/n headers: ' + headers;
                error += '/n config: ' + config;
                console.log(error); // Log error
            });
    }]);
}());

Site factory有一个 Site 对象,该对象保存 getter 和 setter 函数以及从服务器返回的所有数据:

/**
 *  site.factory.js
 */
(function () {
    'use strict';
    var juniper = angular.module('juniper');
    juniper.factory('Site', function($http){
        /**
         *  Site obj is passed to whichever controller needs the site model.
         *  The site obj contains all the methods for getting/setting data as well
         *  as the data itself.
         *
         *  In this case, the site obj is set to the global controller which passes
         *  a reference thru inheritance to all other controllers.
         */
        var Site = {
            /**
             *  Get the site
             *  siteId (int) is the integer id of the site to be retrieved
             */
            getSite: function(siteId) {
                var url = 'http://localhost:8000/api/site/' + siteId;
                return $http({
                    method: 'GET',
                    url: url
                });
            }
        };
        // Return the site obj.
        // You'll need to call the site obj methods once assigning it to a controller's attr.
        return Site;
    });
}());

这就是全局控制器,其中包含站点工厂获取的模型。下面是地图指令和地图模板:

<!-- I didn't specify an ng-model. I didn't see a reason to insert an ng-model since the map directive uses data from the Global controller and nothing else. Is there a way for me to specify that this directive's model is just a portion of the Global model, like by glb.site.location here or by setting the `scope` in the directive declaration ...? Is there a value to doing either of these? -->
<!-- Also didn't specify an ng-controller because I could stick the functions handling this directive's UI in the directive's link function. Is this smart practice, bad practice, or just "a practice"? -->
<div class="Pod">
  <h3 class="Pod__Head show-for-small-only">Location</h3>
  <div class="Pod__Body">
    <div id="map-canvas"></div>
  </div>
</div>

映射指令。请注意,我侦听来自全局控制器的 $scope.$broadcast。我还设置了scope:false,这给了它全局控制器的作用域:

/**
 *  jn-map.directives.js
 */
(function () {
    'use strict';
    // Create template-path variable for easy maintenance
    var path = '/bundles/driversidesite/juniper/';
    var juniper = angular.module('juniper');
    juniper.directive("jnMap", function($rootScope) {
      return {
        restrict: "E",
        transclude: false, // Tell where to transclude the element using the ng-transclude attr
        templateUrl: path + 'jn-map/jn-map.html',
        scope: false,
        link: function(scope, elements, attrs) {
            $rootScope.$on("$site:loaded", $.proxy(function(){
                var mapOptions = {
                  center: {
                    lat: parseFloat(this.site.locations[0].latitude),
                    lng: parseFloat(this.site.locations[0].longitude)
                  },
                  zoom: 8
                };
                var map = new google.maps.Map(document.getElementById('map-canvas'),
                    mapOptions);
            }, scope.glb)); // Pass in the Global controller's model
        }
      }
    });
}());

这对我来说确实是一种反模式。我只会在站点可用后将 map 指令添加到 DOM 中。

在控制器中:

promise.success(function(data) {
    $scope.loadedSite = data;
}

在模板中:

<jn-map site="loadedSite" ng-if="loadedSite"></jn-map>

该指令将具有包含站点的作用域,并且只有在加载站点后才会调用,这要归功于ng-if

juniper.directive("jnMap", function($rootScope) {
  return {
    restrict: "E",
    templateUrl: path + 'jn-map/jn-map.html',
    scope: {
      site: '='
    },
    link: function(scope, element, attrs) {
            var mapOptions = {
              center: {
                lat: parseFloat(scope.site.locations[0].latitude),
                lng: parseFloat(scope.site.locations[0].longitude)
              },
              zoom: 8
            };
            new google.maps.Map(element[0], mapOptions);
          }
  };
});
请注意,此指令在指令的元素中显示

映射,而不是在另一个不相关的元素中显示映射。 element是一个类似jQuery的元素包装器对象,因此element[0]是原始的DOM元素。

最新更新