Watching for when the state changes in the AngularJS framework using the `$scope.$on('$stateChangeStart')` and

My AngularJs application has the functionality to detect a change in state (using ui.router) and prompt the user to save any unsaved changes. Currently, I am utilizing a confirm dialog for this task:

$scope.$on('$stateChangeStart', () => {
    if (self.changed && confirm('There are unsaved changes. Do you want to save them?'))
        this.save();
});

I have been looking into switching to using the $modal dialog from the bootstrap UI library. However, I encountered an issue where the $modal.open() call is asynchronous and returns immediately, causing the state to change before the dialog can open.

$scope.$on('$stateChangeStart', () => {
    if (self.changed)
       this.$dialog.open({...}).result.then(()=>{
            this.save();
        });
});

I am wondering if there is a solution to prevent this problem or if I should stick with the plain JavaScript confirm dialog?

Answer №1

In tackling the issue at hand, I devised a solution within my application that involves utilizing an AppCtrl (parent) to manage navigation and dirty state concerns.

    function AppCtrl($rootScope, events, modalDialog) {
       var vm = this,
           handlingUnsavedChanges = false;

       function isDirty() {
            return $rootScope.$broadcast(events.CAN_DEACTIVATE).defaultPrevented;
       }

       function onStateChangeStart(event, toState, toParams) {
            if (handlingUnsavedChanges) {
                // continue with state change if dirty state has already been checked
                return;
            }

            // check for dirty state
            if (isDirty()) {
                // prevent navigation
                event.preventDefault();
                modalDialog
                    .confirmNavigation()
                    .then(function () {
                        // ignore changes
                        handlingUnsavedChanges = true;
                        $state.go(toState.name, toParams);
                    });
            } 
            // Otherwise allow state change
        }

        $rootScope.$on('$stateChangeStart', onStateChangeStart);
    }

<div ng-controller="AppCtrl as app">
   <div ui-view />
</div>

Subsequently, you can incorporate an event handler for the CAN_DEACTIVATE event within your route controller to assess dirty states:

    function UserDetailCtrl($scope, events) {
        function isDirty() {
            // Implement your logic to return a boolean value
        }

        function canDeactivate(e) {
            if (isDirty()) {
                e.preventDefault();
            }
        }

        $scope.$on(events.CAN_DEACTIVATE, canDeactivate);
    }

Answer №2

Using ui-router and configuration settings in the run section of your app is a great way to achieve this.

The key element here is monitoring the

$rootScope.$on('$stateChangeStart')
event. I have provided a detailed solution on Plunker: http://plnkr.co/edit/RRWvvy?p=preview. The core code can be found in scipt.js, and it looks like this:

routerApp.run(function($rootScope, $uibModal, $state) {
  $rootScope.modal = false; // Setting this ensures that the modal is not constantly loaded, skipping the initial application entrance

  $rootScope.$on('$stateChangeStart',
    function(event, toState, toParams, fromState, fromParams) {

      if ($rootScope.modal) {
        event.preventDefault(); // Stops the transition
        $rootScope.modal = false;
        var modalInstance = $uibModal.open({
          templateUrl: 'modal.html'
        });

        modalInstance.result.then(function(selectedItem) {
          console.log('Changing state to:' + toState.name);
          $state.go(toState, {}, {reload:true});
        }, function() {
          console.log('Going back to state:' + fromState.name);
          $state.go(fromState, {}, {reload:true});
        });
      } else {
        $rootScope.modal = true;
      }
    });
});

Answer №3

To handle unsaved changes, it is important to listen for the $locationChangeStart event in your Angular application. If there are unsaved changes present, make sure to use event.preventDefault() before proceeding with your confirmation dialog using ui-bootstrap modal. Keep in mind that the order of events may vary in different versions of Angular, so relying on the $stateChangeStart event might not be sufficient. I can provide a working example if you run into any issues. Here's a snippet of code to get you started:

$scope.$on('$locationChangeStart', () => {
    if (self.changed) {
       event.preventDefault();
       this.$dialog.open({...}).result.then(()=>{
            this.save();
            // Add logic here for redirection after saving
        });
    }
});

Answer №4

If you are looking to handle state change events, it is recommended to implement the event on the app and utilize the $modal service. For a more detailed explanation, you can refer to the following link:

While the example provided works for all state changes, you have the option to customize it for specific states as shown below:

app.run(function ($rootScope) {

  $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
  if(toState.name =="desiredState") {
  event.preventDefault();
  // add your custom logic here
  }
  });

});

Answer №5

As I am not very familiar with utilizing the broadcast feature, I adopted a different method.

Instead of using it, I decided to cancel the current event (state change) before displaying my JQuery Dialog. If the user chooses 'yes', then I trigger $state.go. If they choose 'cancel/no', we do not need to take any action as we have already canceled the event.

$rootScope.$on('$stateChangeStart',function (event, toState, toParams, fromState, fromParams, options) {

    console.log('$stateChangeStart- fromState= '+fromState.name +'-toState= '+toState.name);

    /*When a user attempts to change the state, a confirmation dialog is displayed
     * Selecting 'Yes' will proceed with the state change and enter pause mode for collection.
     * Choosing 'No' will halt the state change process.
     */
    if (/*some condition*/) {                    

        /*Avoids an additional cycle of listening to stateChangeStart
        when stateChangeStart gets triggered again by $state.go initiated from our modal dialog*/
        if (service.stateChangeTriggeredByDialog) {
            service.stateChangeTriggeredByDialog = false;
            return;
        }

        if (fromParams.paramName !== toParams.paramName) {
            event.preventDefault();                        

            Dialog.confirm({
                dialogClass: 'my-customDialog',
                template: $translate.instant('STATE_CHANGE_MSG'),
                resizable: false,
                width: 550                            
            }).then(function () {   
                console.log('User chose to continue with the state change');  
                service.stateChangeTriggeredByDialog = true;
                $state.go(toState, toParams, { inherit: false });
            }, function () {
                console.log('User chose to stop the state change');                            
            });                                            
        }
    }});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Disable inline imports when implementing an interface in vscode by selecting the "Implement interface" option

When using TypeScript, if I perform an auto-fix on a class name by selecting "Implement interface", it will generate the methods with inline imports like this: getInbox(): Observable<import('../../model/Message').Interactions[]> { t ...

What are some ways to leverage a promise-returning callback function?

Here is a function that I have: export const paramsFactory = (params: paramsType) => { return ... } In a different component, the same function also contains await getPageInfo({ page: 1 }) after the return .... To make this work, I need to pass a cal ...

Unlocking the parent scope in a custom attribute directive

I am new to utilizing angular js. I came across a similar inquiry on How to access parent scope from within a custom directive *with own scope* in AngularJS? However, the solution provided did not work for my basic test. Here is the sandbox. http://jsfi ...

Having trouble updating properties of child components in Angular

I have a data filtering functionality where I enter values in a filter popup and successfully retrieve results. I then store this data in local storage to retain it when navigating back from another page. However, upon returning to the filter component, I ...

Launch a bootstrap modal using jQuery, showcasing a designated HIDDEN section (excluding nav-tabs)

This unique modal box does not have a visible tab. Instead, it utilizes the href attribute for navigating to other pages. <div id="bootmodal" class="modal fade" tabindex="-1" data-width="370" data-backdrop="static" data-keyboard="false" style="display: ...

Error: Attempted to access keys from a non-object value. - Gruntfile.js

Can anyone help me resolve the following issue: Error Message: PhantomJS 1.9.8 (Windows 7) ExampleController should query the webservice FAILED Error: [$injector:modulerr] Failed to instantiate module TestWebApp due to: TypeError: Requeste ...

MERN Stack: Efficient File Uploads

After recently delving into programming with the MEAN Stack, I am in the process of building a social network using the MEAN.io framework. My current challenge lies in getting file uploads to function correctly. I aim to receive files from forms within the ...

Whenever I try to send an email in Node.js, I encounter 404 errors. Additionally,

I have an Angular application with a form that makes AJAX requests. Emailing works fine, but no matter what I set the response to, I get an error for the path '/send'. I assume Node.js expects the path '/send' to render a template or da ...

Utilizing a string as an argument in a function and dynamically assigning it as a key name in object.assign

Within my Angular 5 app written in TypeScript, I have a method in a service that requires two arguments: an event object and a string serving as the key for an object stored in the browser's web storage. This method is responsible for assigning a new ...

Troubleshooting problems with data binding in Angular Ionic

Just starting out with Angular and experimenting with building an app in Ionic. I have a screen with 2 input fields and I want to achieve the following. When a user inputs something in the price field, I want the weight field to update accordingly. Simil ...

What is the best way to determine if a user is currently in a voice channel using discord.js?

Is there a way for me to determine if a user is currently linked to a voice channel? I am trying to implement a command that allows me to remove a user from a voice channel, and here is how I am attempting to check: const user: any = interaction.options.ge ...

Using jquery mobile to implement an event handler for the close button on a page dialog

Is it possible to trigger a callback before closing a dialog-style page when the close button is clicked? I want to catch and handle the click event of the close button on the dialog page. <div data-role="page" id="page1"> <div data-role="he ...

Three.js - spinning texture applied to spherical shape

Would it be possible to rotate a loaded texture on a sphere geometry in Three.js without rotating the object itself? I am seeking a way to rotate just the texture applied to the material. Imagine starting with a sphere like this: https://i.sstatic.net/Ol3y ...

Trouble getting the ng-hide animation to work on the md-button element

I have implemented a basic CSS animation effect: .sample-show-hide { -webkit-transition: all linear 1.5s; transition: all linear 1.5s; } .sample-show-hide.ng-hide { opacity: 0; } .sample-show-hide.ng-show { opacity: 1; } My attempt to a ...

Closing the Angularstrap dropdown when clicking elsewhere

Is there a method to close an angularstrap dropdown only when clicking outside of it? The current behavior is that it closes when you click inside the dropdown. Thank you ...

What is the best way to invoke a class function within a static object?

Here's an example for you: export class MyClass { myString: string; constructor(s: string) { this.myString = s; } myFunction() { return "hello " + this.myString; } } export class MainComponent { static object1: MyClass = JSON. ...

When using npm bootstrap in a React project, the JavaScript features may not function properly

I am currently working on a React project and I needed to add a navbar. To achieve this, I installed Bootstrap using the following command: npm install bootstrap After installation, I imported Bootstrap into my index.js file like so: import 'bootstra ...

AngularJS IP Address masking

Looking for an IP address mask plugin that works with AngularJS. I attempted to use the "Jquery Input IP Address Control" plugin, but it wasn't functioning properly. The problem I encountered was that the "ngModel" attribute wasn't capturing the ...

The data from Restangular is currently being divided

RestService.one('suppliers', 'me').getList('websites').then( (data) -> $scope.websites = data $scope.websites.patch() ) I'm conducting a quick test with this code snippet. When mak ...

Ways to pass scope between the same controller multiple times

There is a unique scenario in which I have a controller appearing in 2 different locations on a page. This arrangement is necessary for specific reasons. To illustrate, the simplified HTML structure is as follows: <aside ng-if="aside.on" ng-controller ...