alternative to angular.element(document).ready

会有一股神秘感。 提交于 2019-12-11 08:17:14

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!