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

[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] 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...

[Angular] Sending file with Observable and showing loading screen

Intro When I tried sending file data on last time, I had confused with "promise.then", "async/await" and "Observable". [Angular][ASP.NET Core] Upload chunked files So I wanted to distinct them, and this time, I tried to use "Observable" because HttpClient return Observable<any>. Call observables in order I sended file in these steps. Read file by FileReader Create directory for saving chunks send and saving chunks merge chunks to one file and delete chunks Each steps used the former steps result. So I could write by Promise.then like below. this.executeStep1() // return Promise<UploadResult> .then(result => this.executeStep2(result)) // return Promise<UploadResult> .then(result => this.executeStep3(result)) // return Promise<UploadResult> .catch(reason => console.log(reason)); Result I could write with pipe & flatMap. file-uploader.service.ts public upload(file: File): Observable<U...