【问题标题】:Angular 6: refresh route from a partial componentAngular 6:从部分组件刷新路由
【发布时间】:2018-12-21 01:12:55
【问题描述】:

我有一个英语和法语课程的应用程序。我想在另一个组件中为语言下拉列表创建一个标题,该组件将出现在程序的所有页面中。当用户更改标题中的语言时,我希望这件事发生:

  1. 前端调用后端并更改用户的语言偏好,这样下次他进入时,他会看到他选择的语言的课程
  2. 无论在哪里,路线都会更改为课程列表页面并使用更新的偏好加载课程。

目前,它只有在我刷新页面时才有效,而不是立即。 当我在每个组件中编写语言按钮时,它都起作用了。

编辑:它在除课程列表之外的所有页面中都有效,可能是因为我们已经在这个组件中,所以我们不调用函数 getCoursesByLanguage(当我们在其他组件上时,我用户router.navigate 到触发 getCoursesByLanguage 的课程列表组件)。我如何使它在课程列表页面上工作?

这是相关代码:

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');
      }

      getUserName() {
        return this.user.name;
      }

      getLanguage() {
        return this.language;
      }

      getLanguageName() {
        return this.languageName;
      }

      getFlag() {
        return this.flag;
      }

      // determine flag and language name depends on language value
      setFlagLanName() {
        if (this.language == 0) {
          this.flag = "UK";
          this.languageName = "English";
        }

        else {
          this.flag = "FR";
          this.languageName = "French";
        }

        localStorage.setItem('languageName', this.languageName);
        localStorage.setItem('flag', this.flag);
      }

      // manage toggle action
      changeAppLanguage() {
        if ( this.language == 0 )
            this.language = 1;

        else
            this.language = 0;

        this.setFlagLanName();
        this.updateBackendLanguage(this.language);
      }

      // update language in backend
      updateBackendLanguage(lan: number) {
        this.userService.updateLanguage(this.user.id, lan).subscribe(
          () => { this.getUserFromBackend(); },
          error  => this.errorMessage = <any>error
        );
      }

      // update user after changes - call the backend again for GET
      getUserFromBackend() {
        this.userService.getUser(this.user.id).subscribe(
          user => {
            this.user = user;
            this.language = user.language;
            localStorage.setItem('user', JSON.stringify(this.user));
            localStorage.setItem('language', this.user.language.toString());
          },
          error  => this.errorMessage = <any>error
        );
      }
  }

user.service.ts

import { environment } from '../../environments/environment';
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { Observable, throwError } from 'rxjs';
import { catchError, groupBy, tap } from 'rxjs/operators';

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

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable()
export class UserService {
  private url = environment.educationBE_url+'/users';

  constructor(private http: HttpClient) {  }

  // Get Single user by id. will 404 if id not found
  getUser(id: number): Observable<IUser> {
    const detailUrl = `${this.url}/${id}` + '.json';

    return this.http.get<IUser>(detailUrl)
        .pipe(catchError(this.handleError));
  }

  // update user preference for language view
  updateLanguage(user_id: number, language: number) {
    const userUrl = `${this.url}/${user_id}` + '.json';
    let body = JSON.stringify({language: language});

    return this.http.patch(userUrl, body, httpOptions)
      .pipe(
        tap(_ => console.log(`updated user ${user_id} with this entry: ${language}`)),
        catchError(this.handleError)
      );
  }

  // // Handle Any Kind of Errors
  private handleError(error: HttpErrorResponse) {

    // A client-side or network error occured. Handle it accordingly.
    if (error.error instanceof ErrorEvent) {
      console.error(`Error: ${error.error.message}`);
    }

    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong.
    else {
      console.error(`Error Code: ${error.status}\nMessage: ${error.message}`);
    }

    // return an Observable with a user-facing error error message
    return throwError(
      'Something bad happend; please try again later.');
  }
}

course-list.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Routes } from '@angular/router';

import { ICourse } from '../course';
import { CourseService } from '../course.service';
import { UserProgressService } from '../../users/user-progress.service';

@Component({
  selector: 'lg-course-list',
  templateUrl: './course-list.component.html',
  styleUrls: ['./course-list.component.sass']
})

export class CourseListComponent implements OnInit {
  courses: ICourse[] = [];
  errorMessage: string;

  // variables for laungage
  language: number;
  languageName: string;
  flag: string;
  
  // the courses we display in html file
  coursesByLanguage: ICourse[] = [];

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

  ngOnInit() {
    this.getCourseList();
  }

  getUpdatedLanguageAndFlag() {
    this.language = this.userProgressService.getLanguage();
    this.flag = this.userProgressService.getFlag();
    this.languageName = this.userProgressService.getLanguageName();
  }

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

  // get from courses only the relavent ones
  getCourseListByLanguage() {
    this.getUpdatedLanguageAndFlag();
    this.coursesByLanguage = this.courses.filter( course => course.language == this.language);
  }
  
  
  // change laungage
  toggleLanguage(){
    this.userProgressService.changeAppLanguage();
    this.getCourseListByLanguage();
  }

}

【问题讨论】:

    标签: angular typescript routes local-storage partial


    【解决方案1】:

    这似乎是一个时间问题。

    通常,建议在 UI 中订阅,尽可能接近需要订阅结果的事件。

    尝试将订阅移至此处(伪代码):

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

    通过将以下两个函数放入订阅中,您将确保在实际更改语言之前不会执行它们。

    注意:要使其正常工作,您需要您的 .changeAppLanguage() 方法返回一个 observable。所以这段代码也需要改一下(伪代码):

      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);
      }
    

    重要提示:此代码均未经过语法检查,因为代码中没有提供 stackblitz。

    一般的想法是您的服务不应该订阅。他们应该返回一个 Observable。然后组件代码应该订阅。任何需要订阅发出值之后执行的代码都将在订阅。

    这将有助于确保您的流程以正确的顺序发生。

    【讨论】:

    • 我试图这样做,但我得到了这个错误:ERROR TypeError: Cannot read property 'subscribe' of undefined
    • 是的,如果没有 stackblitz,我无法运行它,因此无法为您准确获取语法。请参阅我上面的进一步说明。
    • 我会尝试上传到 stackblitz。我只需要订阅该语言,因为这是我从后端获得的唯一内容。我根据语言确定的标志和语言名称。语言包含 0 表示英语和 1 表示法语,如果语言 = 0,则标志为“UK”,如果语言 = 1,则标志为“FR”,languageName = English/French 也取决于语言。所以我只需要观察语言?但是语言是一个字符串所以我不知道如何实现它
    • user-progress.service 不像一般的服务做 HttpClient 请求,它的主要目的是成为组件和 user.service 之间的桥梁,它使用 HttpClient 调用后端。 user-progress.service 计算用户是否观看了视频或完成了测验,并包含用户可观察数据并存储在本地存储中,因此每个组件都可以访问最新更新
    • 我理解这个问题。它在除课程列表之外的所有页面上工作,因为我们已经在组件中,所以它没有调用 getCoursesByLanguage。我需要使部分组件触发课程列表组件生命周期的开始,或者以某种方式从标题中调用课程列表中的函数 getCoursesByLanguage。我该怎么做?
    【解决方案2】:

    您正在这里检索所有课程:

      ngOnInit() {
        this.getCourseList();
      }
    

    当你得到它们时,你会立即过滤它们:

      // Get 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);
      }
    

    所以现在courses 被过滤为仅针对特定语言的那些:English1、English2、English3 等。

    然后您稍后再致电toggleLanguage 并重新过滤课程。

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

    但此时,courses 已经是过滤列表,因此您无法将它们重新过滤为另一种语言。

    解决此问题的一种方法是将所有课程的列表与过滤后的课程分开。

    所以是这样的:

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

    然后在这里设置:

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

    然后过滤到courses变量中。

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

    现在,您始终将所有课程的完整列表过滤为仅针对 courses 属性中特定语言的课程。

    这行得通吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-01
      • 2018-10-01
      • 2019-07-17
      • 1970-01-01
      • 1970-01-01
      • 2017-10-24
      • 1970-01-01
      • 2019-05-28
      相关资源
      最近更新 更多