TL;DR
$timeout(function () { // wait 'till the button exists
const elem = $('.button-yes')[0];
elem.setAttribute('ng-disabled', 'MyFunction()'); // set the attribute
$compile(elem)(angular.element(elem).scope()); // Angular-ify the new attribute
});
Live demo: working plunk
Introduction
That problem you're encountering, it's a real one, and it has apparently been for years.
Here's the latest version of the code used by $ionicPopup
(last updated in December 2015)
This template is the one used by your Ionic-1 popups (from the first lines of the code linked above):
var POPUP_TPL =
'<div class="popup-container" ng-class="cssClass">' +
'<div class="popup">' +
'<div class="popup-head">' +
'<h3 class="popup-title" ng-bind-html="title"></h3>' +
'<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
'</div>' +
'<div class="popup-body">' +
'</div>' +
'<div class="popup-buttons" ng-show="buttons.length">' +
'<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +
'</div>' +
'</div>' +
'</div>';
There's one line in particular that's interesting to us: the button template:
<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>
As you can see, there's just no built-in way to alter its button's attributes.
Two approaches
From here, you've got two fixes:
- We can contribute to their project on GitHub, implement the missing functionality, write the tests for it, document it, submit an issue, a Pull Request, ask for a newer version to be released and use the newer version.
This is the ideal solution, 'cause it fixes everyone's problems forever. Although, it does take some time. Maybe I'll do it. Feel free to do it yourself though, and tag me, I'll +1 your PR
- Write a dirty piece of code that monkey-patches your specific problem in your specific case
This isn't ideal, but it can be working right now.
I will explore and expand on the (quick 'n dirty) option #2 below.
The fix
Of the 3 things you've tried so far:
- the first one is simply not a thing (although it could be if we implement it, test it, document it and release it)
- the third one is rather unmaintainable (as you know)
That leaves us with the second thing you mentioned:
Adding the attribute after the popup was created, using JavaScript
The problem is that the setAttribute()
method is executed before the popup is actually shown, so I would need a way to detect when the popup is open, and execute the method only then.
You're right, but that's only part one of a two-fold problem.
Part 1: The button isn't created yet
Actually, you can delay that call to setAttribute
to later, when the popup is shown. You wouldn't wanna delay it by any longer than would be noticeable by a human, so you can't reasonably go for anything longer than 20ms.
Would there be some callback when the popup is ready, we could use that, but there isn't.
Anyways, I'm just teasing you: JavaScript's "multi-tasking" comes into play here and you can delay it by 0 millisecond!
In essence, it has to do with the way JS queues what it has to do. Delaying the execution of a piece of code by 0ms puts it at the end of the queue of things to be done "right away".
Just use:
setTimeout(function () {
$(".button-yes")[0].setAttribute("ng-disabled", "MyFunction()");
}, 0); // <-- 0, that's right
And you're all set!
Well, you do have a button whose ng-disabled
attribute indeed is "MyFunction()"
. But it's not doing anything...
So far, you simply have an HTML element with an attribute that doesn't do anything for a simple HTML button: Angular hasn't sunk its teeth into your new DOM and hooked itself in there.
Part 2: Angular isn't aware of the new attribute
There's a lot to read here about this, but it boils down to the following: Angular needs to compile your DOM elements so that it sets things in motion according to your Angular-specific attributes.
Angular simply hasn't been made aware that there's a new attribute to your button, or that it should even concern itself with it.
To tell Angular to re-compile your component, you use the (conveniently named) $compile
service.
It will need the element to compile, as well as an Angular $scope
to compile it against (for instance, MyFunction
probably doesn't exist in your $rootScope
).
Use it once, like so:
$compile(/* the button */ elem)(/* the scope */ scope);
Assuming the following element is your button:
const elem = $(".button-yes")[0];
... you get its actual scope through its corresponding Angular-decorated element thingy:
const scope = angular.element(elem).scope();
So, basically:
const elem = $('.button-yes')[0];
elem.setAttribute('ng-disabled', 'MyFunction()');
$compile(elem)(angular.element(elem).scope());
Tadaaa! That's it!
... sort of. Until there's some user interaction that would alter the corresponding $scope
, the button is actually not even displayed.
Bonus Part: Avoid $scope.$apply()
or $scope.$digest()
Angular isn't actually magically picking up things changing and bubbling it all to the right places. Sometimes, it needs to explicitly be told to have a look around and see if the elements are in sync with their $scope
.
Well, more specifically, any change that happens asynchronously won't be picked up by itself: typically, I'm talking about AJAX calls and setTimeout
-delayed functions. The methods that are used to tell Angular to synchronise scopes and elements are $scope.$apply
and $scope.$digest
... and we should thrive on avoiding them :)
Again, there's lots of reading out there about that. In the meantime, there's an Angular service (again), that can (conceptually, it's not the literal implementation) wrap all your asynchronous code into a $scope.$apply()
-- I'm talking about $timeout
.
Use $timeout
instead of setTimeout
when you will change things that should alter your DOM!
Summing it all up:
$timeout(function () { // wait 'till the button exists
const elem = $('.button-yes')[0];
elem.setAttribute('ng-disabled', 'MyFunction()'); // set the attribute
$compile(elem)(angular.element(elem).scope()); // Angular-ify the new attribute
});
Live demo: working plunk