skip to Main Content

I’m working on an eCommerce application in Angular 2 with a node and express api backend. I’m currently working on the social login section where I am using passport-facebook and jwt for authentication. My code concerns revolve around the Angular 2 portion. When a user clicks on the login/register with facebook button it opens a new tab that begins the facebook login flow. That window will then return the jwt to the opener via window.postMessage. The opener then sends the window a success message and the opened window will close. Everything works correctly but it’s not very clean, i.e. I had to hack it up to make it work. Ideally I would call my authorization service to handle the facebook login instead of handling everything in my component but inside the event handler for the window.postMessage ‘this’ no longer references the component, it references the window object so I can’t call the service via ‘this’. I can’t figure out how to get a reference to the component so I can call the service. Does anyone have any suggestions?

Below is my login component in typescript. I can include other code if need be but I don’t know that it is needed.

import { Component, OnInit, AfterViewChecked } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { ILoginUser } from './loginUser.model';


@Component({
  moduleId: module.id,
  templateUrl: 'login.component.html',
  styleUrls: ['login.component.css']
})
export class LoginComponent implements OnInit {
constructor(private _authService: AuthService, private _router: Router) { 
   if (window.addEventListener) {
     window.addEventListener("message", this.receiveMessage, false);
    } else {
      (<any>window).attachEvent("onmessage", this.receiveMessage);
   }
  }

  ngOnInit() { }

  user: ILoginUser = {
    username: "",
    password: ""
  }

  error: boolean = false;
  success: boolean = false;
  errorMessage: string = "";
  redirect: boolean = false;

  login() {
    this._authService.login(this.user).subscribe(data => {
        console.log (data);
          if (data.success){
            this._router.navigate(['/product']);

        } else {
        this.error = true,
        this.errorMessage = data.message
      }
    }, errorMsg => {
      this.error = true;
      this.errorMessage = errorMsg;
    });
  }

  receiveMessage(event: any)
    {

      if (event.origin !== "http://localhost:3000")
        return;
      localStorage.setItem("token", event.data.token);
      localStorage.setItem("username", event.data.username);
      (<any>window).popup.postMessage("success", "http://localhost:3000");
    }


  ngAfterViewChecked() {
    //since we can't call the authservice directly we check to see if a redirect flag is set to true
    if (this.redirect) {
      this._router.navigate(['/product']);
    }
  }

  authFacebook() {
    (<any>window).popup = window.open('http://localhost:3000/api/auth/facebook');
   //set the redirect flag to true so when angular checks again we can redirect to the products page
   this.redirect = true;
  }
}

3

Answers


  1. You should change the way you declare the eventListener (by using bind(this) or an anonymous function, or change the way you declare your eventHandlers:

    bind(this):

    constructor(private _authService: AuthService, private _router: Router) { 
       if (window.addEventListener) {
         window.addEventListener("message", this.receiveMessage.bind(this), false);
       } else {
          (<any>window).attachEvent("onmessage", this.receiveMessage.bind(this));
       }
    }
    

    anonymous function

    window.addEventListener("message", () => {
       this.receiveMessage();
    }, false)
    

    different declaration of eventHandler

    receiveMessage: any = (event: any) =>  {
      //...
    }
    

    Take your pick 🙂 With the last option it is easy to detach the eventListener

    Login or Signup to reply.
  2. Combining @PierreDuc’s .bind(this) technique and @Mikeumus’s comment:

    import {Component, OnDestroy, Renderer2} from '@angular/core';
    
    @Component({
      selector: 'my-component',
      templateUrl: './my_component.ng.html',
    })
    export class MyComponent implements OnDestroy {
      stopListening: Function;
    
      constructor(private renderer: Renderer2) {
        this.stopListening =
            renderer.listen('window', 'message', this.handleMessage.bind(this));
      }
    
      handleMessage(event: Event) {
        const message = event as MessageEvent;
    
        // Only trust messages from the below origin.
        if (message.origin !== 'http://example.com:8080') return;
    
        console.log(message.data);
      }
    
      ngOnDestroy() {
        this.stopListening();
      }
    }
    

    You can still directly do this if you prefer:

    this.stopListening =
        renderer.listen('window', 'message', (even: Event) => { ... });
    

    You can test it by commenting out the origin check and writing this in the developer JavaScript console:

    window.postMessage("test message data", "http://localhost:8080")
    

    Update the 2nd arg to be the URL of the page you are currently on. This command makes the page think it received a message from itself.

    Login or Signup to reply.
  3. You could also use the @HostListener() function decorator:

    @HostListener('window:message', ['$event'])
    onMessage(event) {
      this.receiveMessage(event);
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search