Angular开发实践(四):组件之间的交互

在Angular应用开发中,组件可以说是随处可见的。本篇文章将介绍几种常见的组件通讯场景,也就是让两个或多个组件之间交互的方法。

根据数据的传递方向,分为父组件向子组件传递子组件向父组件传递通过服务传递三种交互方法。

父组件向子组件传递

子组件通过@Input装饰器定义输入属性,然后父组件在引用子组件的时候通过这些输入属性向子组件传递数据,子组件可通过setterngOnChanges()来截听输入属性值的变化。

先定义两个组件,分别为子组件DemoChildComponent父组件DemoParentComponent.

子组件:

@Component({
  selector: ‘demo-child‘,
  template: `
    <p>{{paramOne}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent {
    @Input() paramOne: any; // 输入属性1
    @Input() paramTwo: any; // 输入属性2
}

子组件通过@Input()定义输入属性paramOneparamTwo(属性值可以为任意数据类型)

父组件:

@Component({
  selector: ‘demo-parent‘,
  template: `
    <demo-child [paramOne]=‘paramOneVal‘ [paramTwo]=‘paramTwoVal‘></demo-child>
  `
})
export class DemoParentComponent {
    paramOneVal: any = ‘传递给paramOne的数据‘;
    paramTwoVal: any = ‘传递给paramTwo的数据‘;
}

父组件在其模板中通过选择器demo-child引用子组件DemoChildComponent,并通过子组件的两个输入属性paramOneparamTwo向子组件传递数据,最后在子组件的模板中就显示传递给paramOne的数据传递给paramTwo的数据这两行文本。

通过 setter 截听输入属性值的变化

在实际应用中,我们往往需要在某个输入属性值发生变化的时候做相应的操作,那么此时我们需要用到输入属性的 setter 来截听输入属性值的变化。

我们将子组件DemoChildComponent进行如下改造:

@Component({
  selector: ‘demo-child‘,
  template: `
    <p>{{paramOneVal}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent {
    private paramOneVal: any;

    @Input()
    set paramOne (val: any) { // 输入属性1
        this.paramOneVal = val;
        // dosomething
    };
    get paramOne () {
        return this.paramOneVal;
    };

    @Input() paramTwo: any; // 输入属性2
}

在上面的代码中,我们可以看到通过paramOne属性的 setter 将拦截到的值val赋值给内部私有属性paramOneVal,达到父组件传递数据给子组件的效果。当然,最重要的是,在 setter 里面你可以做更多的其它操作,程序的灵活性就更强了。

通过ngOnChanges()来截听输入属性值的变化

通过 setter 截听输入属性值的变化的方法只能对单个属性值变化进行监视,如果需要监视多个、交互式输入属性的时候,这种方法就显得力不从心了。而通过使用 OnChanges 生命周期钩子接口的 ngOnChanges() 方法(当组件通过@Input装饰器显式指定的那些变量的值变化时调用)就可以实现同时监视多个输入属性值的变化。

子组件DemoChildComponent新增ngOnChanges

@Component({
  selector: ‘demo-child‘,
  template: `
    <p>{{paramOneVal}}</p>
    <p>{{paramTwo}}</p>
  `
})
export class DemoChildComponent implements OnChanges {
    private paramOneVal: any;

    @Input()
    set paramOne (val: any) { // 输入属性1
        this.paramOneVal = val;
        // dosomething
    };
    get paramOne () {
        return this.paramOneVal;
    };

    @Input() paramTwo: any; // 输入属性2

    ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
        for (let propName in changes) { // 遍历changes
            let changedProp = changes[propName]; // propName是输入属性的变量名称
            let to = JSON.stringify(changedProp.currentValue); // 获取输入属性当前值
            if (changedProp.isFirstChange()) { // 判断输入属性是否首次变化
                console.log(`Initial value of ${propName} set to ${to}`);
            } else {
                let from = JSON.stringify(changedProp.previousValue); // 获取输入属性先前值
                console.log(`${propName} changed from ${from} to ${to}`);
            }
        }
    }
}

新增的ngOnChanges方法接收的参数changes是以输入属性名称为键、值为SimpleChange的对象,SimpleChange对象含有当前输入属性是否第一次变化、先前值、当前值等属性。因此在ngOnChanges方法中通过遍历changes对象可监视多个输入属性值并进行相应的操作。

获取父组件实例

前面介绍的都是子组件通过@Input装饰器定义输入属性,这样父组件可通过输入属性将数据传递给子组件。

当然,我们可以想到一种更主动的方法,那就是获取到父组件实例,然后调用父组件的某个属性或方法来获取需要的数据。考虑到每个组件的实例都会添加到注入器的容器里,因此可通过依赖注入来找到父组件的示例

子组件获取父组件实例相比于父组件获取子组件实例(直接通过模板变量@ViewChild@ViewChildren获取)要麻烦一些。

要在子组件中获取父组件的实例,有两种情况:

  • 已知父组件的类型

    这种情况可以直接通过在构造函数中注入DemoParentComponent来获取已知类型的父组件引用,代码示例如下:

    @Component({
      selector: ‘demo-child‘,
      template: `
        <p>{{paramOne}}</p>
        <p>{{paramTwo}}</p>
      `
    })
    export class DemoChildComponent {
        paramOne: any;
        paramTwo: any;
    
        constructor(public demoParent: DemoParentComponent) {
    
            // 通过父组件实例demoParent获取数据
            this.paramOne = demoParent.paramOneVal;
            this.paramTwo = demoParent.paramTwoVal;
        }
    }
  • 未知父组件的类型

    一个组件可能是多个组件的子组件,有时候无法直接知道父组件的类型,在Angular中,可通过类—接口(Class-Interface)的方式来查找,即让父组件通过提供一个与类—接口标识同名的别名来协助查找。

    首先创建DemoParent抽象类,它只声明了paramOneValparamTwoVal属性,没有实现(赋值),示例代码如下:

    export abstract class DemoParent {
        paramOneVal: any;
        paramTwoVal: any;
    }

    然后在父组件DemoParentComponentproviders元数据中定义一个别名 Provider,用 useExisting 来注入父组件DemoParentComponent的实例,代码示例如下:

    @Component({
      selector: ‘demo-parent‘,
      template: `
        <demo-child [paramOne]=‘paramOneVal‘ [paramTwo]=‘paramTwoVal‘></demo-child>
      `,
      providers: [{provider: DemoParent, useExisting: DemoParentComponent}]
    })
    export class DemoParentComponent implements DemoParent {
        paramOneVal: any = ‘传递给paramOne的数据‘;
        paramTwoVal: any = ‘传递给paramTwo的数据‘;
    }

    然后在子组件中就可通过DemoParent这个标识找到父组件的示例了,示例代码如下:

    @Component({
      selector: ‘demo-child‘,
      template: `
        <p>{{paramOne}}</p>
        <p>{{paramTwo}}</p>
      `
    })
    export class DemoChildComponent {
        paramOne: any;
        paramTwo: any;
    
        constructor(public demoParent: DemoParent) {
    
            // 通过父组件实例demoParent获取数据
            this.paramOne = demoParent.paramOneVal;
            this.paramTwo = demoParent.paramTwoVal;
        }
    }

子组件向父组件传递

依然先定义两个组件,分别为子组件DemoChildComponent父组件DemoParentComponent.

子组件:

@Component({
  selector: ‘demo-child‘,
  template: `
    <p>子组件DemoChildComponent</p>
  `
})
export class DemoChildComponent implements OnInit {
    readyInfo: string = ‘子组件DemoChildComponent初始化完成!‘;
    @Output() ready: EventEmitter = new EventEmitter<any>(); // 输出属性

    ngOnInit() {
        this.ready.emit(this.readyInfo);
    }
}

父组件:

@Component({
  selector: ‘demo-parent‘,
  template: `
    <demo-child (ready)="onReady($event)" #demoChild></demo-child>
    <p>
        <!-- 通过本地变量获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
        readyInfo: {{demoChild.readyInfo}}
    </p>
    <p>
        <!-- 通过组件类获取子组件示例,然后获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
        readyInfo: {{demoChildComponent.readyInfo}}
    </p>
  `
})
export class DemoParentComponent implements AfterViewInit {
    // @ViewChild(‘demoChild‘) demoChildComponent: DemoChildComponent; // 通过模板别名获取
    @ViewChild(DemoChildComponent) demoChildComponent: DemoChildComponent; // 通过组件类型获取

    ngAfterViewInit() {
        console.log(this.demoChildComponent.readyInfo); // 打印结果:子组件DemoChildComponent初始化完成!
    }

    onReady(evt: any) {
        console.log(evt); // 打印结果:子组件DemoChildComponent初始化完成!
    }
}

父组件监听子组件的事件

子组件暴露一个 EventEmitter 属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。

在上面定义好的子组件和父组件,我们可以看到:

子组件通过@Output()定义输出属性ready,然后在ngOnInit中利用ready属性的 emits(向上弹射)事件。

父组件在其模板中通过选择器demo-child引用子组件DemoChildComponent,并绑定了一个事件处理器(onReady()),用来响应子组件的事件($event)并打印出数据(onReady($event)中的$event是固定写法,框架(Angular)把事件参数(用 $event 表示)传给事件处理方法)。

父组件与子组件通过本地变量(模板变量)互动

父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。

在上面定义好的子组件和父组件,我们可以看到:

父组件在模板demo-child标签上定义了一个demoChild本地变量,然后在模板中获取子组件的属性:

<p>
    <!-- 获取子组件的属性readyInfo,显示:子组件DemoChildComponent初始化完成! -->
    readyInfo: {{demoChild.readyInfo}}
</p>

父组件调用@ViewChild()

本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。

如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。

当父组件类需要这种访问时,可以把子组件作为 ViewChild,注入到父组件里面。

在上面定义好的子组件和父组件,我们可以看到:

父组件在组件类中通过@ViewChild()获取到子组件的实例,然后就可以在模板或者组件类中通过该实例获取子组件的属性:

<p>
    <!-- 通过组件类获取子组件示例,然后获取readyInfo属性,显示:子组件DemoChildComponent初始化完成! -->
    readyInfo: {{demoChildComponent.readyInfo}}
</p>
ngAfterViewInit() {
    console.log(this.demoChildComponent.readyInfo); // 打印结果:子组件DemoChildComponent初始化完成!
}

通过服务传递

Angular的服务可以在模块注入或者组件注入(均通过providers注入)。

在模块中注入的服务在整个Angular应用都可以访问(除惰性加载的模块)。

在组件中注入的服务就只能该组件和其子组件进行访问,这个组件子树之外的组件将无法访问该服务或者与它们通讯。

下面的示例就以在组件中注入的服务来进行父子组件之间的数据传递:

通讯的服务:

@Injectable()
export class CallService {
    info: string = ‘我是CallService的info‘;
}

父组件:

@Component({
  selector: ‘demo-parent‘,
  template: `
    <demo-child></demo-child>
    <button (click)="changeInfo()">父组件改变info</button>
    <p>
        <!-- 显示:我是CallService的info -->
        {{callService.info}}
    </p>
  `,
  providers: [CallService]
})
export class DemoParentComponent {
    constructor(public callService: CallService) {
      console.log(callService.info); // 打印结果:我是CallService的info
    }

    changeInfo() {
        this.callService.info = ‘我是被父组件改变的CallService的info‘;
    }
}

子组件:

@Component({
  selector: ‘demo-child‘,
  template: `
    <button (click)="changeInfo()">子组件改变info</button>
  `
})
export class DemoChildComponent {
    constructor(public callService: CallService) {
        console.log(callService.info); // 打印结果:我是CallService的info
    }

    changeInfo() {
        this.callService.info = ‘我是被子组件改变的CallService的info‘;
    }
}

上面的代码中,我们定义了一个CallService服务,在其内定义了info属性,后面将分别在父子组件通过修改这个属性的值达到父子组件互相传递数据的目的。

然后通过DemoParentComponent的providers元数据数组提供CallService服务的实例,并通过构造函数分别注入到父子组件中。

此时,通过父组件改变info按钮子组件改变info按钮在父组件或子组件中改变CallService服务的info属性值,然后在页面可看到改变之后对应的info属性值。

原文地址:https://www.cnblogs.com/laixiangran/p/8639257.html

时间: 2024-12-24 15:02:48

Angular开发实践(四):组件之间的交互的相关文章

Vue2.0子同级组件之间数据交互

接着我们进入Demo,首先我们可以删除掉模板项目中src/components/Hello.vue,然后在App.vue中删除对于Hello子组件的注册和使用还有一些其他无关紧要的东西,此时的App.vue应为这样 一 .我们先来创建中央事件总线,在src/assets/下创建一个eventBus.js,内容如下(eventBus中我们只创建了一个新的Vue实例,以后它就承担起了组件之间通信的桥梁了,也就是中央事件总线.) 二 . 创建一个firstChild组件,引入eventBus这个事件总

Angular开发实践(八): 使用ng-content进行组件内容投射

在Angular中,组件属于特殊的指令,它的特殊之处在于它有自己的模板(html)和样式(css).因此使用组件可以使我们的代码具有强解耦.可复用.易扩展等特性.通常的组件定义如下: demo.component.ts: import { Component, OnInit } from '@angular/core'; @Component({ selector: 'demo-component', templateUrl: './demo.component.html', styleUrls

Angular05 angular架构、搭建angular开发环境、组件必备三要素、angular启动过程

1 angular架构 1.1 组件:是angular应用的基本构建模块,可以理解为一段带有业务逻辑和数据的HTML 1.2 服务:用来封装可重用的业务逻辑 1.3 指令:允许你想HTML元素添加自定义功能 1.4 模块:将应用中的不同部分组织成一个angular框架可以理解的单元 1.5 组件+服务+指令 = 模块 组件+服务+指令 是用来完成业务功能的:模块 是用来打包和分发的 2 开发环境搭建 2.1 安装node.js 很简单,百度即可 安装完后在我们的命令窗口中就可以使用 npm 命令

组件之间数据交互

有时候非父子关系的组件也需要通信.在简单的场景下,使用一个空的 Vue 实例作为中央事件总线: var bus = new Vue() // 触发组件 A 中的事件 bus.$emit('id-selected', 1) // 在组件 B 创建的钩子中监听事件 bus.$on('id-selected', function (id) { // ... })   实例代码如下: <!DOCTYPE html> <html lang="en"> <head&g

Angular 学习笔记——自定义指令之间的交互

<!DOCTYPE html> <html lang="en" ng-app="myApp"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="jquery-1.11.1.js"></sc

vue学习之父组件与子组件之间的交互

1.父组件数据传给子组件 父组件中的msgfather定义数据 在之组件中通过设置props来取得希望从父组件中获得的值 通过设置这两个属性就可以从父组件传数据到子组件 2.子组件传数据给父组件(这里只介绍$emit) 触发child-tell-me事件,并传参 "this is child message!" 在父组件会找到child-tell-me方法并执行.

Angular组件之间通讯

组件之间会有下列3种关系: 1. 父子关系 2. 兄弟关系 3. 没有直接关系 通常采用下列方式处理(某些方式是框架特有)组件间的通讯,如下: 1父子组件之间的交互(@Input/@Output/模板变量/@ViewChild) 2非父子组件(Service/localStorage) 3还可以利用Session等服务器端的解决方法 Angular组件间通讯方法: 1.父子组件之间的通讯 @Input:是属性绑定,父组件向子组件传递数据 @Output:是事件绑定,子组件向父组件传递数据的同时触

Salesforce Lightning开发学习(二)Component组件开发实践

lightning的组件区分标准组件.自定义组件和AppExchange组件.标准组件由SF提供,自定义组件由developer自行开发,AppExchange组件由合作伙伴建立.下面我们写一个简单的列表demo来学习下自定义开发组件Component. 第一步:新建一个对象:电影,API:Movie__c,下表是其相关的字段   字段名 字段API 字段类型 描述 电影名称 Name 文本   主演 ToStar__c 文本(255)   主题曲 Theme__c 文本(255)   导演 D

Git工程开发实践(四)——Git分支管理策略

Git工程开发实践(四)--Git分支管理策略 一.Git版本管理的挑战 Git是非常优秀的版本管理工具,但面对版本管理依然有非常大得挑战.工程开发中,开发者彼此的代码协作必然带来很多问题和挑战:A.如何开始一个Feature开发,而不影响其它Feature?B.由于很容易创建新分支,分支多了如何管理,时间久了,如何知道每个分支是干什么的?C.哪些分支已经合并回了主干?D.如何进行Release的管理?开始一个Release的时候如何冻结Feature, 如何在Prepare Release的时