代码区项目交易流程

Creating A Bind-Once Structural Directive In Angular 7.1.4


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教程

点击收藏

LAST Select different options at an select form and show different content Shumway Shiv: Scant Success NEXT