jonhobbs
jonhobbs

Reputation: 27972

Custom directive not binding correctly in ng-if second time around

I have a custom directive which is using two way binding to my controller (using '=').

<my-streetview 
    latitude="quickView.streetView.latitude" 
    longitude="quickView.streetView.longitude" 
    ng-if="quickView.activeTab === 'street'"
></my-streetview>

I'm using ng-if because I don't wnt the google map/streetview being loaded until the tab which it is in is opened. The trouble is that the first time it shows everything works but the second time the ng-if is true (when you click a different tab then come back to the tab) it seems to set the long and lat to undefined.

I know the following:

A) if I change ng-hide to ng-show it just works. The google map is not being destroyed and created every time you leave nad return to the tab so this makes sense. It's something to do with ng-if destroying something when it's false I think.

B) The lat and long values DO actually get changed in the parent controller because I put a $watch and a console.log() in there to test it. Basically when the ng-if is set to true the first time it creates the streetview fine, the second and subsequent times it either cannot read the values from the parent controller, or is actually setting them to undefinied.

C) Nothing else is different between the first time the ng-if is shown and subsequent times. There is no other code I can think of coming into play.

Any ideas would really be appreciated.

Here is my full code for my street view directive.

angular.module('my.directives')

.constant('myStreetviewDefaults', {

    latitude: 51.816102,
    longitude: -0.811619

})


.directive('myStreetview', function ($timeout, myStreetviewDefaults) {

    return {

        restrict: 'EA',
        scope: {
            latitude: '=',
            longitude: '='
        },


        link: function ($scope, $element, $attrs) {

            $scope.latitude = angular.isDefined($scope.latitude) ? $scope.$eval($scope.latitude) : myStreetviewDefaults.latitude;
            $scope.longitude = angular.isDefined($scope.longitude) ? $scope.$eval($scope.longitude) : myStreetviewDefaults.longitude;


            // Create the panorama
            var mapEl = $('<my-streetview-map></my-streetview-map>');
            mapEl.addClass('my-streetview-map-container');


            $element.append(mapEl);

            var panorama = new google.maps.StreetViewPanorama(mapEl[0], {
                position: {
                    lat: $scope.latitude,
                    lng: $scope.longitude
                },
                pov: {
                    heading: 34,
                    pitch: 10
                }
            });


            // Watch latitude and longitude to reset the center
            $scope.$watchGroup(['latitude','longitude'], function (newValues, oldValues, $scope) {

                panorama.setPosition({
                    lat: $scope.latitude,
                    lng: $scope.longitude
                });

            });


            // Hack to get street view drawing properly on second load
            // https://github.com/allenhwkim/angularjs-google-maps/issues/59
            $timeout(function(){
                google.maps.event.trigger(panorama,'resize'); 
            }, 100);

        }


    };


});

This is the controller code for the Angular UI modal that the streetview sits inside.

angular.module('app')

.controller('QuickViewCtrl', function ($rootScope, $scope, $log, $http, appConfig, $u, $modalInstance, modalSettings) {

    'use strict';

    var master = $scope.master;

    var quickView = this;


    $log.info('Quick View Opened', modalSettings);


    this.close = function(){
        $modalInstance.close();
    }

    ///////////////////////////////////////////////////////////////
    //  Initialize Page
    ///////////////////////////////////////////////////////////////

    var init = function () {

        // Set the initial tab
        quickView.activeTab = modalSettings.initialPanel;


        // Set up the street view
        quickView.streetView = {
            latitude: modalSettings.property.latitude,
            longitude: modalSettings.property.longitude
        };


        $scope.$watch('quickView.streetView', function(newValues, oldValues){
            console.log("Test watching from controller", newValues);
        }, true);



    };

    init();

});

And this is the template for the modal window....

<div class="quickView modal-inner modal--has-header modal--has-footer">

    <!-- Header -->
    <div class="modal-header">

        <!-- Header removed for brevity -->

    </div>
    <div class="modal-main">

        <!-- Tabs -->
        <my-tabset
            my-tabset-active-tab="quickView.activeTab" 
        >
            <div my-tabset-tabs my-tabset-tabs--equal4>
                <a href="#" my-tabset-tab my-tabset-tab-name="overview" is-active="true">
                    <div my-tabset-tab-text>Overview</div>
                </a>
                <a href="#" my-tabset-tab my-tabset-tab-name="gallery">
                    <div my-tabset-tab-text>Gallery</div>
                </a>
                <a href="#" my-tabset-tab my-tabset-tab-name="map">
                    <div my-tabset-tab-text>Map</div>
                </a>
                <a href="#" my-tabset-tab my-tabset-tab-name="street">
                    <div my-tabset-tab-text>Street View</div>
                </a>
            </div>
            <div my-tabset-panels>

                <!-- Overview Panel -->
                <div my-tabset-panel my-tabset-tab-name="overview" is-active="true">

                    <div ng-if="quickView.activeTab === 'overview'">

                        <!-- Overview removed for brevity -->

                    </div>

                </div>

                <!-- Gallery Panel -->
                <div my-tabset-panel my-tabset-tab-name="gallery">

                    <div ng-if="quickView.activeTab === 'gallery'">

                        <!-- Gallery removed for brevity -->

                    </div>

                </div>

                <!-- Map Panel -->
                <div my-tabset-panel my-tabset-tab-name="map">

                    <ui-gmap-google-map 
                        center='quickView.map.center' 
                        zoom='quickView.map.zoom' 
                        options="quickView.map.options" 
                        control="quickView.mapControl" 
                        ng-if="quickView.activeTab === 'map'"
                    >
                        <ui-gmap-marker
                            idKey="'quickViewMapMarker'" 
                            coords='quickView.map.markerPosition'
                        >
                        </ui-gmap-marker>
                    </ui-gmap-google-map>

                </div>

                <!-- Street View Panel -->
                <div my-tabset-panel my-tabset-tab-name="street">

                    <my-streetview 
                        latitude="quickView.streetView.latitude" 
                        longitude="quickView.streetView.longitude" 
                        ng-if="quickView.activeTab === 'street'"
                    ></my-streetview>

                </div>
            </div>
        </my-tabset>

    </div>


    <!-- Footer -->
    <div class="modal-footer">
        Footer
    </div>

</div>

Upvotes: 1

Views: 764

Answers (1)

New Dev
New Dev

Reputation: 49590

The offending lines are:

$scope.latitude = angular.isDefined($scope.latitude) ? $scope.$eval($scope.latitude) : myStreetviewDefaults.latitude;
$scope.longitude = angular.isDefined($scope.longitude) ? $scope.$eval($scope.longitude) : myStreetviewDefaults.longitude;

It's not clear why you are using $scope.$eval($scope.latitude), but it might stem from a misunderstanding of what $scope.$eval does.

$scope.$eval takes an expression, for example: "quickView.streetView.latitude", and evaluates it against the $scope upon which the $eval is called.

You called it with $scope.latitude as a parameter, which means the the evaluated expression was something like 35.344542 - clearly not something defined on the scope - and so you got undefined.

Perhaps you meant to use $attrs.latitude - that would have given you the "quickView.streetView.latitude" expression, but you would have needed to call it on the parent scope, since your directive uses an isolate scope that is not aware of what quickView (etc...) is:

$scope.$parent.$eval($attrs.latitude)

But more so, you don't even need this $eval at all, since $scope.latitude has already acquired the evaluated value automatically via two-way binding. The following would have worked just as well:

$scope.latitude = angular.isDefined($scope.latitude) 
                       ? $scope.latitude 
                       : myStreetviewDefaults.latitude;

Upvotes: 2

Related Questions