Reputation: 2434
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.com/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 comes 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:
Upvotes: 3
Views: 12304
Reputation: 3421
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>
Upvotes: 0
Reputation: 59168
(I post this answer in case someone comes across this problem using angular v2+)
This problem could happen if the expression ngIf
directive is referring to gets updated by a (asynchronous) call coming 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 component. 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;
});
}
}
Upvotes: 0
Reputation: 5176
I'm not sure if this will solve your issue or not, but before you get too far into this, I'd recommend 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 recommend that you:
var ctrl=this
to your constructor code in your controller,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 recommend you refactor this as a component before you get too far.)
Upvotes: 1
Reputation: 6739
I've changed it so it's only a boolean value and not comparing 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.com/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>
Upvotes: 0