Angular 基础 2 - 组件相关

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Angular 基础 2 - 组件相关相关的知识,希望对你有一定的参考价值。

Angular 基础 2 - 组件相关

涉及到:

  • 跨组件交流(传属性与事件)
  • 生命周期
  • 跨组件交流的其他方法(local reference 与 content)

跨组件交流的方式会通过案例的方法进行实现,毕竟这样看起来比较有视觉上的效果,不太干。再基础一点的知识看这里:Angular 基础

实现大致如下:

项目结构如下:

|- app
|   |- cockpit
|   |- server-element

其中 app 包含 servers 的数组,cockpit 显示添加 server 部分功能,而 server-element 负责渲染数组的部分。

跨组件传递自定义属性

即 custom property,这里的即 custom property 指的是 server,即通过 app 将 server 的属性传递到 server-element 进行渲染。

  • app 部分代码

    import  Component  from '@angular/core';
    import  Server  from './server';
    
    export interface Server 
      type: string;
      name: string;
      content: string;
    
    
    @Component(
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    )
    export class AppComponent 
      serverElements: Server[] = [
         type: 'server', name: 'Test Server', content: 'a test' ,
      ];
    
    
    <div class="container">
      <div class="row">
        <div class="col-xs-12">
          <app-server-element
            *ngFor="let serverElement of serverElements"
            [element]="serverElement"
          >
          </app-server-element>
        </div>
      </div>
    </div>
    

    这里直接使用 property binding 将数组中的值传入到 app-server-element 中去。

  • server-element 部分代码为:

    import  Component, OnInit  from '@angular/core';
    
    @Component(
      selector: 'app-server-element',
      templateUrl: './server-element.component.html',
      styleUrls: ['./server-element.component.css'],
    )
    export class ServerElementComponent implements OnInit 
      element: Server;
    
      constructor() 
    
      ngOnInit(): void 
    
    
    <div class="panel panel-default">
      <div class="panel-heading"> element.name </div>
      <div class="panel-body">
        <p>
          <strong *ngIf="element.type === 'server'" style="color: red"
            > element.content </strong
          >
          <em *ngIf="element.type === 'blueprint'"> element.content </em>
        </p>
      </div>
    </div>
    

Angular 基础的同一个组件的案例中,这样的代码是可以跑通的,不过这里会报错:

因为 Angular 默认数据绑定的作用域在同一个组件中,父组件中只使用 2 way binding,自组件也无法获得相应的数据,这时候需要使用 Angular 提供的装饰器去进行实现:

export class ServerElementComponent implements OnInit 
  // @Input('alias')
  @Input() element: Server;

这时候,自组件就可以获取父组件传来的值,并正常渲染:

装饰器中可以传入字符串,当父组件与自组件的变量名完全一致时,这个参数要不要都无所谓,但是如果父组件传到自组件的变量名为 somethingElse,自组件想要将其重命名为 element,就可以使用 @Input('somethingElse') 进行重命名。

跨组件传递自定义事件

servers 的数组存在于 app 中,因此作为自组件的 cockpit 必须要触发父组件中的函数去对原有的数据进行修改,其本身也负责从 DOM 节点中取值。

  • app 部分代码

    主要 controller 中新增两个 onChangeHandler(也可以只使用一个,这个完全看个人偏好),模板中新增对 cockpit 的渲染

    export class AppComponent 
      // 其余部分保持一致
      onServerAdded(serverData:  serverName: string; serverContent: string ) 
        this.serverElements.push(
          type: 'server',
          name: serverData.serverName,
          content: serverData.serverContent,
        );
      
    
      onBlueprintAdded(blueprintData: 
        blueprintName: string;
        blueprintContent: string;
      ) 
        this.serverElements.push(
          type: 'blueprint',
          name: blueprintData.blueprintName,
          content: blueprintData.blueprintContent,
        );
      
    
      // 其他的写法也可以使用:
      // HTML 和 组件中的代码也需要对应更新
      /**
       *
       * onServerAdded(serverData: 
       *   type: 'server' | 'blueprint';
       *   name: string;
       *   content: string;
       * ) 
       *   this.serverElements.push(serverData);
       * 
       */
    
    
    <div class="container">
      <!-- 同样为新增部分,其余不变,同样将函数传到子组件中 -->
      <app-cockpit
        (serverCreated)="onServerAdded($event)"
        (blueprintCreated)="onBlueprintAdded($event)"
      ></app-cockpit>
      <hr />
    </div>
    
  • cockpit 部分代码

    同样因为作用域的关系,如果单纯的调用从 app 中传来的函数,就会报错,因此 cockpit 中也需要用装饰器对函数进行绑定。

    不过因为事件需要传输数据,这里用的装饰器不是 Input,而是 Output。Output 的 alias 使用方法于 Input 相同,不过绑定事件的方式与绑定变量还是不太一样的,后面需要跟一个 EventEmitter,并且规定 EventEmitter 中传输的数据类型。

    import 
      Component,
      ElementRef,
      EventEmitter,
      OnInit,
      Output,
     from '@angular/core';
    
    @Component(
      selector: 'app-cockpit',
      templateUrl: './cockpit.component.html',
      styleUrls: ['./cockpit.component.css'],
    )
    export class CockpitComponent implements OnInit 
      // @Output('aliasName')
      @Output() serverCreated = new EventEmitter<
        serverName: string;
        serverContent: string;
      >();
      @Output() blueprintCreated = new EventEmitter<
        blueprintName: string;
        blueprintContent: string;
      >();
      newServerName = '';
      newServerContent = '';
    
      constructor() 
    
      ngOnInit(): void 
    
      onAddServer() 
        this.serverCreated.emit(
          serverName: this.newServerName,
          serverContent: this.newServerContent,
        );
      
    
      onAddBlueprint() 
        this.blueprintCreated.emit(
          blueprintName: this.newServerName,
          blueprintContent: this.newServerContent,
        );
      
    
    

    这里数据进行 2 way binding

    <div class="row">
      <div class="col-xs-12">
        <p>Add new Servers or blueprints!</p>
        <label>Server Name</label>
        <input type="text" class="form-control" [(ngModel)]="newServerName" />
        <label>Server Content</label>
        <input type="text" class="form-control" [(ngModel)]="newServerContent" />
        <br />
        <button
          class="btn btn-primary"
          (click)="onAddServer()"
          style="margin-right: 15px"
        >
          Add Server
        </button>
        <button class="btn btn-primary" (click)="onAddBlueprint()">
          Add Server Blueprint
        </button>
      </div>
    </div>
    

    至此,主要的功能就实现了:

View Encapsulation

默认情况下,Angular 的 CSS 只会应用于当前组件,如在 app.css 中加入 p color: blue; 这一段代码后,效果如下:

可以看到 cockpit 中的 p 标签并没有受到 CSS 的影响,而 app 模板中的 CSS 则是呈现蓝色。这是因为 Angular 有一个叫做 View Encapsulation 的实现,换言之,每个模板引入的 CSS 文件,只会通过 HTML attribute 的绑定,对当前组件内的元素奇效:

关闭 View Encapsulation 的方法也很简单,在想要关闭 View Encapsulation 的子组件中更新如下代码:

@Component(
  selector: 'app-server-element',
  templateUrl: './server-element.component.html',
  styleUrls: ['./server-element.component.css'],
  encapsulation: ViewEncapsulation.None,
)
p 
  color: blue;

这样这块 css 的代码会被应用到所有关闭 View Encapsulation 的代码。

ViewEncapsulation 有三个选项:None,Emulated(即使用 attributes 绑定样式),
ShadowDown(Using shadow DOM)。

Shadow DOM 不在本篇讨论范围之内。

生命周期

Angular 现在有 8 个 hooks,不过官网上好像没有像 React 一样有一个比较直观的流程图,不过提供了一个案例,其调用结果如下:

其说明如下:

HOOK METHODPURPOSE
ngOnChanges()设置或重设 input properties 时被调用(可多次)
ngOnInit()组件初始化时被调用(只有一次)
ngDoCheck()每次监测到变动时就被调用(可多次)
ngAfterContentInit()ngDoCheck() 第一次调用后被调用,将外部内容投影到当前组件中 (只有一次)
这个被调用主要也是为了联系上下文(父组件),毕竟父组件可以通过 ng-content 向子组件投射额外的内容
ngAfterContentChecked()检查 content 被投影后的内容,在 ngAfterContentInit()ngDoCheck() 调用后被调用
ngAfterViewInit()ngAfterContentChecked() 第一次调用后被调用,用以检查当前 View
ngAfterViewChecked()ngAfterViewInit()ngAfterContentChecked() 被调用后调用,功能就是用来检测 View 中所有的变化都渲染成功
ngOnDestroy()组件被毁灭时调用

其他

跨组件传递自定义属性跨组件传递自定义事件 应该是可以满足大部分的需求了,所以这个以下的部分可以选看。

local reference

与其使用 2 way binding,local reference 可以直接获得当前 DOM 元素,修改方法如下:

  • 模板部分

    <!-- 其余维持不变,只修改获取 server name 和 提交部分 -->
    <!-- <input type="text" class="form-control" [(ngModel)]="newServerName" /> -->
    <input type="text" class="form-control" #serverNameInput />
    
    <button
      class="btn btn-primary"
      (click)="onAddServer(serverNameInput)"
      style="margin-right: 15px"
    >
      Add Server
    </button>
    <button class="btn btn-primary" (click)="onAddBlueprint(serverNameInput)">
      Add Server Blueprint
    </button>
    

    在声明了 #serverNameInput 后,就可以在当前模板中使用这个变量了。

  • controller 部分

    onAddServer(serverNameInput: HTMLInputElement) 
        this.serverCreated.emit(
        serverName: serverNameInput.value,
        serverContent: this.newServerContent,
        );
    
    
    onAddBlueprint(serverNameInput: HTMLInputElement) 
        this.blueprintCreated.emit(
        blueprintName: serverNameInput.value,
        blueprintContent: this.newServerContent,
        );
    
    

local reference 的主要用途就是在模板中更加简单的访问当前元素,尽管也可以直接将值传入函数内,使得该元素在组件内被访问。

⚠️:Angular 不推荐直接通过元素修改 HTML 属性。

@ViewChild

@ViewChild() 是 Angular 提供的装饰器,如果说使用 local reference 让在同一模板内访问访问该元素变得更简单,那么使用装饰器则可以更简单的在组件内访该元素:

  • 模板部分

    <!-- 其余不变 -->
    <input type="text" class="form-control" #serverContentInput />
    

    模板中需要新增对应的 local reference 让 @ViewChild 去获取对应的元素。

  • controller 部分

    export class CockpitComponent implements OnInit 
      // 第一个参数与 local reference名称一致
      @ViewChild('serverContentInput',  static: true )
      // ElementRef 需要从 Angular 中导入
      serverContentInput: ElementRef;
    
      onAddBlueprint(serverNameInput: HTMLInputElement) 
        console.log(this.serverContentInput);
    
        this.blueprintCreated.emit(
          blueprintName: serverNameInput.value,
          blueprintContent: this.serverContentInput.nativeElement.value,
        );
      
    
    

⚠️:Angular 不推荐直接通过 element ref 修改 HTML 属性。

ng-content

假设有一个比较少见的 case,就是迭代需要在父组件中实现,即子组件基本为一个空的 template,Angular 也有办法通过 ng-content directive 实现:

  • app 模板

    <div class="row">
      <div class="col-xs-12">
        <app-server-element
          *ngFor="let serverElement of serverElements"
          [element]="serverElement"
        >
          <p>
            <strong *ngIf="serverElement.type === 'server'" style="color: red"
              > serverElement.content </strong
            >
            <em *ngIf="serverElement.type === 'blueprint'"
              > serverElement.content </em
            >
          </p>
        </app-server-element>
      </div>
    </div>
    

    这里将一部分的 server-element 中的模板内容放了进来

  • server-element 模板

    Angular 可以通过 ng-content 获取标签内的元素进行替换渲染

    <div class="panel panel-default">
      <div class="panel-heading"> element.name </div>
      <div class="panel-body">
        <ng-content></ng-content>
      </div>
    </div>
    

@ContentChild

另外,像 @ViewContent 的用法,使用 @ContentChild 可以在子组件中获取父组件中的元素,获取的数据类型也是 ElementRef

以上是关于Angular 基础 2 - 组件相关的主要内容,如果未能解决你的问题,请参考以下文章

“没有 AuthGuard 的提供者!”在 Angular 2 中使用 CanActivate

前端小白之每天学习记录----angula2--

Angular v14 现已推出

如何通过将 id 存储在变量中来渲染组件?

快速入门!详解 Angular2 路由的分类及使用!

如何通过 Angular 6 中的服务将对象发送到不相关的组件?