使用Angular,您可以做任何事情。 或几乎所有东西。 但是有时这种阴险的“几乎”导致这样一个事实,即开发人员在浪费时间,方法是创建变通办法,或者试图了解为什么发生了某些事情,或者为什么有些事情没有按预期进行。

我们今天翻译的这篇文章的作者说,他想分享一些技巧,这些技巧将帮助Angular开发人员节省一些时间。 他将谈论Angular的陷阱,他(不仅是他)有机会遇到。
1号 您应用的自定义指令不起作用
因此,您找到了一个不错的第三方Angular指令,并决定将其与Angular模板中的标准元素一起使用。 太好了! 让我们尝试这样做:
<span awesomeTooltip="'Tooltip text'"> </span>
您启动应用程序……什么都没有发生。 您像任何普通的,有经验的程序员一样,会查看Chrome开发者工具控制台。 而且您在那里什么都看不到。 该指令不起作用,Angular是静默的。
然后,团队中的一些聪明人决定将指令放在方括号中。
<span [awesomeTooltip]="'Tooltip text'"> </span>
之后,由于浪费了一些时间,我们在控制台中看到以下内容。
事情就是这样:我们只是忘记了使用指令导入模块现在问题的原因非常明显:我们只是忘记了将指令模块导入Angular应用程序模块。
这导致了一条重要规则:切勿使用不带方括号的指令。
您可以
在此处尝试使用指令。
2号 ViewChild返回未定义
假设您创建了一个指向Angular模板中描述的文本输入元素的链接。
<input type="text" name="fname" #inputTag>
您将使用RxJS
fromEvent函数创建一个流,该流将进入该流。 为此,您需要一个指向输入字段的链接,可以使用Angular
ViewChild
装饰器获得该
ViewChild
:
class SomeComponent implements AfterViewInit { @ViewChild('inputTag') inputTag: ElementRef; ngAfterViewInit(){ const input$ = fromEvent(this.inputTag.nativeElement, 'keyUp') } ... }
在这里,使用RxJS
fromEvent
函数,我们创建了一个流,输入该字段的数据将落入该流中。
测试此代码。
失误发生什么事了
实际上,以下规则适用于此:如果
ViewChild
返回
undefined
,请在模板中查找
*ngIf
。
<div *ngIf="someCondition"> <input type="text" name="fname" #inputTag> </div>
在这里,他是问题的元凶。
此外,请在问题元素上方检查模板是否包含其他结构性指令或
ng-template
。
考虑此问题的可能解决方案。
▍解决问题的方式№1
如果不需要,您可以简单地隐藏模板元素。 在这种情况下,该元素将始终继续存在,并且
ViewChild
将能够在
ngAfterViewInit
挂钩中返回指向它的链接。
<div [hidden]="!someCondition"> <input type="text" name="fname" #inputTag> </div>
▍解决问题的方式№2
解决此问题的另一种方法是使用setter。
class SomeComponent { @ViewChild('inputTag') set inputTag(input: ElementRef|null) { if(!input) return; this.doSomething(input); } doSomething(input) { const input$ = keysfromEvent(input.nativeElement, 'keyup'); ... } }
在这里,只要Angular为
inputTag
属性分配了特定的值,我们就会根据在输入字段中输入的数据创建一个流。
以下是与此问题相关的几个有用资源:
- 在这里您可以看到Angular 8中
ViewChild
的结果可以是静态的也可以是动态的。 - 如果您在使用RxJ时遇到困难,请观看此视频课程。
3号 更新由* ngFor生成的列表时的代码执行(元素出现在DOM中之后)
假设您有一些有趣的自定义指令来组织可滚动列表。 您将把它应用于使用Angular
*ngFor
创建的列表。
<div *ngFor="let item of itemsList; let i = index;" [customScroll] > <p *ngFor="let item of items" class="list-item">{{item}}</p> </div>
通常,在这种情况下,更新列表时,需要考虑列表中发生的更改,调用类似
scrollDirective.update
类的
scrollDirective.update
来配置滚动行为。
似乎可以使用
ngOnChanges
挂钩完成此操作:
class SomeComponent implements OnChanges { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; ngOnChanges(changes) { if (changes.itemsList) { this.scroll.update(); } } ... }
没错,这里我们面临一个问题。 在浏览器显示更新的列表之前,将调用该挂钩。 结果,错误地执行了用于滚动列表的指令参数的重新计算。
*ngFor
完成工作后,如何立即拨打电话?
您可以按照以下3个简单步骤进行操作:
▍第1步
将链接放置到应用了
*ngFor
(
#listItems
)的元素上。
<div [customScroll]> <p *ngFor="let item of items" #listItems>{{item}}</p> </div>
▍步骤2
使用Angular
ViewChildren
装饰器获取这些元素的列表。 它返回
QueryList
类型的实体。
▍第3步
QueryList
类具有只读的
changes属性,该属性在每次列表更改时都会
QueryList
事件。
class SomeComponent implements AfterViewInit { @Input() itemsList = []; @ViewChild(CustomScrollDirective) scroll: CustomScrollDirective; @ViewChildren('listItems') listItems: QueryList<any>; private sub: Subscription; ngAfterViewInit() { this.sub = this.listItems.changes.subscribe(() => this.scroll.update()) } ... }
现在问题已解决。
在这里,您可以尝试相应的示例。
4号 可以在没有参数的情况下运行查询时发生的ActivatedRoute.queryParam问题
以下代码将帮助我们理解此问题的本质。
该代码的某些片段被注释为
#x
。 考虑他们:
- 在应用程序的主模块中,我们定义了路由,并在其中添加了
RouterModule
。 配置了路由,以便如果URL中未提供路由,我们会将用户重定向到/home
。 - 作为下载组件,我们在主模块
AppComponent
指定。 AppComponent
使用<router-outlet>
输出适当的路由组件。- 现在最重要的是。 我们需要从URL获取
queryParams
作为路线的
假设我们获得了以下URL:
https:
在这种情况下,
queryParams
将如下所示:
{accessToken: 'someTokenSequence'}
让我们在浏览器中查看所有这些工作。
测试实现路由系统的应用程序在这里,您可能对问题的实质有疑问。 我们得到了参数,一切按预期进行...
查看上面的浏览器屏幕快照,以及控制台中显示的内容。 在这里,您可以看到
queryParams
对象已发出两次。 第一个对象为空;它在Angular路由器的初始化过程中发出。 只有在此之后,我们才能获得包含请求参数的对象(在我们的示例中为
{accessToken: 'someTokenSequence'}
)。
问题是,如果URL中没有请求参数,则路由器将不返回任何内容。 即-在发出第一个空对象之后,也可能不会指示可能没有参数的第二个对象也为空。
在执行不带参数的请求时,不会发出第二个对象结果,事实证明,如果代码期望第二个对象可以从中接收请求数据,那么如果URL中没有请求参数,则它将不会启动。
如何解决这个问题? 在这里,RxJ可以帮助我们。 我们将基于
ActivatedRoute.queryParams
创建两个可观察对象。 像往常一样-考虑逐步解决问题的方法。
▍第1步
如果
queryParams
不为空,则第一个可观察对象
paramsInUrl$
将
paramsInUrl$
数据:
export class AppComponent implements OnInit { constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
▍步骤2
仅当在URL中未找到请求参数时,第二个可观察对象
noParamsInUrl$
才会
noParamsInUrl$
空值:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() { ...
▍第3步
现在,使用RxJS
合并功能
合并观察到的对象:
export class AppComponent implements OnInit { title = 'QueryTest'; constructor(private route: ActivatedRoute, private locationService: Location) { } ngOnInit() {
现在,观察到的
param$
对象仅
param$
值-不管
queryParams
中是否
queryParams
(
queryParams
带有查询参数的对象)或否(
queryParams
空对象)。
您可以
在此处尝试此代码。
5号 慢页
假设您有一个显示一些格式化数据的组件:
该组件解决了两个问题:
- 它显示一个元素数组(假定此操作执行一次)。 另外,它通过调用
formatItem
方法格式化显示的formatItem
。 - 它显示鼠标的坐标(此值显然会经常更新)。
您不希望该组件出现任何性能问题。 因此,请仅执行性能测试以遵守所有手续。 但是,此测试过程中会出现一些奇怪的现象。
很多formatItem调用和很多CPU负载怎么了 但是事实是,当Angular重绘模板时,它将调用模板中的所有函数(在我们的示例中为
formatItem
函数)。 结果,如果在模板功能中执行了任何繁重的计算,则会给处理器带来压力,并影响用户对相应页面的感知方式。
如何解决? 预先执行在
formatItem
中执行的计算,并在页面上显示已经准备好的数据就足够了。
现在,性能测试看起来更加不错。
仅6种格式项调用和较低的处理器负载现在,该应用程序运行得更好。 但是,这里使用的解决方案具有一些并不总是令人愉快的功能:
- 由于我们在模板中显示鼠标的坐标,因此
mousemove
事件仍会触发更改检查。 但是,由于我们需要鼠标的坐标,因此无法摆脱这一点。 - 如果
mousemove
事件处理程序仅需要执行一些计算(不会影响页面上显示的内容),则可以使用以下方法来加快应用程序的速度:
- 您可以在事件处理函数内使用
NgZone.runOutsideOfAngular
。 这样可以防止在mousemove
事件mousemove
开始进行更改检查(这只会影响此处理程序)。 - 您可以通过使用polyfills.ts中的以下代码来防止某些事件的zone.js补丁。 这将影响整个Angular应用程序。
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];
如果您对提高Angular应用程序性能的问题感兴趣-
在此处 ,
此处 ,
此处和
此处 -有关此的有用材料。
总结
既然您已经阅读了本文,那么Angular-developer工具库中应该会出现5个新工具,您可以使用它们解决一些常见问题。 希望您在这里找到的提示可以帮助您节省时间。
亲爱的读者们! 您是否知道什么可以帮助您节省开发Angular应用程序的时间?
