将数据传递到“路由器插座”子组件

我有一个父组件,它到服务器并获取一个对象:

// parent component


@Component({
selector : 'node-display',
template : `
<router-outlet [node]="node"></router-outlet>
`
})


export class NodeDisplayComponent implements OnInit {


node: Node;


ngOnInit(): void {
this.nodeService.getNode(path)
.subscribe(
node => {
this.node = node;
},
err => {
console.log(err);
}
);
}

在其中一个孩子的展示中:

export class ChildDisplay implements OnInit{


@Input()
node: Node;


ngOnInit(): void {
console.log(this.node);
}


}

看来我不能直接把数据注入 router-outlet。看起来我在 web 控制台中得到了错误:

Can't bind to 'node' since it isn't a known property of 'router-outlet'.

这有点道理,但是我该怎么做呢:

  1. 从服务器获取“节点”数据,从父节点获取 组件?
  2. 将我从服务器检索到的数据传递到子路由器插座?

看起来 router-outlets不是这样工作的。

147088 次浏览
<router-outlet [node]="..."></router-outlet>

只是无效。路由器添加的组件作为兄弟添加到 <router-outlet>,而不会替换它。

参见 https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

@Injectable()
export class NodeService {
private node:Subject<Node> = new BehaviorSubject<Node>([]);


get node$(){
return this.node.asObservable().filter(node => !!node);
}


addNode(data:Node) {
this.node.next(data);
}
}
@Component({
selector : 'node-display',
providers: [NodeService],
template : `
<router-outlet></router-outlet>
`
})
export class NodeDisplayComponent implements OnInit {
constructor(private nodeService:NodeService) {}
node: Node;
ngOnInit(): void {
this.nodeService.getNode(path)
.subscribe(
node => {
this.nodeService.addNode(node);
},
err => {
console.log(err);
}
);
}
}
export class ChildDisplay implements OnInit{
constructor(nodeService:NodeService) {
nodeService.node$.subscribe(n => this.node = n);
}
}

君特的回答是伟大的,我只是想指出另一种方式而不使用观察。

这里我们必须记住这些对象是通过引用传递的,所以如果您想在子对象上做一些工作而不影响父对象,我建议使用 Günther 的解决方案。但是如果它不重要,或者实际上是 想要的行为,我建议如下。

@Injectable()
export class SharedService {


sharedNode = {
// properties
};
}

在你的父代中,你可以赋值:

this.sharedService.sharedNode = this.node;

在子(AND 父)中,在构造函数中注入共享服务。如果您希望在模块中的所有组件上都使用单例服务,请记住在模块级提供程序数组中提供服务。或者,只需在父 只有的提供者数组中添加服务,那么父和子将共享相同的服务实例。

node: Node;


ngOnInit() {
this.node = this.sharedService.sharedNode;
}

正如 Newman 所指出的,你也可以在 html 模板或 getter 中使用 this.sharedService.sharedNode:

get sharedNode(){
return this.sharedService.sharedNode;
}

服务范围:

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


@Injectable()
export class DataService {
onGetData: EventEmitter = new EventEmitter();


getData() {
this.http.post(...params).map(res => {
this.onGetData.emit(res.json());
})
}

组成部分:

import {Component} from '@angular/core';
import {DataService} from "../services/data.service";
    

@Component()
export class MyComponent {
constructor(private DataService:DataService) {
this.DataService.onGetData.subscribe(res => {
(from service on .emit() )
})
}


//To send data to all subscribers from current component
sendData() {
this.DataService.onGetData.emit(--NEW DATA--);
}
}

有3种方法可以将数据从“父母”传递给“子女”

  1. 通过共享服务: 您应该将您希望与孩子共享的数据存储到一个服务中
  2. 通过儿童路由器解析器,如果你必须接收不同的数据

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. Through Parent Router Resolver if your have to receive the same data from parent

    this.data = this.route.parent.snaphsot.data['dataFromResolver'];
    

Note1: You can read about resolver here. There is also an example of resolver and how to register the resolver into the module and then retrieve data from resolver into the component. The resolver registration is the same on the parent and child.

Note2: You can read about ActivatedRoute here to be able to get data from router

这个问题之后,在 Angular 7.2中,您可以使用历史状态将数据从父级传递给子级。这样你就可以

发送:

this.router.navigate(['action-selection'], { state: { example: 'bar' } });

检索:

constructor(private router: Router) {
console.log(this.router.getCurrentNavigation().extras.state.example);
}

但要小心保持一致。例如,假设您希望使用路由器插座在左侧栏上显示一个列表,并在右侧显示所选项的详细信息。比如:


第一(x) | ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ..。

项目2(x) | ... ... 选定项目详情... ... 。

项目3(x) | ... ... ... ... ... ... ... ... ... ... ... ... 。

第4(x) | ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ..。


现在,假设您已经单击了一些项。单击浏览器返回按钮将显示前一项的详细信息。但是,如果您同时单击了(x)并从列表中删除了该项,该怎么办呢?然后执行反击,将显示您的删除项目的细节。

是的,您可以将数据直接传递到路由器插座组件。遗憾的是,正如其他答案中提到的,您不能使用角度模板绑定来实现这一点。您必须在打印脚本文件中设置数据。当涉及到可观测物时,有一个很大的警告(如下所述)。

方法如下:

(1) 在父模板中连接到路由器插座的 activate事件:

<router-outlet (activate)="onOutletLoaded($event)"></router-outlet>

(2) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

onOutletLoaded(component) {
component.someProperty = 'someValue';
}

成交。

然而,为了清楚起见,上面的 onOutletLoaded版本被简化了。只有当您能够保证所有子组件具有完全相同的输入时,它才能工作。如果您的组件具有不同的输入,请使用类型保护:

onOutletLoaded(component: MyComponent1 | MyComponent2) {
if (component instanceof MyComponent1) {
component.someInput = 123;
} else if (component instanceof MyComponent2) {
component.anotherInput = 456;
}
}

为什么这种方法优于服务方法?

这种方法和服务方法都不是与子组件通信的“正确方法”(这两种方法都远离纯模板绑定) ,因此您只需决定哪种方法更适合项目。

然而,这种方法避免了与“创建通信服务”方法相关联的紧密耦合(即,父方需要该服务,而子方都需要该服务,从而使子方无法在其他地方使用)。

在许多情况下,这种方法感觉更接近于“角度方式”,因为您可以继续通过@Input 将数据传递给子组件。它也很适合已经存在的或第三方组件,您不希望或不能与您的服务紧密结合。

另一方面,它可能感觉不像有棱角的方式,当..。

注意

这种方法需要注意的是,由于您在类型脚本文件中传递数据,因此您不再可以选择使用模板(例如 \{\{ myObservable$ | async }})中使用的管道异步模式来自动使用并将可观察数据传递给子组件。

相反,每当调用 onOutletLoaded 函数时,您都需要设置一些内容来获取当前可观察的值。这可能还需要对父组件的 onDestroy函数进行一些拆分。这没有什么太不寻常的,经常有需要这样做的情况,例如当使用一个可观察的,甚至没有到达模板。

还有另外一种方法,我相信我的方法可以解决很多问题:

这是处理路线的默认方式:

{ path: 'sample/page1', component: sampleComponent1 },
{ path: 'sample/page2', component: sampleComponent2 },
{ path: 'sample/page3', component: sampleComponent3 },

但是不要这样,让我们写下如下的路线:

{ path: 'sample/:sub', component: sampleComponent },

你可以看到,我们合并了路线,并制作了一个路线参数。我们可以得到组件的参数值:

// sampleComponents.ts :
sub = this.route.snapshot.params['sub'];

现在在该组件的 html 文件中,我们将这些页面作为子组件导入。然后我们可以直接把数据发送给他们。

// sampleComponent.html :
<app-cmp-page1 *ngIf="sub==='page1'" [data]="data"></app-cmp-page1>
<app-cmp-page2 *ngIf="sub==='page2'" [data]="data"></app-cmp-page2>
<app-cmp-page3 *ngIf="sub==='page3'" [data]="data"></app-cmp-page3>