98

Since I've created @Directive as SelectableDirective, I'm little bit confused, about how to pass more than one value to the custom directive. I have searched a lot but didn't get proper solution in Angular with Typescript.

Here is what my sample code is:

Parent Component as MCQComponent:

import { Component, OnInit } from '@angular/core';
import { Question } from '../question/question';
import { AppService } from '../app.service/app.service';
import { SelectableDirective } from '../selectable.directive/selectable.directive';
import { ResultComponent } from '../result-component/result.component';

@Component({
    selector: 'mcq-component',
    template: "
         .....
        <div *ngIf = 'isQuestionView'>
            <ul>
                <li *ngFor = 'let opt of currentQuestion.options' 
                    [selectable] = 'opt'
                    (selectedOption) = 'onOptionSelection($event)'>
                    {{opt.option}}
                </li>
            </ul>
            .....
        </div>

    "
    providers: [AppService],
    directives: [SelectableDirective, ResultComponent]
})
export class MCQComponent implements OnInit{
    private currentIndex:any = 0;
    private currentQuestion:Question = new Question();
    private questionList:Array<Question> = [];
    ....
    constructor(private appService: AppService){}
    ....
}

This is a parent component having custom directive [selectable] which takes one param called opt.

Here is the code for this directive:

import { Directive, HostListener, ElementRef, Input, Output, EventEmitter } from '@angular/core'
import { Question } from '../question/question';

@Directive({
    selector: '[selectable]'
})
export class SelectableDirective{
    private el: HTMLElement;
    @Input('selectable') option:any;

    ...
}

So here I want to pass more parameters from parent component, how do I achieve this?

Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
Sagar Ganesh
  • 2,454
  • 3
  • 20
  • 32

4 Answers4

160

From the Documentation

As with components, you can add as many directive property bindings as you need by stringing them along in the template.

Add an input property to HighlightDirective called defaultColor:

@Input() defaultColor: string;

Markup

<p [myHighlight]="color" defaultColor="violet">
  Highlight me too!
</p>

Angular knows that the defaultColor binding belongs to the HighlightDirective because you made it public with the @Input decorator.

Either way, the @Input decorator tells Angular that this property is public and available for binding by a parent component. Without @Input, Angular refuses to bind to the property.

For your example

With many parameters

Add properties into the Directive class with @Input() decorator

@Directive({
    selector: '[selectable]'
})
export class SelectableDirective{
    private el: HTMLElement;

    @Input('selectable') option:any;   
    @Input('first') f;
    @Input('second') s;

    ...
}

And in the template pass bound properties to your li element

<li *ngFor = 'let opt of currentQuestion.options' 
    [selectable] = 'opt' 
    [first]='YourParameterHere'
    [second]='YourParameterHere'
    (selectedOption) = 'onOptionSelection($event)'>
    {{opt.option}}
</li>

Here on the li element we have a directive with name selectable. In the selectable we have two @Input()'s, f with name first and s with name second. We have applied these two on the li properties with name [first] and [second]. And our directive will find these properties on that li element, which are set for him with @Input() decorator. So selectable, [first] and [second] will be bound to every directive on li, which has property with these names.

With single parameter

@Directive({
    selector: '[selectable]'
})
export class SelectableDirective{
    private el: HTMLElement;

    @Input('selectable') option:any;   
    @Input('params') params;

    ...
}

Markup

<li *ngFor = 'let opt of currentQuestion.options' 
    [selectable] = 'opt' 
    [params]='{firstParam: 1, seconParam: 2, thirdParam: 3}'
    (selectedOption) = 'onOptionSelection($event)'>
    {{opt.option}}
</li>
Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
  • But what should I write in parent component? – Sagar Ganesh Aug 09 '16 at 06:27
  • @Shree see the edited anwser. In the `li` you pass a parameter in the same way – Suren Srapyan Aug 09 '16 at 06:28
  • But what if I have multiple attr directives? – Sagar Ganesh Aug 09 '16 at 06:30
  • you will give the parameter for which it is with the @Input decorator – Suren Srapyan Aug 09 '16 at 06:31
  • thanks a lot, I really appreciate your reply, now I got it. – Sagar Ganesh Aug 09 '16 at 06:33
  • shouldn't it be [defaultColor]="violet" instead of defaultColor="violet" ? – Chris Tarasovs Aug 20 '17 at 18:14
  • 4
    @ChrisTarasovs when you use `[defaultColor]="violet"`, it tries to execute the right part and find a property with name violet, without `[]` brackets it uses the right part as string – Suren Srapyan Aug 20 '17 at 18:31
  • What happens if multiple directives accept inputs with the same name?Seems like this would cause an unresolvable conflict for the developer. Is it considered best practice to do this? It seems like it may be a better practice to pass an object to the directive containing properties representing any additional arguments. – crush May 23 '19 at 15:13
  • @SurenSrapyan 1) shouldn't `[defaultColor]="violet"` be like `[defaultColor]="'violet'"`, value wrapped in `'`. If not using `'`, should be like `defaultColor="violet"` 2) If a property name is the same as the selector then we can pass the value as `[selectable] = 'opt'`. Now for other property/ies, need one or more properties or use param as you have used. Can't we pass multiple parameters as `opt` rather than using additional properties or use params as you have? 3) so do i then access params in the directive as `params.firstParam` and shouldn't params be of type `any` in this case? – learning... Jun 21 '19 at 16:50
  • this example simply not working for me: `appHighlight [highlightColor]="'orange'"` – BruneX Nov 07 '19 at 16:22
  • In case you need to pass an object to embedded component. – AjitChahal Nov 10 '19 at 20:06
21

to pass many options you can pass a object to a @Input decorator with custom data in a single line.

In the template

<li *ngFor = 'let opt of currentQuestion.options' 
                [selectable] = 'opt'
                [myOptions] ="{first: opt.val1, second: opt.val2}" // these are your multiple parameters
                (selectedOption) = 'onOptionSelection($event)' >
     {{opt.option}}
</li>

so in Directive class

@Directive({
  selector: '[selectable]'
})

export class SelectableDirective{
  private el: HTMLElement;
  @Input('selectable') option:any;
  @Input('myOptions') data;

  //do something with data.first
  ...
  // do something with data.second
}
ilDug
  • 553
  • 1
  • 4
  • 11
  • This is what I was looking for. That way I can get away with only one directive binding pr. html element. Thanks a bunch @Dag ! – MartinJH Aug 25 '17 at 12:43
  • 3
    @MartinJH It's worth mentioning this solution does not work well when using the change detection strategy `OnPush`. If you want to use that strategy, it's better to avoid using objects as @Input(). – enf0rcer Aug 24 '18 at 14:45
  • @enf0rcer You can of course still use this method with OnPush, you just need to push a new object to the directive. So, embedding the object literal in the template is a no-go - you'd need to push it through an observable on the component. – crush May 23 '19 at 15:15
14

Another neat option is to use the Directive as an element and not as an attribute.

@Directive({
   selector: 'app-directive'
})
export class InformativeDirective implements AfterViewInit {

    @Input()
    public first: string;

    @Input()
    public second: string;

    ngAfterViewInit(): void {
       console.log(`Values: ${this.first}, ${this.second}`);
    }
}

And this directive can be used like that:

<app-someKindOfComponent>
    <app-directive [first]="'first 1'" [second]="'second 1'">A</app-directive>
    <app-directive [first]="'First 2'" [second]="'second 2'">B</app-directive>
    <app-directive [first]="'First 3'" [second]="'second 3'">C</app-directive>
</app-someKindOfComponent>`

Simple, neat and powerful.

Aharon Ohayon
  • 1,171
  • 1
  • 17
  • 20
4

Similar to the above solutions I used @Input() in a directive and able to pass multiple arrays of values in the directive.

selector: '[selectorHere]',

@Input() options: any = {};

Input.html

<input selectorHere [options]="selectorArray" />

Array from TS file

selectorArray= {
  align: 'left',
  prefix: '$',
  thousands: ',',
  decimal: '.',
  precision: 2
};
Pingolin
  • 3,161
  • 6
  • 25
  • 40
Deepak
  • 571
  • 7
  • 19