【Angular】关于ViewChild和ContentChild的区别

一般来说,我们编写组件或者抽取组件的时候,总是会考虑到组件的复用性以及扩展性。当我们想让组件变成嵌套关系的时候,我们就需要考虑这个子组件是写死在父组件下面?还是抽取出来作为一个外部组件然后插入到父组件中?其实这个主要是看个人选择了。在angular中我们可以使用这两种方式封装组件。而ViewChildcontentChild这两个装饰器官网上面的定义是这样的:

ViewChild:属性装饰器,用于配置一个视图查询。 变更检测器会在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。 如果视图的 DOM 发生了变化,出现了匹配该选择器的新的子节点,该属性就会被更新。

ContentChild:用于配置内容查询的参数装饰器。用于从内容 DOM 获取与此选择器匹配的第一个元素或指令。如果内容 DOM 发生了更改,并且有一个新的子项与选择器匹配,则该属性将被更新。

其实,这两个装饰器其实就是为了获取子组件的引用而已,相当于vue和react中的ref。只不过angular这个可以通过ViewChildren或者ContentChildren来获取多个引用。这个所谓的引用或者angular的视图查询或者内容查询获取到的值其实是这个子组件生成出来的实例。我们可以像使用vue或者react似的使用这两个查询来直接修改子组件的属性或者调用子组件的方法。

接下来我会举一个例子来说明这两个(或者说四个)装饰器的用法。

  • 现在我们有一个业务场景:假设有一个新闻列表:

    <ul>
      <li>
          <span>1</span>
          <span>【原神】钟离将在1.5版本复刻,大家准备好原石了吗?</span>
          <span>2021/4/16</span>
      </li>
    </ul>
    

    li中有三个span,分别表示新闻id,新闻标题,新闻时间。我们现在的需求是,将这个组件抽离出来。然后将li的部分封装成一个子组件。如下:

  • 首先,我们封装这个子组件<news-item>

    import {Component, Input} from '@angular/core';
    import {New} from './models';
    
    @Component({
      selector:'news-item',
      template:`
        <li [style]="{display:'flex',justifyContent:'center',alignItems:'center'}">
          <p>id:{{Item.id}}</p>
          <p>{{Item.title}}</p>
          <p>{{Item.time}}</p>
        </li>
      `,
      ]
    })
    export class NewsItem {
      @Input() Item:New;
      constructor() {
      }
    }
    

    然后定义一下New

    export interface New {
      id:number;
      title:string,
      time:string;
    }
    
  • 然后,我们将ul整体封装成一个父组件。然后使用两种不同的方式来封装。

    • 第一种方法:将子组件封装进父组件中;

    • 第二种方法:子组件独立在外,使用的时候才将其放入父组件的标签之内;

  • 先用第一种方法:

    NewsListByViewChild.ts代码如下:

    import {AfterViewInit, Component, Input, QueryList, ViewChild, ViewChildren} from '@angular/core';
    import {New} from './models';
    import {NewsItem} from './NewsItem';
    
    @Component({
      selector:'news-list-view',
      template:`
        <ul>
          <news-item *ngFor="let n of NewsList" [Item]="n"></news-item>
        </ul>
      `
    })
    export class NewsListByViewChild implements AfterViewInit{
      public NewsList:New[] = [
        {
          id:1,
          title:'view-标题1',
          time:'2021/4/17'
        },
        {
          id:2,
          title:'view-标题2',
          time:'2021/4/19'
        },
        {
          id:3,
          title:'view-标题3',
          time:'2021/5/6'
        }
      ]
      //使用viewchild获取单个的符合条件的组件或者指令
      @ViewChild(NewsItem) newItem:NewsItem;
      //使用viewChildren获取多个的符合条件的组件或者指令,QueryList是一个类似于数组的可迭代对象,我们可以使用get([索引值])的方法来取得每一个元素
      @ViewChildren(NewsItem) allNewsItem:QueryList<NewsItem>
      constructor() { }
      //ViewChild设置的查询,只会在AfterViewInit之后的生命周期访问到。
      ngAfterViewInit() {
          console.log('newItem:',this.newItem);
          console.log('allNewsItem:',this.allNewsItem);
          //使用get()来获取单独的元素
          console.log('allNewsItem01:',this.allNewsItem.get(0));
          console.log('allNewsItem02:',this.allNewsItem.get(1));
          console.log('allNewsItem03:',this.allNewsItem.get(2));
          //然后我们可以直接调用子组件的属性和方法。
          setTimeout(()=>{
              this.newItem.Item.id = 99;
              this.allNewsItem.get(1).title = '【原神】新角色优菈闪亮登场!'
          })
      }
    }
    
    

    将其导入使用:

    app.component.ts中调用

    @Component({
      selector: 'app-root',
      template: `
      <news-list-view></news-list-view>
      `,
    })
    export class AppComponent {}
    
  • 第二种方法:

    NewsListByContentChild.ts代码如下:

    import {AfterContentInit, Component, ContentChild, ContentChildren, QueryList} from '@angular/core';
    import {NewsItem} from './NewsItem';
    
    @Component({
      selector:'news-list-content',
      template:`
        <ul>
          <ng-content></ng-content>
        </ul>
      `
    })
    export class NewsListByContentChild implements AfterContentInit{
      @ContentChild(NewsItem) newsItem:NewsItem;
      @ContentChildren(NewsItem) allNewsItem:QueryList<NewsItem>
      constructor() {}
      //ContentChild设置的查询,只会在调用AfterContentInit之后才能访问到
      ngAfterContentInit() {
        console.log("NewsItem:",this.newsItem);
        console.log("allNewsItem:",this.allNewsItem);
      }
    }
    
    

    然后将其导入app.component.ts中使用

    //导入语句省略
    @Component({
      selector: 'app-root',
      template: `
      <news-list-content>
          <news-item *ngFor="let n of NewsList" [Item]="n"></news-item>
        </news-list-content>
      `,
    })
    export class AppComponent {
        public NewsList:New[] = [
        {
          id:4,
          title:'content-标题1',
          time:'2021/3/21'
        },
        {
          id:5,
          title:'content-标题2',
          time:'2021/4/23'
        },
        {
          id:6,
          title:'content-标题3',
          time:'2021/5/30'
        }
      ]
    }
    
总结:

ViewChild和ContentChild一开始了解不了就是因为一开始没有找到在vue或者react中对应什么api。其实就是相当于ref。只不过是写法有不同而已。当我们熟悉了之后,使用这两个(或者四个)其实就得心应手了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、安装最新版本的 nodejs 注意:请先在终端/控制台窗口中运行命令 node -v 和 npm -v, 来验...
    liuguangsen阅读 2,285评论 0 1
  • Angular介绍 Angular安装、创建项目、目录结构、组件、服务 创建组件、绑定数据、绑定属性、数据循环、条...
    地瓜的笔记阅读 692评论 0 2
  • Angular7入门总结篇 6 2019.01.08 19:34:05 字数 4854阅读 46093 发表于 h...
    痞_4fc8阅读 1,603评论 0 5
  • 有时不得不面对一些需要在组件中直接操作DOM的情况,如我们的组件中存在大量的CheckBox,我们想获取到被选中的...
    阿踏阅读 1,262评论 0 1
  • 一.课程简介 (注意:这里的AngularJS指的是2.0以下的版本) AngularJS的优点: 模板功能强大丰...
    壹点微尘阅读 1,014评论 0 0

友情链接更多精彩内容