我有一个指令,需要将应用它的元素移动到文档正文:
@Directive({
selector: '[myDirective]'
})
export class MyDirective implements AfterViewInit {
constructor(private elem: ElementRef,
private renderer: Renderer2) {
}
ngAfterViewInit() {
// Move element to body
this.renderer.appendChild(document.body, this.elem.nativeElement);
}
}
现在我在一个ngIf
的元素中使用此指令,我根据某些条件切换:
<div myDirective *ngIf="visible">Test</div>
<button (click)="visible = !visible">Toggle</button>
当它运行时,div将按预期在按钮后显示,因为它已附加到正文。此时切换按钮仍然有效,我可以显示/隐藏div。
当我介绍路线时,问题出现了:
<a routerLink="/route/1">Link</a>
<div myDirective *ngIf="visible">Test</div>
<button (click)="visible = !visible">Toggle</button>
现在,如果我显示div,并导航到另一条路线,div应该被销毁,但它仍然可见!如果我不将div移动到身体,那么它的行为与预期一致。
首先,我尝试删除ngOnDestroy
中的元素,但这不适用于动画,因为它会删除元素而不用播放它:离开动画。
我目前的解决方法是将路由器注入指令并订阅第一个将div恢复到其原始容器的事件:
this.routerSubscription = router.events.first().subscribe(() => {
this.renderer.appendChild(this.parentNode, this.elem.nativeElement);
});
虽然这有效,但它并不好,因为现在该指令在路由器中具有依赖性,而它应该对它一无所知。
如何改进这个黑客以确保元素被正确销毁?
DEMO: https://stackblitz.com/edit/angular-qximjw?file=app%2Fapp.component.ts
要重现此问题:
答案 0 :(得分:2)
您可以添加代码以删除ngOnDestroy
上的元素:
ngOnDestroy() {
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
document.body.removeChild(this.elem.nativeElement);
}
但请注意,当您导航到相同的路线时,angular不会破坏它(这意味着ngOnDestroy不会触发)。您可以尝试添加其他路线并导航到该路线进行确认。
请参阅demo。
答案 1 :(得分:2)
我觉得它按预期工作。如果您使用浏览器检查模式并单击RouterLink按钮,您将注意到HomeComponent正在重新呈现,因为您在两个路由器选项上提供相同的组件。
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'route/:id',
component: HomeComponent
}
];
因此,如果您的指令被追加并且未从DOM中删除,则当HomeComponent再次呈现时,将创建该组件的新实例以及您的指令,应用程序将追加新的<div>
单击(新)按钮时,在您的身体上。为了更清楚,我建议你用下面的代码替换HomeComponent代码,并查看你的JavaScript控制台。
@Component({
selector: 'home-component',
template: `
<a routerLink="/route/1">Link</a>
<div myDirective *ngIf="visible" @transition>Test</div>
<button (click)="visible = !visible">Toggle</button>
`,
animations: [
trigger('transition', [
transition(':enter', [
style({ opacity: 0 }),
animate('.35s ease', style({ opacity: '*' }))
]),
transition(':leave', [
style({ opacity: '*' }),
animate('.35s ease', style({ opacity: 0 }))
])
])
]
})
export class HomeComponent {
visible = false;
constructor() {
console.log('New instance')
}
}
我能想到的解决方法是将Toggle按钮放在Component中的 router-outlet 边界之外。
@Component({
selector: 'app-toggle-button',
template: `
<div myDirective *ngIf="visible" @transition>Test</div>
<button (click)="visible = !visible">Toggle</button>
`,
animations: [
trigger('transition', [
transition(':enter', [
style({ opacity: 0 }),
animate('.35s ease', style({ opacity: '*' }))
]),
transition(':leave', [
style({ opacity: '*' }),
animate('.35s ease', style({ opacity: 0 }))
])
])
]
})
export class ToggleComponent {
visible = false;
}
之后,您可以将其添加到app.component.html文件中:
<app-toggle-button></app-toggle-button>
<router-outlet></router-outlet>
在这种情况下,即使您浏览多个路线,您也将始终拥有单个 ToggleComponent实例。
如果这有助于你,请告诉我,欢呼。