Access 'this' Inside Promise
Asked Answered
S

4

8

In the typescript function below, 'this' doesn't resolve to the instance of EmailValidator. How can I correct this function so it resolves to the correct instance of EmailVaildator and in turn, so that I can access _registerServices?

class EmailValidator {

    constructor(private _registerServices: RegisterServices) { }

    isAvailable(c: AbstractControl): Promise<ValidationResult> {
        let q = new Promise((resolve, reject) => {
            this._registerServices.emailIsAvailable(antiForgeryToken(), c.value)
                .then(result => {
                    // Need to actually check the result.
                    resolve({ "emailtaken": true })
                },
                error => {
                    // Need to communicate the server error? Probably not.
                    resolve({ "servererror": true })
                });
        });

        return q;
    }
}
Sidonius answered 18/5, 2016 at 0:32 Comment(2)
Hmm. It looks like the fat arrow should already be doing that. And looking at the generated Javascript, it does seem to alias this properly. Are you sure that's the issue you are seeing?Orthopter
@Orthopter I've since discovered the problem is a bit hidden and that the issue was elsewhere. I've discovered how to correct my issue, but there are details around 'why' the problem occurred that I'd really appreciate some guidance on. I'll post my solution.Sidonius
O
9

You are losing this, because you are passing around the isAvailableEmail as a "raw" function here:

email: ['', Validators.required, this._emailValidator.isAvailableEmail]

You can fix this by binding it to this (using the fat arrow):

email: ['', Validators.required,
  (control) => { this._emailValidator.isAvailableEmail(control) }
]
Orthopter answered 18/5, 2016 at 1:43 Comment(3)
Thanks Thilo. I like your solution - it's the smallest change - but will stick with mine, because it's consistent with how the Angular team appear to have implemented their validators. I'll makr your answer as the accepted one because it best answers the original question.Sidonius
Note that they have "static" functions like Validators.required that don't need to be configured, and validator factories that generate validator function that capture all their configuration, like Validators.minLength(8). You could do something like EmailValidator(registerServices) to produce a function that captures the registerServices and does what your isAvailableEmail does.Orthopter
So, a validator should not be a class so much as a function that creates another function (like your isAvailableEmail). And that generated function is self-contained.Orthopter
S
2

It turned out that the 'this' reference was undefined even if it was used as follows:

class EmailValidator {

    constructor(private _registerServices: RegisterServices) { }

    isAvailable(c: AbstractControl): EmailValidator {
        return this; // 'This' is undefined!
    }
}

I gather this has something to do with how the method was called, perhaps passing a non-static method where a static method was expected:

...
this.registerForm = fb.group({
    email: ['', Validators.required, this._emailValidator.isAvailableEmail],
    password: ['', Validators.compose([Validators.required, Validators.minLength(8)])],
    phoneNumber: ['', Validators.required],
    country: ['', Validators.required]
    });
...

If someone could offer some guidance on what's occurring here, that'd be fantastic.

My Solution

I reordered my code and produced the following:

class EmailValidator {

    static isAvailableEmail(services: RegisterServices): (AbstractControl) => Promise<ValidationResult> {
        let g = (c: AbstractControl) => {
            return new Promise((resolve, reject) => {
                services.emailIsAvailable(antiForgeryToken(), c.value)
                    .then(result => {
                        // Need to actually check the result.
                        resolve({ "emailtaken": true })
                    },
                    error => {
                        // Need to communicate the server error? Probably not.
                        resolve({ "servererror": true })
                    });
            });
        };

        return g;
    }
}

And amended its usage:

...
this.registerForm = fb.group({
    email: ['', Validators.required,
        EmailValidator.isAvailableEmail(this._registerService)],
    password: ['', Validators.compose([Validators.required, Validators.minLength(8)])],
    phoneNumber: ['', Validators.required],
    country: ['', Validators.required]
    });
...

Which works correctly.

Sidonius answered 18/5, 2016 at 0:56 Comment(0)
L
1

You have the problem because you are passing the value of isAvailable which is a function. You are not executing it, you are just passing the reference to the function.

One way to solve it is as in @Thilo's answer

Another way is to assign isAvailable to a lambda expression instead of a function. like this:

class EmailValidator {

    constructor(private _registerServices: RegisterServices) { }

    isAvailable = (c: AbstractControl): Promise<ValidationResult> => {
        let q = new Promise((resolve, reject) => {
            this._registerServices.emailIsAvailable(antiForgeryToken(), c.value)
                .then(result => {
                    // Need to actually check the result.
                    resolve({ "emailtaken": true })
                },
                error => {
                    // Need to communicate the server error? Probably not.
                    resolve({ "servererror": true })
                });
        });

        return q;
    }
}
Lunalunacy answered 18/5, 2016 at 1:53 Comment(0)
I
0

I would offer to write it a bit different

class EmailValidator {

    constructor(private _registerServices: RegisterServices) { }

    isAvailable(c: AbstractControl): Promise<ValidationResult> {
        return this._registerServices.emailIsAvailable(antiForgeryToken(), c.value)
            .then(result => {
                // Need to actually check the result.
                return { "emailtaken": true }
            })
 // shorter .then(result => ({ "emailtaken": true }))
            .catch(error => {
                // Need to communicate the server error? Probably not.
                return { "servererror": true }
            });
 // shorter .catch(error => ({ "servererror": true }))

        });

    }
}
Inexperience answered 18/5, 2016 at 6:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.