Angular组件通信与服务

上次我们讲完了angular自定义指令,今天就来学习一下angular是如何实现数据流动和组件通信的,当然服务也是我们要讲的一个部分。

父组件:
import { Component } from '@angular/core';

import { Hero } from './hero';

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

@Component({
  selector: 'my-app',
  template: `
    

{{title}}

My Heroes

  • {{hero.id}} {{hero.name}}
`, styles: [` ... 此处省略 `] }) export class AppComponent { title = 'Tour of Heroes'; heroes = HEROES; selectedHero: Hero; //注意ts语法规范,函数里面参数跟冒号确认参数类型,:void表示此函数无返回值 onSelect(hero: Hero): void { this.selectedHero = hero; } } 我们看到父组件里面有一个hero-detail子组件,有一个变量hero通过selectedHero传递,需要注意的是hero上的[]不能少,不然变量传递不到子组件。
子组件
import { Component, Input } from '@angular/core';

import { Hero } from './hero';
@Component({
  selector: 'hero-detail',
  template: `
    

{{hero.name}} details!

{{hero.id}}
//在这个输入框里对做修改,父组件里面hero的name不会变化,那么如何做到双向绑定,很简单 用ngModel实现
` }) export class HeroDetailComponent { @Input() hero: Hero; }

在这里告诉大家一个小技巧,@ Input里面可以给参数,不给的话默认就是hero对应到父组件的hero属性。举个例子吧。。。

我把父组件里面的hero改成了herow,那么如何在子组件里面正常拿到selectedHero呢
  
子组件:
  export class HeroDetailComponent {
    @Input(‘herow’) hero: Hero;  引号不能少
 }
  这个时候的子组件的hero,就是父组件的selectedHero值

下面我们来看一下,父子组件如何实现数据通信。

// child-component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'child-component',
    template:'

{{count}}

' ... }) export class ChildComponent { @Input() count: number = 0; @Output() change = new EventEmitter(); increaseNumber() { this.count ++; this.change.emit(this.count); } descreaseNumber() { this.count --; this.change.emit(this.count); } }

从上述子组件的代码我们应该能够看出,这是再尝试修改count的值后,把它回传给父组件让他也更新Initialcount的值,这里有一个@Output(),change是事件触发器,并传递count的值,接下来我们看下父组件对此如何作出回应。

// father-component.ts
import { Component } from '@angular/core';
import { ChildComponent } from '../child-component/child-component';

@Component({
    template: `
        
    `,
    ...
})
export class FatherComponent {
    initialCount: number = 5;

    countChange($event) {
          this.initialCount = $event;
    }
}
可以看出来是通过change把父子组件联系起来。

其实根本没必要这么麻烦来实现双向数据绑定,上面绕了个这么大的湾不就是想把数据双向同步。

 template: `
        
    `,
简不简单,就是加了个()

既然双向数据绑定可以通过[()]来实现,那么上面的Eventemitter岂不是会显得多余,其实也不是,我们可以看下面这个例子。

// child-component.ts
@Component({
    selector: 'child-component',
    template:'

{{count}}

' ... }) export class ChildComponent { @Input() count: number = 0; @Output() change = new EventEmitter(); increaseNumber() { this.count ++; if(this.count % 10 == 0) this.change.emit(this.count%10); } descreaseNumber() { this.count --; if(this.count % 10 == 0) this.change.emit(this.count%10); } } // father-component.ts import { Component } from '@angular/core'; import { ChildComponent } from '../child-component/child-component'; @Component({ template: `

now staged in {{count}} phase

`, ... }) export class FatherComponent { initialCount: number = 5; count = 1; countChange($event) { this.count = $event } }

子获得父实例

从上面代码可以看出,在每次increase或decrease操作后,都会检测能否被10整除,是的话就会修改父组件的count值。下面介绍另外一种方式,在子组件中对父组件的属性作出修改。

import {Host,Component,forwardRef} from 'angular2/core';

@Component({
    selector: 'child',
    template: `
        

child

` }) class Child { //使用@Host() 注入 constructor(@Host() @Inject(forwardRef(()=> App)) app:App) { setInterval(()=> { app.i++; }, 1000); } } @Component({ selector: 'App', template: `

App {{i}}

` }) export class App { i:number = 0; }

父获得子实例

子元素指令在父constructor时是获取不到的,所以必须在组件的ngAfterViewInit生命周期钩子后才能获取,如果对组件生命周期不了解的话,请自行google。

import {ViewChild,Component} from 'angular2/core';

@Component({
    selector: 'child',
    template: `
        

child {{i}}

` }) class Child { i:number = 0; } @Component({ selector: 'App', directives: [Child], template: `

App {{i}}

` }) export class App { @ViewChild(Child) child:Child; ngAfterViewInit() { setInterval(()=> { this.child.i++; }, 1000) } }

上面介绍得都是围绕父子组件的,如果不是父子而是同级组件,则如何进行通信呢,这个时候就需要引入服务了,我们先看一下什么是服务,如何使用。

//hero.service.ts 这就是一个简单的服务
import { Injectable } from '@angular/core';

import { Hero } from './hero';

const HEROES: Hero[] = [
  {id: 11, name: 'Mr. Nice'},
  {id: 12, name: 'Narco'},
  {id: 13, name: 'Bombasto'},
  {id: 14, name: 'Celeritas'},
  {id: 15, name: 'Magneta'}
];

@Injectable()
export class HeroService {
  getHeroes(): Promise {
    return Promise.resolve(HEROES);
  }
}

既然我们新建了一个服务,那么怎么使用他呢?

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-app',
  template: `
    

{{title}}

My Heroes

  • {{hero.id}} {{hero.name}}
`, styles: [` ... `], providers: [HeroService] }) export class AppComponent implements OnInit { title = 'Tour of Heroes'; heroes: Hero[]; constructor(private heroService: HeroService) { } //到这里就明白了服务是干嘛用的了把 getHeroes(): void { this.heroService.getHeroes().then(heroes => this.heroes = heroes); } //组件初始化时调用方法,赋值heroes ngOnInit(): void { this.getHeroes(); }

其实我们不难看出,一个服务就是一段通用功能的代码,里面可以很多函数供组件调用,那么服务可以作为一个中转站,联系同级组件,实现通信。具体看代码:

import {Component} from 'angular2/core';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroListComponent} from './hero-list.component';

@Component({
    selector: 'my-app',
    templateUrl: 'app/template/app.html'
})
export class AppComponent {}

// app.html


很好,现在我们创建了这个场景,2个同级组件hero-list,hero-detail

// hero-list.component
import {Component, OnInit} from 'angular2/core';
import {Hero} from '../interface/hero';
import {HeroService} from '../service/hero.service';

@Component({
    selector: 'hero-list',
    providers: [HeroService], 
    templateUrl: 'app/template/hero-list.html',
    styleUrls: ['app/css/hero-list.css']
})
export class HeroListComponent implements OnInit {
    constructor(private _heroService: HeroService) { }

    public heroes: Hero[];
    public selectedHero: Hero;

    getHeroes() {
        this._heroService.getHeroes().then(heroes => this.heroes = heroes);
    }
    //我们想让选择英雄后,对应的hero-detail组件作出改变
    onSelect(hero: Hero) {
        //这一步就是具体的实现
        this._heroService.change(hero);
    }

    ngOnInit() {
        this.getHeroes();
    }
}

我们稍微调整一下service的代码,其实也就是加一个方法而已。

import { Injectable } from '@angular/core';
import { Hero } from './hero';

const HEROES: Hero[] = [
  {id: 11, name: 'Mr. Nice'},
  {id: 12, name: 'Narco'},
  {id: 13, name: 'Bombasto'},
  {id: 14, name: 'Celeritas'},
  {id: 15, name: 'Magneta'}
];

@Injectable()
export class HeroService {
  selectedHero:Hero;

  getHeroes(): Promise {
    return Promise.resolve(HEROES);
  }
  
  //对selectedHero做出了更改
  change(hero:Hero){
    this.selectedHero = hero
}
   getHero(){
     return this.selectedHero
}
}

最后看一下hero-detail是如何写的

// hero-detail.component
import {Component} from 'angular2/core';
import {HeroService} from '../service/hero.service';

@Component({
  selector: 'hero-detail',
  templateUrl: 'app/template/hero-detail.html'
})
export class HeroDetailComponent {
  public hero: Hero;
 
  constructor(heroService:HeroService){
       this.hero = heroService.getHero()
}
}

这样就见间接实现了同级组件之间的信息传递,通过一个service完成。最后介绍一个angular里面很重要的一个东西viewChild。
ViewChild 是属性装饰器,用来从模板视图中获取匹配的元素。视图查询在 ngAfterViewInit 钩子函数调用前完成,因此在 ngAfterViewInit 钩子函数中,才能正确获取查询的元素。

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    

Welcome to Angular World

Hello {{ name }}

`, }) export class AppComponent { name: string = 'Semlinker'; @ViewChild('greet') greetDiv: ElementRef; ngAfterViewInit() { console.dir(this.greetDiv); } } // 其实也就是说了如何获取#greet元素

ngAfterViewInit是angular里面生命周期,在content加载完毕后调用,关于者可以参考angular文档.

@ViewChild 使用类型查询
child.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
    selector: 'exe-child',
    template: `
      

Child Component

` }) export class ChildComponent { name: string = 'child-component'; } import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from './child.component'; app.component.ts @Component({ selector: 'my-app', template: `

Welcome to Angular World

`, }) export class AppComponent { @ViewChild(ChildComponent) //用于匹配ChildComponent childCmp: ChildComponent; ngAfterViewInit() { console.dir(this.childCmp); } } 下面是打印结果
Angular组件通信与服务_第1张图片
image.png

所以可以看出childCmp是ChildComponent的一个实例,可以用childCmp.name打印出他的属性。

我们来看一下viewChildren

import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'my-app',
  template: `
    

Welcome to Angular World

`, }) export class AppComponent { //由于有多个ChildComponent,所以类型是QueryList @ViewChildren(ChildComponent) childCmps: QueryList; ngAfterViewInit() { console.dir(this.childCmps); } }
Angular组件通信与服务_第2张图片
image.png

可见想要获取子元素可以用this.childCmps._results[index]获取。我们最后看一个官方文档给出的例子,注意指令可以写在组件里面。

import {Component, Directive, Input, ViewChild} from '@angular/core';
@Directive({selector: 'pane'})
export class Pane {
  @Input() id: string;
}
@Component({
  selector: 'example-app',
  template: `
    
    
    
    
Selected: {{selectedPane}}
`, }) export class ViewChildComp { @ViewChild(Pane) set pane(v: Pane) { setTimeout(() => { this.selectedPane = v.id; }, 0); } selectedPane: string = ''; shouldShow = true; toggle() { this.shouldShow = !this.shouldShow; } }

这里可能之前没见过,在一个组件里面写指令,并把他用到component上。通过toggle方法,控制2个pane的显影。
今天就先介绍到这,下回可能带来angular的生命周期专题或者其他专题,敬请期待

你可能感兴趣的:(Angular组件通信与服务)