Converting AngularJS directives to Angular components

Loading

AngularJS directives were powerful but required a different approach compared to Angular components. If you’re migrating from AngularJS to Angular, it’s crucial to refactor directives into Angular components.


1️⃣ Understand AngularJS Directives Structure

Example: AngularJS Directive

angular.module('myApp').directive('customCard', function() {
return {
restrict: 'E',
scope: {
title: '@',
content: '@'
},
template: `
<div class="card">
<h3>{{title}}</h3>
<p>{{content}}</p>
</div>
`
};
});
<custom-card title="AngularJS" content="This is a directive."></custom-card>

Issues:

  • Uses scope (deprecated in Angular)
  • Uses restrict (E, A, C, M) which isn’t needed in Angular
  • Uses template with interpolation that will work differently in Angular

2️⃣ Convert to an Angular Component

In Angular, we use TypeScript-based components. The equivalent Angular component would be:

Step 1: Create an Angular Component

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

@Component({
selector: 'app-custom-card',
template: `
<div class="card">
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
`,
styleUrls: ['./custom-card.component.css']
})
export class CustomCardComponent {
@Input() title!: string;
@Input() content!: string;
}

Step 2: Use the Component in HTML

<app-custom-card title="Angular" content="This is an Angular component."></app-custom-card>

Why?

  • Uses @Input() properties instead of scope variables
  • No need for restrict: 'E'
  • Uses selector: 'app-custom-card'
  • Uses TypeScript and a class-based structure

3️⃣ Handling Two-Way Binding (@Input and @Output)

In AngularJS, two-way data binding was common via = in scope.

Example: AngularJS Directive with Two-Way Binding

angular.module('myApp').directive('customCounter', function() {
return {
restrict: 'E',
scope: {
count: '='
},
template: `
<div>
<button ng-click="count = count + 1">+</button>
<span>{{ count }}</span>
<button ng-click="count = count - 1">-</button>
</div>
`
};
});
<custom-counter count="counterValue"></custom-counter>

Convert to Angular Component

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
selector: 'app-custom-counter',
template: `
<div>
<button (click)="increment()">+</button>
<span>{{ count }}</span>
<button (click)="decrement()">-</button>
</div>
`
})
export class CustomCounterComponent {
@Input() count!: number;
@Output() countChange = new EventEmitter<number>();

increment() {
this.count++;
this.countChange.emit(this.count);
}

decrement() {
this.count--;
this.countChange.emit(this.count);
}
}
<app-custom-counter [(count)]="counterValue"></app-custom-counter>

Uses @Input() and @Output() instead of $scope.count = count.
Supports two-way binding with [(count)] using EventEmitter.


4️⃣ Handling Transclusion (ng-transclude to ng-content)

AngularJS used ng-transclude for content projection.

Example: AngularJS Directive with Transclusion

angular.module('myApp').directive('customPanel', function() {
return {
restrict: 'E',
transclude: true,
template: `
<div class="panel">
<h3>{{title}}</h3>
<div ng-transclude></div>
</div>
`,
scope: {
title: '@'
}
};
});
<custom-panel title="My Panel">
<p>This content goes inside the panel.</p>
</custom-panel>

Convert to Angular Component

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

@Component({
selector: 'app-custom-panel',
template: `
<div class="panel">
<h3>{{ title }}</h3>
<ng-content></ng-content>
</div>
`
})
export class CustomPanelComponent {
@Input() title!: string;
}
<app-custom-panel title="My Panel">
<p>This content goes inside the panel.</p>
</app-custom-panel>

Uses ng-content for content projection instead of ng-transclude.


5️⃣ Handling Lifecycle Hooks (link & compile to ngOnInit)

AngularJS used link and compile for directive lifecycle hooks.

Example: AngularJS Directive with link

angular.module('myApp').directive('customDirective', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
element.on('click', function() {
alert('Clicked!');
});
}
};
});

Convert to Angular Component with ngOnInit

import { Component, ElementRef, OnInit, Renderer2 } from '@angular/core';

@Component({
selector: 'app-custom-directive',
template: `<div>Click me</div>`
})
export class CustomDirectiveComponent implements OnInit {
constructor(private el: ElementRef, private renderer: Renderer2) {}

ngOnInit() {
this.renderer.listen(this.el.nativeElement, 'click', () => {
alert('Clicked!');
});
}
}

Uses ngOnInit() instead of link function.
Uses Renderer2 for better DOM manipulation.

Leave a Reply

Your email address will not be published. Required fields are marked *