You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

在Angular 17中如何将ListComponent的lists$传递到ng-template

我需要解决Angular 17环境下,把ListComponent中的lists$ Observable传递到模板内ng-template的问题,以下是当前相关代码:


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

解决方案

当前代码中,ng-template直接使用lists$ | async会因为模板上下文隔离无法获取到ListComponent中的lists$,可以通过以下两种方式解决:

方式一:提前订阅数据,直接传递数组

这种方式避免模板中多次使用async管道导致的重复订阅问题,实现最简单:

  1. 在ListComponent中添加属性存储订阅后的列表数据:
lists: CardListDto[] = [];

ngOnInit(){
  this.lists$ = this.listsService.getLists();
  this.lists$.subscribe(lists => {
    console.log(lists);
    this.lists = lists; // 存储已解析的数组
  });
}
  1. 修改ng-template中的下拉列表代码,直接使用已订阅的数组:
<ng-template #createCardTemplate>
  <!-- 其他表单内容保持不变 -->
  <div>
    <label for="listId">List</label><br>
    <select id="listId" formControlName="listId">
        <option *ngFor="let list of lists" value={{ list.id }}>
            {{ list.name }}
         </option>
    </select>
  </div>
</ng-template>

方式二:利用TemplateRef上下文传递Observable

如果需要保持Observable的响应式特性,可以通过模板上下文传递数据:

  1. 修改ListComponent的openModal方法,创建EmbeddedView时传入上下文:
openModal(modalTemplate: TemplateRef<any>) {
  // 传入包含lists$的上下文
  const contentViewRef = modalTemplate.createEmbeddedView({ lists$: this.lists$ });
  this.modalService
    .open(modalTemplate, { title: 'New Card', contentViewRef })
    .subscribe((action) => {
      console.log('modalAction', action);
    });
}
  1. 修改ModalService的open方法,使用传入的contentViewRef:
open(content: TemplateRef<any>, options?: { size?: string; title?: string; contentViewRef?: any }) {
  const modalComponentFactory = this.resolver.resolveComponentFactory(ModalComponent);
  // 使用传入的已创建好的contentViewRef
  const modalComponent = modalComponentFactory.create(this.injector, [options?.contentViewRef.rootNodes]);

  modalComponent.instance.size = options?.size;
  modalComponent.instance.title = options?.title;
  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();
}
  1. 修改ng-template,声明接收上下文变量:
<ng-template #createCardTemplate let-lists$="lists$">
  <!-- 其他表单内容保持不变 -->
  <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>

额外优化:适配Angular 17新API

Angular 17推荐使用ViewContainerRef替代ComponentFactoryResolver,可以重构ModalService:

import { DOCUMENT } from '@angular/common';
import {
  Inject,
  Injectable,
  Injector,
  TemplateRef,
  ViewContainerRef,
} 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 injector: Injector,
    @Inject(DOCUMENT) private document: Document,
    private viewContainerRef: ViewContainerRef
  ) {}

  open(content: TemplateRef<any>, options?: { size?: string; title?: string; data?: any }) {
    // 使用createComponent创建模态框组件
    const modalComponent = this.viewContainerRef.createComponent(ModalComponent);
    
    modalComponent.instance.size = options?.size;
    modalComponent.instance.title = options?.title;
    modalComponent.instance.data = options?.data;
    modalComponent.instance.closeEvent.subscribe(() => this.closeModal(modalComponent));
    modalComponent.instance.submitEvent.subscribe(() => this.submitModal(modalComponent));

    // 插入模板内容到模态框
    const contentViewRef = content.createEmbeddedView(null);
    modalComponent.instance.contentContainer.insert(contentViewRef);

    this.document.body.appendChild(modalComponent.location.nativeElement);
    this.modalNotifier = new Subject();
    return this.modalNotifier?.asObservable();
  }

  closeModal(component: any) {
    component.destroy();
    this.modalNotifier?.complete();
  }

  submitModal(component: any) {
    this.modalNotifier?.next('confirm');
    this.closeModal(component);
  }
}

同时修改ModalComponent添加内容容器:

import {
  Component,
  ElementRef,
  EventEmitter,
  Input,
  Output,
  ViewChild,
  ViewContainerRef,
} 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;
  @ViewChild('contentContainer', { read: ViewContainerRef }) contentContainer!: ViewContainerRef;

  @Output() closeEvent = new EventEmitter();
  @Output() submitEvent = new EventEmitter();

  close(): void {
    this.closeEvent.emit();
  }

  submit(): void {
    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" #contentContainer></div>
  <div class="modal-footer">
    <button (click)="submit()">Submit</button>
  </div>
</div>
<div class="modal-backdrop" (click)="close()"></div>

内容的提问来源于stack exchange,提问作者someone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:19:51