Marcin Jahn | Dev Notebook
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • Home
  • Programming
  • Technologies
  • Projects
  • About
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects
  • An icon of the Core section Core
    • Programs Execution
    • Stack and Heap
    • Asynchronous Programming
      • Overview
      • Event Queues
      • Fibers
      • Stackless Coroutines
  • An icon of the .NET section .NET
    • HTTPClient
    • Async
      • How Async Works
      • TAP Tips
    • Equality
    • Comparisons
    • Enumerables
    • Unit Tests
    • Generic Host
    • Logging
    • Configuration
    • Records
    • Nullability
    • Garbage Collector
    • IL and Allocations
    • gRPC
    • Source Generators
    • Platform Invoke
    • ASP.NET Core
      • Overview
      • Middleware
      • Razor Pages
      • Routing in Razor Pages
      • Web APIs
      • Filters
      • Identity
      • Validation
      • Tips
    • Entity Framework Core
      • Overview
      • Testing
      • Tips
  • An icon of the Angular section Angular
    • Overview
    • Components
    • Directives
    • Services and DI
    • Routing
    • Observables (RxJS)
    • Forms
    • Pipes
    • HTTP
    • Modules
    • NgRx
    • Angular Universal
    • Tips
    • Standalone Components
  • An icon of the JavaScript section JavaScript
    • OOP
    • JavaScript - The Weird Parts
    • JS Functions
    • ES Modules
    • Node.js
    • Axios Tips
    • TypeScript
      • TypeScript Environment Setup
      • TypeScript Tips
    • React
      • React Routing
      • MobX
    • Advanced Vue.js Features
  • An icon of the Rust section Rust
    • Overview
    • Cargo
    • Basics
    • Ownership
    • Structures
    • Enums
    • Organization
    • Collections
    • Error Handling
    • Generics
    • Traits
    • Lifetimes
    • Closures
    • Raw Pointers
    • Smart Pointers
    • Concurrency
    • Testing
    • Tips
  • An icon of the C/C++ section C/C++
    • Compilation
    • Structures
    • OOP in C
    • Pointers
    • Strings
    • Dynamic Memory
    • argc and argv Visualization
  • An icon of the GTK section GTK
    • Overview
    • GObject
    • GJS
  • An icon of the CSS section CSS
    • Responsive Design
    • CSS Tips
    • CSS Pixel
  • An icon of the Unity section Unity
    • Unity
  • An icon of the Functional Programming section Functional Programming
    • Fundamentals of Functional Programming
    • .NET Functional Features
    • Signatures
    • Function Composition
    • Error Handling
    • Partial Application
    • Modularity
    • Category Theory
      • Overview
      • Monoid
      • Other Magmas
      • Functors
  • An icon of the Algorithms section Algorithms
    • Big O Notation
    • Array
    • Linked List
    • Queue
    • Hash Table and Set
    • Tree
    • Sorting
    • Searching
  • An icon of the Architecture section Architecture
    • What is architecture?
    • Domain-Driven Design
    • ASP.NET Core Projects

Directives

Directives are a way to kind of decorate HTML element with some additional logic. Similarly to Components, they can be applied to element using:

  • attributes (most often used)
  • classes
  • element type
  • id

For example, I could have this:

<p appUnderline>Some paragraph</p>

It would apply the UnderlineDirective to the paragraph.

Directive is a TS class with a @Directive decorator applied to it.

There are also some built-in directives.

Directives can be:

  • “casual” - do not add/remove elements from the DOM
  • structural - add/remove elements from the DOM

Built-in Directives

ngStyle

It allows us to set styling of elements based on some condition:

<p [ngStyle]="{ color: getColor() }">ABC</p>

The getColor() method would return some valid color string based on some condition.

ngClass

It’s very similar to ngStyle, but it applies classes based on some logic.

<p [ngClass]="{ highlighted: username === 'admin' }"
>
{{username}}
</p>

The .highlighted CSS class will be applied to this paragraph only if it’s an admin.

*ngIf

A conditional:

<p *ngIf="userExists">Username is {{ username }}</p>
<!-- With else: -->
<p *ngIf="userExists; else noUser">Username is {{ username }}</p>
<ng-template #noUser>
<p>User does not exist</p>
</ng-template>

The “else” syntax really sucks compared to Vue.js… It would be easier to use another *ngIf with reversed condition.

*ngIf Behind the Scenes

Angular transforms the *ngIf into something like this:

<ng-template [ngIf]="userExists">
<p>Username is {{ username }}</p>
</ng-template>

This is the “raw” version of ngIf. It would actually work if we placed it in an app.

Star

The star prefixing ngIf is an information that this directive is structural. The star turns the element that the directive is applied to into a child of <ng-template>.

We can’t have more than one structural directive on one element!

ngSwitch

Similarly to switch in most programming languages, we have ngSwitch directive in Angular. This one is a bit unique, because we use it as a combination of a few directives.

<div [ngSwitch]="someValue">
<p *ngSwitchCase="1">It's 1</p>
<p *ngSwitchCase="2">It's 2</p>
<p *ngSwitchCase="3">It's 3</p>
<p *ngSwitchCaseDefault>It's something else</p>
</div>

Only one case is displayed at a time.

*ngFor

Iteration can be done with *ngFor:

<p *ngFor="let text of texts">
{{ text }}
</p>

We can also get the index of the iteration with:

*ngFor="let text of texts; let i = index"

The i variable may be used within the loop now.

Custom Directives

Some conventions:

  • the file names for directive are usually in the format {name}.directive.ts.
  • the selector is usually camelCase.

Here’s a simple example:

@Directive({
standalone: true,
selector: '[appMyDirective]'
})
class MyDirective implements OnInit {
constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
ngOnInit() {
this.renderer.setStyle(elementRef.nativeElement, 'color', 'blue');
}
}

Usage:

<p appMyDirective>Some text</p>

The background of the paragraph will be blue.

Registration

Our directive has to be registered in a module, just like any component that we create. Similarly to components, we place the directives in the declarations array.

A few highlights:

  • the [appMyDirective] selector is in square brackets, which means that the directive will be used as an attribute. If our selector was without the square brackets, it would be specifying an HTML element that this directive would be applied to (like p). If it has a dot at the beginning, it would select elements with a specified class.
  • the elementRef in the constructor is the element that the directive was applied to. It gets injected into the instance automatically by Angular. The name can be whatever we want, just the type needs to be ElementRef.
  • it’s a good idea to use Renderer2.
  • we can do something with our element. ngOnInit lifecycle hook is a good place to do it.

Angular CLI

We can generate directives quickly with ng g d <name>.

Reacting to Events

Directives can listen to events on the elements they are attached to. Here’s an example:

@Directive({
selector: '[appMyDirective]'
})
class MyDirective {
constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
@HostListener('mouseenter') onMouseEnter(eventData) {
// do something...
}
}

@HostBinding

There’s an even easier way to modify DOM properties than using references or Renderer2. It’s a decorator called HostBinding. It allows to bind to a specified property on the DOM element to access it easily.

Here’s an example:

@Directive({
selector: '[appMyDirective]'
})
class MyDirective implements OnInit {
@HostBinding('style.color') elColor: string;
constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
ngOnInit() {
this.elColor = 'white';
}
}

Another example shows how we can toggle a class on some element:

@HostBinding('class.open') isOpen = false;

Setting isOpen to true adds the class open to the element.

Renderer2

There’s nothing wrong with using Renderer2. It’s just another convenient way to access the DOM.

Directive’s Inputs

Our directives can support some input arguments, very similarly to the way how Components accept inputs. This way, users of the directive can have some influence on how it works.

Here’s an example:

@Directive({
selector: '[appMyDirective]'
})
class MyDirective implements OnInit {
@Input() color: string; // it could have some default value if it's not provided
@HostBinding('style.color') elColor: string;
constructor(private renderer: Renderer2, private elementRef: ElementRef) { }
ngOnInit() {
this.elColor = this.color;
}
}

This is a silightly modified code compared to the previous example. Now the color applied to our element is no longer hardcoded. Here’s how the binding is done in the template:

<p appMyDirective [color]="'blue'">Some text</p>

Angular figures out if the provided input belongs to the directive or the element itself. (What if there’s a conflict?)

Reusing directive’s name for an input

Some built-in directives, such as [ngClass] accept the inut (a class name) directly under the directive’s name, like this:

<p [ngClass]="{ highlighted: username === 'admin' }">{{Username}}</p>

We can do it as well in our directives:

@Directive({
selector: '[appMyDirective]'
})
class MyDirective {
@Input('appMyDirective') color: string;
// ...
}

We’d use that directive as follows:

<p [appMyDirective]="'blue'">Some text</p>

The appMyDirective has two meanings now:

  • it’s an attribute which marks DOM elements that we want to apply our directive to;
  • it’s an inut of our directive allowing users to set some value.

Angular allows us to use the shorthand of including the appMyDirective in square brackets to communicate to the framework both of the meanings listed above.

Other Inputs

We can use the technique described above together with other inputs that have their own individual names.


@Output

Other than @Inputs, directives can also have @Outputs. That makes them even more similar to Components.

Custom Structural Directives

As it was described in the *ngIf section, structural components have a star prefixing the directive’s name. That causes the elements to be placed in <ng-template>, which is Angular’s way to specify templates.

Here’s an example of how to build our own structural components. This is an imlementation of *ifNot:

@Directive({
selector: '[ifNot]'
})
class IfNotDirective {
@Input() set ifNot(condition: bool) {
if (!condition) {
this.vsRef.createEmbeddedView(this.templateRef); // display content
} else {
this.vcRef.clear(); // remove content
}
}
constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) { }
}

We’d use it like this:

<p [*ifNot]="userFound">User not found</p>

In the directive’s class we used the following (auto-injected) elements:

  • TemplateRef<any> - this represents the <ng-template> that is generated due to a ’*’
  • ViewContainerRef - this represents the place where the element (that our directive is applied to) should be located.

Basically, TemplateRef<any> represents the WHAT, and the ViewContainerRef represents the WHERE, helping our directive to do its job.

←  Components
Services and DI  →
© 2023 Marcin Jahn | Dev Notebook | All Rights Reserved. | Built with Astro.