Angular Directive for a Specific Component

July 5th 2019 Angular

Some Angular directives only make sense when they are applied to a specific Angular component. For example, the host component might be injected in the directive constructor so that directive code can manipulate it:

@Directive({
  selector: '[appGeneral]'
})
export class GeneralDirective {
  constructor(private checkboxComponent: MatCheckbox) {
    // manipulate MatCheckbox instance
  }
}

When the above directive is applied to a <mat-checkbox> element, it works fine. However, when applied to any other component, it fails to instantiate because the dependency injector can't find a MatCheckbox instance to inject:

NullInjectorError: StaticInjectorError(AppModule)[GeneralDirective -> MatCheckbox]: 
  StaticInjectorError(Platform: core)[GeneralDirective -> MatCheckbox]: 
    NullInjectorError: No provider for MatCheckbox!

This can be avoided by marking the argument optional for dependency injector and checking its value before using it:

@Directive({
  selector: '[appGeneral]'
})
export class GeneralDirective {
  constructor(@Optional() private checkboxComponent: MatCheckbox) {
    if (this.checkboxComponent != null) {
      // manipulate MatCheckbox instance
    }
  }
}

This approach works fine. If the directive is applied to a wrong component, it will simply be ignored. However, Juan Mendes suggested a simpler solution in a comment to an older blogpost.

The directive's selector option accepts a large subset of CSS selectors. It's not limited to an attribute name as one could incorrectly assume based on the code generated by Angular CLI. This means that the component restriction can be included in the selector and no other code changes are necessary:

@Directive({
  selector: 'mat-checkbox[appSpecific]'
})
export class SpecificDirective {
  constructor(private checkboxComponent: MatCheckbox) {
      // manipulate MatCheckbox instance
  }
}

If the appSpecific attribute is added to any other element than <mat-checkbox>, it will be completely ignored. Angular will not even instantiate it. This will result in better performance and simpler code (because no checks are needed in the directive code) than my first approach.

Copyright
Creative Commons License