如何在Angular中正确地操作DOM ?

通常,当我们在Angular中使用JavaScript技术时,我们几乎会忘记框架的特性。让我们去使用它们。 web开发中一个有趣的主题是DOM操作。在Angular中,有很多方法可以操作DOM。 让我们使用它们而不是直接的JavaScript方法。

通常来说,在dom操作时会有两种概念:

  1. 调整DOM元素
  2. 调整DOM结构

调整DOM元素

我们熟悉很多修改DOM元素的JavaScript方法。例如:

  • classList.add()
  • setAttribute()
  • Style.setPropperty()

这是js中原生的一些DOM方法

在Angular中,有多种方法可以修改DOM元素。我们将讨论几乎每一种方法,并从中选择最好的。

方法

方法1

概念:

  1. 模板引用变量
  2. ElementRef
  3. @Viewchild/@Viewchildren
  4. AfterViewInit

定义:

  1. 模板引用:对特定DOM元素的引用。
  2. ElementRef: ElementRef是一个类,它包含所有本地的DOM元素
  3. @viewchild/@viewchildren: 从DOM中选择子元素或所有子元素
  4. AfterViewInit: 这是一个生命周期钩子,在Angular组件初始化它的视图之后被调用。
@Component({
  selector: 'app-root',
  template:`<span #el>I am manoj.</span>
  <span>I am a web developer.</span>`,
  styles:[`[highlight]{background: green; color: white}`]
})
export class AppComponent implements AfterViewInit{
  @ViewChild('el') span:ElementRef;

  ngAfterViewInit(){
    this.span.nativeElement.setAttribute('highlight', '');
  } 
}

步骤:

Step1:在这个例子中,我使用模板引用和@viewchild查询来获取一个HTML元素。

<span #el>I am manoj.</span>
<span>I am a web developer.</span>
@ViewChild(‘el’) span: ElementRef;

Step2: DOM元素的setAttribute用于添加属性。

this.span.nativeElement.setAttribute(‘highlight’, ‘’);

页面上展示没有任何问题,它运行得很好。但是这种方法有一个问题。这里,我们混合了呈现和表示逻辑。

通常,组件(components)代表逻辑,例如定义数组、对象和迭代等等。呈现逻辑实际上是修改DOM元素。我们应该在一个单独的指令中维护渲染逻辑。我们可以通过数据绑定机制来进行组件和指令的通信。

现在让我们考虑另一种解决这个问题的方法。

方法2

让我们创建一个指令,并将所有的呈现逻辑放在该指令中。

概念:

  • ElementRef
  • @Input()
  • ngOnInit()

定义:

  • ElementRef:它帮助去访问DOM元素
  • @Input() : 数据绑定来通信组件和指令。
  • ngOnInit() : 初始生命周期钩子,它在Angular创建组件后被调用。

例如:

指令:

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective implements OnInit{
  
  @Input() appHighlight;
  
  constructor( private element: ElementRef) { }
 
  ngOnInit(){
    this.element.nativeElement.setAttribute(this.appHighlight, '');
  }
}

组件:

@Component({
  selector: 'app-root',
  template:`<span [appHighlight]="'highlight'">I am manoj.<span>
  <span>I am a web developer.</span>`,
  styles:[`[highlight]{background: green; color: white;}`]
})
export class AppComponent{}

步骤:

**Step1:**将ElementRef注入到指令文件的构造函数中。

constructor( private element: ElementRef) { }

Step2:

添加@input装饰器到指令中

@Input() appHighlight;

Step3:

使用setAttribute()原生元素方法在ngOnInit()生命周期钩子中添加一个属性。

ngOnInit(){this.element.nativeElement.setAttribute(this.appHighlight, ‘’)}

Step4:

将指令应用到组件模板中的span元素:

<span [appHighlight]=”’highlight’”>I am manoj.</span>

输出和前面一样,但是这里渲染逻辑从组件移动到指令。它有助于在任何地方重用相应的组件和指令。

但在这里,我们使用ElementRef类直接访问DOM元素。允许直接访问DOM会使我们的应用程序更容易受到XSS攻击。大多数人在所有地方都使用ElementRef。现在的问题是,我们应该使用什么来安全地访问DOM元素?对,我们有个解决办法。让我们看看另一种方法。

方法3

概念:

1渲染器(Renderer)-使直接DOM访问安全,并且它是一个独立的平台。它修改DOM元素而不直接接触DOM。呈现程序是由一些方法组成的服务。它有助于操作DOM。

下面我列出了一些渲染器方法

1.addClass()

2.removeClass()

3.setStyle()

4.removeStyle()

5.setProperty()

例子:

constructor( private element: ElementRef, 
private renderer: Renderer2) { }
 
ngOnInit(){
    this.renderer.setAttribute(this.element.nativeElement,this.appHighlight, '')
}

步骤:

Step1:将渲染器注入到指令文件的构造函数中。

constructor( private renderer: Renderer2) { }

Step2: 使用renderer setAttribute()方法在ngOnInit()生命周期钩子中添加一个属性。

ngOnInit(){ this.renderer.setAttribute(this.element.nativeElement , this.appHighlight, ‘’)}

这里的输出是一样的。但是我们正确且更安全地修改了DOM。像ngClass、ngStyle这样的属性指令会基于渲染器修改DOM元素。因此,接下来,我们将使用Renderer来修改DOM。这也是一种更恰当的方式。

调整DOM结构

1creatElement()

2remove()

3appendChild()

4removeChild()

下面是一些修改DOM结构的JavaScript方法的例子。这些方法我们已经很熟悉了。现在我们来看看如何在Angular中修改DOM结构。

从DOM中移除一个子组件

方法1

概念:

1Template Reference

2ElementRef

3@ViewChildren()

4AfterViewChecked

5Renderer

6QueryList

定义:

  1. Template Reference: 特定DOM元素的引用。
  2. ElementRef: 帮助去访问DOM元素。
  3. @ViewChildren(): 以QueryList的形式从视图dom返回指定的元素或指令
  4. AfterViewChecked: 它在默认更改检测程序完成一个更改检查周期后调用。
  5. Renderer: 使直接DOM访问更安全
  6. QueryList: 返回类型。它只是一个项目列表。Angular在添加或删除列表项时更新QueryList。它只在ngAfterViewInit()生命周期钩子之前初始化。
Copy
@Component({
  selector: 'app-parent',
  template: `<p>parent works!</p>
  <app-child #child></app-child>
  <button (click)='removeChild()'>remove child</button>`,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent implements AfterViewChecked{

  @ViewChildren('child', {read: ElementRef}) childComp:QueryList<ElementRef>

  constructor(private renderer: Renderer2, private host: ElementRef) {
  }

  ngAfterViewChecked() {
    console.log(this.childComp.length)
  }

  removeChild(){
    this.renderer.removeChild(this.host.nativeElement,                                                   this.childComp.first.nativeElement);
  }
}

步骤:

  1. 使用template reference和ViewChildren()来获取HTML元素。
  2. 将Renderer和ElementRef注入构造函数
  3. 使用removecchild()方法来删除子组件
  4. 为了获得宿主元素,我们需要使用ElementRef。

显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MSdUB27A-1632643270004)(/Users/jiangyumin/Documents/img/domoutput1.png)]

点击移除按钮后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qk0Bnqkb-1632643270006)(/Users/jiangyumin/Documents/img/click.png)]

由此可见,我们成功地删除了子组件。但是,我想再次向您展示带有控制台日志的输出屏幕。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhcAbQEt-1632643270009)(/Users/jiangyumin/Documents/img/childcount.png)]

问题:

我们已经删除了子组件,但子组件计数仍然是1。为什么?如何解决?

是的,显然我们会想到这个问题。别担心,我们有办法。

DOM和View的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ATEDYnpE-1632643270010)(/Users/jiangyumin/Documents/img/daigram.png)]

Angular有一个视图概念。看看图表;它将清楚地说明视图和DOM的关系。视图只是DOM的引用。当我们运行Angular应用时,它会创建多个视图。

对于每个组件的创建,视图也是由Angular创建的。我们看到了外部的组件层次结构。但引擎盖下的视图层次结构也是基于组件层次结构创建的。

如果我们在DOM中做了任何改变,比如拖动、添加或删除DOM元素,视图应该立即更新。否则,这将是一个问题。

变更检测基于视图的层次结构运行。

使用方法1,我们从DOM中删除了子组件。但是视图层次结构保持不变,并且视图没有更新。所以它展示了一个child。在这里,这是一个小场景,没有问题,但如果我们删除了10个组件,而视图没有更新,Angular仍然会对所有被删除的组件运行变更检测。这将严重影响我们的应用程序。

View Container (viewContainerRef):

视图容器使DOM结构更改安全。

方法:

  1. insert()
  2. Move()
  3. Remove()
  4. createEmbeddedView()

初始化视图容器:

我们可以将任何DOM元素作为视图容器。但通常情况下,所有人都将作为视图容器。ng-container只是一个HTML标签,但它是Angular特有的。

视图有两种类型:

  1. Embedded Views-始终是视图容器的一部分。
  2. Host Views-始终是视图容器的一部分,但也是独立的。

让我们看看如何使DOM元素成为视图。

步骤:

  1. 在模板文件中添加ng-container标签。

    <ng-container #viewcontainer></ng-container>
    
  2. 添加@viewchild以选择元素。

@ViewChild(‘viewcontainer’, {‘read’: ViewContainerRef}) viewcontainer;

在这里,ViewContainerRef是一个非常重要的部分,它使DOM节点成为视图容器。

  1. 创建视图并将视图添加到视图容器中。
Viewcontainer.CreateEmbeddedView(TemplateRef);

创建一个模板

它非常类似于创建视图容器。让我们看看如何创建它们。

步骤:

  1. 在模板文件中添加ng-template标签。
<ng-template #t></ng-template>
  1. 使用它创建一个模板。
@ViewChild(‘t’, {‘read’: TemplateRef}) template: Templateref;

例:

模板文件:

<p>parent works!</p>
<ng-container #viewcontainer></ng-container>
<ng-template>
    <app-child #child></app-child>
</ng-template>
<button (click)='removeChild()'>remove child</button>

类文件:

  @ViewChildren('child', {read: ElementRef}) 
  childComp:QueryList<ElementRef>
  @ViewChild('viewcontainer', {'read': ViewContainerRef}) viewcontainer;
  @ViewChild(TemplateRef) template: TemplateRef<null>;

  constructor(private renderer: Renderer2, private host: ElementRef) {}

  ngAfterViewChecked() {
    console.log("Child components count", this.childComp.length)
  }

  ngAfterViewInit(){
    this.viewcontainer.createEmbeddedView(this.template);
  }

  removeChild(){
    this.viewcontainer.remove();
  }

解决方案:

这个例子只是我们问题的一个解决方案。现在,如果我们移除DOM元素,Angular会直接更新视图。

显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agj2mTVt-1632643270012)(/Users/jiangyumin/Documents/img/result.png)]

最后,我们做到了。我希望我们大家都明白view:

像*ngIf, *ngFor, *ngSwitch这样的结构指令是基于视图容器来修改DOM结构的。

接下来,我们将使用viewContainerRef和templateRef来改变DOM结构。我们可以将呈现逻辑分离到单独的指令中,就像在修改DOM元素一节中所做的那样。这将非常有用。我们可以使用像*ngFor, *ngIf这样的指令.

注意

如果DOM元素是由Angular创建的,默认情况下视图也会被创建。如果我们使用Jquery或JavaScript直接更改DOM结构,则不会更新视图。Angular并不知道DOM元素已经创建。所以更改检测对那个DOM元素不起作用。

总结:

  1. 调整DOM元素–使用渲染器服务
  2. 调整DOM结构–使用ViewContainerRef和TemplateRef类
  3. 将呈现逻辑分离成指令。它有助于复用代码。
  4. 避免使用JavaScript或Jquery直接操作DOM。尝试使用框架的可用特性。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值