3

异步验证器防抖的补充 以及 记录动态组件遇到的问题

 2 years ago
source link: https://segmentfault.com/a/1190000041688103
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

异步验证器防抖的补充 以及 记录动态组件遇到的问题

这里对上文的异步验证器防抖做一点补充。
https://segmentfault.com/a/11...

vehicleBrandNameNotExist(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }
      return timer(this.debounceTime).pipe(
        // 调用服务, 获取结果
        switchMap(() => this.vehicleBrandService.existByName(control.value)),
        // 对结果进行处理,null表示正确,对象表示错误
        map((exists: boolean) => (exists ? {vehicleBrandNameExist: true} : null)),
      )
    };
  }

上文的最后提到,发现这种写法可以。经过老师的判断后,发现几种写法的关键在于一个问题:

每次获得新值的时候,fromControl是否会取消对上一个数据源的订阅?

这种问题从源码中找比较困难,于是我搜索了相关资料:

找到一篇同样介绍这种方法的文章。

如下图,同样用的是time().pipe, 他的解释说,

当有新的control值时,fromControl会取消对上一个正在等待完成的observable的订阅,因此,旧的observable不会发出值。


image.png

我画了一张图来说明:

看图就可以了解到,fromControl取消了对旧值的订阅,旧的observable并不会发出值。

image.png


明白了这个道理之后,防抖就可以简化成这种写法:

vehicleTypeNameIsAvailable(VehicleTypeId?: number): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      if (control.value === '') {
        return of(null);
      }

      return of(null).pipe(
        delay(1000),
        // 调用服务, 获取结果
        switchMap(() => this.vehicleTypeService.nameIsAvailable(control.value, VehicleTypeId)),
        // 对结果进行处理,null表示正确,对象表示错误
        map((available: boolean) => (available ? null : {vehicleTypeNameNotAvailable: true})),
      )
    };
  }

直接返回null作为observable, 如果delay(1000),通过了,那就调用service服务。

经过测试,执行得很好,符合预期。

后台遇到的问题

起因是这样的:
是这样的,我在user实体添加了Ding字段,作为推送所有客户端信息的钉钉。
image.png


但是在登录的时候发现,只要登录的用户里有这个字段,后台就报错
image.png


报错如下:
image.png

image.png

报了HTTP,和栈溢出等方面的错误。看不大懂,于是去谷歌:
image.png

搜了很多,大部分都是说因为添加了 many to One,many to many 等关系, Serializable 序列化或者反序列化导致栈溢出,

建议是:添加@JsonIgnore ,虽然可行,没有报错,但是添加之后前台就接受不到这个字段了,问题是我需要这个字段。

后来解决无果,老师看之后,才发现,c层的请求没有添加@JsonView,这才导致出错。

添加之后才解决问题。

总结:先查看哪个请求报的错,看看相关代码有无问题,不行了再谷歌,因为有时候谷歌并不能很好地解决自己的问题。

动态组件遇到的问题

v层是这样:

<form [formGroup]="formGroup" *ngIf="task">
 <....> // 这里是 描述<imput>
 <....> // 这里是 有无截止日期<select>
 <....> // 这里是  截止日期<input>

  <!--  动态表单项-->
  <div class="ad-banner-example">
    <ng-template appFormItem></ng-template>
  </div>
</form>

按理来是这样:应该显示红框中的内容,即我上述代码写的动态表单
<ng-template appFormItem></ng-template>

image.png


但是结果是这样:动态表单并没有显示出来。

image.png


这里展示一下代码配置:

c层设置了一个这个属性,可以往其中添加动态表单项。

@ViewChild(FormItemDirective, {static: true})
appFormItem!: FormItemDirective;

这个是指令定义,用其中的viewContainerRef往里添加动态表单组件。

@Directive({
  selector: '[appFormItem]'
})
export class FormItemDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

用图表示是这样:

image.png


我在控制台中得到了这样的报错:

image.png

也就是说,父组件通过 @viewChild 访问到子组件的属性 是 undefined!

先来看看什么是@ViewChild

@ViewChild是一个属性装饰器。它提供了一个强大的方式来访问子元素和属性。当我们想要访问父组件中的子组件元素、表单属性脏检查、触发事件,以及父组件中的指令时,@ViewChild是一种非常简单的访问方式。@ViewChild是一种非常简单的访问子元素的方法。

为什么会出现这种访问子元素属性是undefined的情况呢?我查找了 @viewChild 的相关资料。

我得到了这么一个资料:

当父组件开始渲染时,ViewChild装饰器不可用。它将不会在ngOnInit()生命周期钩子中可用。它将在ngAfterViewInit生命周期钩子中可用。

看到这,明白了报错原因

在ngOnInit的期间我们访问了 @viewChild ,但是它不可用,所有控制台报了错


那么新的问题来了:
即使它在ngOnInit不可用,但是在ngOnInit后,angular 难道不会再渲染一次,使它可用吗?

我的意思是:当页面稳定下来后,我们能看到它可用。

这里又涉及到 @ViewChild 的另一个属性: static

@ViewChild(FormItemDirective, {static: true})

在我的代码中,设置它为static。

static

staic属性有什么用? 我找了相关内容

Static stands for whether the ViewChild is “static” content e.g. Always available on the page, no matter page bindings, API calls, ngIfs etc. When set to true, we are telling Angular that the ViewChild will be available at anytime, so simply query for the ChildComponent at the earliest lifecycle hook available and then never query again.

简单来说就是:

Static 代表 ViewChild 是否是“静态”内容.

  • 设置为 true 时,我们告诉Angular ViewChild将随时可用,因此只需在可用的最早生命周期钩子查询并加载,然后再也不查询。
  • 设置为 false,我们是说 ViewChild 将在以后可用,因此我们必须检查每次运行时的 ViewChild,若不可用,则加载。明显,这会产生更高的性能负载,因为我们必须始终检查在组件更改时它是否可用。

来源:
https://tutorialsforangular.c...

暂时不知道这个负载有多大。

会有两个结果:

  1. 设置为true, 在 ngOnInit 时 ViewChild 可用,但是以后都不再查询和加载。
  2. 设置为false, 在 ngOnInit 时不可用,在组件变更等访问操作时,查询,若不可用,则加载。

理解了含义之后,来找找我自己代码的问题。

我发现了问题所在:
image.png

问题原因:在static为true时,使用了*ngIf。

在ngOnInit时,task实体没有值,所以不会加载下面的子组件。

解决方案:

  • task有值时手动渲染一遍。
  • 把 static 改为 false , 每次用之前查询一遍,若未加载则加载。但是对性能有一定影响。

经过测试,两种方法都能很好的生效。我用了第一种。

  • 若想在 ngOnIni t中使 @ViewChild 可用 ,并节约性能,或者在页面上始终可用,并且从不隐藏, static 设为true , 但要注意是否能加载成功。
  • 若想以某种方式动态地调用,或者想每次都查询是否可用, static 设置 false,

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK