99

I generated new @Directive by Angular CLI, it was imported it to my app.module.ts

import { ContenteditableModelDirective } from './directives/contenteditable-model.directive';

import { ChatWindowComponent } from './chat-window/chat-window.component';

@NgModule({
  declarations: [
    AppComponent,
    ContenteditableModelDirective,
    ChatWindowComponent,
    ...
  ],
  imports: [
    ...
  ],
  ...
})

and I try to use in my component (ChatWindowComponent)

<p [appContenteditableModel] >
    Write message
</p>

even if within directive is only Angular CLI generated code:

 import { Directive } from '@angular/core';

 @Directive({
   selector: '[appContenteditableModel]'
 })
 export class ContenteditableModelDirective {

 constructor() { }

 }

I got the error:

zone.js:388 Unhandled Promise rejection: Template parse errors: Can't bind to 'appContenteditableModel' since it isn't a known property of 'p'.

I tried almost every possible changes, following this angular docs everything should work but it does not.

Any help?

msanford
  • 10,127
  • 8
  • 56
  • 83
Tomas Javurek
  • 1,123
  • 1
  • 7
  • 14

5 Answers5

165

When wrapping a property in brackets [] you're trying to bind to it. So you have to declare it as an @Input.

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

@Directive({
 selector: '[appContenteditableModel]'
})
export class ContenteditableModelDirective {

  @Input()
  appContenteditableModel: string;

  constructor() { }

}

The important part is, that the member (appContenteditableModel) needs to be named as the property on the DOM node (and, in this case, the directive selector).

naeramarth7
  • 5,374
  • 1
  • 20
  • 25
  • I have input `@Input ('appContenteditableModel') model : any;` and also output `@Output ('appContenteditableModel') update : EventEmitter = new EventEmitter();` in my directive. It seems that the model works well but the emitter called by `this.update.emit(value)` does not change the value in parent component. What I do wrong? `[(appContenteditableModel)]="draftMessage.text"` – Tomas Javurek Nov 20 '16 at 17:44
  • Actually I try to "simulate" [(ngModel)] outside of element – Tomas Javurek Nov 20 '16 at 17:51
  • `@Output` is for emitting events only. If you want to keep the value in sync with the parent's, you may consider adding the `@HostBinding` annotation. – naeramarth7 Nov 20 '16 at 18:03
  • If I undersand well `@HostBinding` will help to keep the value in sync within the html element, am I right? This element I need to be edited by user `contenteditable="true"` so that input I need to keep in sync with the variable in the same component. – Tomas Javurek Nov 20 '16 at 18:14
  • if using strict mode and your directive requires no value, you need to do `appContenteditableModel = undefined`; – Luizgrs Mar 25 '21 at 18:19
  • 1
    Ah thank you. In case someone else runs into it, my specific issue was that the directive selector (e.g. `selector: 'fooDirective'`) didn't match the `@Input()` property name (e.g. `@Input() barDirective`). I renamed the selector, but didn't think about the property too. – KMJungersen Mar 31 '21 at 18:13
37

If you're using a shared module to define the directive make sure it is both declared and exported by module it's defined in.

// this is the SHARED module, where you're defining directives to use elsewhere
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [NgIfEmptyDirective, SmartImageDirective],
  exports: [NgIfEmptyDirective, SmartImageDirective]
})
Simon_Weaver
  • 120,240
  • 73
  • 577
  • 618
  • and what if they are not in the same module? – Ohad Sadan Jan 15 '19 at 07:10
  • @OhadSadan I'm not sure exactly what you mean. This is an example of when you *don't* have them in the same module, and I'm just saying make sure to declare AND export directives if you're creating them in a shared module (which you must then then import them into a different module). – Simon_Weaver Jan 15 '19 at 07:13
  • In your 'main' module you only need to import the 'directives module' and then all your components can see them. – Simon_Weaver Jan 15 '19 at 19:28
  • This is a minute detail but often missed. Thank you ! – Sami Aug 04 '20 at 15:21
3

For me the fix was moving the directive references from root app.module.ts (the lines for import, declarations, and/or exports) to the more specific module src/subapp/subapp.module.ts my component belonged to.

SushiGuy
  • 955
  • 10
  • 17
2

I was facing the same issue with a directive declared in a shared module. I'm using this directive to disable a form control.

import { Directive, Input } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[appDisableControl]'
})
export class DisableControlDirective {

  constructor(private ngControl: NgControl) { }

  @Input('disableControl') set disableControl( condition: boolean) {
    const action = condition ? 'disable' : 'enable';
    this.ngControl.control[action]();
  }

}

To work it properly, declare and export the directive in shared module (or any module you are using).

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DisableControlDirective } from './directives/disable-control/disable-control.directive';

@NgModule({
  declarations: [
    DisableControlDirective
  ],
  imports: [
    CommonModule
  ],
  exports: [DisableControlDirective],
  providers: [],
  bootstrap: []
})
export class SharedModule { }

Now we can use this directive in any module where we are importing SharedModule.

Now to disable a reactive form's control, we can use it like this:

<input type="text" class="form-control" name="userName" formControlName="userName" appDisableControl [disableControl]="disable" />

Mistake I was doing it, I was using only selector (appDisableControl) and passing the disable param to this. but to pass an input param, we have to use it like above.

ImFarhad
  • 1,013
  • 8
  • 20
1

In sum, because your directive looks like an anchor directive, remove the brackets and it would work.

Actually, I have not found the corresponding sections related to when the brackets should be removed or not, where only one mention I've found is located at the section on dynamic components:

Apply that to <ng-template> without the square brackets

, which is however not perfectly covered in the Attribute Directives document.

Individually, I agree with you and was thinking that [appContenteditableModel] should be equal to appContenteditableModel and angular template parser might work around whether there is @input() data binding or not automatically, as well. But they seem exactly not processed equally under the hood, even in current Angular Version of 7.

千木郷
  • 859
  • 1
  • 12
  • 22