操作DOM

时间:2018-02-01 01:38:22

标签: angular

我有一个指令,需要将应用它的元素移动到文档正文:

@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

要重现此问题:

  1. 确保网址位于根目录,而不是路径
  2. 点击切换按钮
  3. 点击链接导航至路线
  4. 观看div not not消失

2 个答案:

答案 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实例。

如果这有助于你,请告诉我,欢呼。