Smooth
Smooth

Reputation: 956

How do I load components synchronously in Angular 6?

I have a header component which makes a call to fetch the user token and store the user's data. I then have a different component (admin component) that is not a child of the header which relies on the user data to fetch additional data. The issue I'm running into is that the admin component initializes before the header component has finished making it's call for the user, therefore breaking my admin component. In other words, the code execution breaks because the active company has not been set yet by the header. Is there a way to make sure these load synchronously in Angular 6 so that I can avoid this issue?

header.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';


@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  user: any;
  activeCompany: any;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.authService.getToken().then((user) => {
      this.user = user;
      this.user.Companies = JSON.parse(this.user.Companies.split('"').join('"'));
      this.authService.getActiveCompany$().subscribe((company: any) => {
        if(!company) {
          let company = this.user.Companies[0]
          this.authService.setActiveCompany$(JSON.stringify(company));
        }
        this.activeCompany = company;
      });
    });
  }

  setActiveCompany(company) {
    this.authService.setActiveCompany$(company)
  }
}

admin.component.ts

import { Component, OnInit } from '@angular/core';
import { TagService } from '../../services/tag.service';
import { AuthService } from '../../services/auth.service';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {
  companyId: number;
  tags: any;
  loading: boolean = true;

  constructor(
    private tagService: TagService,
    private authService: AuthService
   ) {}

  ngOnInit() {
    this.authService.getActiveCompany$().subscribe((company: any) => {
      // The line below breaks because the active company has not been set yet by the header 
      this.companyId = company.Id
      this.tagService.getTags(companyId).then((tags) => {
        this.setTags(tags)
        this.loading = false;
      });
    });
  }
}

auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { AppConfig } from '../../app-config';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private activeCompany$: Subject<any>;

  constructor(private http: HttpClient, private _config: AppConfig) {
    let initialActiveCompany;
    if (window.localStorage.getItem('activeCompany')) {
      initialActiveCompany = JSON.parse(window.localStorage.getItem('activeCompany'));
}   else {
      this.getToken().then((user: any) => {
        initialActiveCompany = user.Companies = JSON.parse(user.Companies.split('&quot;').join('"'))[0];
  });
}
this.activeCompany$ = new BehaviorSubject<any>(initialActiveCompany);

}

  getToken() {
    return new Promise(resolve => {
      this.http.get(`${this._config.API_URL}/Token`).subscribe(data => {
        resolve(data);},
        err => {
        console.log("Error retrieving token", err);
      });
    });
  }

  // Returns the observable (read-only) part of this subject
  getActiveCompany$(): Observable<any> {
    return this.activeCompany$.asObservable();
  }

  // Stores the new company value in local storage and pushes it to the subject
  setActiveCompany$(company: any) {
    window.localStorage.setItem('activeCompany', JSON.stringify(company));
    this.activeCompany$.next(company);
  }
}

Upvotes: 4

Views: 1562

Answers (4)

Al Fahad
Al Fahad

Reputation: 2568

  1. Declare a flag variable
  2. After the header component has finished making it's call for the user, set a flag's value to true.
  3. Using *ngIf load admin component if the value of flag is true.

Upvotes: 0

Daniel
Daniel

Reputation: 124

did you try to implement the safe navigation operator instead? With a little bit of re-organization you could achieve the result of waiting the result to arrive to actually render the value.

The current hero's name is {{currentHero?.name}}

You can see the docs here.

Upvotes: 0

Rogelio
Rogelio

Reputation: 1094

I'd advise that you use a resolver service in your admin component. First create a new resolver service using ng g s resolver command in your cli then use this code

import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { AuthService } from '../../services/auth.service';
import {
  Resolve,
  ActivatedRouteSnapshot,
  RouterStateSnapshot
} from "@angular/router";

@Injectable({
  providedIn: "root"
})
export class ResolverService implements Resolve<any> {
  this.companyId: any;
  constructor(private authService: AuthService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> {
    return this.authService.getActiveCompany$().subscribe((company: any) => {
    this.companyId = company.Id
    this.tagService.getTags(companyId).then((tags) => {
    this.setTags(tags)
    this.loading = false;
    });
   });
  }
}

and then add this to your routes-module under your admin component

{
 path: "admin",
 component: AdminComponent,
 resolve: {
 result: ResolverService
 }
}

and then finally in your admin component import ActicatedRoute

import { ActivatedRoute } from "@angular/router";

and add to your constructor

userData = null // store the data you want from your authService here from your resolver

constructor(private actr: ActivatedRoute) {
 this.actr.data.pipe(map(data => data.resuslt)).subscribe(res => {
 this.userData = res;
 });
}

I didn't have time to read through your authservice.ts but i hope you get the concept

Upvotes: 3

Vivek Kumar
Vivek Kumar

Reputation: 5040

I think you should organize your services little bit more for handling asynchronicity.

You should make the activeCompany as BehaviorSubject in the AuthService, and then subscribe for change in admin.

Because you need to call the getTags() whenever the activeCompany is changed.

Upvotes: 5

Related Questions