1

I'm using the angular2-wizard to fill in bank account details. On my second step I ask for account info (number, cvc, expiry date) and these elements are from Stripe.js using the ngx-stripe library to mount my elements. Then when I click to the next step and create my Stripe token createToken() I get this error:

IntegrationError: We could not retrieve data from the specified Element. Please make sure the Element you are attempting to use is still mounted.

My thoughts on why this is happening - Either the Angular2-wizard is removing the DOM elements between steps or I'm not waiting for step 2 (account info) to be rendered before I try and mount the elements. I see these posts that are similar here

  1. Angular 8: Stripe elements nested inside Nebular Components
  2. Please make sure the Element you are attempting to use is still mounted

QUESTIONS -

  1. How can I debug this to see if the elements have been removed or reset or hidden in the DOM, when creating my Stripe token?
  2. How can I wait or ensure the next step (step 2) has been fully rendered in the DOM before I mount my elements?

WHAT I TRIED -

  1. I checked the element "this.debitCardNumberElement" to see if it was still mounted by calling "this.debitCardNumberElement_isMounted()" from the Stripe library, right before I call createToken() and it seems like it still has values and is mounted. The variable had values, it did't have a null or undefined value, so I figure it's still mounted, but could be wrong.
  2. Looked to see if the step library had some emitter to let me know when the second step was finished rendering, but I don't think it does. So maybe there is another way to check?

Here is my example

showAccountFieldsAndMountElements($event) {
  // mount the 3 elements
  this.stripeService.elements(this.stripeElementsOptions).subscribe(elements => {
    this.elements = elements;
    // Only mount the elements the first time
    if (!this.debitCardNumberElement) {
      this.debitCardNumberElement = elements.create('cardNumber', this.debitCardNumberElementOptions);
      this.debitCardNumberElement.mount('#floatingDebitCardNumber');
      this.debitCardNumberElement.on('change', (event) => {
        this.cardNumberErrors = event.error ? event.error.message : null;
        this.cardNumberValid = event.complete;
      })
    }
    if (!this.debitCardExpiryElement) {
      this.debitCardExpiryElement = elements.create('cardExpiry', this.debitCardExpiryElementOptions);
      this.debitCardExpiryElement.mount('#floatingDebitCardExpiry');
      this.debitCardExpiryElement.on('change', (event) => {
        this.cardExpiryErrors = event.error ? event.error.message : null;
        this.cardExpiryValid = event.complete;
      })
    }
    if (!this.debitCardCvcElement) {
      this.debitCardCvcElement = elements.create('cardCvc', this.debitCardCvcElementOptions);
      this.debitCardCvcElement.mount('#floatingDebitCardCvc');
      this.debitCardCvcElement.on('change', (event) => {
        this.cardCvcErrors = event.error ? event.error.message : null;
        this.cardCvcValid = event.complete;
      })
    }
  });
}

createTokenAndShowFinalStep($event) {
  this.stripeService.createToken(this.debitCardNumberElement, data).subscribe((result) => {
    this.loadingAddress = false;
    if (result.token) {
      this.externalToken = result.token.id;
      this.initNext();
    } else if (result.error) {
      // Error creating the token
      console.log(result.error);
      this.alertService.danger(result.error.message);
    }
  });
}
<form-wizard>
  <wizard-step [title]="'Personal Info'" (onNext)="showAccountFieldsAndMountElements($event)">
    <h1>Step1</h1>
  </wizard-step>
  <wizard-step [title]="'Account Info'" (onNext)="createTokenAndShowFinalStep($event)">
    <h1>Step2</h1>
    <div class="form-floating mb-3">
      <div class="form-control stripe-form-control" id="floatingDebitCardNumber" placeholder="ex. 1111 1111 1111 1111"></div>
      <label for="floatingDebitCardNumber">Card Number</label>
      <div *ngIf="cardNumberErrors" class="invalid-feedback d-block" role="alert">
        {{cardNumberErrors}}
      </div>
    </div>

    <div class="row mb-md-3">
      <div class="col-md mb-3 mb-md-0">
        <div class="form-floating">
          <div class="form-control" id="floatingDebitCardExpiry" placeholder="ex. 11/23"></div>
          <label for="floatingDebitCardExpiry">Card Expiration</label>
          <ng-container *ngIf="cardExpiryErrors">
            <span class="text-danger">{{cardExpiryErrors}}</span>
          </ng-container>
        </div>
      </div>
      <div class="col-md">
        <div class="form-floating">
          <div class="form-control" id="floatingDebitCardCvc" placeholder="ex. 123"></div>
          <label for="floatingDebitCardCvc">Card CVC</label>
          <ng-container *ngIf="cardCvcErrors">
            <span class="text-danger">{{cardCvcErrors}}</span>
          </ng-container>
        </div>
      </div>
    </div>
  </wizard-step>
</form-wizard>
chuckd
  • 13,460
  • 29
  • 152
  • 331
  • To see whether an element is present in the DOM you can use a regular DOM inspector from DevTools – Sergey Mar 09 '23 at 06:05
  • 1
    You are calling `this.stripeService.createToken(tokenType, data)`. In the docs it says you should pass a card element to get its data from. – Sergey Mar 09 '23 at 06:11
  • I just made the correction. I was copying and pasting from my project. I am using Chrome debugger/dev tools to inspect. I just don't know if there is a way to dig into something using it I'm unaware of as I'm not an expert with it. I'm also using the debug window in VS Code. – chuckd Mar 09 '23 at 07:08
  • I thought about checking if the element is connected to the DOM. I was looking at something called node.isConnected. My bigger issue is to get the form stepper working in my Stackblitz here https://stackoverflow.com/q/75632961/1186050 If I can get this working, then I can replicate my bug in a new post on SO. – chuckd Mar 09 '23 at 07:11
  • By looking at the docs I'd say that there are 2 ways of working with it. 1 - you use provided card component which has all the elements inside, 2 - you provide all the data user entered via a second argument, but then you need to pass a correct 1st argument. Given the obscurity of the lib you could try to use it with map declarations and then find where the error is thrown from within the lib and try to debug it there. – Sergey Mar 09 '23 at 13:26
  • You are trying to use `this.debitCardNumberElement` as the element, but the lib rather expects a card element that has all the fields within it and it nows how to access it (i.e. an element generated by the lib with all the fields inside at once). In the docs they use `this.elements.create('card', this.cardOptions)` to create a card. This element is passed as a first argument afterwards. – Sergey Mar 09 '23 at 13:31
  • Looking at the source code of the stepper - it hides elements using CSS. So the elements are persisted in the DOM – Sergey Mar 09 '23 at 13:36
  • The best thing for me to do is get the form stepper (Angular2-wizard) working in a Stackblitz. I'm having trouble doing that and posted it here - https://stackoverflow.com/q/75632961/1186050 – chuckd Mar 09 '23 at 18:02
  • I'd consider using Angular Material CDK for stepper. They have great support and popularity. Will have a look at the question tho – Sergey Mar 10 '23 at 06:43
  • @Sergey Thanks for taking a look. I know Angular Material and it's decent, but I'm pretty invested in Angular2-wizard. I'm using it on like 7 different pages and it's been heavily styled. Currently I swapped out the custom components (number, exp. date, cvc) for the single card component that Stripe.js can create and it works, but doesn't have any floating labels (bootstrap), which is how all my forms are styled on the site. – chuckd Mar 10 '23 at 07:26

1 Answers1

0

Rendering of a simple component completes one tick (setTimeout(..., 0)) after model change. If there are other async elements (observables, promises etc.), sructural directives, routing - it will take progressivly more ticks. Debugging is tricky, you can use events or MutationObserver, breakpoints (debugger; statement) to inspect DOM state at each point in time and determine how exactly everything works, sequence of DOM changes.

kemsky
  • 14,727
  • 3
  • 32
  • 51