英文:
Angular does not detect changes through nested components
问题
I have found this problem that is like mine. Yet, my implementation does not work despite following the steps.
I have the following component structure:
- Dashboard
- ActionButton
 - Milestone
- Table
 
 - SupplierSearch
- Table
 
 
 
I have tried to pass array selectedRows from Table to Dashboard, and then to ActionButton using the CustomEvent and elRef.nativeElement.dispatchEvent. When I tried to console.log to see if it is passed to any parent components (Dashboard or Milestone) from Dashboard/Milestone/Table, the array simply does not get passed.
Please note that my code is super dirty right now because I have been trying to resolve this issue for almost a day and tried many ways to resolve it. Please focus on my way to implement this mentioned solution (CustomEvent elRef.nativeElement.dispatchEvent).
I really appreciate the Stackoverflow community for the shared knowledge, thus, please don't downgrade this post if my English is bad or something is inherently wrong with my problem.
Table
import {
  Component,
  ElementRef,
  EventEmitter,
  Input,
  Output,
  TemplateRef,
} from '@angular/core';
import { TableColumnHeader } from './models/table-column-header';
@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss'],
})
export class TableComponent {
  @Input() rowData;
  @Input() headers: TableColumnHeader[] = [];
  @Input() columnTemplate: TemplateRef<any>;
  @Input() loading: boolean = false;
  @Output() selectedRowsEvent = new EventEmitter<any[]>();
  selectedRows = [];
  constructor(private elRef: ElementRef) {}
  onRowSelect(event) {
    this.selectedRows.push(event.data);
    this.selectedRowsEvent.emit(this.selectedRows);
    const evt = new CustomEvent('myCustomEvent', {
      bubbles: true,
      detail: event,
    });
    this.elRef.nativeElement.dispatchEvent(evt);
    console.log(this.selectedRows);
    console.log(event);
    console.log('from table onRowSelected ');
  }
  onRowUnselect(event) {
    this.selectedRows = this.selectedRows.filter(
      (x) => x.nvtAreaName !== event.data.nvtAreaName
    );
    this.selectedRowsEvent.emit(this.selectedRows);
    console.log(this.selectedRows);
    console.log('from table onRowUnselected ');
  }
}
Milestone
import {
  AfterViewInit,
  Component,
  Inject,
  Input,
  OnChanges,
  OnInit,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import { TableColumnHeader } from '../../table/models/table-column-header';
import { NvtAreaDataSource } from '../../../services/nvt-area-data-source.service';
import { AreaProgramDataSource } from '../../../services/area-program-data-source.service';
import { MilestoneTableColumn } from '../../../models/business/milestone-table-column';
import { TableComponent } from '../../table/table.component';
@Component({
  selector: 'app-milestone-search',
  templateUrl: './milestone-search.component.html',
  styleUrls: ['./milestone-search.component.scss'],
})
export class MilestoneSearchComponent
  implements OnInit, OnChanges, AfterViewInit
{
  @Input() selectedGigaArea: string;
  milestoneData = [];
  loading: false;
  private tableComponent!: TableComponent;
  selectedRows = [];
  @Input() pSelectableRows = [];
  @ViewChild(TableComponent)
  columnHeaders: TableColumnHeader[] = [
    { value: 'ONKZ', id: 'onkz', sortable: true },
    { value: 'NVT', id: 'nvtAreaName', sortable: true },
    { value: 'STATUS', id: 'status', sortable: true },
    { value: 'ARVM_START', id: 'arvMStart', date: true },
    { value: 'EXP.ROUGH_START', id: 'expRoughStart', date: true },
    { value: 'EXP.ROUGH_END', id: 'expRoughEnd', date: true },
    { value: 'EXP.FINE_START', id: 'expFineStart', date: true },
    { value: 'EXP.FINE_END', id: 'expFineEnd', date: true },
    { value: 'RM_START', id: 'rmStart', date: true },
    { value: 'AFTER_INST_START', id: 'afterInstStart', date: true },
    { value: 'AFTER_INST_END', id: 'afterInstEnd', date: true },
  ];
  constructor(
    @Inject(NvtAreaDataSource) private nvtAreaDataSource,
    @Inject(AreaProgramDataSource) private areaProgramDataSource
  ) {}
  ngOnInit(): void {
    this.nvtAreaDataSource.connect().subscribe((nvtAreas) => {
      this.milestoneData = [...nvtAreas];
    });
    this.areaProgramDataSource.connect().subscribe((areaPrograms) => {
      this.milestoneData = this.mergeMilestonesData(
        this.milestoneData,
        areaPrograms
      );
    });
  }
  ngOnChanges(changes: SimpleChanges) {
    if (changes.date) {
      // this.pSelectableRows = changes.date.currentValue;
      this.selectedRows = changes.data.currentValue;
    }
    console.log('from milestone onChanges  ' + this.selectedRows.length);
  }
  ngAfterViewInit(): void {
    this.selectedRows = this.tableComponent.selectedRows;
    console.log('from ngAfterViewInit ');
  }
  onNotify(rowsEmitted: any[]): void {
    console.log('from milestone onNotify ');
    this.selectedRows = rowsEmitted;
  }
  mergeMilestonesData(nvtAreas, areaPrograms) {
    return nvtAreas.map((nvtArea) => {
      const areaProgram = areaPrograms.find(
        (x) => x.nvtAreaId === nvtArea.nvtAreaId
      );
      if (!areaProgram) return nvtArea;
      const { status, milestones } = areaProgram;
      let milestonesColumns = {};
      milestones.map((milestone) => {
        const milestonesColumn = Object.entries(
          new MilestoneTableColumn()
        ).reduce(
          (acc, [key, value]) =>
            value === milestone.milestoneType
              ? {
                  ...acc,
                  [key]: milestone.milestoneDate,
                }
              : acc,
          {}
        );
        milestonesColumns = { ...milestonesColumns, ...milestonesColumn };
      });
      return {
        ...nvtArea,
        ...milestonesColumns,
        status,
      };
    });
  }
}
Dashboard
import {
  AfterViewInit,
  Component,
  OnChanges,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import { TableComponent } from '../../table/table.component';
import { AreabarComponent } from '../areabar/areabar.component';
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss'],
})
export class DashboardComponent implements AfterViewInit {
  selectedRowsEvent($event: any) {
    throw new Error('Method not implemented.');
  }
  @ViewChild(TableComponent)
  selectedRows = [];
  private tableComponent!: TableComponent;
  public selectedGigaArea:
<details>
<summary>英文:</summary>
I have found this [problem][1] that is like mine. Yet, my implementation does not work despite following the steps.  
I have the following component structure:  
- Dashboard 
  - ActionButton 
  - Milestone
     - Table
  - SupplierSearch
     - Table
I have tried to pass array `selectedRows` from **Table** to **Dashboard**, and then to **ActionButton** using the `CustomEvent` and `elRef.nativeElement.dispatchEvent`. When I tried to console.log to see if it is passed to any parent components(**Dashboard** or **Milestone**) from **Dashboard/Milestone/Table**, the array simply does not get passed.  
Please note that my code is super dirty right now because I have been trying to resolve this issue for almost a day and tried many ways to resolve it. Please focus on the my way to  implement this mentioned [solution][2] (`CustomEvent` `elRef.nativeElement.dispatchEvent`)   
I really appreciate the Stackoverflow community for the shared knowledge, thus, please don't downgrade this post if my English is bad or something is inherently wrong with my problem.
Table
```typescript
import {
  Component,
  ElementRef,
  EventEmitter,
  Input,
  Output,
  TemplateRef,
} from '@angular/core';
import { TableColumnHeader } from './models/table-column-header';
@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.scss'],
})
export class TableComponent {
  @Input() rowData;
  @Input() headers: TableColumnHeader[] = [];
  @Input() columnTemplate: TemplateRef<any>;
  @Input() loading: boolean = false;
  @Output() selectedRowsEvent = new EventEmitter<any[]>();
  selectedRows = [];
  constructor(private elRef: ElementRef) {}
  onRowSelect(event) {
    this.selectedRows.push(event.data);
    this.selectedRowsEvent.emit(this.selectedRows);
    const evt = new CustomEvent('myCustomEvent', {
      bubbles: true,
      detail: event,
    });
    this.elRef.nativeElement.dispatchEvent(evt);
    console.log(this.selectedRows);
    console.log(event);
    console.log('from table onRowSelected ');
  }
  onRowUnselect(event) {
    this.selectedRows = this.selectedRows.filter(
      (x) => x.nvtAreaName !== event.data.nvtAreaName
    );
    this.selectedRowsEvent.emit(this.selectedRows);
    console.log(this.selectedRows);
    console.log('from table onRowUnselected ');
  }
  // onPage(event) {
  //   this.selectedRows = [];
  //   this.selectedRowsEvent.emit(this.selectedRows);
  // }
}
Table Template
<ng-template #columnTemplate let-rowObject="rowObject" let-id="id">
  <ng-container [ngSwitch]="id">
    <span *ngSwitchDefault>{{ rowObject[id] | translate }}</span>
  </ng-container>
</ng-template>
<ng-template #dateColumnTemplate let-rowObject="rowObject" let-id="id">
  <ng-container [ngSwitch]="id">
    <span *ngSwitchDefault>{{ rowObject[id] | localizedDate }}</span>
  </ng-container>
</ng-template>
<p-table
  (onRowSelect)="onRowSelect($event)"
  (onRowUnselect)="onRowUnselect($event)"
  [paginator]="true"
  [rows]="10"
  [showCurrentPageReport]="true"
  currentPageReportTemplate="{{ 'PAGINATION' | translate }}"
  [rowsPerPageOptions]="[10]"
  [value]="rowData"
  [loading]="loading"
  [tableStyle]="{ 'min-width': '79rem' }"
>
  <ng-template pTemplate="header">
    <tr>
      <th style="width: 4rem">
        <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
      </th>
      <ng-container *ngFor="let header of headers">
        <th
          *ngIf="header.sortable; else simpleHeader"
          [pSortableColumn]="header.id"
        >
          {{ header.value | translate }}
          <p-sortIcon [field]="header.id"></p-sortIcon>
        </th>
        <ng-template #simpleHeader>
          <th>
            {{ header.value | translate }}
          </th>
        </ng-template>
      </ng-container>
    </tr>
  </ng-template>
  <ng-template pTemplate="body" let-rowObject>
    <tr>
      <td>
        <p-tableCheckbox [value]="rowObject"></p-tableCheckbox>
      </td>
      <td *ngFor="let header of headers">
        <ng-container
          [ngTemplateOutlet]="
            header?.date ? dateColumnTemplate : columnTemplate
          "
          [ngTemplateOutletContext]="{ rowObject: rowObject, id: header.id }"
        ></ng-container>
      </td>
    </tr>
  </ng-template>
</p-table>
Milestone
import {
  AfterViewInit,
  Component,
  Inject,
  Input,
  OnChanges,
  OnInit,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import { TableColumnHeader } from '../../table/models/table-column-header';
import { NvtAreaDataSource } from '../../../services/nvt-area-data-source.service';
import { AreaProgramDataSource } from '../../../services/area-program-data-source.service';
import { MilestoneTableColumn } from '../../../models/business/milestone-table-column';
import { TableComponent } from '../../table/table.component';
@Component({
  selector: 'app-milestone-search',
  templateUrl: './milestone-search.component.html',
  styleUrls: ['./milestone-search.component.scss'],
})
export class MilestoneSearchComponent
  implements OnInit, OnChanges, AfterViewInit
{
  @Input() selectedGigaArea: string;
  milestoneData = [];
  loading: false;
  private tableComponent!: TableComponent;
  selectedRows = [];
  @Input() pSelectableRows = [];
  @ViewChild(TableComponent)
  columnHeaders: TableColumnHeader[] = [
    { value: 'ONKZ', id: 'onkz', sortable: true },
    { value: 'NVT', id: 'nvtAreaName', sortable: true },
    { value: 'STATUS', id: 'status', sortable: true },
    { value: 'ARVM_START', id: 'arvMStart', date: true },
    { value: 'EXP.ROUGH_START', id: 'expRoughStart', date: true },
    { value: 'EXP.ROUGH_END', id: 'expRoughEnd', date: true },
    { value: 'EXP.FINE_START', id: 'expFineStart', date: true },
    { value: 'EXP.FINE_END', id: 'expFineEnd', date: true },
    { value: 'RM_START', id: 'rmStart', date: true },
    { value: 'AFTER_INST_START', id: 'afterInstStart', date: true },
    { value: 'AFTER_INST_END', id: 'afterInstEnd', date: true },
  ];
  constructor(
    @Inject(NvtAreaDataSource) private nvtAreaDataSource,
    @Inject(AreaProgramDataSource) private areaProgramDataSource
  ) {}
  ngOnInit(): void {
    this.nvtAreaDataSource.connect().subscribe((nvtAreas) => {
      this.milestoneData = [...nvtAreas];
    });
    this.areaProgramDataSource.connect().subscribe((areaPrograms) => {
      this.milestoneData = this.mergeMilestonesData(
        this.milestoneData,
        areaPrograms
      );
    });
  }
  ngOnChanges(changes: SimpleChanges) {
    if (changes.date) {
      // this.pSelectableRows = changes.date.currentValue;
      this.selectedRows = changes.data.currentValue;
    }
    console.log('from milestone onChanges  ' + this.selectedRows.length);
  }
  ngAfterViewInit(): void {
    this.selectedRows = this.tableComponent.selectedRows;
    console.log('from ngAfterViewInit ');
  }
  onNotify(rowsEmitted: any[]): void {
    console.log('from milestone onNotify ');
    this.selectedRows = rowsEmitted;
  }
  mergeMilestonesData(nvtAreas, areaPrograms) {
    return nvtAreas.map((nvtArea) => {
      const areaProgram = areaPrograms.find(
        (x) => x.nvtAreaId === nvtArea.nvtAreaId
      );
      if (!areaProgram) return nvtArea;
      const { status, milestones } = areaProgram;
      let milestonesColumns = {};
      milestones.map((milestone) => {
        const milestonesColumn = Object.entries(
          new MilestoneTableColumn()
        ).reduce(
          (acc, [key, value]) =>
            value === milestone.milestoneType
              ? {
                  ...acc,
                  [key]: milestone.milestoneDate,
                }
              : acc,
          {}
        );
        milestonesColumns = { ...milestonesColumns, ...milestonesColumn };
      });
      return {
        ...nvtArea,
        ...milestonesColumns,
        status,
      };
    });
  }
}
Milestone template
<app-table
  (selectedRowsEvent)="onNotify($event)"
  [pSelectableRows]="forms.get('selectedRows')"
  *ngIf="milestoneData?.length"
  [rowData]="milestoneData"
  [headers]="columnHeaders"
  [loading]="loading"
>
</app-table>
Dashboard
import {
  AfterViewInit,
  Component,
  OnChanges,
  SimpleChanges,
  ViewChild,
} from '@angular/core';
import { TableComponent } from '../../table/table.component';
import { AreabarComponent } from '../areabar/areabar.component';
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss'],
})
export class DashboardComponent implements AfterViewInit {
  selectedRowsEvent($event: any) {
    throw new Error('Method not implemented.');
  }
  @ViewChild(TableComponent)
  selectedRows = [];
  private tableComponent!: TableComponent;
  public selectedGigaArea: string;
  public totalElements: number;
  @ViewChild(AreabarComponent) areabarComponent: AreabarComponent;
  constructor() {}
  ngAfterViewInit(): void {
    this.selectedRows = this.tableComponent.selectedRows;
    console.log('from ngAfterViewInit ');
  }
  // ngOnChanges(changes: SimpleChanges): void {
  //   console.log('from Dashboard ngOnChanges ');
  //   throw new Error('Method not implemented.');
  // }
  onNotify(rowsEmitted: any[]): void {
    console.log('from Dashboard onNotify ');
    this.selectedRows = rowsEmitted;
  }
}
Dashboard template
<div class="dashboard-wrapper">
  <div id="giga-areas" class="giga-areas">
    <app-areabar
      (selectedGigaAreaEvent)="selectedGigaArea = $event"
      (totalElementsChanged)="totalElements = $event"
    ></app-areabar>
  </div>
  <div class="search-wrapper">
    <h1 class="page-header">{{ "SEARCH.ROLLOUT_PROJECT" | translate }}</h1>
    <nav class="nav-bar">
      <mat-button-toggle-group
        #toggleGroup="matButtonToggleGroup"
        class="toggle-btn"
      >
        <mat-button-toggle value="supplier" checked>{{
          "SUPPLIERS" | translate
        }}</mat-button-toggle>
        <mat-button-toggle value="milestone">{{
          "MILESTONES" | translate
        }}</mat-button-toggle>
      </mat-button-toggle-group>
      <app-action-button (myCustomEvent)="onNotify($event)"></app-action-button>
    </nav>
    <div [className]="toggleGroup.value === 'supplier' || 'hide'">
      <app-supplier-search
        class="nvt-search"
        [selectedGigaArea]="selectedGigaArea"
      ></app-supplier-search>
    </div>
    <div [className]="toggleGroup.value === 'milestone' || 'hide'">
      <app-milestone-search
        (selectedRowsEvent)="onNotify($event)"
        class="nvt-search"
        [selectedGigaArea]="selectedGigaArea"
      ></app-milestone-search>
    </div>
    <div *ngIf="!selectedGigaArea" class="infoText">
      {{ "VIEW_EDIT_NVT_AREA" | translate }}
    </div>
    <div *ngIf="!selectedGigaArea && totalElements > 20" class="infoText">
      {{ "GIGAAREA_OVERLOAD_MESSAGE" | translate }}
    </div>
  </div>
  <app-search class="nvt-search" [selectedGigaArea]="selectedGigaArea">
  </app-search>
</div>
ActionButton
import {
  Component,
  Inject,
  Input,
  OnChanges,
  OnInit,
  SimpleChanges,
} from '@angular/core';
import { AuthenticationProvider } from 'src/app/services/auth/auth-service.injection-token';
import { AuthService } from 'src/app/services/auth/auth.service';
import { MatDialog } from '@angular/material/dialog';
import { PopupsComponent } from '../shared/popups/popups.component';
import { Router } from '@angular/router';
const backUrl = '/home';
const createrolloutprojects = '/createrolloutprojects';
const changeMilestonesUrl = '/changemilestones';
const changeSupplierUrl = '/changesupplier';
const viewDetailsUrl = '/viewdetails';
@Component({
  selector: 'app-action-button',
  templateUrl: './action-button.component.html',
  styleUrls: ['./action-button.component.scss'],
})
export class ActionButtonComponent implements OnInit, OnChanges {
  selectedRows = [];
  constructor(
    public dialog: MatDialog,
    @Inject(AuthenticationProvider)
    private permissionService: AuthService,
    private router: Router
  ) {}
  ngOnInit(): void {
    this.router.navigate([backUrl]);
    console.log('from action button component ');
    console.log(this.selectedRows);
  }
  ngOnChanges(changes: SimpleChanges): void {
    console.log('ngonchanges trigged ');
    console.log(this.selectedRows);
  }
  getPermission(permissionKey: string): boolean {
    return !this.permissionService.hasPermission(permissionKey);
  }
  onNotify(rowsEmitted: any[]): void {
    console.log('from action button onNotify');
    this.selectedRows = rowsEmitted;
  }
  openPopupDialog(): void {
    console.log('from openPopupDialog');
    const dialogRef = this.dialog.open(PopupsComponent, {
      width: '900px',
      height: '404px',
      disableClose: true,
      autoFocus: false,
      data: {
        title: 'CHANGE_AREA_PROGRAM_STATE.TITLE',
        plainTextDescription:
          'CHANGE_AREA_PROGRAM_STATE.PLAIN_TEXT_DESCRIPTION',
        bulletPointDescription:
          'CHANGE_AREA_PROGRAM_STATE.BULLET_POINT_DESCRIPTION',
        linkText: '',
        externalLink: 'https://...', //<- url belonging to lintText
        info: 'CHANGE_AREA_PROGRAM_STATE.INFO',
      },
    });
    dialogRef.afterClosed().subscribe((result) => {
      console.log(result);
    });
  }
}
ActionButton template
<div>
  <button mat-button [matMenuTriggerFor]="menu">
    <!-- *ngIf="selectedRows.length > 0" -->
    {{ "MENU" | translate }}
  </button>
  <mat-menu #menu="matMenu">
    <button
      mat-menu-item
      (click)="openPopupDialog()"
      [disabled]="getPermission('PP_AREA_PROGRAM#COMMISSION')"
    >
      {{ "COMMISSIONED" | translate }}
    </button>
    <button
      mat-menu-item
      (click)="openPopupDialog()"
      [disabled]="getPermission('PP_AREA_PROGRAM#EXPANSION')"
    >
      {{ "EXPANSION.START" | translate }}
    </button>
    <button
      mat-menu-item
      (click)="openPopupDialog()"
      [disabled]="getPermission('PP_AREA_PROGRAM#CANCEL')"
    >
      {{ "CANCEL" | translate }}
    </button>
  </mat-menu>
</div>
答案1
得分: 0
以下是翻译好的部分:
无法使其传递的原因
- 如果传递复杂对象,则子组件上的Ngonchanges不会触发
 - 所以它停留在仪表板(父组件)处,无法传递到子组件
 
https://i.stack.imgur.com/NRjIa.png
- 解决方法是传递订阅对象
- 
另一个原因是未传递的原因之一:
> 我们有这个视图:
>
> - 仪表板
> - 操作按钮
> - 里程碑
> - 表
> - 供应商搜索
> - 表
>
> 我一直在通过表 -> 里程碑 -> 仪表板 -> 操作按钮传递
>
> 但是我一直在UI的供应商搜索视图上选择行,因此它从表从未传递到里程碑。 
解决方法
- 
通过事件发射器将数组传递到最上层的父组件(仪表板)
 - 
然后创建Subject$(可观察对象)以将复杂数据广播到子组件
> Subject就像一个Observable,但可以广播给多个观察者。Subjects就像EventEmitters:它们维护多个监听器的注册表。 — 来源 rxjs
>代码片段
dashboard.ts selectedRows$ = new Subject<any[]>(); onNotify(rowsEmitted: any[]): void { console.log('from Dashboard onNotify'); this.selectedRows = rowsEmitted; this.selectedRowsCount = rowsEmitted.length; console.log(this.selectedRows); this.selectedRows$.next(rowsEmitted); }- onNotify是将数组传递到父组件的最后一个函数
 - 然后创建匿名观察者并订阅(
next()) 
 - 
然后将Subject selectedRows$传递给子组件操作按钮
dashboard.html <app-action-button [selectedRows]="selectedRows$"> </app-action-button> - 
然后创建匿名观察者并订阅
action-button.ts ngOnInit(): void { // this.router.navigate([backUrl]); console.log('from action button component '); console.log(this.selectedRows); this.selectedRows.subscribe((selectedArray) => console.log('from action button ngOnInit: ' + selectedArray) ); 
英文:
Reasons why I could not get it passed
- Ngonchanges on the child component does not get triggered if pass complex object
 - so it stuck at the dashboard (parent component) and does not get passed to the child component
 
https://i.stack.imgur.com/NRjIa.png
- workaround is to pass the subscribe object
- 
another reason why it was not passed:
> We have this view:
>
> - Dashboard
> - ActionButton
> - Milestone
> - Table
> - SupplierSearch
> - Table
>
> I have been passing through table -> mileston -> dashboard -> actionbutton
>
> but I have been selecting rows on the SupplierSearch view of the table on the ui. thus it has never been passed to the Milestone from the Table
> 
Workaround
- 
pass the array up to the parent most component (Dashboard) with event emitters
 - 
then create the Subject$ (observable) two broadcast the complex data to the child component
> A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners. — source rxjs
>code snippet
dashboard.ts selectedRows$ = new Subject<any[]>(); onNotify(rowsEmitted: any[]): void { console.log('from Dashboard onNotify '); this.selectedRows = rowsEmitted; this.selectedRowsCount = rowsEmitted.length; console.log(this.selectedRows); this.selectedRows$.next(rowsEmitted); }- on notify is the last function in the chain to pass the array up to the parent component
 - then it create the anonymous observer and subscribe (
next()) 
 - 
Subject selectedRows$ then will be passed to the child component action button
dashboard.html <app-action-button [selectedRows]="selectedRows$"> </app-action-button> - 
it will the create the anonymous observer and subscribe
action-button.ts ngOnInit(): void { // this.router.navigate([backUrl]); console.log('from action button component '); console.log(this.selectedRows); this.selectedRows.subscribe((selectedArray) => console.log('from action button ngOnInit: ' + selectedArray) ); 
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论