Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/pages/snippets/disable-components-using-directive.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Disable components using directive
description: "Example of directive to disable interactive components when a certain condition is met."
tags: ["angular", "directives"]
pubDate: Feb 25, 2023
contributedBy: "@pawelkubiakdev"
---

The code snippet below shows how to disable (angular material) components if the shouldDisable$ stream from the ExampleService emits true.
This can be a useful solution when we want to block actions when the user does not have the appropriate permissions resulting from:
- user role
- a feature that is blocked for the user etc.

(*) `DestroyedDirective` was implemented according to the idea of [Kristiyan Kostadinov](https://twitter.com/_crisbeto/status/1582475442715385858).


```typescript
@Directive({
selector: '[disableInteractiveElements]',
standalone: true,
hostDirectives: [DestroyedDirective]
})
export class DisableInteractiveElementsDirective implements OnInit {
private readonly destroyed$ = inject(DestroyedDirective).destroyed$;

constructor(private readonly service: ExampleService,
private readonly elementRef: ElementRef,
@Optional() @Self() private readonly button: MatButton,
Copy link
Collaborator

@yharaskrik yharaskrik Feb 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to make this more generic by doing something like

private readonly elementRef: ElementRef

as that will get the generic host element

then you could do something like

    this.service.shouldDisable$
    .pipe(takeUntil(this.destroyed$))
    .subscribe((shouldDisable) => {
      if (this.elementRef.hasOwnProperty('disabled')) {
        this.elementRef.disabled = shouldDisable;
      } 
    });

Then it would work for any host element that has a disabled property.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, to make it more generic. Will adjust it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd give it a try in a sample app. I thiiiink it will work but not positive.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys,
@santoshyadavdev - I've added the link to the tweet about the destroyed directive.
@yharaskrik - I remembered why I once implemented it this way. Using elementRef.nativeElement.disabled = true, it doesn't work for MatSelect.
For MatButton it works in most cases, i.e. it works when we trigger the change of the enabled/disabled status change in runtime, but it doesn't work when we want the button to be disabled on init. At first glance, it looks like it's some kind of problem with change detection on the material side.

I added elementRef to the code snippet so that we can disable other elements that will allow us to do so, e.g. input, html select, or button.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved @yharaskrik let me know if it looks good now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya looks good to me

@Optional() @Self() private readonly select: MatSelect) {}

ngOnInit(): void {
this.service.shouldDisable$
.pipe(takeUntil(this.destroyed$))
.subscribe((shouldDisable) => {
if (this.button) {
this.button.disabled = shouldDisable;
} else if (this.select) {
this.select.disabled = shouldDisable;
} else if (this.elementRef.nativeElement && ('disabled' in this.elementRef.nativeElement)) {
this.elementRef.nativeElement.disabled = shouldDisable;
}
});
}
}
```

Usage in template:

```html
<button mat-button mat-raised-button>Always available</button>
<button disableInteractiveElements mat-button mat-raised-button>Could be disabled</button>
<mat-select disableInteractiveElements placeholder="Settings">
<mat-option value="'setting1'">Setting 1</mat-option>
<mat-option value="'setting2'">Setting 2</mat-option>
</mat-select>
<input disableInteractiveElements type="text" />
```

In the case shown above, the first button will always be active, while the other elements, i.e. the second button, select and input, will be active if `service.shouldDisable$` emits false.