Pankaj Parkar
Indian | 30 | Ex MVP | Angular GDE
🤔
Ctrl + click
Directive
Component
@Component({
standalone: true,
selector: 'app-awesome',
templateUrl: './awesome.component.html',
styleUrl: './awesome.component.scss',
imports: [],
})
export class AwesomeComponent {
}
@Directive({
selector: '[app-my-custom]',
standalone: true,
})
export class MyCustomDirective {
}
export interface Directive {
selector?: string;
inputs?: ({
name: string,
alias?: string,
required?: boolean,
transform?: (value: any) => any,
}|string)[];
outputs?: string[];
providers?: Provider[];
exportAs?: string;
queries?: {[key: string]: any};
host?: {[key: string]: string};
jit?: true;
standalone?: boolean;
hostDirectives?: (Type<unknown>|{
directive: Type<unknown>,
inputs?: string[],
outputs?: string[],
})[];
}
- selector
- inputs
- outputs
- providers
- exportAs
- queries
- host
- jit
- standalone
- hostDirectives
export declare interface Component
extends Directive {
changeDetection?: ChangeDetectionStrategy;
viewProviders?: Provider[];
moduleId?: string;
templateUrl?: string;
template?: string;
styleUrl?: string;
styleUrls?: string[];
styles?: string | string[];
animations?: any[];
encapsulation?: ViewEncapsulation;
interpolation?: [string, string];
preserveWhitespaces?: boolean;
standalone?: boolean;
imports?: (Type<any> | ReadonlyArray<any>)[];
schemas?: SchemaMetadata[];
}
- changeDetection
- viewProviders
- moduleId
- templateUrl
- styleUrls / style
- animations
- encapsulation
- interpolation
- preserveWhitespaces
- standalone
- imports
- schemas
- selector
- inputs
- outputs
- providers
- exports
- queries
- host
- jit
- standalone
- hostDirectives
Component
- changeDetection
- viewProviders
- templateUrl / template
- styleUrls/styles
- preserveWithWhitespace
- animations
- encapsulation
- interpolation
- imports
- schemas
Directive
Component is a Directive with a Template
Template / View
Types of Directive
Structural
Attribute
(wider)
*(Conditional Rendering)
Disclaimer: This types are given to better understanding of angular directive API.
- selector
- inputs
- outputs
- providers
- exportAs
- queries
- host
- jit
- standalone
- hostDirectives
Match the following
Principal Application Devloper @AON
pankajparkar
#devwhorun 🏃♂️
*(Conditional Rendering)
Feature Flag
<div *featureFlag="isFastLoginEnabled; else notEnabled">
<button>Login with one click</button>
</div>
<ng-template #notEnabled>
<div>Contact support</div>
</ng-template>
if (this.isFastLoginEnabled) {
// perform specific action
}
@Directive({
selector: '[featureFlag]',
standalone: true,
})
export class FeatureFlagDirective {
templateRef = inject(TemplateRef);
viewContainer = inject(ViewContainerRef);
featureFlagService = inject(FeatureFlagService);
hasView = false;
@Input() set featureFlag(feature: FeatureFlagKeys) {
if (this.featureFlagService.getFeature(feature) && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else {
this.viewContainer.clear();
this.hasView = false;
}
}
@Input() set featureFlagElse(elseTemplateRef: TemplateRef<any>) {
if (!this.hasView()) {
this.viewContainer.createEmbeddedView(elseTemplateRef);
}
}
}
@Component({
selector: 'my-menu-item',
standalone: true,
imports: [],
hostDirectives: [CdkMenuItem],
template: `
<ng-content></ng-content>
`,
})
export class MenuItemComponent { }
@Component({
selector: 'my-menu',
standalone: true,
imports: [],
hostDirectives: [CdkMenu],
template: `
<ng-content></ng-content>
`,
})
export class MenuComponent { }
<button [cdkMenuTriggerFor]="menu">
Click me!
</button>
<ng-template #menu>
<my-menu>
<my-menu-item>Tes M</my-menu-item>
<my-menu-item>Tes M</my-menu-item>
<my-menu-item>Tes M</my-menu-item>
</my-menu>
</ng-template>
<select>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
Create a new element with behaviour using multiple directives
pankajparkar
Create a new element with behaviour using multiple directives
pankajparkar
Applying directives directly on component
<app-ribbon appBold appBlink>
Flash⚡️ Sell
</app-ribbon>
@pankajparkar
www.pankajparkar.com
@Directive({
selector: '[appBold]',
standalone: true
})
export class BoldDirective {
@HostBinding('style.font-weight')
bold = 'bolder';
}
My Text
My Text
@pankajparkar
@Directive({
selector: '[appBlink]',
standalone: true,
})
export class BlinkDirective {
@HostBinding('style.animation')
animation = 'blinker 1s step-start infinite';
@Input() theme = 'red';
@Output() elementClick = new EventEmitter<MouseEvent>();
@HostListener('click', ['$event'])
click(event: MouseEvent) {
this.elementClick.emit(event);
}
}
@Component({
selector: 'app-ribbon',
standalone: true,
imports: [
BoldDirective,
FlashDirective,
],
hostDirectives: [
BoldDirective,
FlashDirective,
],
})
export class RibbonComponent {
...
}
Create a new element with behaviour using multiple directives
pankajparkar
export function validatePhone(control: AbstractControl): ValidationErrorOrNull {
if (control.value && control.value.length != 10) {
return { 'phoneNumberInvalid': true };
}
return null;
}
@Directive({
selector: '[appPhoneNumberValidator]',
providers: [{
provide: NG_VALIDATORS,
useExisting: PhoneNumberValidatorDirective,
multi: true
}]
})
export class PhoneNumberValidatorDirective implements Validator {
validate(control: AbstractControl) : ValidationErrorOrNull {
return validatePhone(control);
}
}
<input appPhoneNumberValidator
type="text" name="phone"
[(ngModel)]="phone" />
For validating form field / control
pankajparkar
@Directive({
selector: '[appBlockCopyPaste]'
})
export class BlockCopyPasteDirective {
constructor() { }
@HostListener('paste', ['$event'])
blockPaste(e: KeyboardEvent) {
e.preventDefault();
}
@HostListener('copy', ['$event'])
blockCopy(e: KeyboardEvent) {
e.preventDefault();
}
@HostListener('cut', ['$event'])
blockCut(e: KeyboardEvent) {
e.preventDefault();
}
}
@Directive({
selector: '[appBlockCopyPaste]',
host: {
'[class.default-input]': 'true',
'(paste)': 'preventEvent($event)',
'(copy)': 'preventEvent($event)',
'(cut)': 'preventEvent($event)',
}
})
export class BlockCopyPasteDirective {
preventEvent(e: KeyboardEvent) {
e.preventDefault()
}
}
<input type="text" appBlockCopyPaste />
listen and react to the event
<input
type="text"
name="name"
ngModel
#userName="ngModel"
minlength="2"
required />
<div *ngIf="userName.errors?.required">
Name is required
</div>
<div *ngIf="userName.errors?.minlength">
Minimum of 2 characters
</div>
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel extends NgControl {
...
}
easily extract directive instance on template
pankajparkar
@ViewChildren(NgModel) models: QueryList<NgModel>;
@ViewChild(NgForm) form: NgForm;
<form (ngSubmit)="save()" ngForm>
<input type="text" name="phone" [(ngModel)]="phone" />
<input type="text" name="phone" [(ngModel)]="address" />
...
<button [disabled]="form.invalid">
SAVE
</button>
</form>
without `exportAs` how it could have been done
pankajparkar
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<!-- ID Column -->
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef> ID </th>
<td mat-cell *matCellDef="let row"> {{row.id}} </td>
</ng-container>
<!-- Other columns hidden for brevity -->
</table>
<mat-paginator
[pageSizeOptions]="[5, 10, 25, 100]">
</mat-paginator>
</div>
extend existing behaviour of an component
@Directive({
selector: 'mat-paginator',
standalone: true,
})
export class MatPaginatorDefaultDirective {
// private paginator = inject(MatPaginator,
// { self: true });
constructor(
@Self() private paginator: MatPaginator,
) {}
ngOnInit() {
if (!this.paginator.pageSizeOptions) {
this.paginator.pageSizeOptions = [5, 10];
}
}
}
Reference: https://stackblitz.com/edit/fepvih
pankajparkar
<div class="page-container">
<user-details #userDetails />
</div>
<button (click)="print(userDetails)">
Print
</button>
@ViewChild(UserDetails, {read: ElementRef})
userDetails: ElementRef;
print(element) {
window.print(element);
}
print(element) {
window.print(element);
}
Helps to extract stuff out of host component
pankajparkar
@Directive({
selector: '[elementUtil]',
exportAs: 'elementUtil',
queries: {
form: new ViewChild(NgForm),
}
})
export class ComponentAsDomDirective {
dom = inject(ElementRef).nativeElement;
form! NgForm;
// form = viewChild(NgForm);
}
<div class="page-container">
<user-details #component="elementUtil" />
</div>
<button (click)="print(component.dom)">
Print
</button>
Helps to extract stuff out of host component
pankajparkar
- selector
- inputs
- outputs
- providers
- exportAs
- queries
- host
- jit
- standalone
- hostDirectives
pankajparkar
(~ @HostBinding, @HostListener)
(~ viewChildren, contentChildren, etc)
@pankajparkar
pankajparkar.dev
By Pankaj Parkar
Mastering Angular Directives: A Comprehensive Guide