Show modal based on localStorage variable

hi, setting up authentication and want to save a auth_token into localStorage. If that token is null I want a modal to come up forcing them to register/login.

The Modal documentation tells you how to call a modal by ng-click=“openModal()” but how do I call in a script. I already tried adding the modal code to my AppCtrl and the below to the app.html template, which throws no errors but doesn’t make the modal show.

<nav-page id="app-page" ng-controller="AppCtrl">
  <script>
    if (!localStorage.auth_token) {
      $scope.modal.show();
    }
   </script>
.controller('AppCtrl', function($scope, Modal) {
// Main app controller, empty for the example

   Modal.fromTemplateUrl('templates/login.html', function(modal) {
     $scope.modal = modal;
   }, {
     // Use our scope for the scope of the modal to keep it simple
     scope: $scope,
     // The animation we want to use for the modal entrance
     animation: 'slide-in-up'
   });

  $scope.openModal = function() {
    $scope.modal.show();
  };
  $scope.closeModal = function() {
     $scope.modal.hide();
  };
})

Hey @ajbraus, in this case, I recommend using the $rootScope and triggering/listening for an authentication event which you can then use to pop open your modal.

So, you might have something like this (untested):

angular.module('myapp', ['ionic'])

.factory('Auth', function($rootScope) {
  return {
    checkLogin: function() {
      // Check if logged in and fire events
      if(this.isLoggedIn()) {
         $rootScope.$broadcast('app.loggedIn'); 
      } else {
        $rootScope.$broadcast('app.loggedOut'); 
      }
    },
    isLoggedIn: function() {
      // Check auth token here from localStorage
    },
    login: function(user, pass) {
      // Do the login
     // When done, trigger an event:
     $rootScope.$broadcast('app.loggedIn'); 
   },
   logout: function(user, pass) {
     // Same thing, log out user
     $rootScope.$broadcast('app.loggedOut');
   }
}) 

.controller('MyCtrl', function($scope, Auth) {
  $scope.$on('app.loggedOut', function(e) {
    // Show the modal here
  });

  Auth.checkLogin();
});

Does that help? Not sure this is the best solution but it’s one way to do it.

1 Like

Edit: I opened a new more detailed topic for this: Automatically open Modal

// Show modal here

that’s actually the part I’m interessed in! :smile:

I can’t seem to get my modal to open from within the controller. Here’s what I’ve got:

app.controller('AppCtrl', function ($scope, User, Modal) {
    
    Modal.fromTemplateUrl('templates/modal.html', function (modal) {
        $scope.modal = modal;
    }, {
        scope: $scope,
        animation: 'slide-in-up'
    });
    
    $scope.openModal = function() {
        $scope.modal.show();
    };
    
    $scope.closeModal = function() {
        $scope.modal.hide();
    };

    $scope.$on('user.notAuthorized', function(e) {
          /**
           * I can't get the modal to open from here. How would I call it?
           * All of the following doesn't work:
           * $scope.openModal();
           * $scope.modal.show();
           * this.openModal();
           */
    });
    
    User.isAuthorized();
});

Any help is highly appreciated!

@daniel,

Is there any gap in execution between User.isAuthorized and the user.notAuthorized callback? I’m just curious if $scope.modal is still null when $scope.onModal is called.

Can I see the markup for your Modal HTML?

@max
Not sure what you mean with $scope.onModal. I don’t think I have that…?

Here’s my modal template:

<div class="modal" ng-controller="ModalCtrl">
    <header class="bar bar-header bar-dark">
        <h1 class="title">Welcome</h1>
    </header>
    
    <content has-header="true" padded="true" class="cards-bg" scroll="false">
        <div class="card">
            <div class="item item-divider assertive" ng-show="errorMessage">
                {{errorMessage}}
            </div>
            <div class="item item-text-wrap">
                <form name="login" class="list list-inset" ng-submit="submit(User.email, User.password)">
                    <label class="item item-input">
                        <input type="email" placeholder="Email" required ng-model="User.email">
                    </label>
                    <label class="item item-input">
                        <input type="password" placeholder="Password" required ng-model="User.password">
                    </label>
                    <button type="submit" class="button button-block {{buttonClass}}" ng-disabled="buttonDisabled">
                        <i class="icon ion-loading-c" ng-show="submitting"></i>
                        {{buttonText}}
                    </button>
                </form>
            </div>
        </div>       
    </content>
</div>

I have a problem with modal when opening it from a mobile … it shows the modal structure without the data from the local storage. N.B when opening it from PC browser it works perfectly …

app.js controller

// Open the modal
  $scope.showTask = function() {
  	$scope.taskModal.show();
  	var offerId= event.target.id;
	getOfferDetails(offerId);
  };

the function called before.

function getOfferDetails(offerId){
      var offers= JSON.parse(window.localStorage.getItem('offers'));
      var offer= offers[offerId];

      document.getElementById("headerBrandName").innerHTML=offer.brancheId.brandId.name;
      document.getElementById("offerBrandName").innerHTML=offer.brancheId.brandId.name;
      document.getElementById("brandLogo").src=offer.brancheId.brandId.logoLink;
      document.getElementById("offerBrandDescription").innerHTML=offer.brancheId.brandId.description;
    			    	
      document.getElementById("offerImage").src=offer.pictureLink;
      document.getElementById("offerNoOfLikes").innerHTML=offer.noOfLikes;
    			    	
      document.getElementById("offerDescription").innerHTML=offer.description;
 }

am i missing something?!
Thanks for your help …

So I got this to work. A bit of a different structure, putting the login and logout as custom $resource actions in the User service. My problem now is hiding the login modal after the successful handshake with the server and storing the auth_token.

What can I call in the SessionCtrl to make it close its own template - the modal “welcome.html” that I called in AppCtrl?

$scope.modal.hide(); does not work - probably because the modal is in a higher order scope.

As long as the parent scope has the modal in it, $scope.modal.hide() should do the trick. Can you post a sample?

In my app_controler.js I have this call before anything else happens to check if the user is logged in.

AuthService.checkLogin();    
$scope.$on('app.loggedOut', function(e) {
  Modal.fromTemplateUrl('templates/welcome.html', function(modal) {
    $scope.welcomeModal = modal;
    $scope.welcomeModal.show();
  }, {
    scope: $scope,
    animation: 'slide-in-up'
  });
});

the Welcome modal is governed by SessionCtrl and in that controller I manage signing_in with email and password:`

$scope.login = function() {
  User.sign_in({ email: $scope.user.email, password: $scope.user.password },
    function(data) {
      //set some variables here      
      $rootScope.$broadcast('app.loggedIn');
      $scope.modal.hide();
      // $scope.welcomeModal.hide();
    },
    function(error) {
      alert(error.error)          
    }
  );
};

$scope.modal.hide() or $scope.welcomeModal.hide(); both case this error

Cannot call method 'hide' of undefined

I can see with my AngularJS chrome plugin that welcomeModal is defined at some level of the scope. I just dont’ know how to access it from a different controller.

Here is a working sample:

After 500msec, it will check the AuthService. If not logged in, it will open modal. in the modal, click the button and it will log you in. Then, the modal automatically closes.

These codepen examples are great! Thanks for your help.

So it looks like the main difference here is to keep all the methods that I call from the modal inside the parent controller.

I reworked things a bit but it still won’t recognize $scope.modal even in the same controller where the modal is set into $scope

AuthService.checkLogin(); // => broadcasts either "app.loggedIn" or "app.loggedOut"

// Load the modal from the given template URL
Modal.fromTemplateUrl('templates/login_modal.html', function(modal) {
  $scope.modal = modal;
}, {
  scope: $scope,
  animation: 'slide-in-up'
});

$scope.$on('app.loggedIn', function(event) {
  console.log('LOGGED IN!');
  $scope.modal.hide();
});

$scope.$on('app.loggedOut', function(event) {
 console.log('NOT LOGGED IN!');
 $scope.modal.show();
});

Logs “NOT LOGGED IN” but shows Error - TypeError: Cannot call method ‘show’ of undefined

Why don’t you fork my example, put your code in and then let me know. I can’t do more without seeing the actual problem as I’m not able to recreate it.

Great idea, here is what I got. I don’t see why the modal does not show $on(“app.loggedIn”)
http://cdpn.io/GHAgC

You’ve got quite a few bugs in the demo.

In your "AuthService’ , you’re injecting a User service that does not exists.

Modal.fromTemplateUrl('templates/login_modal.html : That template does not exist. Change it to : Modal.fromTemplateUrl('templates/modal.html

You have to wrap the login check in a timeout. Otherwise, the modal will not be added to the scope before the broadcast occurs:

        $timeout(function() {
            AuthService.checkLogin();
        }, 1000);

Here is a slightly modified example : http://codepen.io/calendee/pen/mKJez

It doesn’t close because I didn’t adjust your AuthService. My original example does close.

So I decided the paradigm was not right in general so I switched to having my fall-back urlRoute be “/login” and then in the login controller I run AuthService.checkLogin(); immediately and if successful then I just forward the user to the home page.

Thanks!

Well this is a bit late but incase others run into this issue, you can use resolve in your state and redirect from there without touching the controller or displaying a modal. If you have a User Authentication service similar to above you can simply do this:

.state('tab.main-view', {
    url: '/main',
    resolve: {
        redirect: function(User, $location) {
            if (!User.isLoggedIn()) {
                $location.path('/login');
            }
        }
    },
    views: {
        'main-tab': {
            templateUrl: 'templates/main.html',
            controller: 'MainCtrl'
        }
    }
})

and have your login page like any other tab.

1 Like

I’m unable to get the redirect working from aliyaka’s example. I even tried this and the redirect goes to a blank Dashboard page.

.state('tab.friends', {
  url: '/friends',
  resolve: {
    redirect: ['Auth', '$stateParams', '$location', function(Auth, $location, $stateParams) {
        if (!Auth.isLoggedIn()) {
            console.log('ROUTE_TO_ACCOUNT');
            $location.path('/account');
        }
    }]
  },
  views: {
    'tab-friends': {
      templateUrl: 'templates/tab-friends.html',
      controller: 'FriendsCtrl'
    }
  }
})

.state('tab.charts', {
  url: '/charts',
  views: {
    'tab-charts': {
      templateUrl: 'templates/tab-charts.html',
      controller: 'ChartsCtrl'
    }
  }
})

.state('tab.friend-detail', {
  url: '/friend/:friendId',
  views: {
    'tab-friends': {
      templateUrl: 'templates/friend-detail.html',
      controller: 'FriendDetailCtrl'
    }
  }
})

.state('account', {
  url: '/account',
  templateUrl: 'templates/account.html',
  controller: 'AccountCtrl'
})
// if none of the above states are matched, use this as the fallback


$urlRouterProvider.otherwise('/tab/dash');

I am not sure, your code looks correct and should work. I am not using this approach anymore though. https://github.com/fnakstad/angular-client-side-auth checkout this project. It is a cleaner/more robust solution.

3 Likes