Angular - 神游!
简要指南,帮助您理解 ZoneJs 并解决问题。
不如就不一开始就解释主题了,直接讲个故事吧。好吧,事情是这样的——那是一个普通的上班早晨。我像往常一样一边喝着咖啡,一边努力思考如何解决一个问题。这个问题是实现一个进度条,用来跟踪页面加载完成前的所有 API 调用(页面当然要加载 API 数据!)。由于这个应用要处理海量数据,所以这个加载器需要能够跟踪大量的 API 调用。“这能有多难?”我心想。但这个一开始看起来微不足道的问题,后来却变成了一场噩梦。
最初...
我当时对如何使用 Angular 的强大工具进行开发几乎一窍不通。所以,就像遇到其他问题一样,我开始寻找在 Angular 中实现这个功能的各种方法。我阅读了许多博客,也浏览了一些 Stack Overflow 上的帖子。但找到的几乎都没什么用。在当前情况下,它们似乎都不够高效。没有任何模块或库可以实现这个功能。我开始感到焦虑。但后来我想到了一个在我看来合理的解决方案,我又重新燃起了热情。
有时候最简单的解决方案是最好的,但有时候并非如此。
解决方案很简单。创建一个进度条组件,然后使用一个服务来触发进度条前进。就这么简单!
我首先创建了一个用于消息的模型类:
export class Message {
id: string;
message: string;
active: boolean;
}
模型准备就绪后,接下来我创建了进度加载组件,即 ProgressBarComponent:
// other imports
import { Message } from '../../models/interfaces';
@Component({
selector: 'progress-bar',
templateUrl: './progress.bar.component.html',
styleUrls: ['./progress.bar.component.scss']
})
export class ProgressBarComponent implements OnChanges {
@Input() messages: Message[] = [];
@Output() loadingEmitter = new EventEmitter<boolean>();
constructor() { }
public activeMessage: Message = { id: '', message: '', active: false };
public progressCount = 0;
ngOnChanges() {
/* Code to check progress count and set the active message on the loader */
/* Actual code removed for the sake of brevity. */
}
}
以及触发活动消息的服务,即:
// other imports
import { Message } from '../../../../models/interfaces';
@Injectable({
providedIn: 'root'
})
export class LoadTrackerService {
constructor() {}
public loaderMessages: Message[] = [
{ id : 'm_id_1', message: 'Load Started,API 1 called', active: true },
{ id : 'm_id_2', message: 'API 2 called', active: false },
{ id : 'm_id_3', message: 'API 3 called', active: false },
{ id : 'm_id_4', message: 'API 4 called', active: false }
{ id : 'm_id_5', message: 'API 5 called, Load Complete', active: false }
];
public loadingPercent: number;
public loading = true;
public messageSubject = new BehaviorSubject<Message[]>(this.loaderMessages);
setMessage(messageId: string) {
if (this.activateMessage(messageId)) {
this.messageSubject.next(this.loaderMessages);
}
}
activateMessage(messageId: string): Boolean {
/* Code to activate message on the loader and return boolean on
activation*/
/* Actual code removed for the sake of brevity. */
}
}
现在,messageSubject将由 APIService(所有 API 调用都通过该服务发起)触发,并由加载器组件订阅以检索活动消息并递增加载器。一切都说得通,直到我意识到真正的问题所在。
开局不利
我很快意识到没有办法跟踪 API 调用,我所能做的就是在 APIService 的每个方法中触发 LoadTrackerService,如下所示:
@Injectable({
providedIn: 'root'
})
export class APIService {
constructor(
private loadTracker: LoadTrackerService) {}
api_call_1() {
/* Http call for API 1*/
this.loadTracker.setMessage('m_id_1');
}
api_call_2() {
/* Http call for API 2*/
this.loadTracker.setMessage('m_id_2');
}
api_call_3() {
/* Http call for API 3*/
this.loadTracker.setMessage('m_id_3');
}
// and so on...
}
上述方案当然适用于 API 调用次数不多的情况,但在实际应用中,如果 API 调用次数达到数百次,这种方法会使代码变得冗杂且重复。我需要一种更好、更简洁的方案。
进入安全区(js) ;)
经过大量的研究和阅读各种关于 Angular 的深度文章后,我偶然发现了这篇文章。原来 Angular 会在一个叫做Zone的机制中处理和跟踪所有的 API 调用。Zone 是处理逻辑上相互关联的异步调用的机制。Angular(或者更准确地说是 ZoneJS)将其称为微任务。现在,如何利用这个神奇的功能就变得非常清晰了。
我首先通过 fork Angular 默认区域创建了一个新的区域,并将其命名为 trackerZone。务必将此区域逻辑放在解析器(Angular 路由解析器)中,以便在跳转到实际页面之前触发并解析它。
import { Injectable, NgZone } from '@angular/core';
// other imports...
@Injectable()
export class ProjectResolver implements Resolve<any> {
constructor(
private ngZone: NgZone,
private loadTracker: LoadTrackerService,
) { }
public trackerZone: NgZone;
resolve() {
return this.resolveInTrackerZone();
}
resolveInTrackerZone() {
this.trackerZone = this.ngZone['_inner'].fork({
properties: {
countSchedule: 0,
loaderRef: this.loadTracker
},
onScheduleTask(delegate, currentZone, targetZone, task)
{}
});
让我快速解释一下这里发生了什么。要访问默认的 Angular Zone,我们可以从 'angular/core' 导入它。所以我把它实例化到一个名为 ngZone 的私有变量中,这样我们之后就可以用它来 fork 实例了。接下来,我创建了自己的trackerZone。
现在我们可以 fork 这个 Zone 实例,并将其赋值给我们的 trackerZone。
现在我们可以将属性/值/引用传递给属性对象中的 trackerZone。同时,我们还会获得一个onScheduleTask回调方法,该方法会在每次任务触发时执行。值得一提的是,除了微任务之外,还有其他类型的任务,我们这里就不赘述了,但它们同样重要。如果您想更深入地了解,我强烈推荐这篇博客。任务是一个包含各种属性的对象,例如类型、数据等(如下所示)。
下一步是使用 trackerZone.run() 方法在跟踪器区域内运行所有 API 调用。只需完成这些步骤,Angular 就能触发 Zone 功能并生成微任务。
/
import { Injectable, NgZone } from '@angular/core';
// other imports...
@Injectable()
export class ProjectResolver implements Resolve<any> {
constructor(
private ngZone: NgZone,
private loadTracker: LoadTrackerService,
) { }
public trackerZone: NgZone;
resolve() {
return this.resolveInTrackerZone();
}
resolveInTrackerZone() {
this.trackerZone = this.ngZone['_inner'].fork({
properties: {
countSchedule: 0,
loaderRef: this.loadTracker
},
onScheduleTask(delegate, currentZone, targetZone, task)
{
const result = delegate.scheduleTask(targetZone,
task);
const url = task['data']['url'] || '';
const tracker = this.properties.loaderRef;
if (task.type === 'macroTask' && task._state !==
'unknown') {
/* Triggering the message service based on URL */
}
return result;
}
}
});
this.trackerZone.run(() => {
/* return Observable / API call / Parallel Calls*/
});
}
}
这里我们使用delegate.scheduleTask(targetZone, task)在 trackerZone 中手动安排了任务。现在我们只需要将 URL 与消息 ID 映射起来,然后触发服务即可。
if (task.type === 'macroTask' && task._state !== 'unknown') {
this.properties.countSchedule += 1;
if (url.indexOf('id_1') > -1) {
tracker.setMessage('m_id_1');
} else if (url.indexOf('id_2') > -1) {
tracker.setMessage('m_id_2');
} else if (url.indexOf('id_3') > -1) {
tracker.setMessage('id_3');
}
// and so on...
}
就这些了!我真的很喜欢 ZoneJS 让整个过程变得如此简单便捷。当然,为了确保万无一失,使用 HTTP 拦截器也是一种方法,但我感觉 ZoneJS 更优雅、更直观。这只是我个人的看法。
最后,这是我的第一篇博客文章。请不要因为我写作新手而嫌弃我,我会努力进步的。如果您喜欢这篇文章,请在下方评论区留下一些鼓励的话语。
和平🖖
文章来源:https://dev.to/suvi/angular-zoned-out-hi0