AEM 6.5+SPA+Angular 9:子组件如何订阅父容器组件?
在AEM 6.5 Angular 9 SPA中子组件订阅父组件变更的实现方案
一、获取最近父组件引用并订阅变更
1. Angular依赖注入(直接父组件场景)
如果子组件是直接嵌套在目标父组件内,使用@Optional()+@SkipSelf()注入最近的父组件实例是最高效的方式:
子组件代码:
import { Component, OnInit, Optional, SkipSelf, OnDestroy } from '@angular/core'; import { ParentComponent } from './parent.component'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'] }) export class ChildComponent implements OnInit, OnDestroy { private parentSub: Subscription; parentBgColor: string; parentId: string; textColor = '#000000'; constructor( @Optional() @SkipSelf() private parentComp: ParentComponent ) { } ngOnInit(): void { if (!this.parentComp) return; // 订阅父组件背景色变更 this.parentSub = this.parentComp.bgColorChange$.subscribe(color => { this.parentBgColor = color; this.adjustTextColor(); }); // 获取父组件ID this.parentId = this.parentComp.componentId; } ngOnDestroy(): void { this.parentSub?.unsubscribe(); } // 根据背景色计算适配文本颜色 private adjustTextColor(): void { const rgb = this.hexToRgb(this.parentBgColor); const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; this.textColor = brightness > 125 ? '#000000' : '#ffffff'; } private hexToRgb(hex: string): {r: number, g: number, b: number} { const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return match ? { r: parseInt(match[1], 16), g: parseInt(match[2], 16), b: parseInt(match[3], 16) } : { r: 255, g: 255, b: 255 }; } }
父组件代码(需暴露可订阅的属性流):
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Component({ selector: 'app-parent', templateUrl: './parent.component.html', styleUrls: ['./parent.component.css'] }) export class ParentComponent implements OnChanges { @Input() bgColor = '#ffffff'; @Input() componentId: string; private bgColor$ = new BehaviorSubject<string>(this.bgColor); // 对外暴露可订阅的变更流 bgColorChange$ = this.bgColor$.asObservable(); ngOnChanges(changes: SimpleChanges): void { if (changes.bgColor) { this.bgColor$.next(changes.bgColor.currentValue); } } }
2. AEM ModelManager遍历(跨容器场景)
如果子组件和父组件之间隔了AEM系统容器组件,使用ModelManager遍历组件模型的父层级,找到最近的目标父组件:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { ModelManager, Model } from '@adobe/aem-spa-page-model-manager'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'] }) export class ChildComponent implements OnInit, OnDestroy { private modelSub: Subscription; parentBgColor: string; parentId: string; textColor = '#000000'; ngOnInit(): void { const componentPath = this.getComponentPath(); if (!componentPath) return; // 订阅当前组件模型变更 this.modelSub = ModelManager.getInstance().getData(componentPath).subscribe(model => { this.findClosestParent(model); }); } // 获取当前组件的AEM路径(从DOM属性提取) private getComponentPath(): string { const elem = document.querySelector('app-child'); return elem?.getAttribute('data-cq-path') || ''; } // 向上遍历父模型,找到目标父组件 private findClosestParent(currentModel: Model): void { let parentModel = currentModel[':parent']; while (parentModel) { // 通过资源类型判断是否是目标父组件 if (parentModel['sling:resourceType'] === 'your-project/components/parent-container') { this.parentBgColor = parentModel['bgColor']; this.parentId = parentModel[':id']; this.adjustTextColor(); // 订阅父模型变更 this.subscribeToParent(parentModel[':path']); break; } parentModel = parentModel[':parent']; } } private subscribeToParent(parentPath: string): void { this.modelSub.add( ModelManager.getInstance().getData(parentPath).subscribe(updatedParent => { this.parentBgColor = updatedParent['bgColor']; this.adjustTextColor(); }) ); } ngOnDestroy(): void { this.modelSub?.unsubscribe(); } private adjustTextColor(): void { const rgb = this.hexToRgb(this.parentBgColor); const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; this.textColor = brightness > 125 ? '#000000' : '#ffffff'; } private hexToRgb(hex: string): {r: number, g: number, b: number} { const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return match ? { r: parseInt(match[1], 16), g: parseInt(match[2], 16), b: parseInt(match[3], 16) } : { r: 255, g: 255, b: 255 }; } }
二、组件拖拽时的监听方案
Angular本身没有直接监听AEM组件拖拽的事件,但可以通过两种方式触发组件逻辑更新:
- ModelManager全局事件:组件拖拽完成后,AEM会更新页面模型,
ModelManager会发出modelChanged事件:ModelManager.getInstance().getEvents().subscribe(event => { if (event.type === 'modelChanged') { // 拖拽完成后重新获取父组件信息 const componentPath = this.getComponentPath(); if (componentPath) { ModelManager.getInstance().getData(componentPath).subscribe(model => { this.findClosestParent(model); }); } } }); - DOM MutationObserver:监听DOM节点插入,判断组件是否被放入目标父容器:
ngAfterViewInit(): void { const observer = new MutationObserver(mutations => { mutations.forEach(mut => { if (mut.addedNodes.length && this.elementRef.nativeElement.parentElement) { const parentIsTarget = this.elementRef.nativeElement.parentElement .matches('[data-cq-path*="parent-container"]'); if (parentIsTarget) { this.findClosestParent(this.getCurrentModel()); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); // 记得在ngOnDestroy中调用observer.disconnect() }
三、场景适配
- 背景色与文本颜色适配:子组件获取父组件背景色后,通过颜色亮度计算逻辑动态切换文本颜色,确保在任何背景色下文本都可读。
- 父组件ID获取:依赖注入方案直接从父组件实例取
componentId;ModelManager方案从父模型的:id属性提取,用于构建后端数据结构。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

