When has ng-switch finished rendering?

2019-08-11 02:38发布

I'm using ui-router and trying to instantiate a widget that takes as a parameter a DOM element specified by id. This DOM element is in a <div ng-switch> and I want to call the widget constructor when the element is guaranteed to exist.

<div ng-switch on="state">
  <div ng-switch-when="map">
    <div id="map"></div>
  </div>
</div>

From the ui-router lifecycle, I understand that I should hook into $viewContentLoaded. This, however, doesn't work - the DOM element within the ng-switch isn't created at that point:

app.config(['$stateProvider', function ($stateProvider) {
  $stateProvider
    .state('/', {url: '/', templateUrl: 'index.html'})
    .state('map', {url: 'map', templateUrl: 'map.html', controller: 'MapCtrl'})
}]);

app.controller('MapCtrl', ['$rootScope', '$scope', '$state', function MapCtrl($rootScope, $scope, $state) {
  $scope.state = $state.current.name;  // expose the state to the template so we can ng-switch. Apparently there's no better way: https://github.com/angular-ui/ui-router/issues/1482

  $scope.$on('$viewContentLoaded', function mapContentLoaded(event, viewConfig) {
    var mapElement = document.getElementById('map');
    console.log('Attempting to create map into', mapElement);
    var map = new google.maps.Map(mapElement);  // <-- but mapElement will be null!
  });
}]);

What does work is using a setTimeout() of 50ms in the controller, which is brittle, but by that time the DOM element is created. Alternatively, I can set an interval, check for the presence of the map DOM element, and clear the interval when it's found.

What is the proper way of figuring out when an ng-switch has rendered its DOM? This isn't documented.

Here's the Plunkr.

1条回答
2楼-- · 2019-08-11 03:08

I think you're falling in the trap that many experienced front-end developers fall in to when using Angular. In most of other JS libraries we modify the DOM after it's been created and then add functionality to it. However, in Angular the functionality is defined in the HTML. Functionality and interactivity is created by using directives.

In jQuery something like this is fine:

<div id="foobar">
    Click here to do stuff
</div>

<script type="text/javascript">
    $(function () {
        $('#foobar').on('click', function () {
            someService.doStuff();
        });
    });
</script>

Whereas in Angular something like the below is more idiomatic:

<div id="foobar" ng-controller="Main" ng-click="doStuff()">
    Click here to do stuff
</div>

<script type="text/javascript">
    app.controller('Main', ['$scope', 'somerService', function ($scope, someService) {
        $scope.doStuff = function () {
            someService.doStuff();
        }
    }]);
</script>

As for your GoogleMap directive this is by far the simplest way to accomplish it. Albeit this is incredibly basic and may not do everything you need it to.

app.directive('googleMap', [function() {
    return {
      link: function(element) {
        new google.maps.Map(element);
      }
    }
  }
]);

Your map.html:

<div ng-switch on="state">
  <div ng-switch-when="map">
    <div google-map id="map"></div>
  </div>
</div>

As you mentioned however, this would recreate the Google map every time that controller is hit. One way around that is to save off the element and Map api and replacing it on subsequent calls:

app.directive('googleMap', [function () {
    var googleMapElement,
            googleMapAPI;
    return {
        link: function (element) {
            if (!googleMapElement || !googleMapAPI) {
                googleMapAPI = new google.maps.Map(element);
                googleMapElement = element;
            }
            else {
                element.replaceWith(googleMapElement);
            }

        }
    }
}]);
查看更多
登录 后发表回答