Refreshing the route in Angular 6 using a partial component

I am developing an app that offers courses in both English and French. My goal is to create a language dropdown header component that will be visible across all pages of the program. When a user selects a different language from the dropdown, I want the following actions to take place:

  1. The frontend should call the backend to update the user's language preference so that the next time they visit, they will see the courses in their chosen language.
  2. Regardless of the current page, the route should change to the course-list page and load the courses according to the updated language preference.

Currently, this functionality only works after refreshing the page and not immediately. It worked when I included the language button in every component.

EDIT: The language switch is functioning on all pages except for the course-list page, possibly because we are already within this component and don't trigger the `getCoursesByLanguage` function (when navigating from other components, I use router.navigate to the course-list component which triggers `getCoursesByLanguage`). How can I make it work on the course-list page?

This is the relevant code:

app.component.html

<div class='container-fluid' id='main'>
      <lg-header></lg-header>
      <router-outlet></router-outlet>
</div>

header.component.html

<div style="float:right; padding-right:30px">
  <button id="button-logout" mat-button (click)="toggleLanguage()">
    <img width="27" height="17" style="margin-right: 10px;" src="./assets/images/{{flag}}_Flag.png"/>
    <span>{{languageName}}</span>
  </button>
</div>

header.component.ts

import { Component, OnInit, Pipe, PipeTransform } from '@angular/core';
import { ActivatedRoute, Router, Routes } from '@angular/router';
import { LocalStorage } from '@ngx-pwa/local-storage';

import { IUser, IUserCourses } from '../users/user';
import { UserProgressService } from '../users/user-progress.service';

@Component({
  selector: 'lg-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.sass']
})
export class HeaderComponent implements OnInit {
  // variables for laungage
  language: number;
  languageName: string;
  flag: string;

  constructor(private route: ActivatedRoute,
              private router: Router,
              private userProgressService: UserProgressService) {
                userProgressService.connectUser();
                this.getUpdatedLanguageAndFlag();
              }

  // get from service updated data from backend and localStorage
  getUpdatedLanguageAndFlag() {
    this.language = this.userProgressService.getLanguage();
    this.flag = this.userProgressService.getFlag();
    this.languageName = this.userProgressService.getLanguageName();
  }

  ngOnInit() { }

  // change laungage
  toggleLanguage(){
    this.userProgressService.changeAppLanguage();
    this.getUpdatedLanguageAndFlag();
    if (this.router.url == '/courses') {
      // I need to trigger here getCourseListByLanguage in course-list from here
    }
    else
      this.router.navigate(["/courses"]);
  }
}

user-progress.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, groupBy } from 'rxjs/operators';
import { LocalStorage } from '@ngx-pwa/local-storage';

import { UserService } from './user.service';
import { IUser, IUserCourses } from './user';

@Injectable({
  providedIn: 'root'
})

export class UserProgressService {
      private user: IUser;
      private errorMessage: string;
      private language: number;
      private flag: string;
      private languageName: string;

      constructor(private userService: UserService) { }

      // get user from local store
      connectUser() {
        this.user = JSON.parse(localStorage.getItem('user'));
        this.language =+ localStorage.getItem('language');
        this.flag = localStorage.getItem('flag');
        this.languageName = localStorage.getItem('languageName');
      }

      ...
      

Answer №1

It seems like a timing issue is causing the problem.

Typically, it is recommended to subscribe in the user interface as close to the event that requires the subscription result.

You can try moving the subscribe function here (pseudo code):

  toggleLanguage(){
    this.userProgressService.changeAppLanguage().subscribe(x => {
      this.getUpdatedLanguageAndFlag();
      this.router.navigate(["/courses"]);
    };
  }

By placing the two functions within the subscribe block, you ensure they are only executed once the language is actually changed.

PLEASE NOTE: In order for this to work correctly, your .changeAppLanguage() method must return an observable. Therefore, the code needs to be adjusted accordingly (pseudo code):

  changeAppLanguage(): Observable<any> {
    if ( this.language == 0 )
        this.language = 1;

    else
        this.language = 0;

    this.setFlagLanName();
    return this.userService.updateLanguage(this.user.id, this.language);
  }

MOST IMPORTANTLY: This code has not been syntax checked due to lack of provided stackblitz with the code.

The main concept to remember is that services should not directly subscribe. Instead, they should return an Observable. The component code should handle the subscription. Any actions that need to take place after the subscription emits a value should be placed inside that subscribe block.

This approach will help ensure that your processes happen in the correct order.

Answer №2

You have code here that retrieves all the courses:

  ngOnInit() {
    this.getCourseList();
  }

After retrieving them, you filter them immediately:

  // Obtain list of courses from service
  getCourseList() {
    this.courseService.getCourses()
      .subscribe(
        courses => {
          this.courses = courses;
          this.getCourseListByLanguage();
        },
        errorMessage => this.errorMessage = <any>Error
      );
  }

  getCourseListByLanguage() {
    this.getUpdatedLanguageAndFlag();
    this.coursesByLanguage = this.courses.filter( course => course.language == this.language);
  }

Now your courses are filtered for a specific language: English1, English2, English3, etc.

Later on, you call toggleLanguage and re-filter the courses.

  toggleLanguage(){
    this.userProgressService.changeAppLanguage();
    this.getCourseListByLanguage();
  }

But at that point, courses is already filtered, so you can't re-filter them to another language.

To solve this issue, you should keep a separate list of all courses apart from the filtered ones.

Something like this:

  courses: ICourse[] = [];
  allCourses: ICourse[] = [];

Then populate it here:

  getCourseList() {
    this.courseService.getCourses()
      .subscribe(
        courses => {
          this.allCourses = courses;    // Populate all courses here
          this.getCourseListByLanguage();
        },
        errorMessage => this.errorMessage = <any>Error
      );
  }

Filter into the courses variable afterwards.

  getCourseListByLanguage() {
    this.getUpdatedLanguageAndFlag();
    this.coursesByLanguage = this.allCourses.filter( course => course.language == this.language);
  }

Now you're always filtering the complete list of all courses into only those for the specific language in the courses property.

Does this solution work?

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to send variables to a proxy server URL?

Is there a way to pass specific variables to my proxy in order to make API calls with customized parameters, similar to the scenario outlined below? This is how my proxy configuration appears: { "/darksky/*": { "target": "https://api.darksky.net/ ...

Utilizing a Service to Render Components in Angular 6

I recently created an Angular library that includes one component and service. I then integrated this library into my Angular application. However, I am now facing the challenge of rendering the library component using the library service from within the c ...

Encountering issues with app functionality following update to Ionic RC4 version

Since upgrading from Ionic rc3 to rc4, I've been facing difficulties running my app smoothly. The app compiles without any errors when I use ionic-app-scripts build --prod, but when I try to run it on my iPhone, all I get is a blank screen and an err ...

Utilize an external JavaScript function within a React and TypeScript document

I have encountered an issue in my React/Next.js/TypeScript file where I am trying to load the YouTube iframe API in my useEffect hook. Here is the code snippet: useEffect(() => { const tag = document.createElement('script'); tag.src = ...

The library's service under NG6 is not currently defined

Currently, I am in need of assistance, pointers, or guidance in debugging and understanding an issue with a derived class that extends a service provided by a library in an Angular 6 project. I am working on migrating an existing Angular 5.x project to th ...

Is it possible to transmit an argument to a function without using parentheses?

I am currently working with the following code snippet: this.somePromiseFn<T> // this function is actually a promise .then(res => res as T) .catch(err => this.handleError<T>(err)); handleError<T>(err: HttpErrorResponse) { // pe ...

Encountering a delay in receiving server data causing errors in Angular 2

Within the service class constructor, I make an http call to the server to fetch product category data and store it in the productCategories array. However, when I try to retrieve the productCategories array in the ngInit() function of my component, I enco ...

change in the value of a nested formArray within a parent formArray

I am working with a form that includes a formArray called FinYearArray, which in turn contains another formArray called RecipientArray. Both of these arrays have formControls for the amount, and whenever there is a change in these fields, the totals need t ...

Having trouble showcasing local images on an S3-hosted Angular 2 app

I have set up my angular 2 app on s3 with redirect rules in the bucket configuration, following instructions from a useful answer. <RoutingRules> <RoutingRule> <Condition> <HttpErrorCodeReturnedEquals>404</HttpError ...

Angular routerLink causes ngOnInit to be triggered only once

I have come across numerous questions similar to mine, however, the majority are linked to params. The answers provided did not aid in resolving my specific issue. Essentially, the problem I am facing is as follows: After a user logs in, their username is ...

What is the best way to integrate environment-specific configuration options into an AngularJS and Typescript project?

Currently, I am working on a project using AngularJS, Typescript, and VisualStudio. One of the key requirements for this project is to have a configuration file containing constants that control various settings such as REST API URLs and environment names. ...

The MVC Routing feature in my .NET Core React application seems to be struggling to recognize my Controller

Currently, I am diving into the world of React applications integrated with ASP.NET Core. As a beginner, my main goal right now is to simply display a "Hello World" message on the homepage. To kick things off, I utilized Visual Studio's default React. ...

Expanding ngFor in Angular 2

Is it possible to pass two arguments with ngFor? Here is an example that I would like to achieve: <mat-card *ngFor="let room of arr; let floor of floorArr"> <mat-card-content> <h3>Room Number: {{room}}</h3> <p>Floor ...

The React effect hooks do not re-run when the dependencies are modified

Explore code on CodeSandbox Child Component: import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' const Child = forwardRef<any, {}>((_, ref) => { const [obj, setObj] = useState<any | null>(null) ...

Experimenting with Date Object in Jest using Typescript and i18next

I have included a localization library and within my component, there is a date object defined like this: getDate = () => { const { t } = this.props; return new Date().toLocaleString(t('locale.name'), { weekday: "long", ...

The Angular language service is experiencing difficulties in VS Code when it comes to newly created components using the CLI

I am currently facing an issue with the angular language service in an older project that already has a few components created. The problem arises when trying to generate NEW components using the CLI. For instance, a new component generated today: https:/ ...

Having trouble getting the Angular 2 quickstart demo to function properly?

Just starting out with Angular 2, I decided to kick things off by downloading the Quickstart project from the official website. However, upon running it, I encountered the following error in the console: GET http://localhost:3000/node_modules/@angular/ ...

The identity of the property cannot be located within undefined

<ion-content padding> <ion-list> <ion-item> <ion-label fixed>Username</ion-label> <ion-input type="text" value="" (ngModel)]="userData.username"></ion-input> </ion-item& ...

The supplied parameters do not correspond with any valid call target signature for the HTTP delete operation

I've been attempting to utilize the http delete method in Angular 2, but I keep encountering a typescript error: Supplied parameters do not match any signature of call target.. Below is the code I am using: let headers= new Headers(); ...

Blocking JavaScript execution and the message queue

Questioning the possibility of a timeout function with 0ms delay setTimeout(function, 0) being triggered after a loop execution block or will the encompassing function always complete first? setTimeout(function(){ //something in here }, 0); function myFu ...