返回 登录
1

我为什么不再喜爱Angular了

原文:How I stopped loving Angular
作者:Igor Minin
翻译:lloog

译者注:作者对比其他框架,介绍Angular的不足之处。

我已经使用AngularJS多年,至今仍在工作中使用。基于到它之前历史上形成的架构,我们不得不说它不仅是JS框架的进化,而且是整个Web的里程碑。

2017年,每一个新产品/项目都必须选择一个发展框架。很长一段时间以来,我确信新的Angular2 / 4(仅仅是下面的Angular)将成为未来几年企业发展的主要趋势。我甚至没有想过要在工作中利用别的技术。

但今天我拒绝在自己的下一个项目中使用它。

免责声明:这篇文章完全是主观的,但这是我对正在发生的事情的看法,这只涉及企业级应用。

AngularJS

image

在多年的改进中,框架的大部分缺陷都被修复,库函数现在是稳定的,社区的规模是巨大的。公平地说,如果不打破现有的数千个应用程序,就很难改进AngularJS。

尽管巨大的angular团队的努力和总体框架加速(特别是在1.5版之后),我仍然认为速度是它的主要缺点。当然,这是可以原谅的,即使在这么多年之后,它还是有点向后兼容。

Angular

image

而现在,Angular已经从头开始彻底改写,成为许多新的Web应用程序的新基础。

当然,到今天的道路漫长而充满变革,今天,Angular 4是稳定的,已经准备好全面投入应用。

Angular给我们的最酷的事情之一就是TypeScript普及。 我自己也知道,并且很久以前就在使用它(实际上是从第一个版本)。 然而,大多数人都知道,这得益于Angular。

TypeScript

我不会详细介绍TypeScript,因为在网络上已经有很多关于TypeScript主题的帖子上。 然而,对于企业开发,TypeScript给我们带来非常惊人的功能。 从type系统,成员可见性等,到ES7 / 8,甚至支持IE9。

主要TypeScript上升 - 非常丰富的工具集和伟大的IDE支持。 从我的经验来看,如果您正确使用,将大大更少单元测试。

Vue

如果你正在阅读这个 - 95%的机会,你已经知道Vue.js是什么。
但是对于其他5%的人来说,Vue.js是超轻量级(但功能非常丰富)的框架,它们结合了AngularJS和React两者。

它实际上大部分与React类似,但模板与AngularJS(HTML + Mustache)几乎相同。

实际上,它与AngularJS有很大的不同,但从整体的角度来看,如果您有React或AngularJS经验,那么很容易理解。

过去的大型AngularJS项目

我最近的项目,不久前已经变成了产品,其中就利用了AngularJS 1.5-1.6。

尽管存在稳定的Angular版本已经很长时间了,但由于某些因素(主要是组织而不是技术),我们决定不迁移它。 然而,在我们当前web开发中从一开始就利用的一个功能就是TypeScript。

以下是本项目组件的示例:

import {Component} from "shared-front/app/decorators";
import FileService, {ContentType, IFile} from "../file.service";
import AlertService from "shared/alert.service";

// Custom decorator - info below
@Component({
    template: require("./file.component.html"),
    bindings: {
        item: "<",
    },
})
export default class FileComponent {
    // Ugly duplication, yet much better/stable/encapsulated than using ng-annotate, ng-min etc.
    public static $inject = ["fileService"];
    public item: IFile;

    constructor(private fileService: FileService, private alertService: AlertService) {
    }

    public isVideo() {
        return this.item.contentKeyType === ContentType.VIDEO;
    }

    public downloadFile() {
        // Promise-based HTTP
        this.fileService.download(this.getFileDownloadUrl()).then(() => {
            this.alertService.success();
        });
    }

    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

从我的角度来看,这看起来很不错,即使你不是TS的粉丝。 使用单元和E2E测试也很容易测试(确实如此)。

AngularJS已经有了进一步的发展,发展形式与React相同,如果它再快一些,那么利用它开发大项目还是不错的。

如果您的团队在AngularJS中非常有经验,这仍然有效。 即使如此,我认为我们大多数人愿意向前走,选择现代的东西。

现在 - 中型Angular项目

因此,我们决定像这样,在几个月前为我们的新项目选择了Angular 2(后4)。

选择显而易见,因为我们的团队在第一个版本中拥有大量的专业知识。更重要的是,现在,我使用了alpha - rc版本,而框架问题被指责为0。x版本号。

遗憾的是,这些问题大部分都是与架构相关的,而且不会在短时间内修复。

这是我们在Angular中的组件的例子:

import {Component} from '@angular/core';

import FileService, {ContentType, IFile} from "../file.service";
import AlertService from "shared/alert.service";

// Core decorator with type checking
@Component({
  selector: 'app-file',
  templateUrl: './file.component.html',
  styleUrls: ['./file.component.scss']
})
export class FileComponent {

    Input() item: IFile;
    // No more ugly string-based $inject
    constructor(private fileService: FileService, private alertService: AlertService) {
    }

    public isVideo() {
        return this.item.contentKeyType === ContentType.VIDEO;
    }

    public downloadFile() {
        // Now by default HTTP service returns Observable
        this.fileService.download(this.getFileDownloadUrl()).subscribe(() => {
            this.alertService.success();
        });
    }

    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

有点冗长,但更干净。

优势

Angular CLI——对AngularJS的唯一重大升级

在开始Angular4应用程序开发之后,首先要安装的是Angular CLI。

这需要CLI简化创建新组件/模块/服务等(还有很多其他有用的特性)。在我看来,新的Angular最好的地方是,工具很容易使用,而且真正加速了开发过程。

这就是我们在AngularJS中缺少的东西。在没有这种工具的情况下,每个人都在重新发明轮子。疯狂数量的不同种子应用(首先),数百种不同的方法,同样的东西,无管理状态。现在已经不再是问题了,我们所有人都有一个共同的方法来启动/构建/测试角度应用程序。

当然,CLI也有一些弱点,特别是在为定制/非标准需求配置它方面。然而,它比用于React(crea -react- app)或Vue(Vue - cli)的工具功能要大得多。尽管如此,现在每天都变得更好。

缺点或“我如何不爱Angular”

首先,我不会再写一个像Angular 2这样糟糕的帖子。

然而,尽管上面写的关于Angular的早期版本的文章已经过时了,但在大多数的观点上都是正确的。我可能会说,作家有时可能过于软弱。

一点,我不完全同意,是关于RxJS,只因为它是如此强大:

Ajax请求是单一的,运行方法类似于Observable.prototype.map,当管道中只有一个值不会有语义意义。另一方面,Promise代表一个尚未实现的值,这正是HTTP请求所提供的内容。我花了几个小时强迫Observables执行,然后放弃使用Observable.prototype.toPromise将Observable转换为Promise并简单地使用Promise.all,它比任何Rx.js提供的工作好得多。

实际上,感谢RxJS,如果您开始将流数据视为流,则易于操作。

有时候Observables可以非常有用。 然而悲伤的事实是,我们不会看到一个本地Object.observe:

在与有关各方进行了多次讨论之后,我计划从TC39(目前位于ES规范进程的第二阶段)撤回Object.observe提案,并希望在年底之前消除V8的支持(根据chromestatus.com的数据,该功能在Chrome浏览量的0.0169%中使用)。

此外,尽管Rx提供了所有的优秀特性,但让它成为框架的核心并不是最好的方法。

我认为像我这样的人(真正喜欢Rx)可以很容易地将它自己整合起来,而不是强制它作为默认。 这对我来说不是一个问题,但是我知道的很多人很难理解Observables,Subjects等。所有这些都是Angular复杂度高的部分,似乎对使用框架新手来说难度很大(大多数人刚刚通 HTTP熟悉Observables和Promise)。

另一点,我不同意,是TypeScript - 它真的是一个了不起的语言/工具,请看下面。

特别是如果您已经使用Angular或者只是计划,上面的帖子是强烈建议。

不过,下面我会写一些没有在那篇文章中指出的,我自己的一些想法。

TypeScript in Angular

对我来说最令人失望的是 - 在Angular中使用TypeScript发生了什么。

下面的一些不好的例子。

API设计差

在Angular中使用TypeScript的主要问题之一就是API设计。

TypeScript本身非常适合制作稳定,严格的代码,而不会造成错误的移动。 它基本上是为了制作公共API而创建的,但是Angular团队却无法将其用作优势。

例子:

HttpParams

由于某些原因,Angular团队决定让HttpParams不可变。不要误解我的意思,不变性真的很好,但前提是它是一个通用的方法。如果你认为Angular的大部分类都是不可变的,那么你错了。

所以这个代码例如:

let params = new HttpParams();
params.set('param', param);
params.set('anotherParam', anotherParam);
...
this.http.get('test', {params: params});

对请求不会有任何参数。 为什么? 没有TypeScript或Angular错误或任何类型的警告。 但是,您的GET没有参数。

只需在TypeScript中打开该类,就可以找到注释(用打字符btw):

这个类是不可变的 - 所有的变异操作返回一个新的实例。

这当然是不直观的。

这里是文档中的所有信息:

http
  .post('/api/items/add', body, {
    params: new HttpParams().set('id', '3'),
  })
  .subscribe();

路由器API

对于新的Angular路由,我有很多抱怨,API是主要的问题。

没有更多的命名路线

因为一些奇怪的原因,Angular团队决定删除对命名路线的支持。 它基本上是路由的字符串名称,稍后可以轻松使用,而不用担心url。

在AngularJS应用程序中,下面列出的是至关重要的:

  • 在控制器中使用程序重定向
  • 能够使用enum进行路由,可以轻松搜索和重构整个应用程序(Alt + F7而不是全局搜索字符串)
  • 为我们节省了大量的工作,在拥有上百条现有路由之后,出现了一个新的根路由需求(虚拟)——只需更改一个路由配置文件,而不是打破现有的URL,这要归功于路由名称。

我不知道为什么这个(非常需要的)功能被删除,让有些人不得不自己实现。

事件

现在要使用路由参数,我们必须订阅路由器事件。 好的,没关系,但你只能订阅所有的,即使你只需要一个。 你必须使用instanceof(这是一种新的方法,不同于其他大多数地方):

this.router.events.subscribe(event => {
  if(event instanceof NavigationStart) {
    ...
  }
}

查询参数

如果路由器事件对于某些人来说看起来很好,那么将查询参数设置为Observable的想法看起来很奇怪:

this.activatedRoute.queryParams.subscribe(params => {
    const val1 = params['key'];
    const valN = params['keyN'];
});

导航

另一个奇怪的决定是让所有的路由器交互基于命令,甚至是一个数组。所以最流行和最简单的方法是这样的:

this.router.navigate(['/some']);
...
this.router.navigate(['/other']);

为什么这是不好的?

因为在这种情况下,命令具有any[]的签名。 对于那些不熟悉TypeScript的人,这基本上禁用了类型检查功能,从而告诉编译器,这里可以有任何东西。

那就是当路由是Angular中最松散的部分。

例如,在我们的AngularJS应用程序中,我们尝试尽可能使路由类型化,即使使用枚举而不是简单的字符串。 这大大有助于在大型代码库中查找路由,并高度帮助重构。 因此,在上述情况下,您可以按Shift + F6,而不是全局搜索“某些”字符串。

同样,Angular并没有使用这种很好的TypeScript优势。

 延迟加载 

这个部分可以是另一个帖子,但我想指出的是,任何类型的文稿都忽略了模块名,就像一个接一个的字符串

{
  path: 'admin',
  loadChildren: 'app/admin/admin.module#AdminModule',
},

Forms API

首先,在Angular中有两种形式:模板驱动和反应性。

当然,他们的工作方式完全不同。

然而,我主要关心的是反应性形式的API:

// What is the empty parameter and why is it needed?
// Why name is an array with a validator??
this.heroForm = this.fb.group({
  name: ['', Validators.required ],
  street: ''
});

或文档中的一些示例:

this.heroForm = this.fb.group({
  // Empty field is actually a FormControl called "name"?
  name: '', 
  ...
  // ??
  secretLairs: this.fb.array([])
});

甚至没有提到关于组合两种表单类型的东西 - 你不能仅仅使用属性绑定,如[disabled]表示反应形式…

这些只是一些糟糕的API设计的例子,还有更多的,但我认为这个部分已经可以了。

__metadata

可悲的是,Angular中的TypeScript用法基于Decorator的使用。

Decorator是伟大的,但在运行时,在当前网络(ES7相当远)__metadata中有很大的缺失。 默认情况下禁用它,只有指定了编译器选项才会发送。

__metadata只是存储有关于类/类型/方法/参数等的信息,用装饰器标记。 在运行时需要这些信息。 像Java语言中的反射。

没有元数据,您仍然可以在编译时使用装饰器,但在这种情况下没有太多的好处。

然而,在我们的AngularJS应用程序中,我们使用了这个装饰器,如@Component:

export const Component = (options: ng.IComponentOptions = {}) => controller => angular.extend(options, {controller});

它只是将我们的TypeScript类封装在AngularJS组件对象中,并使它们成为控制器。

然而,在Angular,尽管这种语言功能仍然是实验性的,但它已经成为了框架的核心。这意味着您必须在任何情况下使用反射元数据。虽然这是很有争议的决定。

抽象

内部类的数量、抽象和许多与TypeScript相关的技巧,都无助于社区的采用,甚至可能会对TypeScript本身产生不好的影响。

这种问题的主要例子是Angular中的依赖注入。

这个概念很棒,尤其是单元测试。 然而,在现实中,在前端似乎没有太多的使用类似java的东西。 我们已经在AngularJS应用程序中积极使用了这个应用程序很长一段时间,但是在使用Vue组件测试工作一段时间后,我真的开始怀疑DI的用处。

这也适用于Angular。在过去,我认为他们在制作应用模块、IoC以及所有这些方面都非常出色。例如,它似乎对延迟加载非常有用。在现实中——整体缩小Vue.js大约是60KB,可以完全解耦,没有任何问题。那么我们真的需要所有的模块样板文件吗?

回到DI。在一般的依赖关系(如服务)中,DI看起来很简单,有自己的构造函数:

constructor(heroService: HeroService) {
  this.heroes = heroService.getHeroes();
}

但这只适用于TypeScript类,因此如果您想添加一个常量,则必须使用@Inject:

constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

Ah,您所注入的服务,必须用@ injectable()注释。

但并不是所有服务,只有那些有自己的依赖项的服务,否则您可以省略decorator。

如果建议总是这样做,为什么不强制执行呢?

考虑在每个服务类中添加@Injectable(),即使没有依赖项,在技术上也不需要它。这就是为什么:未来的打样:当你以后添加依赖时,不需要记住@Injectable()。

一致性:所有的服务遵循相同的规则,您不必想知道为什么一个decorator会丢失。

另一句来自官方文件的名言:

总是写@ injectable(),不只是@ injectable。如果您忘记了括号,应用程序将神秘地失效。

总的来说,这给人留下了一种印象,即在“Angular”中,TypeScript被错误的使用。

再一次,我要指出语言本身确实有助于发展。

模板的语法

模板语法是大多数开发人员抱怨的主要内容。这是非常客观的观点。

不同方法/用法的许多不同指令的例子:

<div [ngStyle]="{'color': color, 'font-size': size, 'font-weight': 'bold'}">
  style using ngStyle
</div>

<input [(ngModel)]="color" />

<button (click)="size = size + 1">+</button>

<div [class.required]="isReq">Hello Wordl!</div>  
<div [className]="'blue'">CSS class using property syntax, this text is blue</div>
<div [ngClass]="{'small-text': true, 'red': true}">object of classes</div>
<div [ngClass]="['bold-text', 'green']">array of classes</div>
<div [ngClass]="'italic-text blue'">string of classes</div>

最初,开发人员将新语法定位为AngularJS中的指令溢出的转义。

诺言只有两件事:[]和()。

Properties <input [value]=”firstName”>
Events <button (click)=”buy($event)”>
Two-way <input [(ng-model)]=”userName”>

不幸的是,在现实中,指令的数量并不比AngularJS的要少得多。

另外,这里有一个很好的例子,说明了来自官方文档的双向绑定语法:

把香蕉放在盒子里,记住括号内的括号。

文档

我没有真正看到一点描述Angular文档。 这是非常的不幸,你只需要阅读它来了解这样的企业框架是不行的。

它也比AngularJS文档更差,因为没有更多的版本选择器 - 只有主要版本如2,4和5都在那里。 而且之间有很多突破性变化。

相反,Vue文档。他们不仅写得更干净、更详细,而且还能使用像俄语这样的6种语言。

RxJS

此外,Angular文档没有任何含有Rx和Observable信息的合理页面,或者如何使用它。 没有与官方RxJS文档的链接。 即使Rx是框架的核心部分,Observable的创建也是不同的:

// rx.js Rx.Observable.create(); vs // Angular new Observable()

查看封装

Angular有这种View封装的概念。

它基本上是阴影DOM模拟或其本机支持的用法。

Shadow DOM本身很好,有可能让您甚至在一个页面上使用不同的CSS框架,不同的组件,没有风格重叠。

然而,今天的本地支持相当低。

因此,默认情况下Angular模拟Shadow DOM。

这是组件的一个简单的CSS示例:

.first {
  background-color: red;
}
.first .second {
  background-color: green;
}
.first .second .third {
  background-color: blue;
}

Angular将会像这样处理它:

.first[_ngcontent-c1] {
  background-color: red;
}
.first[_ngcontent-c1]   .second[_ngcontent-c1] {
  background-color: green;
}
.first[_ngcontent-c1]   .second[_ngcontent-c1]   .third[_ngcontent-c1] {
  background-color: blue;
}

不知道为什么要这样做,在每个规则中添加自定义属性。

Vue有同样的可能性,但看起来更干净:


.first[data-v-50646cd8] {
  background-color: red;
}
.first .second[data-v-50646cd8] {
  background-color: green;
}
.first .second .third[data-v-50646cd8] {
  background-color: blue;
}

甚至没有提到,Vue在默认情况下并没有强制使用它,而且启用它就像在样式标签中添加范围一样简单。

也请注意,Vue(Vue - CLI webpack)允许您以同样的方式轻松地切换CSS /SASS/ SCSS,但Angular CLI需要运行诸如ng设置默认值之类的操作。styleExt scss。不知道为什么,因为在引擎盖下它使用相同的webpack,而且可以同时使用这两种扩展而没有任何问题(webpack并不真正关心扩展,它只是内部管道的额外步骤)。

然而,这不是主要的问题,我们在开始使用外部组件时遇到的真正问题。

在我们的例子中,我们使用了最流行的UI框架之一 - PrimeNG,它们有时使用这样的选择器:

body .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
    font-size: 1.1em;
}

这些选择器通过定义已经具有比使用此外部元素的组件样式更高的优先级。

要解决这个问题,你必须这样写东西:

body :host >>> .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
  font-size: 2em;
}

有时它只能通过寓言来做,很重要。

当然,所有这些都与PrimeNG有关,但如果你真正开始使用Angular,那就是你也会遇到的问题。

关于稳定性的话很少

在上面的例子中,我们使用>>> - 作为/ deep /它是阴影穿透选择器的别名。 它允许我们对某些规则“忽略”Shadow DOM,并且对于某些外部组件可能是无价的。

在最近版本的Angular中,开发团队决定根据新标准废除/ deep / 和 >>>。

没有生成错误或警告,只有选择器停止工作。事实证明,现在只有:ng -deep works——在Angular全局中,影子穿刺选择器的相似物。

这不是主要版本的更新(4.2.6 - > 4.3.0,特别感谢NPM版本^),只是有一天,我们的很多样式/布局由于某种原因而被打破。

当然,并不是我们团队的每个人每天都阅读过Angular变化日志,而且可能也错过了最新的标准/趋势。因此,我们首先我们以自己的风格去寻找一个问题——我们不得不花相当多的时间和精力去寻找和解决这个问题。

此外,很快:ng - deep也将停止工作。我真的不知道如何修复那些不那么好的框架,就像前面提到的PrimeNG。

我们的结论是:默认设置-阴影DOM模拟,它产生的问题比它解决的问题多。

自定义HTML解析器

这也是一个单独的帖子,但做得很短:Angular真的写了自己的HTML解析器。最可能的是,因为解决大小写敏感问题。

没有理由再发起一场关于Angular不能成为标准的圣战,但是很多人认为这是一个奇怪的想法,因为对于AngularJS简单的HTML(不区分大小写)已经足够了。

使用AngularJS,可能会出现这种情况:您已经创建了一些,并没有为其创建测试。 一段时间后,组件的模块被删除/重构/等等。

无论如何 - 现在根本不显示。

现在,解析器确定未知的标签,并抛出一个打破整个构建的错误。

但是现在任何外部/ Web组件都需要禁用此检查,或启用允许任何内容的CUSTOM_ELEMENTS_SCHEMA。

你可以自己检查来源:

...
const TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
  'base': new HtmlTagDefinition({isVoid: true}),
  'meta': new HtmlTagDefinition({isVoid: true}),
  'area': new HtmlTagDefinition({isVoid: true}),
  'embed': new HtmlTagDefinition({isVoid: true}),
  'link': new HtmlTagDefinition({isVoid: true}),
  'img': new HtmlTagDefinition({isVoid: true}),
  'input': new HtmlTagDefinition({isVoid: true}),
  'param': new HtmlTagDefinition({isVoid: true}),
  'hr': new HtmlTagDefinition({isVoid: true}),
  'br': new HtmlTagDefinition({isVoid: true}),
  'source': new HtmlTagDefinition({isVoid: true}),
  'track': new HtmlTagDefinition({isVoid: true}),
  'wbr': new HtmlTagDefinition({isVoid: true}),
  'p': new HtmlTagDefinition({
    closedByChildren: [
      'address', 'article', 'aside', 'blockquote', 'div', 'dl',      'fieldset', 'footer', 'form',
      'h1',      'h2',      'h3',    'h4',         'h5',  'h6',      'header',   'hgroup', 'hr',
      'main',    'nav',     'ol',    'p',          'pre', 'section', 'table',    'ul'
    ],
    closedByParent: true
  }),
...
  'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
  'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
  'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}),
  'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
  'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}),
  'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}),
  'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}),
  'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}),
  'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc'
  ...

这里的关键部分——所有这些错误都发生在运行时的浏览器控制台,不可能不让你的webpack构建失败,但是除了白色屏幕,你看不到任何东西。这是因为使用默认的JIT编译器。

这可以通过另一个AOT编译器来预编译模板来修复,只需使用—AOT标志,就这样。但不,这里也有一个不好的地方:ng服务配置很差,而且会使编译时间变慢(开始时很慢,比Vue慢很多)。这就是为什么默认情况下它没有启用的原因(应该是)。

两种不同的编译器的存在可能听起来很危险,而且确实会一直导致问题。

我们有很多不同的与aot相关的错误,包括一些仍然是开放的。像{不能使用默认出口)(https://github.com/angular/angular/issues/11402):

另外,请注意优雅的解决方案:

不要使用默认的导出:)

只要放置导出类型并运行

或者很多类似的问题

AOT并不总是能够理解闭包,因此下面的代码会导致奇怪的编译器错误:

@NgModule({
  providers: [
    {provide: SomeSymbol, useFactor: (i) => i.get('someSymbol'), deps: ['$injector']}
  ]
})
export class MyModule {}

所以你必须以更原始和编译器友好的方式编写代码:

export factoryForSomeSymbol = (i) => i.get('someSymbol');

@NgModule({
  providers: [
    {provide: SomeSymbol, useFactor: factoryForSomeSymbol, deps: ['$injector']}
  ]
})
export class MyModule {}

此外,我们注意到,模板中的错误通常是非信息性的,在HTML中很难搜索那些错误并且IDE支持并不是最好的。

Zone.js

另一个伟大的概念,在新的Angular中,是Zone.js。它允许监视异步操作的执行上下文。然而,尤其是对新手来说,这些堆栈跟踪非常大,很难理解。这里有一个例子:

core.es5.js:1020 ERROR Error: Uncaught (in promise): Error: No clusteredNodeId supplied to updateClusteredNode.
Error: No clusteredNodeId supplied to updateClusteredNode.
    at ClusterEngine.updateClusteredNode (vis.js:47364)
    at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
    at vis-graph-display.service.ts:63
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)
    at ClusterEngine.updateClusteredNode (vis.js:47364)
    at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
    at vis-graph-display.service.ts:63
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)
    at resolvePromise (zone.js:770)
    at zone.js:696
    at zone.js:712
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)

有些人确实喜欢它,但在我们的项目中,我们并没有从中得到太多的价值。我希望这能在以后的生产中有所帮助,但不能肯定。

此外,虽然还有很长的路要走,但是我们必须开发,而且控制台中的错误通常不止一个,所以寻找潜在的问题总是因为堆栈跟踪而复杂化。

甚至没有提到,有时,Chrome控制台对所有人来说都不够。此外,问题的原因可能在控制台的某个地方。

UI框架

另一个部分,实际上不是框架本身的重点,是现有的UI组件/框架的数量非常少。遗憾的是,我认为大多数web开发人员/小型团队不可能从头编写大部分UI组件。获得现有的UI框架而不是创建自己的树和网格通常是理智的(并且更简单,更快)。

是的,我知道这不是UI组件选择前端框架的好方法,但大多数时候它对实际开发至关重要。

以下是Angular的UI框架列表:https://angular.io/resources(UI组件部分)。

我们来看看最受欢迎的免费和开放源代码。

Angular Material 2

对于Angular Material 2而言,我最大的希望是,Angular团队正在开发的产品,要绝对符合Google的准则。

可悲的是,即使它已经存在了相当长的一段时间,组件集也相当小。

当我开始写这篇文章时,没有任何网格(当我们为我们的项目选择UI框架时,组件更少)。 而不久之前它终于到了。 然而功能还是非常基本的。

我认为,Angular Material 2只能适用于较小或最佳情况的中型项目。 此外,大多数时候你需要的东西像多选,那些是没有的。

非常少的文档和少量的示例也值得注意。

最后一点是悲伤的特征计划。

stepper - in - progress,计划Q3 2017,sticky -header - in - progress,计划Q3 2017,virtual -repeat - Not started,fab速拨-不计划,fab工具-不计划等。

功能状态树正在进行中步进正在进行中,计划Q3 2017粘性标题正在进行中,计划Q3 2017虚拟重复未启动,计划Q4 2017 fab speed-dial未启动,未计划fab工具栏未启动,未计划底部 表格未开始,未计划bottom-nav未开始,未计划。

还有一个共同的用户界面 - 但我真的建议你远离它,因为它只是Angular Material的延伸。 而且还不是那么稳定。 现在还有2个不同的网格 - 一个官方形式的Angular,另一个来自Covalent(我不认为这是好的)。

BootStrap

出于同样的原因,我不会描述基于bootstrap的框架,比如ng2 - bootstrap(little better)和ngx - bootstrap。它们一点也不差,但最简单的方法就是使用Bootstrap CSS,而且这里没有复杂的组件(尽管一些devs可以使用模式、datepicker和typeahead)。

同样的,我个人很喜欢的语义,但它仍然是很基本的。

Prime Faces

目前最流行的框架和最复杂的组件是PrimeNG。有栅格,树,甚至是树表!

起初,我对Prime Faces真的很怀疑,因为我有过一段旧的JSF体验,还有很多痛苦的回忆。他们甚至看起来都一样(不太现代,你可以得到免费的主题)。然而,在开始我们的项目的时候,没有什么好的选择,所以我还是要感谢开发团队。他们在短期内创造了一个庞大的工具,通常是这样的。

然而,我们所面临的问题是非常巨大的。

文档有时是非常无用的。忘记添加一些模块了——一些东西刚刚坏掉,没有任何错误。有很多调试需要,这就导致了没有注释的源代码。

总的来说,尽管有这么多组件,我不建议使用PrimeNG作为项目的UI组件。

Clarity

这里的希望之光 - 年轻(创建不到一年前)清晰的vmware库函数。

组件集是伟大的,文档是干净的,它只是看起来不错。

框架不仅具有自己的UI组件,而且也是CSS指南。 像自己的Bootstrap(某些样式像网格/按钮/等等)有些类似。 感谢有一个一致和相当不错/简约的组件外观。

网格功能非常好,稳定,源代码本身(哇,单元测试的写作是允许的吗?)

然而,现在的表单是非常基本的,没有datepicker或者是select2的组件。 它现在正在进行中:DatePicker,选择2.0(总是设计很光滑,即使进度缓慢,我们肯定对结果感到高兴的)。

我想说的是,“清晰的设计系统”——是我仍然相信Angular的唯一原因。它也是非常适合企业开发的框架。毕竟VMware是严肃的维护者,希望有一个光明的未来。

我们刚开始使用它,可能会遇到很多问题,但今天我们完全满意它,它运行得很好。

但只有一个

是的,我认为今天只有一个有价值的Angular框架。问题:这意味着什么?

回答:只有像VMware这样的大公司才有可能开发这样的框架。它需要大量的技能、时间和资源。你真的需要这种企业吗?请考虑一下。

现在让我们来看看主要竞争对手之一(年轻的btw)发生了什么。

Vue UI框架

有关参考,以下是现有Vue.js框架与现有网格组件的列表:

元素(〜15k星),Vue材料(比Angular Material 2年轻得多,但功能更强大),Vuetify(材料再次也是很多组件),Quasar,中文的框架也应该被提及:iView和Muse -UI(iView看起来很不错,但文档不好)。

这是一个简单但显而易见的例子,使用Vue创建这样的组件要容易得多。 这甚至允许您选择许多组件,而不只是希望一个组件,这需要一些巨大的开发团队支持的。

我们的结论

感谢Clarity,希望我们的Angular项目在将来会变得更好。然而,对我们来说,很明显,Angular的所有问题以及所有这些并发症都没有得到很好的解决,即使是一个大型项目。

实际上,所有这些只是增加了所需的开发时间,而不会使支持或重构代码更简单。所以对于我们的下一个项目,我们选择了Vue.js.

这只是为了启动vue-cli的基本webpack模板来了解库函数的运行速度。尽管我一直是一体化框架的粉丝,但Vue做的大部分角色都是(没有任何问题/很多努力)。

当然,UI框架的数量也是很大的优势。

Vue已经有一些东西,这在Angular中并没有像Nuxt.js那样的服务器端渲染,或者是与Weex的跨平台(顺便说一下,这是由伟大的阿里巴巴支持的)。

有一件事我当然想念 - 更好的TypeScript支持,因为我们使用它已经多年并让我们避免了很多的痛苦。

希望微软很快就会合并它。然后它会出现在webpack模板中。

为什么没有反应?在与AngularJS合作之后,我们的团队更容易进入Vue,因为我们对所有的v - if,v - model和v - for都非常熟悉。

我自己也喜欢Aurelia(虽然不是所有关于它的东西),但它现在还不是很流行。与Vue的爆炸性增长相比,它似乎完全不为人所知。

我希望,在一两年后,Angular将摆脱过激现象,解决主要问题,最终应该能成为企业框架。 然而,今天我建议您寻找其他更轻便优雅的解决方案。

相信我,在使用Angular4年之后,很难放弃。 但是在使用Vue之后…

这篇文章最初发表在gist.github.com。

评论