Reputation: 882
I'm at a loss on how to accomplish this:
I want to define in a component, a section that will be rendered by a sibling component, but using the original component's context: Imagine the following components: A, B, C and side-panel.
In the side panel I want to show a form that is defined in A B or C, so that when people are creating extra components they can define the options form that will be shown in the side panel.
For a static setup I could create a service to pass the data from the side panel into and out of my current active component (A, B, or C), but I think there might be a more flexible option with EmbeddedViewRef and templates that would truly scale well and would not require editing the side panel or the data passing service when the application is extended by a new component D.
app.component.html
<side-panel>
</side-panel>
<router-outlet></outlet>
In the side-panel component, I want to render options which change according to which component is being shown in the router-outlet
a.component.html
<div>
{{option1 * 10}}
</div>
<ng-template #options>
<input type="number" [(ngModel)]="option1">
</ng-template>
I would like the #options template to be displayed in the side-panel component which is a sibling of A component.
When rendering a template you can inject a context object but I would like the context to be the same as A's context so that changes and options selected in the side panel affect A component.
Any help into what concepts I should read about would be much appreciated.
Angular CDK Portals address precisely this problem. They allow for a component's template to be rendered in another place - a portalHost. https://material.angular.io/cdk/portal/overview
Upvotes: 1
Views: 1166
Reputation: 1338
Not sure if I understand correctly what you want but I think you can use content projection to project component A, B or C within your Side-Panel component using the <ng-content>
tag.
// side-panel component:
<ng-content></ng-content>
// app component
<app-side-panel>
<router-outlet></router-outlet>
</app-side-panel>
Here is a full working example: https://stackblitz.com/edit/angular-rzh9cw
UPDATE: Reading your question again I think what you are having is a state management problem:
I would like the #options template to be displayed in the side-panel component which is a sibling of A component.
<ng-template #options>
<input type="number" [(ngModel)]="option1">
</ng-template>
Since #options
template will affect the properties of component A both from inside and from outside the component, you need to define #options
as a separated component so that you can reuse it in both places:
Options Component:
<input #input type="number" (change)="changeOption(input.value)">
Component A:
<div>
{{option1 * 10}}
</div>
<app-options></app-options>
Side Panel Component:
<app-options></app-options>
Now, in order for OptionsComponent to make changes in Component A, you can use a service to store the state of these options you want to change and share across the app, that way components A, B or C can listen to it and react accordingly:
Options Service:
@Injectable({ providedIn: 'root' })
class OptionsService {
private options$ = new BehaviorSubject({
option1: 1,
option2: 'on',
option3: 'blue'
});
public options: Observable<Options>;
constructor() {
this.options = this.options$.asObservable();
}
public changeOptions(options: Options) {
this.options$.next(options);
}
}
Options Component (Uses OptionsService to trigger changes):
constructor(private optionsService: OptionsService) {}
public changeOption(value): void {
this.optionsService.changeOptions({
option1: value,
option2: 'off',
option3: 'red'
});
}
and finally Component A can subscribe to these changes:
constructor(private optionsService: OptionsService) {}
public ngOnInit() {
this.optionsService.options.subscribe((options: Options) => {
console.log(options);
this.option1 = options.option1;
});
}
Upvotes: 1