Angular:配置默认的 QueryParamsHandling [英] Angular: configure default QueryParamsHandling

查看:57
本文介绍了Angular:配置默认的 QueryParamsHandling的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经构建了一个 angular 9 应用程序,并使用 @ngx-translate 添加了本地化.我已经配置了我的应用程序,以便它采用 lang 查询参数并相应地更改区域设置.

@Component({选择器:'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.scss']})导出类 AppComponent 实现 OnInit, OnDestroy {构造函数(私有路由:ActivatedRoute,私有translateService:TranslateService){this.translateService.setDefaultLang('en');this.route.queryParamMap.subscribe((params) => {让 lang = params.get('lang');console.log('语言', lang);如果(语言!== 空){this.translateService.use(lang);}});}}

然后我在侧边栏上添加了 3 个按钮来更改查询参数(并切换语言)

<a [routerLink]='[]' [queryParams]="{}"><app-flag [country]="'en'" [appHoverClass]="'brightness-250'"></app-flag></a><a [routerLink]='[]' [queryParams]="{'lang':'nl'}"><app-flag [country]="'nl'" [appHoverClass]="'brightness-250'"></app-flag></a><a [routerLink]='[]' [queryParams]="{'lang':'fr'}"><app-flag [country]="'fr'" [appHoverClass]="'brightness-250'"></app-flag></a>

这工作正常.但是当一个普通的 routerLink 被按下,或者在一个 router.navigate() 调用时,查询参数再次丢失.

我不想用 [queryParamsHandling]="'preserve'" 指令装饰我的应用程序中的每一个 routerLink,因为这是一项乏味的工作并且可怕的做法.这个主题已经有一个 GitHub 问题活跃,但 angular 团队几乎没有研究它(已经 4 年了):https://github.com/angular/angular/issues/12664

有没有办法(任何方式)在导航时默认保留查询参数(或只是 lang 查询参数)?

我已经在默认的角度路由器之上创建了一个 ExtendedRouter

import { Router, QueryParamsHandling, NavigationExtras, UrlTree } from '@angular/router';导出类 ExtendedRouter {构造函数(私有路由器:路由器){}私人 _defaultQueryParamsHandling: QueryParamsHandling = null;公共获取 defaultQueryParamsHandling() {返回 this._defaultQueryParamsHandling;}公共设置 defaultQueryParamsHandling(值:QueryParamsHandling){this._defaultQueryParamsHandling = 值;}公共导航(命令:任何[],额外的?:NavigationExtras){返回 this.router.navigate(命令,{queryParamsHandling: extras.queryParamsHandling ??this.defaultQueryParamsHandling ??'',片段:extras.fragment,保留片段:额外的.preserveFragment,queryParams: extras.queryParams,相对:extras.relativeTo,replaceUrl: extras.replaceUrl,skipLocationChange: extras.skipLocationChange});}public navigateByUrl(url: string | UrlTree, extras?: NavigationExtras) {返回 this.router.navigateByUrl(url, {queryParamsHandling: extras.queryParamsHandling ??this.defaultQueryParamsHandling ??'',片段:extras.fragment,保留片段:额外的.preserveFragment,queryParams: extras.queryParams,相对:extras.relativeTo,replaceUrl: extras.replaceUrl,skipLocationChange: extras.skipLocationChange});}public createUrlTree(命令:任何[],额外的?:NavigationExtras){返回 this.router.createUrlTree(commands, extras);}公共序列化Url(网址:UrlTree){返回 this.router.serializeUrl(url);}}

但这与 [routerLink] 指令无关.我也尝试过创建一个,但我需要的所有字段都限定为 private.

import { Directive, Renderer2, ElementRef, Attribute, Input } from '@angular/core';从@angular/router"导入 { RouterLink, Router, ActivatedRoute };从'../../helpers/extended-router'导入{扩展路由器};@指示({选择器:'[extendedRouterLink]'})导出类 ExtendedRouterLinkDirective 扩展 RouterLink {私有路由器2:路由器;私有路由2:ActivatedRoute;私有命令 2: any[] = [];构造函数(路由器:路由器,路由:ActivatedRoute,@Attribute('tabindex')tabIndex:字符串,渲染器:Renderer2,el:ElementRef<any>,私有extendedRouter:ExtendedRouter){超级(路由器,路由,tabIndex,渲染器,el);this.router2 = 路由器;this.route2 = 路线;}@输入()设置extendedRouterLink(commands: any[] | string | null | undefined) {如果(命令!= null){this.commands2 = Array.isArray(commands) ?命令:[命令];} 别的 {this.commands2 = [];}super.commands = 命令;}获取 urlTree() {返回 this.router2.createUrlTree(this.commands, {相对:this.route2,queryParams: this.queryParams,片段:this.fragment,queryParamsHandling: this.queryParamsHandling,保留片段:this.attrBoolValue(this.preserveFragment),});}private attrBoolValue = (s: any) =>{返回 s === '' ||!!s;}}

有没有人知道如何解决这个问题而不必在每个 [routerLink] 上定义一个 [queryParamsHandling]?

解决方案

这种方法有一个小问题:

@指示({选择器:'a[routerLink]'})导出类 QueryParamsHandlingDirective 扩展 RouterLinkWithHref {queryParamsHandling: QueryParamsHandling = 'merge';}

问题在于它扩展了 RouterLinkWithHref,这意味着 <a routerLink=""> 将有 2 个指令(一个扩展另一个)附加到它.

还有 这是RouterLinkWithHrefclick 处理程序中发生了什么:

@HostListener('click')onClick(): 布尔 {常量附加 = {skipLocationChange: attrBoolValue(this.skipLocationChange),replaceUrl: attrBoolValue(this.replaceUrl),状态:this.state,};this.router.navigateByUrl(this.urlTree, extras);返回真;}

更重要的是它在传送到浏览器时的外观:

 RouterLinkWithHref.prototype.onClick = function (button, ctrlKey, metaKey, shiftKey) {if (button !== 0 || ctrlKey || metaKey || shiftKey) {返回真;}if (typeof this.target === 'string' && this.target != '_self') {返回真;}变量附加 = {skipLocationChange: attrBoolValue(this.skipLocationChange),replaceUrl: attrBoolValue(this.replaceUrl),状态:this.state};this.router.navigateByUrl(this.urlTree, extras);返回假;};

这意味着当你点击一个 标签时,QueryParamsHandlingDirective.onClick 将被调用,然后 RouterLinkWithHref.onClick>.但是由于 RouterLinkWithHref.onClick 是最后调用的,它不会将 queryParamsHandling 设置为 merge.

<小时>

解决方案是稍微修改自定义指令,使其不继承任何东西,而只是设置一个属性:

@Directive({选择器:'a[routerLink]'})导出类 QueryParamsHandlingDirective {构造函数(路由器链接:RouterLinkWithHref){routerLink.queryParamsHandling = '合并';}}

StackBlitz.

I've built an angular 9 app, and added localization with @ngx-translate. I've configured my app so that it takes the lang query parameter and changes the locale accordingly.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
  constructor(private route: ActivatedRoute, private translateService: TranslateService) {
    this.translateService.setDefaultLang('en');
    this.route.queryParamMap.subscribe((params) => {
      let lang = params.get('lang');
      console.log('language', lang);
      if (lang !== null) {
        this.translateService.use(lang);
      }
    });
  }
}

I then added 3 buttons on my sidebar to change the query parameter (and switch the language)

<div class="p-1 text-center">
  <a [routerLink]='[]' [queryParams]="{}">
    <app-flag [country]="'en'" [appHoverClass]="'brightness-250'"></app-flag>
  </a>
  <a [routerLink]='[]' [queryParams]="{'lang':'nl'}">
    <app-flag [country]="'nl'" [appHoverClass]="'brightness-250'"></app-flag>
  </a>
  <a [routerLink]='[]' [queryParams]="{'lang':'fr'}">
    <app-flag [country]="'fr'" [appHoverClass]="'brightness-250'"></app-flag>
  </a>
</div>

This is working fine. But when a normal routerLink is pressed, or at a router.navigate() call, the query parameters are lost again.

I don't want to decorate each and every routerLink in my application with the [queryParamsHandling]="'preserve'" directive since this is a tedious job and horrible practice. There is already a GitHub issue active for this topic, but the angular team is pretty much not working on it (for 4 years already): https://github.com/angular/angular/issues/12664

Is there a way (any way) to have the query parameters (or just the lang query parameter) preserved by default when navigating?

I've already created an ExtendedRouter on top of the default angular router

import { Router, QueryParamsHandling, NavigationExtras, UrlTree } from '@angular/router';

export class ExtendedRouter {
  constructor(private router: Router) {
  }

  private _defaultQueryParamsHandling: QueryParamsHandling = null;
  public get defaultQueryParamsHandling() {
    return this._defaultQueryParamsHandling;
  }
  public set defaultQueryParamsHandling(value: QueryParamsHandling) {
    this._defaultQueryParamsHandling = value;
  }

  public navigate(commands: any[], extras?: NavigationExtras) {
    return this.router.navigate(commands, {
      queryParamsHandling: extras.queryParamsHandling ?? this.defaultQueryParamsHandling ?? '',
      fragment: extras.fragment,
      preserveFragment: extras.preserveFragment,
      queryParams: extras.queryParams,
      relativeTo: extras.relativeTo,
      replaceUrl: extras.replaceUrl,
      skipLocationChange: extras.skipLocationChange
    });
  }

  public navigateByUrl(url: string | UrlTree, extras?: NavigationExtras) {
    return this.router.navigateByUrl(url, {
      queryParamsHandling: extras.queryParamsHandling ?? this.defaultQueryParamsHandling ?? '',
      fragment: extras.fragment,
      preserveFragment: extras.preserveFragment,
      queryParams: extras.queryParams,
      relativeTo: extras.relativeTo,
      replaceUrl: extras.replaceUrl,
      skipLocationChange: extras.skipLocationChange
    });
  }

  public createUrlTree(commands: any[], extras?: NavigationExtras) {
    return this.router.createUrlTree(commands, extras);
  }

  public serializeUrl(url: UrlTree) {
    return this.router.serializeUrl(url);
  }
}

But this doesn't deal with the [routerLink] directive. I've tried creating one as well, but all fields I need are scoped to private.

import { Directive, Renderer2, ElementRef, Attribute, Input } from '@angular/core';
import { RouterLink, Router, ActivatedRoute } from '@angular/router';
import { ExtendedRouter } from '../../helpers/extended-router';

@Directive({
  selector: '[extendedRouterLink]'
})
export class ExtendedRouterLinkDirective extends RouterLink {

  private router2: Router;
  private route2: ActivatedRoute;
  private commands2: any[] = [];
  constructor(router: Router, route: ActivatedRoute, @Attribute('tabindex') tabIndex: string, renderer: Renderer2, el: ElementRef<any>, private extendedRouter: ExtendedRouter) {
    super(router, route, tabIndex, renderer, el);
    this.router2 = router;
    this.route2 = route;
  }

  @Input()
  set extendedRouterLink(commands: any[] | string | null | undefined) {
    if (commands != null) {
      this.commands2 = Array.isArray(commands) ? commands : [commands];
    } else {
      this.commands2 = [];
    }
    super.commands = commands;
  }

  get urlTree() {
    return this.router2.createUrlTree(this.commands, {
      relativeTo: this.route2,
      queryParams: this.queryParams,
      fragment: this.fragment,
      queryParamsHandling: this.queryParamsHandling,
      preserveFragment: this.attrBoolValue(this.preserveFragment),
    });
  }

  private attrBoolValue = (s: any) => {
    return s === '' || !!s;
  }

}

Anyone an idea how to get around this without having to define a [queryParamsHandling] on each [routerLink]?

解决方案

There is a small problem with this approach:


@Directive({
  selector: 'a[routerLink]'
})
export class QueryParamsHandlingDirective extends RouterLinkWithHref {
  queryParamsHandling: QueryParamsHandling = 'merge';
}

The problem is that it extends RouterLinkWithHref, meaning an <a routerLink=""> will have 2 directives(one which extends the other) attached to it.

And this is what happens inside RouterLinkWithHref's click handler:

@HostListener('click')
onClick(): boolean {
  const extras = {
    skipLocationChange: attrBoolValue(this.skipLocationChange),
    replaceUrl: attrBoolValue(this.replaceUrl),
    state: this.state,
  };
  this.router.navigateByUrl(this.urlTree, extras);
  return true;
}

What's more important is how this looks when it's shipped to the browser:

 RouterLinkWithHref.prototype.onClick = function (button, ctrlKey, metaKey, shiftKey) {
  if (button !== 0 || ctrlKey || metaKey || shiftKey) {
      return true;
  }
  if (typeof this.target === 'string' && this.target != '_self') {
      return true;
  }
  var extras = {
      skipLocationChange: attrBoolValue(this.skipLocationChange),
      replaceUrl: attrBoolValue(this.replaceUrl),
      state: this.state
  };
  this.router.navigateByUrl(this.urlTree, extras);
  return false;
};

This means that when you click on an <a> tag, the QueryParamsHandlingDirective.onClick will be invoked, and then RouterLinkWithHref.onClick. But since RouterLinkWithHref.onClick is called last, it won't have the queryParamsHandling set to merge.


The solution is to slightly modify the custom directive so that it does not inherit anything, but simply sets a property:

@Directive({
  selector: 'a[routerLink]'
})
export class QueryParamsHandlingDirective {
  constructor (routerLink: RouterLinkWithHref) {
    routerLink.queryParamsHandling = 'merge';
  }
}

StackBlitz.

这篇关于Angular:配置默认的 QueryParamsHandling的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆