问题
I've noticed that most questions relating to this subject are with regards to an alternative for the jQuery $(document).ready
function in angular, which is angular.element($document).ready
however i want a testable/best practice alternative to this.
I am currently injecting Bing Maps, which needs to have loaded before the code in my controller is executed.
currently i wrap the controller code in the document ready:
angular.element($document).ready(function() {
self.map = new Microsoft.Maps.Map(document.getElementById('map'), {
credentials: $scope.credentials,
enableClickableLogo: false,
enableSearchLogo: false,
showDashboard: false,
disableBirdseye: true,
allowInfoboxOverflow: true,
liteMode: true,
minZoom: 2
});
$scope.$watch('zoom', function (zoom) {
self.map.setView({animate: true, zoom: zoom});
});
if ($scope.onMapReady) {
$scope.onMapReady({ map: self.map });
}
});
Which works, but i'm unable to test it, so i assume this is incorrect usage.
I tried setting a variable in the directive, of $scope.loaded = true;
as i read that if the directive link function is hit the DOM must be loaded. I then tried replacing the document ready with:
$scope.$watch('loaded', function () {
self.map = new Microsoft.Maps.Map(document.getElementById('map'), {
credentials: $scope.credentials,
enableClickableLogo: false,
enableSearchLogo: false,
showDashboard: false,
disableBirdseye: true,
allowInfoboxOverflow: true,
liteMode: true,
minZoom: 2
});
if ($scope.onMapReady) {
$scope.onMapReady({ map: self.map });
}
});
$scope.$watch('zoom', function (zoom) {
self.map.setView({animate: true, zoom: zoom});
});
the 'loaded' watch works as expected but naturally the zoom is hit on load and thats before the map is set. I feel like i could change the document ready to a $timeout
function but that seems to be a workaround rather than the correct solution, is there a best practice alternative to angular.element($document).ready
that works in the same way but allows me to test it's contents successfully?
回答1:
Generally Angular application is already bootstrapped on document ready
. This is default behaviour for automatic bootstrapping with ng-app
, and manual bootstrapping with angular.bootstrap
should be performed on ready
as well.
The question is specific to current case (Microsoft's Bing Maps API). Considering that ready is suggested by Microsoft, a developer is on his/her own with better alternatives.
<script src="https://www.bing.com/api/maps/mapcontrol"></script>
is loaded synchonously, but it triggers a number of dependencies to load which aren't loaded yet at the moment when initial document ready
is triggered. Actually, it requires ready
inside another ready
in order to complete the initialization, this is exactly what the original code and Microsoft example show, and it doesn't look very good.
In order to avoid race conditions application bootstrap can be postponed to the moment when all prerequisites will be loaded, i.e. window load event instead of document ready. It may provide considerable delay but it guarantees that scripts that the application relies on were loaded, regardless of how their transport is performed:
angular.element(window).on('load', () => {
angular.bootstrap(document.body, ['app']
});
The alternative that API provides to control initialization process is global callback function:
<script src="https://www.bing.com/api/maps/mapcontrol?callback=globalCallbackName"></script>
A callback can be packed with a service instead of relying on <script>
:
angular.module('bingMaps', [])
.factory('bingMapsLoader', ($q, $window, $document, $timeout) => {
var script = document.createElement('script');
script.src = 'https://www.bing.com/api/maps/mapcontrol?callback=bingMapsCallback';
script.async = true;
$document.find('body').append(script);
return $q((resolve, reject) => {
$window.bingMapsCallback = resolve;
$timeout(reject, 30000);
});
});
bingMapsLoader
promise can be chained to guarantee that API was initialized, put into router resolver, etc.
Additionally, controller constructor is executed before directive is being compiled. Whether third-party APIs are used or not, it is correct to move all DOM-specific code to pre/post link function in Angular 1.4 and lower and to controller $onInit
or $postLink
hook in Angular 1.5 or higher:
app.controller('FooController', function (bingMapsLoader) {
this.$postLink = () => {
bingMapsLoader.then(() => this.mapsInit());
};
this.mapsInit = () => {
Microsoft.Maps.Map(...);
};
...
来源:https://stackoverflow.com/questions/44674270/alternative-to-angular-elementdocument-ready