im provided code you could see the pipeline of function calls and how the ng-template passes to modal.compoent.html. ..................................................................................................................................................
List.component.html
<div class="list">
<div class="header">
<h3>{{list.name}}</h3>
<span
style="
display: flex;
width: 25%;
justify-content: space-between;">
<h3>{{cards.length}}</h3>
<button class="list-options-btn"></button>
</span>
</div>
<div class="btn-container">
<button class="card-add-btn" (click)="openModal(createCardTemplate)">Add new card</button>
</div>
<div class="cards-container" *ngIf="lists$ | async as lists">
<app-card
*ngFor="let card of cards"
[card]=card
[lists]=lists
></app-card>
</div>
<ng-template #createCardTemplate>
<div>
<label for="name">Name</label><br>
<input id="name" formControlName="name" type="text">
</div>
<div>
<label for="description">Description</label><br>
<input id="description" formControlName="description" type="text">
</div>
<div>
<label for="dueDate">Due Date</label><br>
<input id="dueDate" formControlName="dueDate" type="date">
</div>
<div>
<label for="priority">Priority</label><br>
<select id="priority" formControlName="priority">
<option value="1">Low</option>
<option value="2">Medium</option>
<option value="3">High</option>
</select>
</div>
<div>
<label for="listId">List</label><br>
<select id="listId" formControlName="listId">
<option *ngFor="let list of lists$ | async" value={{ list.id }}>
{{ list.name }}
</option>
</select>
</div>
</ng-template>
</div>
List.component.ts
import { Component, Input, TemplateRef } from '@angular/core';
import { CardDto, Priority } from 'src/Dtos/CardDto';
import { CardListDto } from 'src/Dtos/CardListDto';
import { Observable } from 'rxjs';
import { ListsService } from 'src/services/lists.service';
import { ModalService } from 'src/services/modal.service';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent {
@Input()
list: CardListDto = new CardListDto;
@Input()
cards: CardDto[] = [];
lists$: Observable<CardListDto[]> | null = null;
constructor(
private listsService: ListsService,
private modalService: ModalService){}
openModal(modalTemplate: TemplateRef<any>) {
this.modalService
.open(modalTemplate, { title: 'New Card', data: this.lists$ })
.subscribe((action) => {
console.log('modalAction', action);
});
}
ngOnInit(){
this.lists$ = this.listsService.getLists();
this.lists$.subscribe(lists => console.log(lists));
}
}
Modal.sevice.ts
import { DOCUMENT } from '@angular/common';
import {
ComponentFactoryResolver,
Inject,
Injectable,
Injector,
TemplateRef,
} from '@angular/core';
import { Subject } from 'rxjs';
import { ModalComponent } from 'src/app/modal/modal.component';
@Injectable()
export class ModalService {
private modalNotifier?: Subject<string>;
constructor(
private resolver: ComponentFactoryResolver,
private injector: Injector,
@Inject(DOCUMENT) private document: Document
) {}
open(content: TemplateRef<any>, options?: { size?: string; title?: string; data?: any }) {
const modalComponentFactory = this.resolver.resolveComponentFactory(ModalComponent);
const contentViewRef = content.createEmbeddedView(null);
const modalComponent = modalComponentFactory.create(this.injector, [contentViewRef.rootNodes]);
modalComponent.instance.size = options?.size;
modalComponent.instance.title = options?.title;
modalComponent.instance.data = options?.data; // Pass lists$ as a property of the ModalComponent
modalComponent.instance.closeEvent.subscribe(() => this.closeModal());
modalComponent.instance.submitEvent.subscribe(() => this.submitModal());
modalComponent.hostView.detectChanges();
this.document.body.appendChild(modalComponent.location.nativeElement);
this.modalNotifier = new Subject();
return this.modalNotifier?.asObservable();
}
closeModal() {
this.modalNotifier?.complete();
}
submitModal() {
this.modalNotifier?.next('confirm');
this.closeModal();
}
}
Modal.component.ts
import {
Component,
ElementRef,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Observable } from 'rxjs';
import { CardListDto } from 'src/Dtos/CardListDto';
@Component({
selector: 'modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.css'],
})
export class ModalComponent{
@Input() size? = 'md';
@Input() title? = 'Modal title';
@Input() data: any;
@Output() closeEvent = new EventEmitter();
@Output() submitEvent = new EventEmitter();
constructor(private elementRef: ElementRef) {}
ngOnInit(){
(<Observable<CardListDto[]>>this.data).subscribe(
data => {
console.log(data)
}
)
}
close(): void {
this.elementRef.nativeElement.remove();
this.closeEvent.emit();
}
submit(): void {
this.elementRef.nativeElement.remove();
this.submitEvent.emit();
}
}
**Modal.component.html **
<div class="modal {{ size }}">
<div class="modal-header">
{{ title }}
<span class="modal-close" (click)="close()">✕</span>
</div>
<div class="modal-content">
<ng-content></ng-content>
</div>
<div class="modal-footer">
<button (click)="submit()">Submit</button>
</div>
</div>
<div class="modal-backdrop" (click)="close()"></div>
In Angular 17 instead of using ComponentFactoryResolver
use ViewContainerRef
this needs to be passed as a param from the component. Then the below code block, will help you achieve what you want!
...
const contentViewRef = vcr.createEmbeddedView(
content,
{ lists: options!.data }
// {
// injector: this.injector,
// }
);
const modalComponent = vcr.createComponent(ModalComponent, {
projectableNodes: [contentViewRef.rootNodes],
// environmentInjector: this.envInjector,
// injector: this.injector,
});
modalComponent.setInput('size', options?.size);
modalComponent.setInput('title', options?.title);
modalComponent.setInput('data', options?.data);
...
We can pass the second argument as context
where we define the property { lists: options!.data, }
, also we use setInput
an inbuilt function for setting @Input
values.
We can also pass the injectors(environment, or normal if needed)
In the HTML side, we must define a property on the template that will store this value!
...
<ng-template #createCardTemplate let-lists="lists">
...
import { CommonModule } from '@angular/common';
import { Component, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { CardListDto, ModalComponent } from './app/modal/modal.component';
import { Observable, of } from 'rxjs';
import { ModalService } from './app/modal.service';
import { CardComponent } from './app/card/card.component';
export interface CardDto {}
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, ModalComponent, CardComponent],
template: `
<div class="list">
<div class="header">
<h3>{{list.name}}</h3>
<span
style="
display: flex;
width: 25%;
justify-content: space-between;">
<h3>{{cards.length}}</h3>
<button class="list-options-btn"></button>
</span>
</div>
<div class="btn-container">
<button class="card-add-btn" (click)="openModal(createCardTemplate)">Add new card</button>
</div>
<div class="cards-container" *ngIf="lists$ | async as lists">
<app-card
*ngFor="let card of cards"
[card]=card
[lists]=lists
></app-card>
</div>
<ng-template #createCardTemplate let-lists="lists">
<div>
<label for="name">Name</label><br>
<input id="name" formControlName="name" type="text">
</div>
<div>
<label for="description">Description</label><br>
<input id="description" formControlName="description" type="text">
</div>
<div>
<label for="dueDate">Due Date</label><br>
<input id="dueDate" formControlName="dueDate" type="date">
</div>
<div>
<label for="priority">Priority</label><br>
<select id="priority" formControlName="priority">
<option value="1">Low</option>
<option value="2">Medium</option>
<option value="3">High</option>
</select>
</div>
<div>
<label for="listId">List</label><br>
<select id="listId" formControlName="listId">
<option *ngFor="let list of lists | async" [value]="list.id">
{{ list.name }}
</option>
</select>
</div>
</ng-template>
</div>
`,
})
export class App {
@Input()
list: CardListDto = new CardListDto();
@Input()
cards: CardDto[] = [];
lists$: Observable<CardListDto[]> | null = of([
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' },
]);
constructor(
private vcr: ViewContainerRef,
private modalService: ModalService
) {}
openModal(modalTemplate: TemplateRef<any>) {
this.modalService
.open(this.vcr, modalTemplate, { title: 'New Card', data: this.lists$ })
.subscribe((action) => {
console.log('modalAction', action);
});
}
ngOnInit() {
// this.lists$ = this.listsService.getLists();
// this.lists$!.subscribe((lists) => console.log(lists));
}
}
bootstrapApplication(App);
import {
Component,
ElementRef,
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Observable } from 'rxjs';
export class CardListDto {
name!: string;
}
@Component({
selector: 'app-modal',
standalone: true,
imports: [],
templateUrl: './modal.component.html',
styleUrl: './modal.component.css',
})
export class ModalComponent {
@Input() size? = 'md';
@Input() title? = 'Modal title';
@Input() data: any;
@Output() closeEvent = new EventEmitter();
@Output() submitEvent = new EventEmitter();
constructor(private elementRef: ElementRef) {}
ngOnInit() {
(<Observable<CardListDto[]>>this.data).subscribe((data) => {
console.log(data);
});
}
close(): void {
this.elementRef.nativeElement.remove();
this.closeEvent.emit();
}
submit(): void {
this.elementRef.nativeElement.remove();
this.submitEvent.emit();
}
}
<div class="modal {{ size }}">
<div class="modal-header">
{{ title }}
<span class="modal-close" (click)="close()">✕</span>
</div>
<div class="modal-content">
<ng-content></ng-content>
</div>
<div class="modal-footer">
<button (click)="submit()">Submit</button>
</div>
</div>
<div class="modal-backdrop" (click)="close()"></div>
import { DOCUMENT } from '@angular/common';
import {
EnvironmentInjector,
Inject,
Injectable,
Injector,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { Subject, of } from 'rxjs';
import { ModalComponent } from './modal/modal.component';
@Injectable({
providedIn: 'root',
})
export class ModalService {
private modalNotifier?: Subject<string>;
constructor(
private envInjector: EnvironmentInjector,
private injector: Injector,
@Inject(DOCUMENT) private document: Document
) {}
open(
vcr: ViewContainerRef,
content: TemplateRef<any>,
options?: { size?: string; title?: string; data?: any }
) {
const contentViewRef = vcr.createEmbeddedView(
content,
{ lists: options!.data }
// {
// injector: this.injector,
// }
);
const modalComponent = vcr.createComponent(ModalComponent, {
projectableNodes: [contentViewRef.rootNodes],
// environmentInjector: this.envInjector,
// injector: this.injector,
});
modalComponent.setInput('size', options?.size);
modalComponent.setInput('title', options?.title);
modalComponent.setInput('data', options?.data);
modalComponent.instance.closeEvent.subscribe(() => this.closeModal());
modalComponent.instance.submitEvent.subscribe(() => this.submitModal());
modalComponent.hostView.detectChanges();
this.document.body.appendChild(modalComponent.location.nativeElement);
this.modalNotifier = new Subject();
return this.modalNotifier?.asObservable();
}
closeModal() {
this.modalNotifier?.complete();
}
submitModal() {
this.modalNotifier?.next('confirm');
this.closeModal();
}
}