カテゴリー
【Angular&rxjs】rxjs#fromEventとViewChildでコンポーネント間のデータ受け渡しを考えてみる
※ 当ページには【広告/PR】を含む場合があります。
2019/12/28
2022/08/10
Angular コンポーネント間 データ受け渡し
1. 親子関係のあるコンポーネント間で、
@Input/@Outputデコレーターを用いて
EventEmitter経由でデータをやり取りする方法
2. 親子関係のないコンポーネント間で、
サービスを利用して、
データを共有・監視する方法
構造ディレクティブ
動的コンポーネント
動作環境
$ ng --version
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 8.1.3
Node: 10.16.3
OS: linux x64
Angular: 8.1.3
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Package Version
-----------------------------------------------------------
@angular-devkit/architect 0.801.3
@angular-devkit/build-angular 0.801.3
@angular-devkit/build-optimizer 0.801.3
@angular-devkit/build-webpack 0.801.3
@angular-devkit/core 8.1.3
@angular-devkit/schematics 8.1.3
@ngtools/webpack 8.1.3
@schematics/angular 8.1.3
@schematics/update 0.801.3
rxjs 6.4.0
typescript 3.4.5
webpack 4.35.2
Ivy
プロジェクト構造
ng new
tree -I node_modules -L 5
.
├── README.md
├── angular.json
├── browserslist
├── e2e
├── karma.conf.js
├── package.json
├── src
│ ├── app
│ │ ├── app.component.html
│ │ ├── app.component.scss
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── components
│ │ │ ├── my-lazyloaded-compo
│ │ │ │ ├── my-lazyloaded-compo.component.scss
│ │ │ │ └── my-lazyloaded-compo.component.ts
│ │ │ └── my-happy-compo
│ │ │ ├── my-happy-compo.component.scss
│ │ │ └── my-happy-compo.component.ts
│ │ └── factories
│ │ └── lazyloading-compo-factory.service.ts
│ ├── assets
│ ├── environments
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.scss
│ └── test.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
├── tslint.json
└── yarn.lock
my-lazyloaded-compo
my-happy-compo
lazyloading-compo-factory
$ yarn ng g c components/myLazyloadedCompo # 任意の位置で生成可能な動的コンポーネント
$ yarn ng g c components/myHappyCompo # (通常の)静的コンポーネント
$ yarn ng g s factories/lazyloadingCompoFactory # 動的コンポーネントを生成するファクトリーサービス
コンポーネントの作成・実装
my-happy-compo
LazyloadingCompoFactory
import { Component, OnInit, ViewChild, ViewContainerRef, ElementRef } from '@angular/core';
import { LazyloadingCompoFactory } from '../../factories/lazyloading-compo-factory.service.ts';
@Component({
selector: 'app-my-happy-compo',
template: `
<div>
<p #message_from_children>You're now selecting...:</p>
</div>
<ng-container #dynamic></ng-container>
`,
styleUrls: ['./my-happy-compo.component.scss']
})
export class MyHappyCompoComponent implements OnInit {
@ViewChild('dynamic', {
read: ViewContainerRef,
static: true
}) vc: ViewContainerRef;
@ViewChild('message_from_children', { static: true }) message: ElementRef;
constructor(
private lcf: LazyloadingCompoFactory
) { }
ngOnInit() {
this.lcf.setRootViewContainerRef(this.vc);
for (let i = 0 ; i < 100 ; i++) {
this.lcf.addDynamicComponent({name: 'Compo' + i, id: i}, this.message);
}
}
}
親コンポーネント
頭コンポーネント
#dynamic
#message_from_children
@ViewChild
#dynamic
<ng-container>
LazyloadingCompoFactory
#message_from_children
InnerText
my-lazyloaded-compo
(静的な)子コンポーネント
頭コンポーネント
肢体コンポーネント
import { Component, Input, AfterViewInit, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Subscription, fromEvent } from 'rxjs';
import { mapTo, debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-my-lazyloaded-compo',
template: `
<div class="box-style" #reactiveBox>
<p>Hello, {{data.name}}!!</p>
<p>Lazy-loaded Compo {{data.id}}</p>
</div>
`,
styleUrls: ['./my-child-lazyloaded-compo1.component.scss']
})
export class MyLazyloadedCompoComponent implements AfterViewInit, OnDestroy {
@Input() data: any;
@Input() elemInParent: ElementRef;
@ViewChild('reactiveBox', { static: false }) reactBox: ElementRef;
private subscription: Subscription;
constructor() { }
ngAfterViewInit(): void {
const terms$ = fromEvent<any>(this.reactBox.nativeElement, 'click').pipe(
mapTo(this.data),
debounceTime(1000),
distinctUntilChanged()
);
this.subscription = terms$.subscribe(inner_data => {
if (this.elemInParent) {
this.elemInParent.nativeElement.innerHTML = 'You\'re now selecting...:' + JSON.stringify(inner_data);
}
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
.box-style {
padding: 40px 16px;
display: block;
border: 1px solid #ccc;
margin-bottom: 16px;
}
app.module.ts
entryComponents
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { MyHappyCompoComponent } from './components/my-happy-compo/my-happy-compo.component';
import { MyChildLazyloadedCompo1Component } from './components/my-lazyloaded-compo/my-lazyloaded-compo.component';
@NgModule({
declarations: [
AppComponent,
MyHappyCompoComponent,
MyLazyloadedCompoComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [
MyLazyloadedCompoComponent // ✍ 追加
]
})
export class AppModule { }
@ngModule
entryComponents
EventEmitterの代わりのfromEvent
EventEmitter
Rxjs
EventEmitter
Rxjs
...
const terms$ = fromEvent<any>(
this.reactBox.nativeElement, 'click' // コンポーネントのDOM要素がクリックされたら...
).pipe(
mapTo(this.data), // クラスメンバのdataを送信
debounceTime(1000), // その後1s間は他から着火したイベントは受け付けない
distinctUntilChanged() // 送信されたdataが既に送信済みなら棄却(dataは一回だけ採用)
);
...
Rxjs
Rxjs
lazyloadingCompoFactory
import { Injectable, ComponentFactoryResolver, ViewContainerRef, ElementRef } from '@angular/core';
import { Subject } from 'rxjs';
import { MyLazyloadedCompoComponent } from '../components/my-lazyloaded-compo/my-lazyloaded-compo.component';
@Injectable({
providedIn: 'root'
})
export class LazyloadingCompoFactory {
rootViewContainer: ViewContainerRef;
constructor(
private cfr: ComponentFactoryResolver
) { }
public setRootViewContainerRef(viewContainerRef: ViewContainerRef) {
this.rootViewContainer = viewContainerRef;
}
public addDynamicComponent(data: any, elemInParentComp?: ElementRef) {
const factory = this.cfr.resolveComponentFactory(MyLazyloadedCompoComponent);
const component = this.rootViewContainer.createComponent(factory);
(component.instance as MyLazyloadedCompoComponent).data = data;
if (elemInParentComp) {
(component.instance as MyLazyloadedCompoComponent).elemInParent = elemInParentComp;
}
this.rootViewContainer.insert(component.hostView);
}
}
ビルド〜動作
app.component.html
<app-my-happy-compo></app-my-happy-compo>
まとめ
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー