Angular 18+ 高级教程 – Angular 的局限和 Github Issues

前言

Angular 绝对有很多缺陷,Issue 非常多,workaround 非常多。

我以前至少有 subscribe 超过 20 个 Issues,几年都没有 right way 处理的。

 

Angular 不支持 Custom @Decorator

Angular 自己是有在用 Decorator (旧版,不是 TypeScript 5.0 后的版本) 的,但是我们可用不了。

相关 Github Issues:

Class decorator stopped working on angular@15

Custom decorators not working using AoT

 

Angular 不支持 PostCSS Configuration

Angular CLI 自己是有在用 PostCSS 来支持 Tailwind CSS 的。但是!它不允许我们设置。

连 postcss-preset-env 我们都设置不了。

像下图这样,办不到😡

相关 Github Issues:

Add postcss-preset-env to Angular build process / allow for configuring custom PostCSS plugins

而且短期没有任何计划要支持😡

 

Angular 不支持 Static Image Hash

Template

<div class="bg-img"></div>

Styles

.bg-img {
  background-image: url('../../public/nana.jpg');
  background-size: cover;
  width: 360px;
  aspect-ratio: 16 / 9;
}

ng build

两个知识点

  1. path 换了

    原本是 '../../public/nana.jpg' (我们写的时候是依据开发时的文件路径)

    变成了 './media/nana-LHLWYWZO.jpg' (这是依据项目发布后的文件路径)

  2. file 换了

    原本是 nana.jpg 换成了 nana-LHLWYWZO.jpg

    有了这个 hash 就可以做缓存了

结论:Angular 在 build 的时候会对 CSS background-image 做 hash 处理。

好,我们接着来看 <img> element。

Template

<img src="../../public/nana.jpg" alt="yangmi">

ng build

两个知识点:

  1. path 没有换

    Angular 没有把开发路径换成发布后的路径,如果我们不做处理会导致最终图片访问失败。

  2.  没有 hash 版本的 img

    Angular 也没有创建出 hash 版本的 img。

结论:<img> element 需要我们自己处理路径和 hash,Angular 完全没有处理,CSS background-image 才有。

解决方案:通常路径可以直接写发布后的路径,hash 可以使用 hash-file.online,总之就是没有工程化了。

相关 Github Issue – Images in templates are not fingerprinted

Angular Team 没有打算去支持它,目前也没有任何 workaround (工程化的方案)。

 

@Directive 指令不支持 CSS

这个 Github Issue – @Directive should support styles and styleUrls properties 排在第 5 名。

组件可以封装 CSS Styles,独立一个 .css 或者 .scss 文件,这个非常方便。

指令虽然没有 Template,但它的职责是 decorate 组件或 Element。你可以用指令去 add class,但这个 class 的样式谁负责呢?

为什么不可以是指令负责?

这就是大家正在争取的 feature,虽然已经争取了 7 年 (from 2017),而且还是第 5 名,但 Angular Team 任然无动于衷...😔

Workaround follow Angular Material 

也谈不上 workaround 吧,就是一些粗糙的手法。

作为 UI 组件库,Angular Material 自然会遇到这个问题,那它怎么弄呢?

简单 -- 全局 CSS Styles 😂

MatRipple 是一个指令

使用方式

<button matRipple>Submit</button>

在任意一个 Element 上添加 attribute matRipple。

效果

点击后出现波纹。

MatRipple 的 CSS Styles 写在 _ripple.scss 里

咦...它们怎么连上的呢?

指令添加了 class

然后在项目的 styles.scss 里 @use @angular/material + @include mat.core

styles.scss 是全局 CSS Styles,每一个组件都会被 effect 到。

mat.core() 会把 _ripple.scss 放到全局,所以它们就连上了。

这个方案最大的问题就是无法按需打包,无论你有没有用到 MapRipple 指令,_ripple.scss 的 CSS Styles 都会被打包进项目里。

 

无能的 Reactive Forms

Reactive Forms 是 Angular v2.0 推出的功能,从 v2.0 到 v18 主要功能几乎完全没有修改和增强过。

只有 v14 的时候勉强加入了类型检测。是的,v2.0 到 v13 一直都是写 AnyScript...😨

这么糟糕的功能,难道没有人吐槽?没有人提 feature request 吗?

当然有!Github Issue – A proposal to improve ReactiveFormsModule,意见是多到...🙈

当然,Angular 团队是不可能去提升这些 feature 的,它们从来都不会搭理社区的意见,除非 Google 团队需要,否则 Reactive Forms 还会继续保持原样好多年呢。

近期一直在吹的 Signal,Reactive Forms 当然也不支持 Github Issue – Signals for Reactive and Template-based Forms

你或许会疑惑...那些用 Angular 的人到底是怎样处理表单的呢?

Reactive Forms 不给力,社区又没见任何一个 workaround library...这不是很奇怪吗?

其实,一点也不奇怪,没轮子就造一个咯...这就是 Angular 用户会干的事儿。

自己做的轮子,自己用,也从不考虑共享给社区。

因为对于这些人来说,量身定做一个轮子是简单的,共享给社区是低价的 (因为别人的需求不一样,而且别人也能自己造)。

这也就造成了 Angular 社区轮子很少的原因 -- 因为不需要。

 

Angular + CSS 4 =💩

Angular 对 CSS 4 的 selector 支持不是很好,某些搭配会产生化学反应,时不时会给你个惊喜🎉。

当 :host 遇上 :has

HelloWorld 组件

<div class="container">
  <div class="wrapper">
    <h1>Hello World</h1>
  </div>
</div>

<div class="wrapper">
  <h1>Hello World</h1>
</div>

一个 .container > .wrapper > h1

一个 .wrapper > h1

HelloWorld Styles

:host {
  display: block;

  &:has(.wrapper) {
    background-color: pink;
  }

  .container:has(.wrapper) {
    background-color: lightblue;
  }
}

组件内有 wrapper 就红色,container 内有 wrapper 就蓝色。

效果

没问题,那我们加一个条件,自由子层有 wrapper 才算

&:has(> .wrapper) {
  background-color: pink;
}

.container:has(> .wrapper) {
  background-color: lightblue;
}

多了 '>' 符号

效果

红色没了...不对啊

组件的子层有 wrapper 啊,怎么会没有呢?

我们 ng build 看看最终生成出来的 CSS,看看区别是什么

// 没有箭头的 ok 
&:has(.wrapper) {
  background-color: pink;
}

// 有箭头的错了
&:has(> .wrapper) {
  background-color: pink;
}

一个有箭头,一个没有箭头,ng build 

[_nghost-%COMP%]:has(.wrapper) {
  background-color: pink;
}

[_nghost-%COMP%]:has(> .wrapper)[_ngcontent-%COMP%] {
  background-color: pink;
}

有箭头的,后面多了一个 [_ngcontent-%COMP%],这就是导致它坏掉的原因。

至于这是啥...我也不知道,也懒得知道,多半是 Angular 团队的不作为。

目前我的 workaround 是使用 ::ng-deep

&:has(::ng-deep > .wrapper) {
  background-color: pink;
}

/* after ng build */
[_nghost-%COMP%]:has(> .wrapper) {
  background-color: pink;
}

嗯...正常了。

这种化学反应只会出现在 :host + :has (CSS 4 selector) 搭配上。

假如不是 :host 而是子层,那效果是这样

.container:has(> .wrapper) {
  background-color: pink;
}

/* after ng build */
[_nghost-%COMP%] .container[_ngcontent-%COMP%]:has(> .wrapper)[_ngcontent-%COMP%] {
  background-color: pink;
}

虽然结尾有 [_ngcontent-%COMP%],但它的匹配却是正确的...我没看懂,估计要源码才能解密了...以后吧。

当 ng-content ::ng-deep 遇上 CSS4 :not() complex selector

我提的 Github Issue – bug(Compiler): incorrect compile when using CSS 4 :not() complex selector

HelloWorld Styles

h1:not(.aa, .bb) {
  background-color: red;
  color: white;
}

这是 CSS4 :not() complex selector。

当 h1 有 .aa 或 .bb 时就不要红色。

HelloWorld Template

<h1 class="aa">Hello World</h1>
<h1 class="bb">Hello World</h1>
<h1>Hello World</h1>

效果

第三个 h1 没有 .aa 也没有 .bb 所以红色。

现在我们加上 ::ng-deep

::ng-deep h1:not(.aa, .bb) {
  background-color: red;
  color: white;
}

效果一摸一样。

好,我们把 h1 用 ng-content 的方式传进来

App Template

<app-hello-world>
  <h1 class="aa">Hello World</h1>
  <h1 class="bb">Hello World</h1>
  <h1>Hello World</h1>
</app-hello-world>

HelloWorld Template

<ng-content />

按理说,我们使用了 ::ng-deep,哪怕是 <ng-content> 内都能渲染到,但是...

没有红色,我们把 CSS4 换成 CSS3 写法

/* stylelint-disable-next-line selector-not-notation */
::ng-deep h1:not(.aa):not(.bb) {
  background-color: red;
  color: white;
}

效果

竟然可以了...😔

我们看看 ng build 后它们各自长啥样

第一个 Only CSS4,没问题

h1:not(.aa, .bb) {
  background-color: red;
  color: white;
}


/* after ng build */
h1[_ngcontent-%COMP%]:not(.aa, .bb) {
  background-color: red;
  color: white;
}

第二个,::ng-deep + CSS4,这个有问题

::ng-deep h1:not(.aa, .bb) {
  background-color: red;
  color: white;
}

/* after ng build */
h1:not(.aa, .bb)[_ngcontent-%COMP%] {
  background-color: red;
  color: white;
}

注意看,结尾又是多了一个 [_ngcontent-%COMP%],这个我们上一 part 的例子雷同,估计是同一个问题。

第三个,::ng-deep + CSS3,没有问题

::ng-deep h1:not(.aa):not(.bb) {
  background-color: red;
  color: white;
}

/* after ng build */
h1:not(.aa):not(.bb) {
  background-color: red;
  color: white;
}

没有 [_ngcontent-%COMP%] 就没有问题。

最后,我开的 Issue 没过多久就被 close 掉了,因为早就有类似的问题了啊。

之后统一追踪 Emulated view encapsulation incorrectly transforms CSS that uses :is() or :where()

总结

想用 CSS4 一定要谨慎,因为 Angular 总是比别人慢好多拍的,也希望这些 Issue 有天会得到团队的重视。

 

Passing undefined to @Input !== optional @Input

在 JavaScript,optional parameter / default parameter value 是这样工作的:

function doSomething(value: string = 'default value'){
    console.log(value);
}
doSomething('a value'); // 'a value'
doSomething();          // 'default value'
doSomething(undefined); // 'default value'

传入 undefined 等价于没有传值。

然而,Angular 的 optional @input 却不是这样工作的:

export class HelloWorldComponent {
  readonly value = input<string>('default value');
  constructor() {
    effect(() => console.log(this.value()));
  }
}

HelloWorld 组件有一个 optional @Input value,并且配有 default value。

使用它

<app-hello-world value="a value" /> <!--log 'a value'-->
<app-hello-world />                 <!--log 'default value'-->

有传值,和没有传值的行为和 JS 一样。

但传入 undefined 却会直接报错。

因为 Angular 把 undefined 也当作值来看待,这点和 JS 不同。

假如我们希望它对待 undefined 等同于没有传值,那我们需要在内部自己做一层处理。

像这样:

export class HelloWorldComponent {
  private readonly defaultValue = 'default value';
  readonly value = input(this.defaultValue, {
    transform: (value: string | undefined) =>
      typeof value === 'string' ? value : this.defaultValue, // undefined 就输出 default value
  });
}

唉...只能说勉强能用吧。

 

Angular 不支持 AddEventListenerOptions (capture, once, passive) 

这个

<button (click)="0">click me</button>

等价于

button.addEventListener('click', () => 0);  

这个

<button (click.capture.once)="0">click me</button>

不等于

button.addEventListener('click', () => 0, { capture: true, once: true });

capture,once,passive 这些通通不支持。

要嘛自己写 DOM Manipulation,要嘛自定义 EventManagerPlugin

相关 Github Issue – UseCapture:true event handlers (31-08-2016 – 今天 14-12-2024 依然 open)

our side 指的是 Google, Angular 是一款 Google 1st 的 Framework。

 

大总结

本篇分享 Angular 用户一定会遇到的大麻烦。

 

posted @ 2024-02-15 10:38  兴杰  阅读(143)  评论(0编辑  收藏  举报