0

I am trying to to navigate through angular components (signup and dashboard). If the user has already signed up then he should be redirected to the dashboard component and if not then to signup component. Running only lib.js file as the server. The angular files are deployed after using ng build command (making use of dist folder). Both the front-end and back-end are in the same folder

Here are the some code snippets:

lib.js (node.js back-end file)

app.use(exp.static(path.join(__dirname, 'dist')));

app.listen(PORT, function() {
    console.log('Express server listening on port ', PORT); // eslint-disable-line
});

app.post('/check_account',function(req,res){
    console.log(req.body);
    connection.query('SELECT * FROM login_table WHERE m_pub_key LIKE '+'\"'+req.body.m_pub_key+'\"' ,function(error,rows,fields){
        if(!!error){
            console.log('error!');
            res.send('Error!');
        }
        else
            {
                console.log(rows);
                if(rows.length!=0){
                    data={
                        is_signed_up: 1
                    }
                    res.send(data);
                }
                else{
                    data={
                        is_signed_up: 0
                    }
                    res.send(data);
                }
            }
    });

});

app.get('/dashboard',function(req,res){
    console.log("inside dashboard backend");
    res.sendFile(path.join(__dirname, 'dist/index.html'));
});

app.get('/signup', function (req,res) {
    console.log("signup backend");
    res.sendFile(path.join(__dirname, 'dist/index.html'));
});

app.get('/', function (req,res) {
    console.log("slash backend");
    res.sendFile(path.join(__dirname, 'dist/index.html'));
});

app.component.ts

  constructor(private router: Router,private _appService: AppService, private zone: NgZone, private http: HttpClient, private route: ActivatedRoute){
    console.log("inside app component constructor");
    this.checkAndInstantiateWeb3();
    this.onReady();

  }

  checkAccount(){
    let data_send = JSON.stringify({
            'm_pub_key': this.m_pub_key
        });

      this.zone.runOutsideAngular(()=>{
        this._appService.post_method(this.url+"check_account", data_send).subscribe(
          data => {
              this.is_signed_up=data["is_signed_up"];
              console.log(this.is_signed_up+"***************8");
              if(this.is_signed_up==1){
                console.log("navigating to dash");
                this.router.navigate(['/dashboard']);
                //this.http.get("/dashboard");
              }
              else{
                console.log("navigating to signuo");
                this.router.navigate(['/signup']);
                //this.http.get("/signup");
              }
          },
          error => {
              // console.log('post error');
          });
});
  }

  public checkAndInstantiateWeb3 = () => {
        if (typeof window.web3 !== 'undefined') {
            console.warn('Using injected web3');
            this.web3 = new Web3(window.web3.currentProvider);
        } else {
            // when running in browser, use incognito mode or log out of metamask
            console.warn('No web 3 detected, falling back to Ganache');
            this.provider = new Web3.providers.HttpProvider('http://localhost:7545');
            this.web3 = new Web3(this.provider);
        }
        window.ethereum.enable();
      }

  public onReady = () => {
        // get initial account balance so it can be displayed
        this.web3.eth.getAccounts((err, accs) => {
          if (err != null) {
            console.log(err);
            alert('There was an error fetching your accounts.');
            return;
          }

          if (accs.length === 0) {
            alert('You are not connected to an Ethereum client.');
            return;
          }
          this.m_pub_key=accs[0];
          console.log(this.m_pub_key);
          this.checkAccount();
        });
      }
}

app-routing.module.ts

const routes: Routes = [
    { path: 'signup', component: SignupComponent },
    { path: '', pathMatch: 'full', redirectTo: 'signup' },
    { path: 'dashboard', component: DashboardComponent },
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule],
})
export class AppRoutingModule { }

Now the problem is that if the user has already signed up and then when he tries to go to any page he is redirected to 1st the signup page and then dashboard page but both the pages overlap and also the dashboard component does not work properly. When user directly goes to the dashboard component using the url then the dashboard component works as expected. Any help in this problem would be appreciated.

EDIT 1

Following the answer given by "k0hamed". Using the canActivate attriute like this { path: 'signup', component: SignupComponent, canActivate:[AuthGuard] } This are the guard and service files i have created and its not working as expected. The val in the auth.guard.ts file does not change and nothing gets loaded when i go to localhost:8080/ or localhost:8080/signup but the dashboard component loads when i type in localhost:8080/dashboard

auth.guard.ts:

export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
    ): Observable<boolean> {
    console.log('AuthGuard#canActivate called');
    return this.signupService.onReady()
             .pipe(tap(val=>!val && this.router.navigate(['/signup'])));
  }
  constructor(private signupService: AuthService, private router: Router) {}
}

auth.service.ts:

export class AuthService {

    provider: any;
    account: any;
    accounts: any;
    web3: any;
    m_pub_key="";
    title = 'project2';
    is_signed_up:any;
    url = "http://localhost:8080/";
    val = of(false);

    constructor(private _appService: AppService, private zone: NgZone, private router: Router){
    }

    onReady():Observable<boolean> {
        // get initial account balance so it can be displayed
        // (async () => {
        if (typeof window.web3 !== 'undefined') {
            console.warn('Using injected web3');
            this.web3 = new Web3(window.web3.currentProvider);
        } else {
            // when running in browser, use incognito mode or log out of metamask
            console.warn('No web 3 detected, falling back to Ganache');
            this.provider = new Web3.providers.HttpProvider('http://localhost:7545');
            this.web3 = new Web3(this.provider);
        }
        window.ethereum.enable();


        this.web3.eth.getAccounts((err, accs) => {
          if (err != null) {
            console.log(err);
            alert('There was an error fetching your accounts.');
            return;
          }

          if (accs.length === 0) {
            alert('You are not connected to an Ethereum client.');
            return;
          }
          this.m_pub_key=accs[0];
          console.log(this.m_pub_key);
          // this.isSigned();
        });

        let data_send = JSON.stringify({
            'm_pub_key': this.m_pub_key
        });
            this._appService.post_method(this.url+"check_account", data_send).subscribe(
              data => {
                  this.is_signed_up=data["is_signed_up"];
                  console.log(this.is_signed_up+"***************8");
                  if(this.is_signed_up==1){
                    console.log("navigating to dash");
                    //this.router.navigate(['/dashboard']);
                    //this.http.get("/dashboard");
                    this.val = of(false);
                    // this.router.navigate(['/dashboard']);
                  }
                  else{
                    console.log("navigating to signup");
                    //this.router.navigate(['/signup']);
                    //this.http.get("/signup");
                    this.val = of(true);
                    // this.router.navigate(['/signup']);
                  }
                  console.log(this.val);
              },
                    // console.log(this.val);
              error => {
                  // console.log('post error');
              });
        // });
        console.log(this.val);
        return this.val
      }
}
KG_1
  • 89
  • 2
  • 9

1 Answers1

0

first of

your shouldn't handle literally every route in the back-end if angular does so

app.get('/*', function(req,res) {
  res.sendFile(path.join( __dirname + '/dist/index.html'));
});

adding this as the last route in your express app will redirect every request not handled above it to angular and angular will use it's own router

second of, you need to use a canActive guard for your use case and move the checking function within it, if user is not signed it will redirect to signup page, else it will allow him to pass, something like:

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    console.log('AuthGuard#canActivate called');
    return this.signupService.isSigned()
             .pipe(tap(val => !val && this.router.navigate(['/signup']))); // Navigate to signup when it returns false
  }
  constructor(private signupService: SignupService, private router: Router) {}
}

While SignupService is a service with method isSigned() that return an Observable<boolean>;

You might need to create another guard for the signup page route, to check if not signed and redirect to dashboard otherwise.

UPDATE: You need to either turn your whole code into promises or observables, to handle asynchronous functions as the guards accepts observable and promise

So for example to use the observables way you need to turn getAcounts function to return observable instead of depending on callback, fortunately rxJs has a build-in function to do so it's bindNodeCallback you can simply do

const getAccountsAsObservable = bindNodeCallback(
    callback => this.web3.eth.getAccounts(callback));

now you can use rxJs magic to chain the code through pipes and you need to handle error with a pipe also and make it return an observable of false.

and your service will be something like:

onReady():Observable<boolean> {
  // ..
  const getAccountsAsObservable = Observable.bindNodeCallback(
    callback => this.web3.eth.getAccounts(callback));

  return getAccountsAsObservable()
    .pipe(
      switchMap(acc => {
        if (accs.length === 0) {
          alert('You are not connected to an Ethereum client.');
          // throw an error to skip to "cathError" pipe
          return throwError(new Error('You are not connected to an Ethereum client.'));
        }
        const m_pub_key=accs[0];
        console.log(this.m_pub_key);
        const data_send = JSON.stringify({
          'm_pub_key': m_pub_key
        });
        return of(data_send);
      }),
      switchMap(data_send => { 
        this._appService.post_method(this.url+"check_account", data_send)
      }),
      map(data => {
        this.is_signed_up=data["is_signed_up"];
        console.log(this.is_signed_up+"***************8");
        if(this.is_signed_up==1){
          console.log("navigating to dash");
          //this.router.navigate(['/dashboard']);
          //this.http.get("/dashboard");
          return false;
          // this.router.navigate(['/dashboard']);
        } else {
          console.log("navigating to signup");
          //this.router.navigate(['/signup']);
          //this.http.get("/signup");
          return true;
          // this.router.navigate(['/signup']);
        }
      }),
      catchError(err => {
        //handle error of getAccounts or post_method here
        console.log(err);
        alert('There was an error fetching your accounts.');
        return of(false);
      })
    )
}

you can turn the whole thing into promises and/or use async/await you can turn _appService.post_method to promise with .pipe(take(1)).toPromise

k0hamed
  • 492
  • 2
  • 11
  • Thanks for the answer but its still not working, please look into the edits that i have made in the question and help me out. Also i change my back-end file as you said, thanks for that. – KG_1 Apr 01 '19 at 07:06
  • it will always return false, as the observables are asynchronous it'll always proceed to return the `of(false)` before even `this._appService.post_method()` executes, give a look at [Observables](https://angular.io/guide/observables) I'll update my answer to include how you should rewrite your service – k0hamed Apr 02 '19 at 10:23
  • The problem is that ```this._appService.post_method``` is executed before the ```data_send``` is set. ```this.web3.eth.getAccounts``` method is responsible for setting the ```data_send``` object but it takes time to execute that method and hence the return value always remains the same and does not change. I have found a work around using ```setTimeout``` function and then navigating to the respective page, it works but i dont think it is the correct way. Thanks for your help. – KG_1 Apr 03 '19 at 06:43
  • sorry didn't notice at first, I updated the answer now, you need to use promises or observables if you're dealing with asynchronous functions in the guard. give it a look. – k0hamed Apr 03 '19 at 10:04
  • Hey, Sorry for the late response. So, I tried your code but it gives me following errors: ```Property 'bindNodeCallback' does not exist on type 'typeof Observable'. Property 'length' does not exist on type '{}'. Cannot find name 'throwError'. Argument of type '(data_send: {}) => void' is not assignable to parameter of type '(value: {}, index: number) => ObservableInput<{}>'. Type 'void' is not assignable to type 'ObservableInput<{}>'. Argument of type '{}' is not assignable to parameter of type 'string'. Cannot find name 'catchError'. Did you mean 'RTCError'?``` – KG_1 Apr 08 '19 at 14:08
  • oh, sorry I wrote it wrong, it's not a method within Observable, it's a stand-alone function from 'rxjs' I'll update the answer – k0hamed Apr 08 '19 at 17:55