最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - ng-if not updating when $scope var changes in AngularJS - Stack Overflow

programmeradmin2浏览0评论

In my view I have this:

<div class="tab-loading-container" ng-if="mapStatus.loading =='true'">
    <div class="tab-loading">Loading map</div>
</div>

In my controller I have:

$scope.mapStatus = {};

and then various functions that update the scope var used by the ng-if, when certain criteria are met, such as being offline etc (for example:

function enableMap () {
    $scope.mapStatus.loading = false;
}

My issue is that although the scope var is getting changed correctly (confirmed with good 'ol console.log and angular chrome extension) the ng-if in the view never updates / gets added / removed to show / remove the div.

I've tried using $apply (though my understanding of it isn't great), for example:

function enableMap () {
    $scope.$apply(function() {
        $scope.mapStatus.loading = false;
    });
}

but that throws errors such as Error: [$rootScope:inprog] $apply already in progress

Feel like I'm missing something obvious :(


More code as requested:

angular.module('app.case.controller', [])
.controller('CaseController', function($rootScope, $scope, $state, $stateParams, $filter, casesFactory, $ionicActionSheet, $ionicModal, $ionicTabsDelegate, $ionicLoading, ConnectivityMonitor) {

/// Other non related code

// location map - refactor into a factory
    $scope.mapStatus = {};

    function initMap () {
        var pos = { 'lat':  52.6136149, 'lng': -1.1936672 };

        var latLng = new google.maps.LatLng(pos.lat, pos.lng);

        var mapOptions = {
            center: latLng,
            zoom: 15,
            mapTypeId: google.maps.MapTypeId.ROADMAP,
            fullscreenControl: false,
            mapTypeControl: false
        };

        $scope.map = new google.maps.Map(document.getElementById('map'), mapOptions);

        google.maps.event.trigger(map, 'resize');

        //Wait until the map is loaded
        google.maps.event.addListenerOnce($scope.map, 'idle', function () {

            enableMap();

            console.log('map loaded');

            var marker = new google.maps.Marker({
                map: $scope.map,
                animation: google.maps.Animation.DROP,
                position: latLng
            });

            google.maps.event.trigger(map, 'resize'); 

            $scope.map.setCenter(latLng);

        });
    }

    function loadGoogleMaps () {    
        $scope.mapStatus.loading = true;

        // This function will be called once the SDK has been loaded
        window.mapInit = function(){
            initMap();
        };

        // Create a script element to insert into the page
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.id = 'googleMaps';
        script.src = ';callback=mapInit';

        document.body.appendChild(script);
    }

    function enableMap () {
        $scope.mapStatus.loading = false;
        $scope.mapStatus.offline = false;
    }

    function disableMap () {
        $scope.mapStatus.offline = true;
    }

    function checkLoaded () {
        if (typeof google == 'undefined"' || typeof google.maps == 'undefined') {
            loadGoogleMaps();
        } else {
            enableMap();
        }       
    }

    function addConnectivityListeners () {

        if (ionic.Platform.isWebView()) {

            // Check if the map is already loaded when the user es online, if not, load it
            $rootScope.$on('$cordovaNetwork:online', function(event, networkState) {
                checkLoaded();
            });

            // Disable the map when the user goes offline
            $rootScope.$on('$cordovaNetwork:offline', function(event, networkState) {
                disableMap();
            });

        } else {

            //Same as above but for when we are not running on a device
            window.addEventListener("online", function(e) {
                checkLoaded();
            }, false);    

            window.addEventListener("offline", function(e) {
                disableMap();
            }, false);  
        }
    }

    function showMap () {

        console.log('showMap() called');

        if (typeof google == 'undefined' || typeof google.maps == 'undefined') {

            console.warn("Google Maps SDK needs to be loaded");

            disableMap();

            if (ConnectivityMonitor.isOnline()){
                loadGoogleMaps();
            }
        }
        else {
            if (ConnectivityMonitor.isOnline()){
                initMap();
                enableMap();
            } else {
                disableMap();
            }
        }

        addConnectivityListeners();
    }

    $scope.initMap = function () {
        showMap();
    };

To confirm the scope vars are being changed here's a screenshot from the AngularJS chrome extension:

In my view I have this:

<div class="tab-loading-container" ng-if="mapStatus.loading =='true'">
    <div class="tab-loading">Loading map</div>
</div>

In my controller I have:

$scope.mapStatus = {};

and then various functions that update the scope var used by the ng-if, when certain criteria are met, such as being offline etc (for example:

function enableMap () {
    $scope.mapStatus.loading = false;
}

My issue is that although the scope var is getting changed correctly (confirmed with good 'ol console.log and angular chrome extension) the ng-if in the view never updates / gets added / removed to show / remove the div.

I've tried using $apply (though my understanding of it isn't great), for example:

function enableMap () {
    $scope.$apply(function() {
        $scope.mapStatus.loading = false;
    });
}

but that throws errors such as Error: [$rootScope:inprog] $apply already in progress

Feel like I'm missing something obvious :(


More code as requested:

angular.module('app.case.controller', [])
.controller('CaseController', function($rootScope, $scope, $state, $stateParams, $filter, casesFactory, $ionicActionSheet, $ionicModal, $ionicTabsDelegate, $ionicLoading, ConnectivityMonitor) {

/// Other non related code

// location map - refactor into a factory
    $scope.mapStatus = {};

    function initMap () {
        var pos = { 'lat':  52.6136149, 'lng': -1.1936672 };

        var latLng = new google.maps.LatLng(pos.lat, pos.lng);

        var mapOptions = {
            center: latLng,
            zoom: 15,
            mapTypeId: google.maps.MapTypeId.ROADMAP,
            fullscreenControl: false,
            mapTypeControl: false
        };

        $scope.map = new google.maps.Map(document.getElementById('map'), mapOptions);

        google.maps.event.trigger(map, 'resize');

        //Wait until the map is loaded
        google.maps.event.addListenerOnce($scope.map, 'idle', function () {

            enableMap();

            console.log('map loaded');

            var marker = new google.maps.Marker({
                map: $scope.map,
                animation: google.maps.Animation.DROP,
                position: latLng
            });

            google.maps.event.trigger(map, 'resize'); 

            $scope.map.setCenter(latLng);

        });
    }

    function loadGoogleMaps () {    
        $scope.mapStatus.loading = true;

        // This function will be called once the SDK has been loaded
        window.mapInit = function(){
            initMap();
        };

        // Create a script element to insert into the page
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.id = 'googleMaps';
        script.src = 'https://maps.googleapis./maps/api/js?key=XXX&callback=mapInit';

        document.body.appendChild(script);
    }

    function enableMap () {
        $scope.mapStatus.loading = false;
        $scope.mapStatus.offline = false;
    }

    function disableMap () {
        $scope.mapStatus.offline = true;
    }

    function checkLoaded () {
        if (typeof google == 'undefined"' || typeof google.maps == 'undefined') {
            loadGoogleMaps();
        } else {
            enableMap();
        }       
    }

    function addConnectivityListeners () {

        if (ionic.Platform.isWebView()) {

            // Check if the map is already loaded when the user es online, if not, load it
            $rootScope.$on('$cordovaNetwork:online', function(event, networkState) {
                checkLoaded();
            });

            // Disable the map when the user goes offline
            $rootScope.$on('$cordovaNetwork:offline', function(event, networkState) {
                disableMap();
            });

        } else {

            //Same as above but for when we are not running on a device
            window.addEventListener("online", function(e) {
                checkLoaded();
            }, false);    

            window.addEventListener("offline", function(e) {
                disableMap();
            }, false);  
        }
    }

    function showMap () {

        console.log('showMap() called');

        if (typeof google == 'undefined' || typeof google.maps == 'undefined') {

            console.warn("Google Maps SDK needs to be loaded");

            disableMap();

            if (ConnectivityMonitor.isOnline()){
                loadGoogleMaps();
            }
        }
        else {
            if (ConnectivityMonitor.isOnline()){
                initMap();
                enableMap();
            } else {
                disableMap();
            }
        }

        addConnectivityListeners();
    }

    $scope.initMap = function () {
        showMap();
    };

To confirm the scope vars are being changed here's a screenshot from the AngularJS chrome extension:

Share Improve this question edited Apr 4, 2019 at 9:54 Arman Fatahi 3,0053 gold badges28 silver badges38 bronze badges asked Jun 15, 2017 at 12:34 JimotheyJimothey 2,4349 gold badges42 silver badges68 bronze badges 7
  • 7 Why are you paring a boolean value with the string 'true', just write it as ng-if="mapStatus.loading" – George Commented Jun 15, 2017 at 12:36
  • Whoops, good spot. However, after changing to mapStatus.loading the div always shows even though the var is set to false in the controller (when certain conditions are met). – Jimothey Commented Jun 15, 2017 at 12:41
  • 1 i think you should use ng-show instead of ng-if – Xatyrian Commented Jun 15, 2017 at 12:42
  • use ng-if="$parent.mapStatus.loading" or ng-show – Sachila Ranawaka Commented Jun 15, 2017 at 12:44
  • Where does the value change to false? Can you post the code for this? – Mike Feltman Commented Jun 15, 2017 at 12:45
 |  Show 2 more ments

4 Answers 4

Reset to default 1

I'm not sure if this will solve your issue or not, but before you get too far into this, I'd remend getting rid of the references to $scope and use controllerAs. I've seen numerous instances where references to properties/methods added directly to $scope fail for no particular reason. Specificially I'd remend that you:

  • remove where you have injected $scope,
  • add var ctrl=this to your constructor code in your controller,
  • change all of the references to $scope in your controller to ctrl,
  • add controllerAs vm to where you load your controller,
  • change all of the references to controller properties in your view to be prefixed with vm.

You may get lucky and this might just solve your problem, but either way, this is still a better way to work with controllers. (If you are on Angular 1.5+ I'd really remend you refactor this as a ponent before you get too far.)

I've changed it so it's only a boolean value and not paring it to a string. Are you ever setting $scope.mapStatus.loading to true anyway? Otherwise, it will never show as it'll always be false.

var myApp = angular.module('myApp', []);

function MyCtrl($scope) {
  $scope.mapStatus = {};

  $scope.hideMe = function() {
    $scope.mapStatus.loading = false;
  }

  $scope.showMe = function() {
    $scope.mapStatus.loading = true;
  }
}
<script src="https://ajax.googleapis./ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
  <div ng-controller="MyCtrl">
    <div class="tab-loading-container" ng-if="mapStatus.loading">
      <div class="tab-loading">Loading map</div>
    </div>
    <button ng-click="hideMe()">Hide Me</button>
    <button ng-click="showMe()">Show Me</button>
  </div>
</div>

(I post this answer in case someone es across this problem using angular v2+)

This problem could happen if the expression ngIf directive is referring to gets updated by a (asynchronous) call ing from outside angular zone. This won't cause change detection in angular, so your view won't be updated until next change detection happens. You can learn more about zones and change detection from these links: zones change detection

I came across this issue when I was integrating google sign in to my ponent. signInChangeListener is called by the 3rd party google library whenever the user signs in or out:

export class GoogleLoginService {
   signedIn: boolean = false;
   ...
   signInChangeListener(isSignedIn) {
       this.signedIn = isSignedIn;
   }
}

In my HTML I had:

<div *ngIf="signedIn"> ... </div>

I had to change my code so that the change is detected by angular:

import { Injectable, NgZone } from '@angular/core';

export class GoogleLoginService {
   signedIn: boolean = false;

   constructor(private ngZone: NgZone) { }
   ...
   signInChangeListener(isSignedIn) {
      // Run the value change inside angular 
      this.ngZone.run(() => {
         this.signedIn = isSignedIn;
      });
   }
}

If you are using John Papa's Controller As pattern with vm make sure you are using the name of the controller in the template and not vm:

controller: 'featureController',
controllerAs: 'featureCtrl'

Use vm.value in the Controller:

const vm = this;

vm.value = true;

Use featureController.value in the template:

<span>Here is the value: {{featureController.value}}</span>
发布评论

评论列表(0)

  1. 暂无评论