未加星标

Creating A Bind-Once Structural Directive In Angular 7.1.4

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二05 | 时间 2019 | 作者 红领巾 ] 0人收藏点击收藏

Over the weekend, I posted my review of Vue.js Up And Running by Callum Macrae . This was my first look at Vue.js; but, coming from an Angular background, many of the concepts discussed in the book felt very familiar. In fact, Vue's "v-once" directive, for one-time binding, is something that I miss from the Angular.js days . As such, I thought it would be a fun experiment to build a "bind-once" structural directive for Angular 7.1.4.

Run this demo in my javascript Demos project on GitHub .

View this code in my JavaScript Demos project on GitHub .

The idea behind the old "::value" one-time binding syntax in AngularJS was that a template expression would be evaluated until it resulted in a non-empty value; then, it wouldn't be evaluated again for the rest of the template's life-span. Using one-time binding was an optimization step that would reduce the number of watchers on a "heavy" page, thereby reducing the degree of processing that had to take place during any given change-detection digest.

In the latest Angular, such a concept is probably unnecessary. Angular already has a very efficient change-detection algorithm. And, in conjunction with OnPush change-detection strategies, intelligent input-bindings, and pure pipes, something like "bind-once" is more a fun idea than it is a necessity. That said, there's always room for some optimization.

In particular, the use of the ngFor directive springs to mind. Since ngFor needs to detect arbitrary changes in a collection, it doesn't use an OnPush change-detection strategy. Instead, it uses a "Differ" to scan the collection on each change-detection digest. As such, it could be a potential optimization to wrap an ngFor loop in a "bind-once" directive.

To start experimenting with one-time bindings, let's create an Angular Directive that will log the execution of a change-detection digest. When a Directive is attached to the change-detection tree, Angular will attempt to invoke the Directive's ngDoCheck() life-cycle method, if it exists, during each relevant digest. Therefore, in order to hook into this life-cycle event, we can create an attribute directive that simply logs from within the ngDoCheck() method:

// Import the core angular services. import { Directive } from "@angular/core"; import { DoCheck } from "@angular/core"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // @Directive({ selector: "[logDoCheck]", inputs:[ "logDoCheck" ] }) export class LogDoCheckDirective implements DoCheck { public logDoCheck!: string; // I get called whenever a change-detection digest has been triggered in the // current view context. public ngDoCheck() : void { console.warn( "[", this.logDoCheck, "]: ngDoCheck() invoked." ); With the [logDoCheck] attribute directive, we can see which parts of our component tree are wired into the change-detection tree. Next, let's look at what a [bindOnce] structural directive could look like. In the following code, we have a directive that injects a template into the view container and then immediately detaches itself from the change-detection tree. Once detached, it then uses the .detectChanges() method on the embedded view to explicitly step back into the change-detection life-cycle as needed: // Import the core angular services. import { Directive } from "@angular/core"; import { EmbeddedViewRef } from "@angular/core"; import { OnChanges } from "@angular/core"; import { SimpleChanges } from "@angular/core"; import { TemplateRef } from "@angular/core"; import { ViewContainerRef } from "@angular/core"; // ----------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------- // @Directive({ selector: "[bindOnce]", inputs:[ "bindOnce" ] }) export class BindOnceDirective implements OnChanges { public bindOnce!: any; private embeddedViewRef: EmbeddedViewRef<void>; // I initialize the bind-once directive. constructor( templateRef: TemplateRef<void>, viewContainerRef: ViewContainerRef ) { this.embeddedViewRef = viewContainerRef.createEmbeddedView( templateRef ); // Since we want manual control over when the content of the view is checked, // let's immediately detach the view. This removes it from the change-detection // tree. Now, it will only be checked when we either re-attach it to the change- // detection tree or we explicitly call .detectChanges() (see ngOnChanges()). this.embeddedViewRef.detach(); // --- // PUBLIC METHODS. // --- // I get called when any of the input bindings are updated. public ngOnChanges( changes: SimpleChanges ) : void { // NOTE: Since this Directive uses an ATTRIBUTE-BASED SELECTOR, we know that the // ngOnChanges() life-cycle method will be called AT LEAST ONCE. As such, we can // be confident that the embedded view will be marked for changes at least once. // -- // We also want to check the view for changes any time the input-binding is // changed. This gives the calling context a chance to drive changes based on a // single expression even when change-detection is limited. this.embeddedViewRef.detectChanges(); Since this structural directive uses an attribute selector, [bindOnce], we know that the ngOnChanges() life-cycle method will execute at least once for the initial value of the "bindOnce" input. As such, it makes sense to put the .detectChanges() call in the ngOnChanges() method. And, by doing so, it gives the calling context the ability to pass an expression into the "bindOnce" input that can change over time. Then, as the input-binding changes, the .detectChanges() method will be called again and the directive will briefly step back into the change-detection tree. In other words, this [bindOnce] directive allows for the single-evaluation of of a DOM fragment:

<div *bindOnce > ... </div>

It allows for evaluation of a DOM fragment until a given value is non-empty:

<div *bindOnce="( !! value )" > ... </div>

And, it allows the ongoing evaluation a DOM fragment any time the reference to a given value changes:

<div *bindOnce="value" > ... </div>

Now, let's see how this can be applied in practice. As I mentioned before, one use-case that jumps to mind is the ngFor loop. So, let's see how a

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

代码区博客精选文章
分页:12
转载请注明
本文标题:Creating A Bind-Once Structural Directive In Angular 7.1.4
本站链接:https://www.codesec.net/view/628595.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(342)