スキップしてメイン コンテンツに移動

[Angular]Transfer data between the components

Create pages with Angular

Create pages with Angular 1
Create pages with Angular 2
Create pages with Angular 3

What I did

  1. getting/setting data for select tag
  2. transfer data between parent component and child components

getting/setting data for select tag

I got list items from service and added options to selectbox.

favorite.ts


export interface Favorite {
  displayValue: string;
  favoriteId: number;
}

favorite.service.ts


import {Injectable} from '@angular/core';
import {Favorite} from './favorite';
@Injectable({
  providedIn: 'root'
})
export class FavoriteService {
  public static favorites: Array<Favorite> = [
    { displayValue: '☆', favoriteId: 1},
    { displayValue: '☆☆', favoriteId: 2},
    { displayValue: '☆☆☆', favoriteId: 3},
  ];
  constructor() { }
}

product-cell.component.ts


import { Component, OnInit, Input } from '@angular/core';
import {CellComponent} from '../cell.component';
import {Product} from '../../../models/product';
import {ProductCategory} from '../../../models/product-category';
import {Favorite} from '../favorite';
import {FavoriteService} from '../favorite.service';

@Component({
  selector: 'app-product-cell',
  templateUrl: './product-cell.component.html',
  styleUrls: ['./product-cell.component.css']
})
export class ProductCellComponent implements OnInit, CellComponent {
  @Input() products: Product[];
  @Input() category: ProductCategory;
  public favorites = new Array<Favorite>();
  constructor() {
    this.favorites = FavoriteService.favorites;
  }
  ngOnInit() {
  }
}

product-cell.component.html


<div class="product-cell-frame">
    <div class="product-cell-left">
      <div class="product-cell-ranking">
        {{products[0].rankingNo}}
      </div>
      <div class="product-cell-favorite">
        <select>
          <option *ngFor="let favorite of favorites" [value]="favorite.favoriteId">
            {{favorite.displayValue}}
          </option>
        </select>
      </div>
    </div>
    <div class="product-cell-right">
        <div class="product-cell-item-name">
            {{products[0].name}}
        </div>
        <div class="product-cell-category">
            {{category.name}}
        </div>
    </div>
</div>


I could get selectbox value from ngModel.

product-cell.component.ts


...
export class ProductCellComponent implements OnInit, CellComponent {
  @Input() products: Product[];
  @Input() category: ProductCategory;
  public favorites = new Array<Favorite>();
  public selectedFavoriteId: number;
  constructor() {
    this.favorites = FavoriteService.favorites;
  }
...
}

product-cell.component.html


<div class="product-cell-frame">
    <div class="product-cell-left">
      <div class="product-cell-ranking">
        {{products[0].rankingNo}}
      </div>
      <div class="product-cell-favorite" [(ngModel)]="selectedFavoriteId">
        <select>
          <option *ngFor="let favorite of favorites" [value]="favorite.favoriteId">
            {{favorite.displayValue}}
          </option>
        </select>
      </div>
    </div>
    <div class="product-cell-right">
        <div class="product-cell-item-name">
            {{products[0].name}}
        </div>
        <div class="product-cell-category">
            {{category.name}}
        </div>
    </div>
</div>


When I changed the selectbox value, "selectedFavoriteId" got "favorite.favoriteId".
And I could only get basic type value as ngModel.
When I changed the option value like "[value]="favorite"", I couldn't get the value of "Favorite" type.

Selecting index

How about selecting the index by script?
It only needed to set value to "selectedFavoriteId".

product-cell.component.ts


...
export class ProductCellComponent implements OnInit, CellComponent {
  @Input() products: Product[];
  @Input() category: ProductCategory;
  public favorites = new Array<Favorite>();
  public selectedFavoriteId: number;
  constructor() {
    this.favorites = FavoriteService.favorites;
    this.selectedFavoriteId = this.favorites[1].favoriteId;
  }
...
}


How about the value what wasn't included in the options?
The value of "selectedFavoriteId" kept value, but the selectbox was empty.

Transfer data between parent component and child components

Sometimes, I wanted to set values for child components after their constructors and ngOnInits fired.
If I used the values only at html templates, I just needed to set values.
But I had to generate values after setting values, I must call methods of child components.

ngOnChanges

Because I couldn't call methods through @Input(), I tried to use ngOnChanges.

product-ranking-block.component.ts


import {Component, OnInit, Input, ComponentFactoryResolver, ViewChild, OnChanges, SimpleChanges} from '@angular/core';
import {ProductCellDirective} from '../../../product-cell.directive';
import {ProductCellItem} from '../../product-cell-item';
import {CellComponent} from '../cell.component';

@Component({
  selector: 'app-product-ranking-block',
  templateUrl: './product-ranking-block.component.html',
  styleUrls: ['./product-ranking-block.component.css']
})
export class ProductRankingBlockComponent implements OnInit, OnChanges {
  @Input() cells: ProductCellItem[];
  @ViewChild(ProductCellDirective, {static: true}) cellHost: ProductCellDirective;
  private components = new Array<CellComponent>();
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }
  ngOnInit() {
    this.load();
  }
  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }
...


When I had changed the "cells", "ngOnChanges" was called.

product-ranking.component.ts


import {Component, OnInit} from '@angular/core';
import {Product} from 'src/app/models/product';
import {ProductCellService} from './product-cell.service';
import {ProductCellItem} from '../product-cell-item';

@Component({
  selector: 'app-product-ranking',
  templateUrl: './product-ranking.component.html',
  styleUrls: ['./product-ranking.component.css']
})
export class ProductRankingComponent implements OnInit {
  rankingFirst: ProductCellItem[];

  constructor(private cellService: ProductCellService) {
    this.rankingFirst = cellService.generateProductCellItems(
      [
        {id: 1, rankingNo: 1, name: 'Pixel 4', categoryId: 1} as Product,
        {id: 2, rankingNo: 1, name: 'Pixel 4 XL', categoryId: 1} as Product,
        {id: 3, rankingNo: 2, name: 'ng-book', categoryId: 2} as Product,
        {id: 4, rankingNo: 2, name: 'RxJs in Action', categoryId: 2} as Product,
        {id: 5, rankingNo: 3, name: 'Surface 6', categoryId: 0} as Product,
        {id: 6, rankingNo: 4, name: 'Adaptive Code', categoryId: 2} as Product,
      ],
      [
        { id: 0, name: 'computer'},
        { id: 1, name: 'phone'},
        { id: 2, name: 'e-book'}
      ]
    );
  }
...
  changeRanking() {
    this.rankingFirst = this.cellService.generateProductCellItems(
      [
        {id: 1, rankingNo: 1, name: 'Pixel 4', categoryId: 1} as Product
      ],
      [
        { id: 0, name: 'computer'}
      ]
    );
  }
}

The arcument of "ngOnChanges" was like below.
{…}
    cells: {…}
        currentValue: (1) […]
            0: Object { component: ProductCellComponent(), products: (1) […], category: undefined }
            length: 1
            <prototype>: Array []
        firstChange: false
        previousValue: (5) […]
            0: Object { component: ProductCellComponent(), products: (1) […], category: {…} }
            1: Object { component: ProductCellComponent(), products: (1) […], category: {…} }
            2: Object { component: ProductMergedCellComponent(), products: (2) […], category: {…} }
            3: Object { component: ProductCellComponent(), products: (1) […], category: {…} }
            4: Object { component: ProductMergedCellComponent(), products: (1) […], category: {…} }
            length: 5
            <prototype>: Array []
        <prototype>: Object { isFirstChange: isFirstChange(), … }
    <prototype>: Object { … }
Because the input value had been updated and I hadn't needed comparing with previous values, I could use "cells" directoly.
Component Interaction - Angular

use #

I could get child components data with #.

product-ranking.component.html


<div id="product-ranking-area">
    <div id="product-ranking-frame">
        <h3 id="product-ranking-title">
            My ranking
        </h3>
        <div id="product-ranking-group">
            <app-product-ranking-block [cells]="rankingFirst" #ranking ></app-product-ranking-block>
        </div>
    </div>
</div>
<button (click)="changeRanking(ranking.message)">ranking button</button>

product-ranking-block.component.ts


import {Component, OnInit, Input, ComponentFactoryResolver, ViewChild, OnChanges, SimpleChanges} from '@angular/core';
import {ProductCellDirective} from '../../../product-cell.directive';
import {ProductCellItem} from '../../product-cell-item';
import {CellComponent} from '../cell.component';

@Component({
  selector: 'app-product-ranking-block',
  templateUrl: './product-ranking-block.component.html',
  styleUrls: ['./product-ranking-block.component.css']
})
export class ProductRankingBlockComponent implements OnInit, OnChanges {
  @Input() cells: ProductCellItem[];
  @ViewChild(ProductCellDirective, {static: true}) cellHost: ProductCellDirective;
  public message = 'hello';
  private components = new Array<CellComponent>();
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }
...

product-ranking.component.ts


import {Component, OnInit} from '@angular/core';
import {Product} from 'src/app/models/product';
import {ProductCellService} from './product-cell.service';
import {ProductCellItem} from '../product-cell-item';

@Component({
  selector: 'app-product-ranking',
  templateUrl: './product-ranking.component.html',
  styleUrls: ['./product-ranking.component.css']
})
export class ProductRankingComponent implements OnInit {
...
    changeRanking(message: string) {
        console.log(message); // got 'hello'
...
  }
}


Get data from child components what had been generated dynamically

Last time, I generated components with "ComponentFactoryResolver".
After generating, how I could get data from child components?

I couldn't use #.
So I kept child components instances when they had been created.

product-ranking-block.component.ts


import {Component, OnInit, Input, ComponentFactoryResolver, ViewChild, OnChanges, SimpleChanges} from '@angular/core';
import {ProductCellDirective} from '../../../product-cell.directive';
import {ProductCellItem} from '../../product-cell-item';
import {CellComponent} from '../cell.component';

@Component({
  selector: 'app-product-ranking-block',
  templateUrl: './product-ranking-block.component.html',
  styleUrls: ['./product-ranking-block.component.css']
})
export class ProductRankingBlockComponent implements OnInit, OnChanges {
  @Input() cells: ProductCellItem[];
  @ViewChild(ProductCellDirective, {static: true}) cellHost: ProductCellDirective;
  private components = new Array<CellComponent>();
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }
...
  public load() {
    const viewContainerRef = this.cellHost.viewContainerRef;
    viewContainerRef.clear();
    this.components = [];

    for (const cell of this.cells) {
      const factory = this.componentFactoryResolver.resolveComponentFactory(cell.component);
      const componentRef = viewContainerRef.createComponent(factory);
      const component = componentRef.instance as CellComponent;
      this.components.push(component);
      component.products = cell.products;
      component.category = cell.category;
    }
  }
  public changeSample() {
    for (const cell of this.components) {
      // I could get child components data.
    }
  }
}


Though I could get access to child components in this way, I didn't know I should do this or not.
If the data wouldn't be changed by child components, I could keep the data on parent component.

Next time, I will try using events.

コメント

このブログの人気の投稿

[Angular][ASP.NET Core] Upload chunked files

Intro I wanted to send files to Web application (made by ASP.NET Core). If the file size had been small, I didn't need do any special things. But when I tried to send a large file, the error was occurred by ASP.NET Core's limitation. Though I could change the settings, but I didn't want to do that, because I hadn't known the file sizes what would been actually using. So I splitted the data into chunks first, and sent them. After receiving all chunks, I merged them into one file. There might be some libraries or APIs (ex. Stream API) what did them automatically, but I couldn't find them. What I did [ASP.NET Core] Make CORS enabled [Angular] Split a large file into chunks [Angular][ASP.NET Core] Send and receive data as form data [ASP.NET Core] Merge chunks into one file [ASP.NET Core] Make CORS enabled Because the client side application(Angular) and the server side application(ASP.NET Core) had been separated, I had to make CORS(Cross-Origin Requests)

[Nest.js] Use WebSocket with ws

Intro Until last time , I had used node-web-rtc to try WebRTC. But because the example was a little complicated for I understood the core functions of using WebRTC. So I look for other frameworks or libraries. PeerJS is a famous library for WebRTC. peers/peerjs: Peer-to-peer data in the browser. - GitHub peers/peerjs-server: Server for PeerJS - GitHub PeerJS - Simple peer-to-peer with WebRTC A problem is I don't know how to integrate to the Nest.js project. I couldn't find examples. So I don't choose at least this time. What shall I choose? According MDN, WebRTC doesn't specify strictly what technology is used on server application for connecting two devices. Signaling and video calling - Web APIs | MDN But in many examples include MDN's one use WebSocket. samples-server/s/webrtc-from-chat at master · mdn/samples-server · GitHub So I try WebSocket in the Nest.js project. Use WebSocket in a Nest.js project Nest.js has a function for using We

[Nest.js] Show static files

Intro I wanted to use Nest.js and WebRTC(node-webrtc). NestJS - A progressive Node.js framework Documentation | NestJS - A progressive Node.js framework And because I wanted to try with simple page(not use JavaScript frameworks), I added static HTML, CSS, JavaScript into a Nest.js project. Prepare Install First, I installed @nestjs/cli. First steps | NestJS - A progressive Node.js framework As same as last time , I couldn't do global install because I had used Volta. But I could installed by volta. volta install @nestjs/cli Create project nest new nest-web-rtc-sample volta pin node@12 Run npm start After doing "npm start", I could getting "Hello World!" from http://localhost:3000. Add static files I could add static files by two ways. @nestjs/serve-static First one of them was using "serve-static". Serve Static | NestJS - A progressive Node.js framework npm install --save @nestjs/serve-static And I needed adding a module into app.modu