0

I have a dynamic form controlled by a Parent component, with an array of Child DTOs, that is rendered using ngFor, and those Child Components are passed the DTO and use their own template. Something like:

Parent Html:

<div class="row parameter-list" *ngFor='let parameter of parameters'>
  <div class="col-md-12">
    <parameter-list-item
    [parameter]="parameter"
    [types]="types"
    [events]="eventsSubject.asObservable()"
    (onDelete)="onDeleteHandler($event)">
    </parameter-list-item>
  </div>
</div>

Parent Component:

@Input()
parameters: Parameter[];

Child Html:

<input type="text" [(ngModel)]="parameter.Name" [disabled]="readonly">
<input type="text" [(ngModel)]="parameter.Description" [disabled]="readonly">

Child Component:

constructor() {
  this.readonly = true;
}

readonly: boolean;

@Input()
parameter: Parameter;

DTO:

export class Parameter {
   Name: string,
   Description: string) { }
}

When I want to change the list, by adding a Parameter to the Parent component, I am doing the following with a (click) handler:

  addParameter(): void {
    console.log("ParameterListComponent.addParameter()");
    let name = "Name" + this.parameters.length;

    let parameter = new Parameter(
      name,
      "Description");

    this.parameters.unshift(parameter);
  }

Essentially letting the unshift trigger the proper lifecycle hooks and whatnot.

What I need to know is if I can somehow communicate with the newly created child component and set it's readonly boolean to false..

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Steve
  • 467
  • 7
  • 28
  • You can use @ViewChild to get references to child components. – alt255 Aug 02 '18 at 02:36
  • Hmm, how would you declare it? The documentation I've seen seems to want you to use ViewChild on a single child element, not an array.. the documentation is a little confusing when you have an Array of children :\ – Steve Aug 02 '18 at 18:53
  • Hmm, found this guy: https://stackoverflow.com/questions/40165294/access-multiple-viewchildren-using-viewchild which is getting me closer to understanding. But where would I define ngAfterViewInit()? In the parent or the child component? And would there be a clean way to say something like "if child.Name == 'Name 20' inside the hook? – Steve Aug 02 '18 at 19:01

2 Answers2

1

There are many ways to communicate information between a parent and child.

Here are a few: enter image description here

As mentioned by another poster, you can use @ViewChild to get a reference to the child component and set it's properties.

Another option (as shown above), is to use the onChanges lifecycle hook. You can use that to watch for changes to any Input properties. So you could watch for a change to the Parameter and have the child set the readonly flag itself based on that parameter change.

Another option is to build a service to manage the flag, have the parent set the flag value and the child flag read it.

For a larger or more complex app, you could consider using NgRx for this communication. NgRx is a state management library that manages data changes and notifications.

Hope this helps.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • Good comment, but I am a little uncertain how to proceed given the complexities.. basically I would need to unshift, wait for the change detection life cycle of the Child Component to finish and then launch an emit or service broadcast to tell the child to flip readonly to false.. I am a little light on my knowledge of the Change Detection stuff, so I was worried about trying to force in detection hooks. Additionally I need to CD the Add Parameter method, and NOT the page load because my back end can return over 20 Parameters when you get the Parent. – Steve Aug 02 '18 at 18:55
  • If you could do a stackblitz that illustrates your issue (without too much code), it would help us better help you. – DeborahK Aug 02 '18 at 18:59
  • How much code would you need? Just the 2 components i am working with or all the scaffold code like the json, app.component etc..? – Steve Aug 02 '18 at 23:41
  • 1
    Looks like you solved it. For future reference, if you try out stackblitz, you'll see that it generates all of the scaffolding. You just need to add the pieces you'd need to show your issue. – DeborahK Aug 03 '18 at 15:49
0

I came up with a not-so-bad solution; I ended up just splitting the arrays in my parent into 2:

  parametersToAdd: Parameter[];

  @Input()
  parameters: Parameter[];

Then I changed my parent html to look something like:

<div class="row parameter-list" *ngFor='let parameterToAdd of parametersToAdd'>
  <div class="col-md-12">
  <parameter-list-item
    [parameter]="parameterToAdd"
    [types]="types"
    [readonly]="false"
    (onDelete)="onDeleteHandler($event)"
  ></parameter-list-item>
  </div>
</div>

<div class="row parameter-list" *ngFor='let parameter of parameters'>
  <div class="col-md-12">
    <parameter-list-item
    [parameter]="parameter"
    [types]="types"
    [readonly]="true"
    (onDelete)="onDeleteHandler($event)"
    ></parameter-list-item>
  </div>
</div>

The add click handler now looks like:

addParameter(): void {
  console.log("ParameterListComponent.addParameter()");
  let name = "Name" + this.parameters.length;

  let parameter = new Parameter(
    name,
    "Description",
    null,
    new Array(),
    new ParameterTypeInfo(ParameterDataType.Boolean, null, null, new 
    Array(),null, null));

  this.parametersToAdd.unshift(parameter);
}

Basically having 2 collections that will be rendered to look the same when the app is running. I needed to do this because our back-end API does not allow you to edit ALL of the fields in our DTO, only some of them. Thus I need the child component to be in 'readonly' mode for the existing records that are pulled from the backend API when the page loads. Conversely if you want to add a new Parameter then you can do whatever you want... So basically existing records cannot have many changes made to them and most of the fields need to be in read-only mode, any new records can be write mode until you save them.. then they get POSTed and the page should refresh with them in the read-only collection.

Steve
  • 467
  • 7
  • 28