0

I have an API call which ends in a boolean check. I'd like to create new users but if an e-mail is exists in the db i want to set canAddUser = false. I do the following:

  canAddUser: boolean;

  setNewUserValues() {
    if (this.formUser.email) {
      this.checkExistingEmail();
    }
  }

  checkExistingEmail() {
    this.userService.findPrivilegedUsersUsingGET().subscribe(users => {
      const mailIndex = users.findIndex(user => user.email === this.formUser.email);
      mailIndex !== -1 ? this.canAddUser = false : this.canAddUser = true;
      console.log(this.canAddUser);
    });
  }

If I log the boolean to the console where I am currently doing, I get the right value. However if I log it elsewhere it's undefined first! then if i trigger the button that fires setNewUserValues() it gets the value right again. What am I missing so badly?

EDIT

Somewhat solved my issue.

  setNewUserValues() {
    if (this.formUser.email) {
      this.checkExistingEmail();
    }
  }

  checkExistingEmail() {
    this.userService.findPrivilegedUsersUsingGET().subscribe(users => {
      this.mailIndex = users.findIndex(user => user.email === this.formUser.email);
    });
    this.validateEmail(this.mailIndex);
    console.log(this.canAddUser);
  }

  private validateEmail(index: number) {
    index !== -1 ? this.canAddUser = false : this.canAddUser = true;
  }

If I pass an e-mail that's existing i got the correct value but if i pass one that's not in the db yet i get false value first then if i trigger it once more then the value is fine again.

Exitl0l
  • 459
  • 2
  • 11
  • 27
  • What did you expect? `canAddUser: boolean;` means `canAddUser = undefined`. – Roberto Zvjerković Mar 06 '19 at 12:35
  • where did you console.log(), because your console.log() in other places might be executed before the value is assigned to the this.canAddUser – Nithya Rajan Mar 06 '19 at 12:35
  • Makes sense. You're not setting anything to your variable as the initial state thus undefined – penleychan Mar 06 '19 at 12:37
  • 1
    Well, your console log call is within the subscribe so it will be ran when it has finished getting the result from the API. until then, `canAddUser` is undefined. – umutesen Mar 06 '19 at 12:37
  • @BearNithi for example right after ** this.checkExistingEmail()** it gets the values wrong as I mentioned. – Exitl0l Mar 06 '19 at 12:37
  • yes that's undefined bcz, your `findPrivilegedUsersUsingGET` is a service call, it's asynchronous. so before it returns the response. the `console.log` after the `checkExistingEmail` executes. so it's undefined – Nithya Rajan Mar 06 '19 at 12:39
  • @UmutEsen but why is it still undefined or not correct if i log `canAddUser` after the function that handles the value assign? – Exitl0l Mar 06 '19 at 12:40
  • 1
    Because it's not "after" the handling of the function. It's after *calling* of the function. Observables are asynchronous and you need to subscribe to them. They take time to call the callback. https://angular.io/guide/observables – Roberto Zvjerković Mar 06 '19 at 12:42
  • @AlexBene, Make use of `Observable` or `Subject` or `BehaviorSubject` instead. – Arpit Kumar Mar 06 '19 at 12:47
  • @ArpitMeena Can you give me a short example to better understand? – Exitl0l Mar 06 '19 at 12:49
  • can not use async values synchronously, this may help you https://stackoverflow.com/questions/39405962/angular2-set-variable-on-subscribe-not-working – ashish pal Mar 06 '19 at 12:54

4 Answers4

2

You can not guarantee to have your flag already assigned to anywhere in your code if you don't subscribe the async operation.

What I would do is change the boolean flag to the Subject:

canAddUser$: Subject<boolean>;

Emit the value when needed:

mailIndex !== -1 ? this.canAddUser$.next(false): this.canAddUser$.next(true);
// this.canAddUser$.next(mailIndex === -1);

And when you need it, subscribe to it:

someFunction() {
    this.canAddUser$.subscribe(canAdd => {
        // Here *canAdd* is a boolean
    })
}
Roberto Zvjerković
  • 9,657
  • 4
  • 26
  • 47
0

Your findPrivilegedUsersUsingGET function is asynchronous. This means it can take a few seconds or even minutes to complete. canAddUser will only be true or false at the end of the async call.

canAddUser: boolean; // undefined 


// this can take a while to complete.
this.userService.findPrivilegedUsersUsingGET().subscribe(users => {
      const mailIndex = users.findIndex(user => user.email === this.formUser.email);
      mailIndex !== -1 ? this.canAddUser = false : this.canAddUser = true;

      // do work here          
      console.log(this.canAddUser);
      this.doWork();
});

doWork() {
     console.log(this.canAddUser);
}
umutesen
  • 2,523
  • 1
  • 27
  • 41
  • I got that what is async I am interested in how to solve this. So the value of canAddUser will be correct not just within the subscribtion but outside of it. – Exitl0l Mar 06 '19 at 12:45
  • The value of canAddUser will be correct outside the subscription **once** asynchronous work is complete. You can write a function and call that from within the subscribe, if you don't want to have so many lines of code in the subscribe. – umutesen Mar 06 '19 at 12:53
0

Basic answer, but you could just initialize your canAddUser to whatever you want be the default.

canAddUser = false; for example.

Askirkela
  • 1,120
  • 10
  • 20
0

You can eagerly load the list of users, and then pass the users to your component's functions via the template.

In your component:

public users$: Observable<any[]>;

public ngOnInit() {
    this.users$ = this.userService.findPrivilegedUsersUsingGET().pipe(share());
}

setNewUserValues(users: any[]) {
    if (this.formUser.email) {
        this.checkExistingEmail(users);
    }
}

checkExistingEmail(users: any[]) {
   this.mailIndex = users.findIndex(user => user.email === this.formUser.email);
   this.validateEmail(this.mailIndex);
   console.log(this.canAddUser);
}

private validateEmail(index: number) {
   index !== -1 ? this.canAddUser = false : this.canAddUser = true;
}

In your template:

<ng-container *ngIf="users$ | async as users">
    <button (click)="setNewUserValues(users)">Example</button>
</ng-container>
<ng-container *ngIf"!(users$ | async">
    Loading users...
</ng-container>

That is one way to remove the side-effects of mixing procedural programming and reactive programming.

The other approach is to make checkExistingEmail() return an observable (or a Promise).

public constructor(private _change: ChangeDetectorRef) {}

setNewUserValues() {
  if (this.formUser.email) {
     this.checkExistingEmail(this.formUser.email).subscribe(canAddUser => {
         console.log("canAddUser:", canAddUser);
         this.canAddUser = canAddUser;
         this._change.markForCheck();
     });
  }
}

public checkExistingEmail(email): Observable<boolean> {
   return this.userService.findPrivilegedUsersUsingGET().pipe(
      map(users => users.findIndex(user => user.email === email) !== -1)
   );
}

The above will print the boolean result to the console, but I don't know what you want to do with this result. The operation is completed async and we need to tell Angular that the state of the component has changed, and the view should be updated so I call "markForCheck()", but if you aren't using OnPush change detection then this might not be required.

As a general rule. If you write code like this.XXXX = YYYY; inside an async callback while inside a component. You're going to run into side-effects, because you are mixing procedural programming with reactive programming.

Often people will ask the question "how do I return a value from an async function". I often cringe when I see their example source code using the this reference to update a property.

Some further reading about reactive programming might help:

https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

https://egghead.io/courses/introduction-to-reactive-programming

It's a good idea to refresh yourself on the subject of reactive forms in Angular:

https://angular.io/guide/reactive-forms

Reactgular
  • 52,335
  • 19
  • 158
  • 208