Reputation: 5740
I have two Angular (v19) components.
I want to have an instance of ComponentB
inside the template of ComponentA
.
ComponentB.parameters
should be bound to the individual fields of ComponentA
in such a way that a change to ComponentB.parameters
updates the individual fields of ComponentA
and an update to any of the fields of ComponentA
should update ComponentB.parameters
.
How to to this?
a.component.ts
@Component({
selector: 'app-a',
templateUrl: './a.component.html',
standalone: true,
imports: [ComponentB]
})
export class ComponentA {
public readonly filter = model<string>();
public readonly orderby = model<string>();
public readonly skip = model<number>();
public readonly top = model<number>();
}
b.component.ts
@Component({
selector: 'app-b',
standalone: true,
})
export class ComponentB {
public readonly parameters = model<Parameters>()
}
parameters.ts
export interface Parameters {
filter?: string;
orderby?: string;
skip?: string;
top?: string;
}
a.component.html
<app-b [(parameters)]="???"></app-b>
Upvotes: 3
Views: 61
Reputation: 1140
Using a computed in A component for building Parameters and split two ways binding to destructure Parameter output.
a.component.ts
@Component({
selector: 'app-a',
templateUrl: './a.component.html',
standalone: true,
imports: [ComponentB]
})
export class ComponentA {
public readonly filter = model<string>();
public readonly orderby = model<string>();
public readonly skip = model<number>();
public readonly top = model<number>();
protected parameters= computed(() => ({filter : filters(), orderby : orderby(), skip :skip(), top :top()}));
protected onParametersChange(params: Parameters | undefined) {
if (params) {
filter.set(params.filter);
orderby .set(params.orderby);
skip .set(params.skip);
top .set(params.top);
}
}
}
b.component.ts
@Component({
selector: 'app-b',
standalone: true,
})
export class ComponentB {
public readonly parameters = model<Parameters>()
}
parameters.ts
export interface Parameters {
filter?: string;
orderby?: string;
skip?: string;
top?: string;
}
a.component.html
<app-b [parameters]="parameters()" (parametersChange)="onParametersChange($event)"></app-b>
Note: working because filter, orderby, skip and top are primitive, if there are object, you will need to change equal signal function.
Upvotes: 0
Reputation: 57986
We can update the parameters to contain the individual signals, so that you can bind them to [(ngModel)]
inside the child component.
export interface Parameters {
filter: ModelSignal<string>;
orderby: ModelSignal<string>;
skip: ModelSignal<number>;
top: ModelSignal<number>;
}
Then we do not require the child component to accept a model
signal, but an input.required
signal instead. I am using required because otherwise I need to wrap each element that uses the inner signal like filter
inside a if condition, because it can be undefined, so input.required
is easier, but let me know if you would like to know how that code would be.
@Component({
selector: 'app-b',
standalone: true,
imports: [FormsModule],
template: ` <br/><br/><br/>
Child<br/>
<input [(ngModel)]="parameters().filter"/><br/>
<input [(ngModel)]="parameters().orderby"/><br/>
<input [(ngModel)]="parameters().skip"/><br/>
<input [(ngModel)]="parameters().top"/><br/>
`,
})
export class ComponentB {
public readonly parameters = input.required<Parameters>();
}
Now we can simply, pass an object called parameters
which contains all the individual signals, into the child, hence we have two way binded between parent and child component.
@Component({
selector: 'app-root',
imports: [ComponentB, FormsModule],
template: `
Parent<br/>
<input [(ngModel)]="filter"/><br/>
<input [(ngModel)]="orderby"/><br/>
<input [(ngModel)]="skip"/><br/>
<input [(ngModel)]="top"/><br/>
<br/><br/><br/>
<app-b [parameters]="parameters"></app-b>
`,
})
export class App {
public readonly filter = model<string>('');
public readonly orderby = model<string>('');
public readonly skip = model<number>(1);
public readonly top = model<number>(2);
parameters: Parameters = {
filter: this.filter,
orderby: this.orderby,
skip: this.skip,
top: this.top,
};
}
import { Component, ModelSignal, input, model } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
export interface Parameters {
filter: ModelSignal<string>;
orderby: ModelSignal<string>;
skip: ModelSignal<number>;
top: ModelSignal<number>;
}
@Component({
selector: 'app-b',
standalone: true,
imports: [FormsModule],
template: ` <br/><br/><br/>
Child<br/>
<input [(ngModel)]="parameters().filter"/><br/>
<input [(ngModel)]="parameters().orderby"/><br/>
<input [(ngModel)]="parameters().skip"/><br/>
<input [(ngModel)]="parameters().top"/><br/>
`,
})
export class ComponentB {
public readonly parameters = input.required<Parameters>();
}
@Component({
selector: 'app-root',
imports: [ComponentB, FormsModule],
template: `
Parent<br/>
<input [(ngModel)]="filter"/><br/>
<input [(ngModel)]="orderby"/><br/>
<input [(ngModel)]="skip"/><br/>
<input [(ngModel)]="top"/><br/>
<br/><br/><br/>
<app-b [parameters]="parameters"></app-b>
`,
})
export class App {
public readonly filter = model<string>('');
public readonly orderby = model<string>('');
public readonly skip = model<number>(1);
public readonly top = model<number>(2);
parameters: Parameters = {
filter: this.filter,
orderby: this.orderby,
skip: this.skip,
top: this.top,
};
}
bootstrapApplication(App);
Upvotes: 0