0

MyComponent uses a service to get data then renders that data. However, the service cannot get data until the user is logged in. If the user goes to the route '/myPage' for the first time, the container component will display the login component. However, even though login component is rendered instead of ng-content (which evaluates to MyComponent), MyComponent is initialized by angular and fetches and causes the service to fetch data too early, before logging in. Therefore when the user successfully logs in and MyComponent finally renders, there's no data the initial data service returned nothing.

How can I postpone the data fetching until after the component is rendered? I've tried all the different lifecycle methods inside MyComponent without success.

app.component

<container>
    <router-outlet></router-outlet>
</container>

container component

<div *ngIf="!userService.getLoggedInUser()">
  <login></login>
</div>

<div *ngIf="userService.getLoggedInUser()">
  <ng-content></ng-content>
</div>

routing module

const routes: Routes = [
  { path: 'myPage', component: myComponent }
];

my component

export class MyComponent {
  data

  constructor(private dataService: DataService) {}

    ngOnInit () {
      //how can i make run this service AFTER the component renders? i know
      //ngOnInit is definitely not the correct lifecycle method, but what is?
      this.dataService.get().then((data) => this.data = data);

    }
  }
Simon
  • 1,681
  • 1
  • 21
  • 34
  • You're looking for: https://angular.io/api/core/AfterViewInit. Used the same way as OnInit (import, implement, then use ngAfterViewInit () { ... }) – Z. Bagley Oct 04 '17 at 21:50
  • I think you go by the wrong way. Because in your case you need everywhere add your condition for check user is logged or not. And that's mean that component loaded and we can find all js files and check it. And maybe somebody can do some xss attack. In angular, we have the more good way to build guard. There you can find a good example: http://jasonwatmore.com/post/2016/09/29/angular-2-user-registration-and-login-example-tutorial – Roman Skydan Oct 04 '17 at 22:01
  • @RomaSkydan thanks for the info. But I'm not exactly sure what the security issue is? Yes, the client checks for logged in user, but I also have a security check server-side with tokens (this is why I need to fetch data after a user has logged in). Could you elaborate on exactly where the security issue is? – Simon Oct 04 '17 at 22:21
  • @Z.Bagley unfortunately, that life cycle method does not work when I replace ngOnInit – Simon Oct 04 '17 at 22:26

2 Answers2

2

ngOnInit is the normal method used to retrieve data for a component. So if something isn't working, my guess is that there is a problem somewhere else.

Route Guard

For example, one common way to present a login dialog is to use a route guard instead of ngIf. That would look something like this:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, Router,
         CanActivate } from '@angular/router';

import { AuthService } from './auth.service';

@Injectable()
export  class AuthGuard implements CanActivate {

    constructor(private authService: AuthService,
                private router: Router) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        return this.checkLoggedIn(state.url);
    }

    checkLoggedIn(url: string): boolean {
        if (this.authService.isLoggedIn()) {
            return true;
        }

        // Retain the attempted URL for redirection
        this.authService.redirectUrl = url;
        this.router.navigate(['/login']);
        return false;
    }
}

You then use this guard on any route that requires a log in:

                { path: 'welcome', component: WelcomeComponent },
                {
                    path: 'movies',
                    canActivate: [AuthGuard],
                    loadChildren: './movies/movie.module#MovieModule'
                },
                { path: '', redirectTo: 'welcome', pathMatch: 'full' },

This should prevent routing to the component too early.

Route Resolver

Another option you can consider is to use a route resolver. It allows you to retrieve the data for a route before routing to the specific component.

I'm not sure that would help in your scenario, but it may be worth looking at.

I have an example of this here: https://github.com/DeborahK/MovieHunter-routing

DeborahK
  • 57,520
  • 12
  • 104
  • 129
0

While this problem definitely indicated an issue with my auth pattern, I wanted to leave my findings here for anyone who stumbles upon this question.

The issue is with how angular handles ngIf. Components inside ngIf are always "initialized" regardless of whether they are displayed. To prevent this behavior, use templates instead.

See Angular4 ng-content gets built when ngIf is false

That being said, even with templates my original auth solution does not work because I cannot nest a <router-outlet> into a template. The correct solution then is to change the logic and use route guards as DeborahK suggests.

Simon
  • 1,681
  • 1
  • 21
  • 34