自定义*ngIf结构指令:点击面板显示其主体并隐藏其他面板主体
嘿,我来帮你搞定这个可复用面板的折叠/展开逻辑,同时满足你自定义结构指令的需求!下面给你两种方案,你可以根据场景灵活选择:
方案1:父组件统一管理激活状态(简单直接)
这种方式不需要自定义指令,通过父组件跟踪当前打开的面板索引,子面板根据这个索引判断是否显示内容,是最常用的实现方式。
子面板组件(panel.component.ts)
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-panel', template: ` <div class="panel panel-default" (click)="onPanelClicked()"> <div class="panel-heading"> <span class="glyphicon glyphicon-chevron-{{ opened ? 'down' : 'right' }} chevronIcon"></span> <h3 class="panel-title">{{ header }}</h3> </div> <div class="panel-body" *ngIf="opened"> <ng-content></ng-content> </div> </div> `, styles: [` .panel-heading { cursor: pointer; } .chevronIcon { margin-right: 8px; } `] }) export class PanelComponent { @Input() header!: string; @Input() index!: number; @Input() activeIndex!: number; @Output() panelClicked = new EventEmitter<number>(); // 通过计算属性判断当前面板是否激活 get opened(): boolean { return this.activeIndex === this.index; } onPanelClicked(): void { this.panelClicked.emit(this.index); } }
父组件(比如app.component.ts)
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-panel *ngFor="let panel of panels; let i = index" [header]="panel.header" [index]="i" [activeIndex]="currentActiveIndex" (panelClicked)="setActiveIndex(i)" > {{ panel.content }} </app-panel> ` }) export class AppComponent { currentActiveIndex = 0; // 默认打开第一个面板 panels = [ { header: '面板1', content: '这是面板1的唯一内容' }, { header: '面板2', content: '这是面板2的专属内容' }, { header: '面板3', content: '这是面板3的独有内容' } ]; setActiveIndex(index: number): void { // 点击已打开的面板可以切换折叠/展开 this.currentActiveIndex = this.currentActiveIndex === index ? -1 : index; } }
方案2:自定义结构指令(满足你的*ngIf自定义需求)
如果你确实需要通过自定义结构指令来控制显示逻辑,我们可以创建一个*panelBody指令,配合全局状态服务来管理所有面板的显示/隐藏。
第一步:创建状态服务(panel-state.service.ts)
用来全局跟踪当前激活的面板ID:
import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class PanelStateService { private _activePanelId = new BehaviorSubject<string | null>(null); activePanelId = this._activePanelId.asObservable(); setActivePanel(id: string): void { // 点击同一个面板可以切换折叠/展开状态 const currentId = this._activePanelId.value; this._activePanelId.next(currentId === id ? null : id); } }
第二步:自定义结构指令(panel-body.directive.ts)
这个指令会根据全局激活状态来决定是否渲染面板内容:
import { Directive, Input, TemplateRef, ViewContainerRef, OnInit, OnDestroy } from '@angular/core'; import { PanelStateService } from './panel-state.service'; import { Subscription } from 'rxjs'; @Directive({ selector: '[panelBody]' }) export class PanelBodyDirective implements OnInit, OnDestroy { @Input() set panelBody(panelId: string) { this._panelId = panelId; this.updateView(); } private _panelId!: string; private subscription!: Subscription; constructor( private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef, private panelStateService: PanelStateService ) {} ngOnInit(): void { // 监听激活状态变化,实时更新视图 this.subscription = this.panelStateService.activePanelId.subscribe(() => { this.updateView(); }); } ngOnDestroy(): void { this.subscription.unsubscribe(); } private updateView(): void { this.panelStateService.activePanelId.subscribe(activeId => { const isActive = activeId === this._panelId; if (isActive) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } }); } }
第三步:更新面板组件
import { Component, Input } from '@angular/core'; import { PanelStateService } from './panel-state.service'; @Component({ selector: 'app-panel', template: ` <div class="panel panel-default" (click)="togglePanel()"> <div class="panel-heading"> <span class="glyphicon glyphicon-chevron-{{ isActive ? 'down' : 'right' }} chevronIcon"></span> <h3 class="panel-title">{{ header }}</h3> </div> <!-- 使用自定义指令替代*ngIf --> <ng-template [panelBody]="panelId"> <div class="panel-body"> <ng-content></ng-content> </div> </ng-template> </div> `, styles: [` .panel-heading { cursor: pointer; } .chevronIcon { margin-right: 8px; } `] }) export class PanelComponent { @Input() header!: string; @Input() panelId!: string; // 每个面板必须有唯一ID constructor(private panelStateService: PanelStateService) {} get isActive(): boolean { let active = false; this.panelStateService.activePanelId.subscribe(id => { active = id === this.panelId; }); return active; } togglePanel(): void { this.panelStateService.setActivePanel(this.panelId); } }
第四步:父组件使用示例
import { Component } from '@angular/core'; @Component({ selector: 'app-root', template: ` <app-panel header="面板1" panelId="unique-panel-1" > 这是面板1的唯一内容 </app-panel> <app-panel header="面板2" panelId="unique-panel-2" > 这是面板2的专属内容 </app-panel> <app-panel header="面板3" panelId="unique-panel-3" > 这是面板3的独有内容 </app-panel> ` }) export class AppComponent {}
两种方案对比
- 方案1更轻量,不需要额外服务,适合简单的面板组场景;
- 方案2通过自定义指令实现了类似*ngIf的控制逻辑,更灵活,适合需要全局控制或者复杂交互的场景。
内容的提问来源于stack exchange,提问作者Yash Chitneni
相关产品推荐
相关产品推荐

