Angular 7 - how to pass input data to child without transforming to string?


#1

I wanted to split my component into sub-components.

I have passed a Array<object> to a child using the @Input decorator.
But as soon as the data enters the child, it takes the form of a string looking like this “[Object] [Object] …”.

It seems to me that because the input takes the form of an attribute in the template, the value must be changed to a string since this is what the attribute value required in the HTML format.

I was suprized. I thought the data management was virtual and not directly mapped to HTML attributes for every Input.

What method do you recommend to pass data to a child? There may be another way than an @Input. Any structure storing globally the data would suite. Maybe dependency injection with a Redux state thing would work.

Maybe Observables would allow to subscribe and pass the event to the child. No idea how it would work though.


#2

Have you considered stringifying the data to JSON format?
https://docs.angularjs.org/api/ng/function/angular.toJson


#3

Yes I thought about that. But it is bad since there is a computation cost and a lalency for converting the Array<Object> using JSON.stringify().

I used React for some time. And it is trivial to pass parameters to a child.

I have a solution for my problem with Angular: a dependency injection with a global state. Like a redux state. But I don’t like global states.


#4

Hi @tvilmart, if you want to bind the actual value you have to wrap the input property in square brackets:

<my-component [someValue]="someObject"></my-component>

Otherwise the part inside the quotes will get treated as a string, not an expression; see the here for details.


#5

COol thanks.

Your doc says " You cannot use property binding to pull values out of the target element. You can’t bind to a property of the target element to read it. You can only set it."

Maybe we can say is for input and () is for output?


#6

Another question.

How to update the child component only when the stringify() of the Array<Object> changes but not when the reference changes?

I thought about 2 options:
-use a *ngFor trackBy on a stringified additional input property. In the doc tracBy can be used not only on iterables.
-use the ChangeDetectionRef and detach reattach it in ngOnChanges of the child, also activate OnPush change detection so we only react on input changes. And I will store the previous stringified value to compare to. What I really lack is the mechanism to block the re-rendering in the ngOnChanges function.

But this is quite theoretical. Please clarify how to do this.


#7

You can do the opposite though by detaching the change detector, and then manually calling detectChanges() inside ngOnChanges() (no need to reattach the detector then):

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnChanges {
  @Input() public data: any;

  constructor(
    private ref: ChangeDetectorRef
  ) {
    this.ref.detach();
  }

  public ngOnChanges(changes: SimpleChanges) {
    Object.keys(changes).some(key => {
      const { currentValue, previousValue } = changes[key];

      if (JSON.stringify(currentValue) !== JSON.stringify(previousValue)) {
        this.ref.detectChanges();
        return true;
      }

      return false;
    });
  }
}

But didn’t you want to avoid JSON.stringify() for performance reasons anyway? ^^


#8

OK thanks for the good info.

“But didn’t you want to avoid JSON.stringify() for performance reasons anyway?”
The HttpClient’s response contains both the string and the object. So I don’t need to perform a stringification. But I don’t want to use the string only because I need to have an object to perform some logic on the data.

How would ngOnChanges() look like if I have 2 @Input, one for the object and one for the string? How do I extract the one I want from the changes variable? Is OnPush strategy preferable for this to work?

ngOnCHanges will call detectChanges. But any change detected will call ngOnCHanges. Don’t we have a loop here?

If you have a codepen that would be very interesting.


#9

Wouldn’t it be more practical then to check the value coming from the service before actually setting it on the component?

I’m not sure if I understand what you’re trying to achieve here… do you want to compare the strings, or the objects derived from the strings, or both (and if both, then why)? Can you maybe provide a working code sample that demonstrates your issue, e.g. on stackblitz?

No, ngOnChanges() will always get called, but this will by itself not trigger any change detection if you detached the detector – here’s a demo (open the console for debug info).


#10

Yes one could do the comparison of strings in the parent. And the parent will only update the binded property if a new string is received. However, it seems to me more structured if the change detection belongs to the child and not to the parent. Moreover, it could have side consequences. Be cause the object has changed, but we did not notify the child about the new object. The view rendering may not need the new object, but maybe the code logic does.

For the case with 2 inputs, I just tried to update your code to have 2 inputs. The string input is used for the change detection mechanism. The object input is used for the code logic. It should look exactly as your code in your first answer. But instead of stringifying in ngOnCHange, I use the string Input parameter. It is straightforward. I was just curious if you had second thoughts about it.

So to conclude, I really like your solution with detatch and manual trigger to detectChanges.

If I am not wrong, I understand now detectChanges is synonymous to re-render view. IT will try to see if the view is dependent on data that has changed, and if it does it wil re-render. But the naming detectChanges is very confusing. And the DectectionChangeREf does not have a clear function saying “re-rerender”.


#11

No, selectively comparing input props for update seems absolutely fine to me. As you know React, it’s a common use case for implementing shouldComponentUpdate() as well – so it’s basically the Angular equivalent I suppose.

It will detect changes in the view, and update the DOM where necessary. Why would you want to enforce a complete rerender though (like, discarding the view’s DOM tree and rebuilding it from scratch)?


#12

Because I still think the react way this is how it works in react. If you render a component, you render all of it. Only pure component children without changing props will not be re-rendered.

“detect change” means changes are found, it does not mean clearly that updating in the DOM will be done.


#13

That’s not quite true; calling render() will create a new React element tree, but it’s for the reconciler to decide where to update the DOM (or whatever your renderer does). So when say the ReactDOM renderer encounters a React element where the props din’t change, it will skip diffing its children. If you want to read a more in-depth explanation of the internal workings, here’s a very good article by Dan Abramov:

It tells the detector to check if there are changes that require updating the DOM. Of course it won’t update the DOM for nothing.


#14

OK thanks for the claification.

The react rendering will maybe reuse elements but it will always recreate callbacks (like click events), unless you bind callbacks in the constructor.