[英]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
共享服务是多个不相关组件之间通信的常用方式。
以下是这种用例的演练。
首先,根据您的情况,我们将监听AppComponent
的Router
事件,获取活动路由,并将其传递给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事件/数据流:
他们都使用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.