在 Angularjs 中处理模型事件
Handling Model events in Angularjs
我是 Angularjs 的新手,希望确保我没有实施反模式。
我有一个 Global controller
可以通过 factory
获取模型。该模型向位于全局控制器范围内的大约 dozen directives
提供数据。
位于全局控制器内的指令之一是 google 映射,需要在我从服务器获取 lat/long 数据后进行初始化。我在全局控制器中使用 .success()
函数中的 $rootScope.$broadcast("$site:loaded")
宣布模型到达,然后我在指令中使用 $rootScope.$on("$site:loaded")
监听它。
我是否实施了反模式,或者这是 A.O.K。?起初我认为已经有一种方法可以利用,当模型到达时 "know",类似于 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
有一个站点对象,其中包含 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 元素。
我是 Angularjs 的新手,希望确保我没有实施反模式。
我有一个 Global controller
可以通过 factory
获取模型。该模型向位于全局控制器范围内的大约 dozen directives
提供数据。
位于全局控制器内的指令之一是 google 映射,需要在我从服务器获取 lat/long 数据后进行初始化。我在全局控制器中使用 .success()
函数中的 $rootScope.$broadcast("$site:loaded")
宣布模型到达,然后我在指令中使用 $rootScope.$on("$site:loaded")
监听它。
我是否实施了反模式,或者这是 A.O.K。?起初我认为已经有一种方法可以利用,当模型到达时 "know",类似于 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
有一个站点对象,其中包含 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 元素。