jQuery / angular.element in directives in modern Angular
Most jQuery calls in a directive changed classes, styles or listeners. Those become bindings in the template and in the host object, driven by signals.
Before · AngularJS
app.directive('expandable', function () {
return {
restrict: 'A',
link: function (scope, element) {
var body = element.find('.body');
var toggle = element.find('.toggle');
toggle.on('click', function () {
body.slideToggle();
element.toggleClass('open');
});
scope.$on('$destroy', function () { toggle.off('click'); });
}
};
});After · Angular@Component({
selector: 'app-expandable',
host: { '[class.open]': 'open()' },
template: `
<button (click)="open.set(!open())">Toggle</button>
<div #body [style.max-height.px]="open() ? height() : 0"><ng-content /></div>
`,
})
export class ExpandableComponent {
open = signal(false);
height = signal(0);
body = viewChild.required<ElementRef<HTMLElement>>('body');
constructor() {
afterNextRender(() => this.height.set(this.body().nativeElement.scrollHeight));
}
}What changes
- find(...).on('click') becomes an event binding in the template, which Angular removes when the component is destroyed.
- toggleClass, css and attr become class, style and attribute bindings; animations such as slideToggle become CSS transitions.
- When the code needs the real element, to measure it or to hand it to a library, get it with viewChild and read it inside afterNextRender; use Renderer2 to change elements outside your template.
Migration tip. Classify every jQuery call before rewriting: state changes become bindings, measurements and focus use viewChild, and plugins need a decision each. The first group is the largest and it disappears into the template.
Related
Planning a migration or a micro-frontend platform?
In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.
Message me on LinkedIn