Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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);
  }
}
like image 731
Smooth Avatar asked Jan 28 '23 07:01

Smooth


2 Answers

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.

like image 99
Vivek Kumar Avatar answered Jan 31 '23 21:01

Vivek Kumar


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

like image 40
Rogelio Avatar answered Jan 31 '23 22:01

Rogelio