angular-ui / bootstrap

PLEASE READ THE PROJECT STATUS BELOW. Native AngularJS (Angular) directives for Bootstrap. Smaller footprint (20kB gzipped), no 3rd party JS dependencies (jQuery, bootstrap JS) required. Please read the README.md file before submitting an issue!
http://angular-ui.github.io/bootstrap/
MIT License
14.29k stars 6.73k forks source link

reject modals with Error objects #6495

Open graingert opened 7 years ago

graingert commented 7 years ago

Bug description:

Now that 1.6.3 has been released, I'm getting a few difficult to diagnose errors in my application due to unhandled rejections missing their stack. I've discovered that a few of these are due to $uibModal by grepping my compiled application bundle.

Link to minimally-working plunker that reproduces the issue:

N/A

Steps to reproduce the issue:

create a uibModal and click the backdrop or change route. It will cause a stackless error message to be logged to the console. And worse logged to sentry.

Version of Angular, UIBS, and Bootstrap

Angular: 1.6.3

UIBS: ^1.3.2

Bootstrap: 3 (N/A)

bastienmoulia commented 7 years ago

Maybe we can change the .dismiss by a .close for $modalStack.dismiss(modal.key, 'backdrop click'); and the other dismiss (Esc.)

Otherwise it is necessary to add a catch at each opening of modal.

this.$uibModal.open({
   ...
}).result.catch(angular.noop);
HappyHappy1996 commented 7 years ago

I have had the same issue after I updated libraries. Please fix this warning. Don't wan't to write empty handlers or stubs at many places. It also logs an error if you click escape after modal is opened.

graingert commented 7 years ago

This should not really be managed by promises at all. Results should be passed up using '&' component callbacks or model two way binding only.

graingert commented 7 years ago

I can't see how any of these rejections are actually useful, so I ignored them all:

import _ from 'lodash';

const pointlessRejections = new Set([
  'escape key press',
  'backdrop click',
  '$uibUnscheduledDestruction',
]);

export const isPointlessRejection = Symbol('isPointlessRejection');

const promises = ['result', 'opened', 'closed', 'rendered'];

function catchRejection(rejection) {
  if (pointlessRejections.has(rejection)) {
    return isPointlessRejection;
  }

  throw rejection;
}

function process(modalInstance) {
  const updates = _(promises)
    .map(p => [p, modalInstance[p].catch(catchRejection)])
    .fromPairs()
    .value();

  return { ...modalInstance, ...updates };
}

function decorateUibModal($delegate) {
  function open(...args) {
    return process($delegate.open(...args));
  }

  return { ...$delegate, open };
}

decorateUibModal.$inject = ['$delegate'];

export default function ignoreSpuriousModalRejections($provide) {
  $provide.decorator('$uibModal', decorateUibModal);
}

ignoreSpuriousModalRejections.$inject = ['$provide'];

use it with:

import angular from 'angular';
import uiBootstrap from 'angular-ui-bootstrap';
import ignoreSpuriousModalRejections from './ignoreSpuriousModalRejections';

const MODULE_NAME = 'your.project.namespace.uib';
angular.module(MODULE_NAME, [uiBootstrap])
  .config(ignoreSpuriousModalRejections);

export default MODULE_NAME;