在Angular 2中传递非父/子关系组件之间的值

时间:2017-01-10 23:27:43

标签: javascript angular typescript

根据我的理解,在Angular 2中,如果你想在不相关的组件之间传递值(即,不共享路由的组件,因此不共享父子关系),你可以通过共享来实现服务。

这就是我在Angular2应用程序中设置的内容。我正在检查一个url中是否存在一系列字符,如果存在则返回true。

  isRoomRoute(routeUrl) {
      if ((routeUrl.includes('staff') || routeUrl.includes('contractors'))) {
          console.log('This url: ' + routeUrl + ' is a roomRoute');
          return true;
      } else {
          console.log('This url: ' + routeUrl + ' is NOT a room route');
          return false;
      }
  }

在根app.component的构造函数中,我正在订阅路由事件:

constructor(private routeService: RouteService,
            private router: Router)  {
    this.router.events.subscribe((route) => {
    let routeUrl = route.url;
    this.routeService.sendRoute(routeUrl);
    this.routeService.isRoomRoute(routeUrl);
    });
}

...然后使用提供的URL来检查url是否包含特定字符串。每次路线改变时都会对此进行评估。

所以这一切都按预期工作。

但是,我在将检查结果传递给另一个不相关(非父子)组件时遇到了问题。

即使我在app.component和un-related(room.component)组件中使用共享服务(routeService),但在一个组件中工作的东西在另一个组件中不起作用。根据我的理解,这里检查的内容的“真实性”应该足以回复真实的陈述。

但是在次要的,不相关的组件中,当我调用该函数时,我得到一个“未定义”错误,如下所示:

  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }

所以这就是我被困住的地方。基本上,关于url是否包含某个字符串的评估已经发生。现在我只需要将该检查的布尔结果传递给辅助的非相关组件。我怎样才能在Angular 2中做到最好?

4 个答案:

答案 0 :(得分:6)

您的理解是正确的,injectable共享服务是多个不相关组件之间通信的常用方式。

以下是这种用例的演练。

首先,根据您的情况,我们会监听Router中的AppComponent个事件,获取有效路线,并将其传递给RouteService,以便服务可以操纵它,和/或将它提供给其他组件。

这就是AppComponent应该是这样的:

export class AppComponent {

    constructor(private _router: Router,
                private _routeService: RouteService) {

        this._router.events.subscribe(event => {
            if (event instanceof NavigationEnd) {
                let url = event.urlAfterRedirects;
                this._routeService.onActiveRouteChanged(url);
            }
        });
    }

}

说到服务,我们在这里将BehaviorSubject作为委托介绍,因此使用该服务的组件可以订阅服务数据更改。有关BehaviorSubject和其他主题的详细信息,请访问:Delegation: EventEmitter or Observable in Angular2

以下是我们的共享RouteService的实现(组件需要use the single instance of the service,因此请确保您已在根级别提供它):

@Injectable()
export class RouteService {

    isRoomRouteSource: BehaviorSubject<boolean> = new BehaviorSubject(false);

    constructor() { }

    onActiveRouteChanged(url: string): void {
        let isRoomRoute = this._isRoomRoute(url);
        this.isRoomRouteSource.next(isRoomRoute);
        // do other stuff needed when route changes
    }

    private _isRoomRoute(url: string): boolean {
        return url.includes('staff') || url.includes('contractors');
    }
}

使用该服务的另一个组件的示例,以及订阅我们的BehaviorSubject更改:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this._routeService.isRoomRouteSource.subscribe((isRoomRoute: boolean) => {
            this.isCurrentRouteRoomRoute = isRoomRoute;
            // prints whenever active route changes
            console.log('Current route is room route: ', isRoomRoute);
        });
     }

}

如果没有必要订阅isRoomRouteSource更改,请说我们只需要存储最后一个值,然后:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this.isCurrentRouteRoomRoute = this._routeService.isRoomRouteSource.getValue(); // returns last value stored
        console.log('Current route is room route: ', this.isCurrentRouteRoomRoute);
     }

}

希望这有帮助!

答案 1 :(得分:1)

只是查看你的代码,看起来这里的内容不正确。

&#13;
&#13;
  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }
&#13;
&#13;
&#13; 它看起来好像上面代码中的this.routeUrl可能是未定义的,除非它在别处定义并在之前定义。你可以做的是在路由事件的服务中设置一个属性,然后在isRoomRoute中你将读取该属性。

&#13;
&#13;
@Injectable()
class routeService {
  constructor(private router: Router) {
    // subscribe to event
    router.subscribe((url) => {
      this.routeUrl = url;
      // other things?  sendRoute??
    });

  }

  // Other methods for this class
  isRoomRoute() {
    return this.routeUrl && (this.routeUrl.includes('staff') || this.routeUrl.includes('contractors'));
  }
}

// Usage later where this service has been injected
@Component({
 // ... other properties
 providers: [routeService]
})
class someComponent {
  constructor(private routeService: routeService) {}
  someMethod() {
    this.routeService.isRoomRoute();  // Check if we are in a room route.
  }
}
&#13;
&#13;
&#13;

在这种情况下,我不确定为什么你不能简单地获取URL并在调用isRoomRoute时解析它,而不是在路由事件上设置一些东西。

答案 2 :(得分:0)

(被要求在评论中发布我正在谈论的样本):

我以这种方式思考实际的Angular事件/数据流:

  • &#34;目标&#34;从组件的EventEmitter中听到发出的事件(因为它有一个对它的引用,并且订阅了该引用)。
  • 有些东西通过EventEmitter发出一个偶数,任何引用它并订阅它的东西都会听到它。

他们都使用EventEmitters来做。所以父母可以挂钩儿童的事件发射器,孩子可以挂钩父母。每个人都可以加入服务。但是,一切都是&#34; app&#34;的孩子。所以最后(虽然服务将成为可行的方式),你可以通过让每个组件都连接到&#34; app&#34;的事件发射器来构建一个完整的组件通信系统。

此示例是一种接近菜单按钮通信问题的方法:当单击一个按钮时,您希望它们都知道它(因此您可以突出显示所选背景并取消其余部分,无论如何)。按钮组件是对等组件,仅仅因为它们具有更高级别的父级而相关。

所以在父组件中(可以像限制为MenuBarComponent,或者广泛为&#34; app&#34;):

<ul>
  <li *ngFor="let btn of buttons">
    // Bind the parent's broadcast listener, to the child Input. 
    <my-child [broadcastListener]="broadcasterParent">
    </my-child>
  </li>
</ul>

在这里,父母通过输入给孩子一个对其(父母的)EventEmitter的引用(广播等只是典型的名字)。因此,当父级从其EventEmitter发出事件时,或者任何子级使用对该发射器的引用来发出事件时,通过输入引用并且已订阅该引用的所有组件都将听到它。

上面那个模板背后的父代码(注意我使用ES6,非常确定你会得到这个想法,我只是使用构造函数来保持简短):

import { Component, EventEmitter } from '@angular/core';
...
constructor  ) {
  // this is the instance given to the child as Input
  this.broadcasterParent = new EventEmitter ( );
}

在孩子身上:

import { Component } from '@angular/core';
...
  constructor ( ) {
    // This is almost certainly better used in OnInit.
    // It is how the child subscribes to the parent's event emitter.
    this.broadcastListener.subscribe( ( b ) => this.onBroadcast ( b ) );
  }

  onButtonClick ( evt ) {
    // Any component anywhere with a ref to this emitter will hear it
    this.broadcastListener.emit ( evt );
  }

  onBroadcast ( evt ) {
    // This will be the data that was sent by whatever button was clicked
    // via the subscription to the Input event emitter (parent, app, etc).
    console.log ( evt );        
}

ChildComponent.annotations = [
  new Component ( {
      ... // TS would use an attribute for this
      inputs: [ 'broadcastListener' ]
      template: `
        <div click)="onButtonClick ( $event )">
           ...
        </div>
      `
  })
];

服务确实或多或少地做了同样的事情,但服务是&#34;浮动&#34;和通过注入访问,而不是固定在hieararchy和访问通过输入(你可以使它更健壮等)。

因此,任何一个被点击的按钮都会发出一个他们都会听到的事件,因为他们都订阅了相同的事件发射器(无论是服务还是其他)。

答案 3 :(得分:0)

我说我会这样做,所以这里和我的第一个答案一样,只是使用“浮动”服务(所以没有父/子的东西)。虽然天真,但这几乎是它的关键。

首先,创建服务EmitService。

import { Injectable, EventEmitter } from '@angular/core';

@Injectable()
export class EmitService {

  private _emitter;

  constructor() {
    this._emitter = new EventEmitter ( );
  }

  subscribeToServiceEmitter ( callback ) {
    return this._emitter.subscribe ( b => callback ( b ) );
  }

  emitThisData ( data ) {
    this._emitter.emit ( data );
  }
}

创建两个组件,它们可以位于应用程序的任何位置。这是CompOneComponent,将其复制以创建CompTwoComponent:

import { Component, OnInit, OnDestroy } from '@angular/core';
// the CLI puts components in their own folders, adjust this import
// depending on your app structure...
import { EmitService } from '../emit.service';

@Component({
  selector: 'app-comp-one',
  templateUrl: './comp-one.component.html',
  styleUrls: ['./comp-one.component.css']
})
export class CompOneComponent implements OnInit, OnDestroy {

  private _sub;

  constructor( private _emitSvc : EmitService ) {}

  ngOnInit() {
    // A subscription returns an object you can use to unsubscribe
    this._sub = this._emitSvc.subscribeToServiceEmitter ( this.onEmittedEvent );

    // Watch the browser, you'll see the traces. 
    // Give CompTwoComponent a different interval, like 5000
    setTimeout( () => {
      this._emitSvc.emitThisData ( 'DATA FROM COMPONENT_1' );
    }, 2000 );

  }

  onEmittedEvent ( data ) {
    console.log ( `Component One Received ${data}` );
  };

  ngOnDestroy ( ) {
    // Clean up or the emitter's callback reference
    this._sub.unsubscribe ( );
  }
}

将所有内容添加到您的应用中;这些组件都是顶级的,但它们不一定是,它们可以存在于任何地方:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { CompOneComponent } from './comp-one/comp-one.component';
import { CompTwoComponent } from './comp-two/comp-two.component';

import { EmitService } from './emit.service';

@NgModule({
  declarations: [
    AppComponent,
    CompOneComponent,
    CompTwoComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  exports: [ ],
  providers: [ EmitService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

就是这样。现在两个组件都可以访问服务(并且已经订阅了它的EventEmitter),因此它们可以告诉它发出事件,并且将接收由任何其他组件触发的任何事件。创建一个CLI应用程序,添加这些东西,运行它,你会看到console.logs按照你的预期激发(注意发出事件的组件也会听到它,你可以用几种不同的方式过滤掉它)。你可以注入EmitService的任何东西都可以使用它而不管它在哪里。