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

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

コメント

このブログの人気の投稿

[TypeScript] Try React 1

Intro This time I tried React. First, I referred Documents and Tutorial. React – A JavaScript library for building user interfaces GitHub - facebook/react: A declarative, efficient, and flexible JavaScript library for building user interfaces. Getting started with React - Learn web development | MDN Create a project To create a new React project, I used "Create React App". GitHub - facebook/create-react-app: Set up a modern web app by running one command. It created these files. .git node_modules public L favicon.ico L index.html L logo192.png L logo512.png L manifest.json L robots.txt src L App.css L App.test.tsx L App.tsx L index.css L index.test.tsx L index.tsx L logo.svg L react-app-env.d.ts L serviceWorker.ts L setupTests.ts .gitignore package.json package-lock.json README.md tsconfig.json Make the page work with IE11 I could view the page by "npm start" or "npm run start".   ...

[PostgreSQL] Play with TypeORM 1

Intro This time, I tried accessing Database by TypeORM. Because I wanted to manage Database tables by ORM, I created a table first.  GitHub - typeorm/typeorm   TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Installation and creating a project Installation TypeORM needed "reflect-metadata" and database driver. npm install --save typeorm reflect-metadata pg typescript tsc npm install --save-dev @types/node And I also installed "ts-node" to skip compiling. npm install --save ts-node Preparing References I could generate a TypeORM project by TypeORM command. npx typeorm init --name gen-typeorm-sample --database postgres I refered Most of all settings from it. Adding files and folders First, I added tsconfig.json. npx tsc --init And I edited like below. tsconfig.json { "compilerOptions": { /* Basic Options */ "incremental": true, "target": "es5", "module": "comm...

[Angular 10 + NgRx + Nest.js] Create project

Intro This time, I added server-side. I choosed Nest.js because I love TypeScript and I had been interested in using TypeScript in server-side programming. Environments Angular ver.10.0.2 @ngrx/store ver.9.2.0 @ngrx/effects ver.9.2.0 @nestjs/ng-universal ver.2.0.1 Couldn't add @ngrx/store into the Angular 10 project First, I created an Angular project and tried adding @ngrx/store into it. And I got an error. Installing packages for tooling via npm. Installed packages for tooling via npm. The package that you are trying to add does not support schematics. You can try using a different version of the package or contact the package author to add ng-add support. According to the issue below, I should add "@latest". npx ng add @ngrx/store@latest Because if I didn't add it, the @ngrx/store version was too low.    cannot use ng add to install ngrx store · Issue #2604 · ngrx/platform · GitHub Though I didn't know I should add "@latest" into ...