惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Forbes - Security
Forbes - Security
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Jina AI
Jina AI
美团技术团队
博客园 - 聂微东
博客园 - 叶小钗
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园_首页
Spread Privacy
Spread Privacy
J
Java Code Geeks
雷峰网
雷峰网
宝玉的分享
宝玉的分享
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy International News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
The Hacker News
The Hacker News
量子位
L
LINUX DO - 热门话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
GRAHAM CLULEY
D
Darknet – Hacking Tools, Hacker News & Cyber Security
月光博客
月光博客
腾讯CDC
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
罗磊的独立博客
V
Vulnerabilities – Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tenable Blog
MyScale Blog
MyScale Blog
T
The Exploit Database - CXSecurity.com
GbyAI
GbyAI
博客园 - 【当耐特】
O
OpenAI News
Schneier on Security
Schneier on Security
S
Secure Thoughts
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Securelist
博客园 - 司徒正美

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
Understanding Dynamic Scoping and TemplateRef
2017-10-01 · via Minko Gechev's blog

This post will be dedicated to template references in Angular and dynamic scoping. Although the main focus of the article will be understanding TemplateRef we will also touch semantics of programming languages by introducing the concept of scope of name binding.

The code samples for this article can be found in my GitHub account.

Introducing Template References

In order to understand TemplateRef lets take a look at the NgForOf directive. Suppose we have a list of puppies and we want to iterate over it in the PuppyListComponent. The Puppy interface will look like this:

interface Puppy {
  name: string;
  photo: string;
  age: number;
  breed: string;
}

Here’s a sample implementation of the PuppyListComponent:

@Component({
  selector: 'puppy-list',
  template: `
  <md-list>
    <md-list-item *ngFor="let puppy of puppies">
      <img mdListAvatar [src]="puppy.photo" alt="...">
      <h3 mdLine> {{puppy.name}} </h3>
      <p mdLine>
        <span>Age: {{puppy.age}} </span>
        <span>Breed: {{puppy.breed}} </span>
      </p>
    </md-list-item>
  </md-list>
  `
})
export class PuppyListComponent {
  @Input() puppies: Puppy[];
}

In the snippet above we define a component with an input called puppies. Inside of the component’s template we iterate over the individual puppies and visualize them on the screen. For this purpose we use the *ngFor directive.

We will refer to directives and components by using their controller names or their selectors.

Later, we can use the component in the following way:

@Component({
  selector: 'puppies-cmp',
  template: `
    <puppy-list [puppies]="puppies"></puppy-list>
  `
})
class PuppiesComponent {
  puppies = [
    { name: 'Dino', age: 1, photo: '...', breed: 'Rottweiler' },
    { name: 'Max', age: 2, photo: '...', breed: 'Beagle' },
    { name: 'Lucy', age: 1, photo: '...', breed: 'Golden Retriever' }
  ];
}

In the PuppiesComponent we declare the individual puppies and pass them as an input to the puppy-list. Now our application looks like this:

Layout

Template Directives

Lets take a step back and look at the PuppyListComponent template again:

@Component({
  // ...
  template: `
  <md-list>
    <md-list-item *ngFor="let puppy of puppies">
      ...
    </md-list-item>
  </md-list>
  `
})
export class PuppyListComponent {...}

It’s important to notice that this template is equivalent to:

@Component({
  // ...
  template: `
  <md-list>
    <ng-template ngFor let-puppy [ngForOf]="puppies">
      <md-list-item>
        ...
      </md-list-item>
    </ng-template>
  </md-list>
  `
})
export class PuppyListComponent {...}

The only difference from the previous example is that instead of placing the *ngFor directive on the md-list-item component, we create a template element with the attributes ngFor, let-puppy and [ngForOf]="puppies". The meaning of the attributes on the ng-template element is as follows:

  • ngFor - hints Angular that we use the NgForOf directive here.
  • let-puppy - creates a new local variable for the template that we can create bindings to. This way we can reference the individual puppies via the puppy variable. We will explain the let- attribute in details later in the article.
  • [ngForOf] - points the collection we are going to iterate over.

In fact, each time Angular sees an attribute prefixed with a * it will desugar the markup to ng-template. That’s what we call a template directive. By default Angular will use the markup between the opening and closing ng-template tags as template for the template directive itself. There are a few template directives that Angular provides, for instance:

  • NgForOf - allows us to iterate over a collection.
  • NgIf - conditionally includes a template based on the value of an expression.
  • NgSwitch - adds / removes DOM sub-trees when the nest match expressions matches the switch expression.
  • etc.

All of them use TemplateRefs internally. We can develop custom template directives as well but that’s a topic of another discussion.

Now, lets suppose we want to define a different page in our application which again, iterates over the individual puppies but this time visualizes them differently. An obvious implementation strategy for such component is to copy the PuppyListComponent and after that update it’s template accordingly in order to align to the requirements of the new layout. Angular, however, allows us to approach more elegantly to the problem.

Passing a Custom TemplateRef

Now, instead of placing the *ngFor directive directly on top of the md-list-item component, lets use an ng-template element and inject the template that NgForOf will use from the outside:

<ng-template ngFor let-puppy [ngForOf]="puppies" [ngForTemplate]="puppyTemplate">
</ng-template>

The only difference from the last snippet is that we explicitly pass the template that Angular will use to render the individual puppies. For the purpose we use the ngForTemplate input of the NgForOf directive. It’s interesting to point out that we can pass TemplateRefs as inputs. In contrast to this, in the initial example Angular would use the markup which is between the opening and closing ng-template tags.

Angular will get the template for NgForOf from the value of the puppyTemplate property declared in the controller of the PuppyListComponent. This will change the definition of PuppyListComponent to:

import { Puppy } from './../puppy/puppy';
import { Component, Input, TemplateRef, ContentChild } from '@angular/core';
import { NgForOfContext } from '@angular/common';

@Component({
  selector: 'puppy-list',
  template: `
  <md-list>
    <ng-template ngFor let-puppy [ngForOf]="puppies" [ngForTemplate]="puppyTemplate">
    </ng-template>
  </md-list>
  `
})
export class PuppyListComponent {
  @Input() puppies: Puppy[];

  @ContentChild(TemplateRef) puppyTemplate: TemplateRef<NgForOfContext<Puppy>>;
}

Now things are getting interesting. Notice that in the class definition we have decorated the puppyTemplate property with the @ContentChild decorator and we pass TemplateRef as its argument. This argument is a selector that Angular will match against the content children of the given component (more about content and view children can be found here). The type of the puppyTemplate property is TemplateRef<NgForOfContext<Puppy>> we’ll explain this in details in the final section of this article.

In short, this means that Angular will get the template reference, that NgForOf will use, as content child passed to the PuppyListComponent from its parent.

Now, lets see how the template of the parent component needs to be changed in order to reflect the update:

@Component({
  selector: 'app-root',
  template: `
    <puppy-list [puppies]="puppies">
      <ng-template let-puppy>
        <md-list-item>
          <img mdListAvatar [src]="puppy.photo" alt="...">
          <h3 mdLine> {{puppy.name}} </h3>
          <p mdLine>
            <span>Age: {{puppy.age}} </span>
            <span>Breed: {{puppy.breed}} </span>
          </p>
        </md-list-item>
      </ng-template>
    </puppy-list>
  `
})
export class PuppiesComponent {
  // ...
}

Notice how as content child of the puppy-list element we pass an ng-template element. This ng-template element will become the value of the puppyTemplate declared inside of the PuppyListComponent.

Now if in another component, lets say, PuppyAvatarsComponent, we want to show only the avatars, we can do that by reusing the PuppyListComponent:

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

@Component({
  selector: 'puppy-avatars',
  template: `
    <puppy-list [puppies]="puppies">
      <ng-template let-puppy>
        <img mdListAvatar [src]="puppy.photo" alt="...">
      </ng-template>
    </puppy-list>
  `,
  // ...
})
export class PuppyAvatarsComponent {
  // ...
}

This way we just pass a reference to a different template. In this template instead of showing detailed information about the individual puppies we just visualize their avatars.

Here’s how this layout is going to look like:

Layout

Introducing Dynamic Scoping

In programming languages there are two main scopes of name bindings:

  • Lexical scoping.
  • Dynamic scoping.

You might have heard about the first one from JavaScript - with var we can declare variables with functional lexical scope, compared to let where the variables are with block lexical scope.

This simply means that in the following case, the variable foo will be visible within the entire function (functional lexical scope):

function baz() {
  if (bar) {
    var foo = 42;
  }
  // 42
  console.log(foo);
}

…compared to this snippet (block lexical scope), where the variable will be visible only inside of the if-statement’s block:

function baz() {
  if (bar) {
    let foo = 42;
  }
  // undefined
  console.log(foo);
}

Both, lexical and dynamic scoping, address the “scope” of a variable declaration and both of them refer to it as “the place of the program where given variable is visible”, however, we can refer to “place of the program” as both - location in the source code or location at runtime.

Lets take a look at an example:

let bar = 42;

function foo() {
  console.log(bar);
}

function baz(cb) {
  let bar = 1.618;
  cb();
}

In this case, since we have lexical scoping in JavaScript once we invoke baz(foo) we’ll get 42 printed on the screen. However, if JavaScript was language with dynamic scoping, the result was going to be 1.618.

In contrast to JavaScript, Angular’s template references use dynamic scoping. Lets take a look why!

Dynamic Scoping in Angular

Here’s our PuppyAvatarComponent:

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

@Component({
  selector: 'puppy-avatars',
  template: `
    <puppy-list [puppies]="puppies">
      <ng-template let-puppy>
        <img mdListAvatar [src]="puppy.photo" alt="...">
      </ng-template>
    </puppy-list>
  `,
  // ...
})
export class PuppyAvatarsComponent {
  // ...
}

As you may already know, the NgForOf directive provides a few more properties such as:

  • index - the index of the current element.
  • odd - whether the current index is an odd number.
  • even - whether the current index is an even number.
  • first - has value true if that’s the first item from the collection, has value false otherwise.
  • last - has value true if that’s the last item from the collection, has value false otherwise.

If we want to get the value of any of these properties all we need to do is declare a variable in our template:

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

@Component({
  selector: 'puppy-avatars',
  template: `
    <puppy-list [puppies]="puppies">
      <ng-template let-puppy let-odd="odd" let-f="first">
        ...
      </ng-template>
    </puppy-list>
  `,
  // ...
})
export class PuppyAvatarsComponent {
  // ...
}

With let-odd="odd" we set the value of the odd variable in the template to the value of the odd variable from the NgForOf directive and with let-f="first" we set the value of the f variable in the template to the value of the first variable from the NgForOf directive.

You might be wondering, what about let-puppy? What’s the magic which happens there and why we don’t specify what variable from the NgForOf directive we want to get its value from? In fact, NgForOf has one more property called $implicit. This property is assigned to all the template variables which we declare with attributes with no value. So if we have:

<ng-template let-puppy let-dog let-odd="odd" let-f="first">
</ng-template>
  • Both puppy and dog will point to the current item we’re iterating over (i.e. $implicit)
  • odd will point to the odd property of NgForOf.
  • f will point to the first property of NgForOf.

Now, in order to be more precise - odd, even, first, last and $implicit are not properties of NgForOf itself but instead, of its context. The implementation of NgForOfContext can be found here

Now lets go back to the PuppyListComponent:

@Component(...)
export class PuppyListComponent {
  // ...
  @ContentChild(TemplateRef) puppyTemplate: TemplateRef<NgForOfContext<Puppy>>;
}

Notice the type of the puppyTemplate property - TemplateRef<NgForOfContext<Puppy>>. This has the following semantics:

TemplateRef element which should be used in context of type NgForOfContext where the $implicit element has type Puppy.

Similarly the other template directives have their own context since they use TemplateRefs which need to be interpreted in given context. For instance, NgIf’s context declared by the class:

export class NgIfContext {
  public $implicit: any = null;
  public ngIf: any = null;
}

As you can see, unlike NgForOfContext, NgIfContext doesn’t have a type parameter because there’s nothing we may want to refer to with the $implicit property.

Now, how exactly does Angular has dynamic scoping of TemplateRefs`?

Lets take a step back and look at our PuppyAvatarsComponent implementation:

@Component({
  selector: 'puppy-avatars',
  template: `
    <puppy-list [puppies]="puppies">
      <ng-template let-puppy="$implicit">
        <img mdListAvatar [src]="puppy.photo" alt="...">
      </ng-template>
    </puppy-list>
  `,
})
export class PuppyAvatarsComponent {
  puppies = Puppies;

  $implicit = {
    name: 'Danny',
    age: 12,
    breed: 'Poodle'
  }
}

Notice that in the ng-template we’ve bound to the $implicit property and in the body of the component PuppyAvatarsComponent we declare a property called $implicit. Although property named $implicit exists in the current lexical scope of the template, it’s value will be ignored and since puppy has dynamic scope and will be bound to the value of $implicit where we use it, i.e. the component with selector puppy-list.

Conclusion

With TemplateRefs Angular allows us to not only perform advanced content projection but also to reuse templates across our application.

Unlike the lexical scoping which is used in most programming languages we’re familiar with, Angular uses dynamic scoping for its template references since we’re interested in the context where given TemplateRef is used, rather than declared.