Angular踩坑记录

2,366 阅读6分钟

一些angular使用中的踩坑记录

记录一些在使用angular的过程中踩到的坑。都是angular6以上版本。遇到新问题就会更新。

1.表格错位的问题

项目中用NG-ZORRO的ui框架,当使用到需要横向滚动并且有很多单元格合并的情况时,经常会出现莫名其妙的错位现象,如以下例子

<div style="width: 800px; padding: 30px;">
  <nz-table
    #groupingTable
    nzBordered
    nzSize="middle"
    [nzWidthConfig]="widthConfig"
    [nzScroll]="scrollConfig"
    >
    <thead>
      <tr>
        <th colspan="4">ceshi1</th>
        <th colspan="2">ceshi2</th>
        <th colspan="2">ceshi3</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td colspan="1">1</td>
        <td colspan="1">2</td>
        <td colspan="1">3</td>
        <td colspan="1">4</td>
        <td colspan="1">5</td>
        <td colspan="1">6</td>
        <td colspan="1">7</td>
        <td colspan="1">8</td>
      </tr>
    </tbody>
  </nz-table>
</div>

这个问题我们找了很久没有找到原因,一度以为是NG-ZORRO的bug,后来先找到了一种野路子办法来规避这个bug,就是在thead标签内加入一个隐藏的colspan全为1的表头,来让自适应不会出现偏差。

    <thead>
      <!-- 多加入一行,使头与体对齐 -->
      <tr>
        <th *ngFor="let th of widthConfig;let k = index" [attr.colspan]="1" [nzLeft]="k === 0 ? '0px' : null" style="border:none; height:0; padding:0;"></th>
      </tr>
      <tr>
        <th colspan="4">ceshi1</th>
        <th colspan="2">ceshi2</th>
        <th colspan="2">ceshi3</th>
      </tr>
    </thead>

野路子可以实现业务,但是作为较真的程序猿,不知所以还是很难受,在一段时间的网上搜索后,发现了问题所在。 html表格的布局采用自动表格布局和固定表格布局两种方式,而我们在框架中用widthConfig固定了所有格子的宽度,但是表格却保留了自动布局的模式,一般情况下都可以兼容,但是在有滚动条的情况下,这种模式就会出现计算的错误,导致错位。

所以,终极解决方案就是将固定了格子宽度的时候让表格使用固定表格布局,修改如下的样式

:host {
  ::ng-deep {
    nz-table {
      table {
        table-layout: fixed;
      }
    }
  }
}

2.加了ng-submit的表单会有莫名的提交操作

这个严格来说不是angular的坑,这个算是原生form表单的问题。有的时候我们会遇到需要在聚焦于formControl时点击enter提交表单的需求,这个时候NG自带的ng-submit就是个很好的选择。

<form (ngSubmit)="submit()" nz-form [formGroup]="form">
    ....
</form>

但是在使用ng-submit时一定要注意,他有一个特性是表单中包裹的所有按钮,只要按钮的type为submit,点击以后都会进行一次提交操作,而button标签,除了IE之外所有浏览器的默认button type都是submit。

所以在使用ng-submit时,里面用到的按钮,一定要修改type

  <button nz-button nzType="default" type="button" (click)="reset()" >{{locale.reset}}</button>

3.触发变更检测

angular使用的是脏检查的机制,要让其进行脏检查,首先需要让angular触发变更检测,但是经常会有各种各样奇怪的原因导致无法触发变更检测,这里介绍两种强制触发变更检测的方式:

  • 通过重构赋值来触发变更检测
  dataSer = [...dataSet];
  obj = {...obj};
  • 通过ChangeDetectorRef来触发变更检测
 constructor(
    private cdr: ChangeDetectorRef
  ) { }
  
  this.cdr.detectChanges(); // 强制触发变更检测

在工作中还会遇到另外一种情况,就是页面触发的数据变更太过于频繁,页面数据处理又比较复杂,过于频繁的变更检测就会导致页面非常卡顿,这个时候就应该避免不必要的变更检测,只在页面真正需要更新的时候进行一次检测。这种情况可以用onPush 模式实现,ng-zorro源码就使用了这种方式来更新组件数据提高性能。

@Component({
  selector: 'sidebar-nav',
  templateUrl: './sidebar-nav.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush, // 该模式下页面不会主动更新
})

...

constructor(
private cdr: ChangeDetectorRef
) { }

...

this.cdr.detectChanges(); // 只在需要变更的时候进行触发

4.对懒加载的监听优化

现在的spa越来越大,为了避免首屏加载时间过长,现在的应用都会加入懒加载,但是有的时候懒加载的模块过大,会造成点击懒加载路由的时候产生长时间的卡顿,用户体验很不好。要解决这个问题,可以从两个方面来进行:

细化懒加载模块的大小

通过将懒加载的模块进行细化拆分,按需要甚至可以拆成一个component一个module,懒加载里面套上懒加载,这样,总的加载时间就被分摊到细化模块之上,从而达到优化了加载时间的目的。

监听懒加载的过程,通过页面反馈来消除卡顿影响

页面在发生懒加载的时候,会触发Router.event事件,起始事件分别为RouteConfigLoadStart和RouteConfigLoadEnd,所以,我们可以在全局添加一个监听,监听懒加载的起始,并添加一个全局的加载来反馈给用户。代码如下

 constructor(
    private router: Router
  ) {
    this.router.events.subscribe(event => {
      if (event instanceof RouteConfigLoadStart) {
        this.isLoading = true;
      }
      if (event instanceof RouteConfigLoadEnd) {
        this.isLoading = false;
      }
    });
  }

ViewChild中read参数的作用

我们常使用viewChild装饰器来获取子组件或者dom元素,来进行组件间的交互,装饰器中有两个参数:

(selector: Type<any> | Function | string, opts: {
    read?: any;
    static: boolean;
})

其中有个read的可选参数,在刚开始使用时无法理解其作用,官方文档中写的也不明所以,网上查询也没有找到相关解释。

后来在工作中遇到一种场景,我通过viewChild获取子组件的Directive来进行一些操作,但是该组件有多个地方用到了这个Directive,如果我直接这样使用:

 @ViewChild(SomeDirective, {static: false}) targetDirect: SomeDirective;

targetDirect会指向该组件的第一个使用SomeDirective的地方,这时后我就想到,如果要指向第二个或者第三个使用SomeDirective的地方,是否就是read参数的作用,在尝试后发现, read参数的作用是读取目标的类型,我们可以采用选中目标dom然后读取dom中的directive的方式来实现:

<div #test SomeDirective ></div>

@ViewChild('test', {read: SomeDirective, static: false}) targetDirect: SomeDirective;