在 ng-template 到 ng-content 之间传递数据(Angular 17)

问题描述 投票:0回答:1

如何将lists$从list.component传递到ng-template

im 提供了代码,您可以看到函数调用的管道以及 ng-template 如何传递到 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.service.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>
angular modal-dialog
1个回答
0
投票

在 Angular 17 中,不要使用

ComponentFactoryResolver
,而是使用
ViewContainerRef
,这需要从组件作为参数传递。那么下面的代码块将帮助您实现您想要的!

...
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);
...

我们可以将第二个参数作为

context
传递,在其中定义属性
{ lists: options!.data, }
,我们还使用
setInput
内置函数来设置
@Input
值。

我们还可以通过注入器(环境,或者如果需要的话正常)

在 HTML 端,我们必须在模板上定义一个属性来存储该值!

...
<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);

莫代尔TS

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();
  }
}

模态 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>

模态服务

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();
  }
}

Stackblitz 演示

© www.soinside.com 2019 - 2024. All rights reserved.