0

As title implies i'm looking for a way to bind an object with multiple properties to component @Inputs without having to explicitly write all of them

Let's say I have this object

let params = {
    delta: 0.2,
    theta: 2.3,
    sigma: 'foo',
}

Instead of having to bind all of them individually like this

<my-component
    [delta]="params.delta"
    [theta]="params.theta"
    [sigma]="params.sigma"/>

I would like bind all of them at once.

<my-component [someDirectiveIDontKnow]="params"/>

How can i do this?

Found a link to a previously asked question but couldn't get that to work properly.

Edit: I'm not asking how to bind @Inputs. Imagine that the component I'm rendering has 40 @Inputs and I'm NOT allowed to rewrite it to just accept one @Input that could contain all the params.

So writing a template that uses this component gets really ugly and big.

....
<my-component
  [param1]="params.param1"
  [param2]="params.param2"
  [param3]="params.param3"
  [param4]="params.param4"
  [param5]="params.param5"
  [param6]="params.param6"
  [param7]="params.param7"
  [param8]="params.param8"
  [param9]="params.param9"
  [param10]="params.param10"
  [param11]="params.param11"
  [param12]="params.param12"
  [param13]="params.param13"
  [param14]="params.param14"
  ... and so on ....
/>
....
springbo
  • 1,096
  • 1
  • 9
  • 15
  • 1
    your component `` should have `@Input params` in its component.ts. Then you bind with brackets ``. – Francisco Santorelli Jun 03 '19 at 14:37
  • I don't know if that's possible, but I certainly don't like it. I suspect it won't be possible because Angular validates input properties - IE if you add `[thing]="true"` for a component that doesn't have a `thing` input property, Angular throws an error. – Vlad274 Jun 03 '19 at 14:40
  • 1
    @Vlad274 it's just passing an object as an input. Of course it's possible. As for the OP, [RTFM !](https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding) –  Jun 03 '19 at 14:40
  • https://angular.io/guide/template-syntax#input-and-output-properties – Alexander Staroselsky Jun 03 '19 at 14:49
  • @Vlad274 Component has all the @Inputs(with default values) But there are like 30 of them and I really feel like it's not DRY to type them all into the template. – springbo Jun 03 '19 at 14:49
  • 1
    To the folks who are missing the point: the question isn't "can I pass an object?" the question is "can I have an object expand/flatten it's properties?" – Vlad274 Jun 03 '19 at 14:52
  • @springbo You can pass the whole object as other people have mentioned, but ultimately it is not violating DRY to do the properties individually. The fact that each param value happens to come from a single object is irrelevant, the child component has some inputs and each needs to be filled. Imagine you had two of these "param" objects - if half of the child inputs came from one and half came from the other, I think it's obvious that specifying which source object to use makes sense. So, unless you're planning on changing the component inputs regularly, this is (IMO) the correct thing to do – Vlad274 Jun 03 '19 at 15:16
  • I see your edit now and i haven’t looked indepth to those things yet, i’d suggest you take a look in the angular template compiler where you generate your template inside your javascript, preferably using a small wrapper component that just uses an input directive name and model and then just loops all arguments. If this is what you could use i can look into it a bit more and provide a complete answer. Look here for more about it https://stackoverflow.com/questions/34784778/equivalent-of-compile-in-angular-2 – Samantha Adrichem Jun 04 '19 at 17:43
  • May i add, it seems to me like bad practise to have ‘magic’ at input bindings. The component will have all those @Input’s in it’s code and will cost you a segnificant readability issue. – Samantha Adrichem Jun 04 '19 at 17:47

2 Answers2

1

In my opinion, It would be best to define them all in a model

You would start with the following model

params.model.ts

import {SomeOtherModel} from './some-other.model'

export interface ParamsModel {
    paramName1: string;
    paramName2?: string;
    paramName3?: number;
    paramName4: SomeOtherModel;
}

Then in your component, you can force your input to take a specific model argument

my.component.ts

import {ParamsModel} from './params.model';

@Component({..})
class MyComponent {
  @Input() params: ParamsModel;
}

app.component.html

<my-component params="paramsModel"></my-component>

app.component.ts

import {ParamsModel} from './params.model';

@Component({..})
class AppComponent implements OnInit {
    paramsModel: ParamsModel;


    ngOnInit(): void {
        this.paramsModel = <ParamsModel>{someValue: someValue};
    }
}

this way you have full code completion.

do note though! Angular does not deepwatch the contents, so changing the contents inside the Params object, will still have the same object ID in javascript, causing angular to not see the changes.

There are a few work-around for this

1: Bind every param (this is exactly what you do not want)

2: When changing contents of the model, destroy the instance and create a new instance everytime, you could do this by adding a constructor in the model and convert it olike this code

export class ParamsModel {
    paramName1: string;
    paramName2?: string;
    paramName3?: number;
    paramName4: SomeOtherModel;

    constructor(config?: ParamsModel) {
        Object.assign(this, config);
    }
}

// first init
this.paramsModel = new ParamsModel(<ParamsModel>{someValue: someValue});

// updated init
this.paramsModel = new ParamsModel(this.paramsModel);
this.paramsModel.changedValue = changedValue; // (could also use an extend function on original params model)

3: Create an observer with events and trigger update events on the other side

4: use ngDoCheck to perform your own check if the contents changed

Samantha Adrichem
  • 833
  • 1
  • 12
  • 23
  • Excellent answer! The second half of this answer is super important and everyone should read it twice. One minor point: "you can force your input to take a specific model argument" - technically not true, this is just syntactic sugar for Typescript and at runtime it can be anything without throwing a specific error (although the logic using it will probably break). This has caused me an annoying amount of pain, so I mention it as a heads up to others – Vlad274 Jun 03 '19 at 15:24
0

There is no generic directive to pass input properties in Angular. However, Angular supports binding any valid JavaScript type be it objects, arrays or primitives.

In the template

<my-component [params]="params"/>

In the class you have to use the @Input decorator to mark an object as an input. You can access it's value in any of the lifecycle hooks, some shown below. Note that params will not be set inside the constructor as view binding is performed after the class is instantiated.

class MyComponent {
  @Input()
  params: any

  constructor() { }   // <-- params not set

  ngOnChanges() { } // <-- anytime params changes

  ngOnInit() { } // <-- once when the component is mounted

}
Avin Kavish
  • 8,317
  • 1
  • 21
  • 36