将数据传递到“路由器出口”子组件


99

我有一个去服务器的父组件并获取一个对象:

// 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工作方式不同。

Answers:


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

可以用来给孩子一个ID吗?我尝试将所有node类型的内容都更改为string它,但似乎不起作用,或者我做错了什么
Wanjia

您可以将任何数据传递给子组件,但子组件需要自行设置ID。你可以试着得到一个ElementRef从路由器出口的事件用它来设置id,但我不通过心脏知道是否或如何做到这一点(只在我的手机)
君特Zöchbauer

46

Günters的回答很好,我只想指出另一种方法而不使用Observables。

在这里,我们必须记住这些对象是通过引用传递的,因此,如果您想对子对象中的对象做一些工作而又不影响父对象,我建议您使用Günther解决方案。但是,如果这无关紧要,或者实际上是期望的行为,我将建议以下内容。

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

在您的父母中,您可以分配值:

this.sharedService.sharedNode = this.node;

并在您的孩子(和父母)中,将共享服务注入到您的构造函数中。如果要在该模块中的所有组件上使用单例服务,请记住在模块级别的提供程序数组中提供服务。另外,只需添加服务父的供应商阵列中,那么家长和孩子将共享相同的服务实例。

node: Node;

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

正如纽曼所指出的,您还可以this.sharedService.sharedNode在html模板中或使用getter:

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

5
您可以/应该直接在html中绑定到sharedService.sharedNode。这样,sharedNode中的更新将保持同步。
newman'4

11

是的,您可以设置通过路由器插座显示的组件的输入。可悲的是,您必须以编程方式执行此操作,如其他答案所述。当涉及到可观察因素时,有一个很大的警告(如下所述)。

这是如何做:

(1)activate在父模板中连接路由器出口的事件:

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

(2)切换到父级的打字稿文件,并在每次激活时以编程方式设置子级组件的输入:

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

为了onOutletLoaded简化起见,简化了上述版本,但是只有在您可以保证所有子组件都具有与您分配的输入完全相同的输入的情况下,该版本才有效。如果您的组件具有不同的输入,请使用类型防护:

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

为什么这种方法比服务方法更可取?

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

但是,此方法使您避免制作中间人对象(服务),该中间人对象将服务和子组件紧密耦合在一起。在许多情况下,这感觉更接近“成角度的方式”,因为您可以继续通过@Inputs将数据传递到子组件。它也非常适合您不想或无法与该服务紧密结合的现有组件或第三方组件。另一方面,当...

警告

使用此方法的警告是,由于您以编程方式传递数据,因此您不再可以选择在模板中传递可观察的数据(惊奇!)。这意味着当您使用管道异步模式时,您将失去为您管理可观察对象的生命周期的角度优势。

相反,无论何时onChildLoaded 调用该函数,您都需要进行一些设置以获取当前的可观察值。这可能还需要拆除父组件的onDestroy功能。这并不是什么太平常的事,在其他情况下也需要这样做,例如使用甚至没有到达模板的可观察对象。


9

服务:

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

8

有3种方式将数据从父母传递给孩子

  1. 通过共享服务:您应该将要与孩子共享的数据存储到服务中
  2. 如果您必须接收其他数据,则通过Children Router Resolver

    this.data = this.route.snaphsot.data['dataFromResolver'];
    
  3. 如果您必须从父级接收相同的数据,请通过父级路由解析器

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

注意1:您可以在此处阅读有关解析器的信息。还有一个解析器示例,以及如何将解析器注册到模块中,然后如何将数据从解析器检索到组件中。解析器注册在父母和孩子上是相同的。

注意2:您可以在此处阅读有关ActivatedRoute的 信息,以便能够从路由器获取数据


1

遵循问题,在Angular 7.2中,您可以使用历史记录状态将数据从父级传递到子级。所以你可以做类似的事情

发送:

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

检索:

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

但要保持一致。例如,假设您想使用路由器出口在左侧栏上显示列表,在右侧显示所选项目的详细信息。就像是:


项目1(x)| ..............................................

项目2(x)| ......选择的项目详细信息.......

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

项目4(x)| ..............................................


现在,假设您已经单击了某些项目。单击浏览器的后退按钮将显示上一项的详细信息。但是,如果与此同时,您单击(x)并从列表中删除该项目怎么办?然后单击鼠标左键,将向您显示已删除项目的详细信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.