簡體   English   中英

在Angular 2中傳遞非父/子關系組件之間的值

[英]Passing Values Between Non-Parent/Child Relationship Components in Angular 2

根據我的理解,在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中做到最好?

您的理解是正確的, injectable共享服務是多個不相關組件之間通信的常用方式。

以下是這種用例的演練。

首先,根據您的情況,我們將監聽AppComponentRouter事件,獲取活動路由,並將其傳遞給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和其他Subjects的更多信息,請訪問:Angular2中的委托:EventEmitter或Observable

以下是我們的共享RouteService的實現(組件需要使用服務的單個實例 ,因此請確保您已在根級別提供它):

@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);
     }

}

希望這有幫助!

只是看看你的代碼看起來像是不正確的。

  isRoomRoute() { if (this.routeService.isRoomRoute(this.routeUrl)) { return true; } } 
它看起來好像上面代碼中的this.routeUrl可能是未定義的,除非它在別處定義並在之前定義。 你可以做的是在路由事件的服務中設置一個屬性,然后在isRoomRoute中你將讀取該屬性。

 @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. } } 

在這種情況下,我不確定為什么你不能簡單地獲取URL並在調用isRoomRoute時解析它而不是在路由事件上設置某些內容。

(被要求在評論中發布我所談論的樣本):

我以這種方式思考實際的Angular事件/數據流:

  • “感興趣”從組件的EventEmitter中聽到發出的事件(因為它有一個對它的引用,並且訂閱了該引用)。
  • 有些東西通過EventEmitter發出一個偶數,任何引用它並訂閱它的東西都會聽到它。

他們都使用EventEmitters來做。 因此,父母可以掛鈎到孩子的事件發射器,並且孩子可以掛鈎到父母的事件。 每個人都可以加入服務。 但是,一切都是“app”的孩子。 所以最后(雖然服務將成為可行的方法),你可以通過讓每個組件掛鈎到“app”的事件發射器來構建一個完整的組件comm系統。

此示例是接近菜單按鈕通信問題的一種方法:當單擊一個按鈕時,您希望它們都知道它(因此您可以突出顯示所選背景並取消其余部分,無論如何)。 按鈕組件是對等組件,僅僅因為它們具有更高級別的父級而相關。

所以在父組件中(可以像限制為MenuBarComponent,或者像“app”一樣廣泛):

<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>
      `
  })
];

服務實際上或多或少都是相同的,但服務是“浮動”和通過注入訪問,而不是固定在hieararchy和通過輸入訪問(你可以使它更健壯等)。

因此,任何一個被點擊的按鈕都會發出一個他們都會聽到的事件,因為他們都訂閱了相同的事件發射器(無論是服務還是其他)。

說我會這樣做,所以這里和我的第一個答案一樣,只是使用“浮動”服務(所以沒有父/子的東西)。 雖然天真,但這幾乎是它的關鍵。

首先,創建服務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的任何東西都可以使用它而不管它在哪里。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM