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.