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

Create pages with Angular 3

Create pages with Angular

Create pages with Angular 1
Create pages with Angular 2

Data bindings with Pikaday

Last time, I didn't know how to use Two-way data bindings to the element what was set date by Pikaday.
According to the document, I could separate [(ngModel)]="dateText" like below.

Before


<input type="text" id="datepicker" [(ngModel)]="dateText">

After


<input type="text" id="datepicker" [value]="dateText"
    (input)="dateText=$event.target.value">


And because when Pikaday set text, the input event wasn't fired, thus I got nothing.
So I changed the event.


<input type="text" id="datepicker" [value]="dateText"
    (change)="dateText=$event.target.value">

Template Syntax - Angular

Background color for IE11

When I had set background-color like "background-color:#000000AA", the color wasn't shown by IE11.
So I had to write like below.

#frame-background{
    background-color: rgba(17, 17, 17, 0.67);
}


When I had used PostCSS, the color was automatically converted to the rgba(r, g, b, a) format.
So I fogot this :P

Use multiple providers

I had already used the provider to load the config file like this.

app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule }    from '@angular/common/http';
import { AppComponent } from './app.component';
import { TopPageComponent } from './top-page/top-page.component';
import { AppConfig } from './app-config.service';

export function initializeApp(settings: AppConfig) {
  return () => settings.load();
}
@NgModule({
  declarations: [
    AppComponent,
    TopPageComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
         useFactory: initializeApp,
         deps: [AppConfig], multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


If I wanted to use multiple providers, what should I do?
I just added the new one.

app.module.ts


...
export function initializeApp(settings: AppConfig) {
  return () => settings.load();
}
export function initializeApp2(itemLoader: ItemLoader) {
  return () => itemLoader.load();
}
@NgModule({
...
  providers: [
    {
      provide: APP_INITIALIZER,
         useFactory: initializeApp,
         deps: [AppConfig], multi: true
    },
    {
      provide: APP_INITIALIZER,
         useFactory: initializeApp2,
         deps: [ItemLoader], multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


If both of the classes were independ each other, there was no problem.
But if ItemLoader class's load() had to wait for finishing the AppConfig's load(), the exception would be occurred.

So I had to call in order.

app.module.ts


...
export function initializeApp(settings: AppConfig,
        itemLoader: ItemLoader) {
  return () => {
      settings.load()
        .then(_ => itemLoader.load());
  }
}
@NgModule({
...
  providers: [
    {
      provide: APP_INITIALIZER,
         useFactory: initializeApp,
         deps: [AppConfig, ItemLoader], multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


Dynamically switch child components

Resources

Dynamic Component Loader - Angular

I wanted to switch components by data conditions.
For example, I had these data of products.

product.ts


import {ProductCategory} from './product-category';
export class Product {
    public id: number;
    public rankingNo: number;
    public name: string;
    public categoryId: number;
}

product-category.ts


export interface ProductCategory {
  id: number;
  name: string;
}

products


[
  {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,
]

categories


[
  { id: 0, name: 'computer'},
  { id: 1, name: 'phone'},
  { id: 2, name: 'e-book'}
]


I wanted displaying like this.


These were the rule of display.
  • Show the items that had the ranking number from 1 to 4.
  • If the items' categories were "e-book", and the ranking numbers were same, the items would be merged.
  • The merged items would be set to "merged cell".

Create components 1

First, I created the components for outside of the cells.

product-ranking.component.html


<div id="product-ranking-area">
    <div id="product-ranking-frame">
        <div id="product-ranking-group">
            <app-product-ranking-block></app-product-ranking-block>
        </div>
    </div>
</div>

product-ranking.component.css


#product-ranking-area{
    background-color: blue;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 90%;
    width: 100%;
}
#product-ranking-frame{
    background-color: aliceblue;
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 96%;
    width: 96%;
}
#product-ranking-group{
    display: flex;
    flex-direction: row;
    height: 90%;
    width: 98%;
}
app-product-ranking-block{
    height: 98%;
    width: 30%;
}


import {Component, OnInit} from '@angular/core';
@Component({
  selector: 'app-product-ranking',
  templateUrl: './product-ranking.component.html',
  styleUrls: ['./product-ranking.component.css']
})
export class ProductRankingComponent implements OnInit {
  constructor() { }
  ngOnInit() { }
}

product-ranking-block.component.html


<div class="product-ranking-block">
    <div class="product-cell-title-frame">
        <div class="product-cell-title"></div>
        <div class="product-cell-edit-button">
            <button>Edit</button>
        </div>
    </div>
</div>>

product-ranking-block.component.css


.product-ranking-block{
    height: 100%;
    width: 100%;
}
.product-cell-title-frame{
    background-color: cadetblue;
    border: solid 1px black;
    border-radius: 5px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    height: 10%;
    width: 100%;
}
.product-cell-title{
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 50%;
}
.product-cell-edit-button{
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 50%;
}
.product-cell{
    height: 100%;
    width: 100%;
}

product-ranking-block.component.ts


import {Component, OnInit, Input} from '@angular/core';
@Component({
  selector: 'app-product-ranking-block',
  templateUrl: './product-ranking-block.component.html',
  styleUrls: ['./product-ranking-block.component.css']
})
export class ProductRankingBlockComponent implements OnInit {
  constructor() { }
  ngOnInit() { }
}


Create components 2

Next, I created the cell components and the interface to set data to cells.

product-cell.component.html


<div class="product-cell-frame">
    <div class="product-cell-left">
        {{products[0].rankingNo}}
    </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>

product-cell.component.css


.product-cell-frame{
    border: black solid 1px;
    border-radius: 5px;
    display: flex;
    flex-direction: row;
    min-height: 60px;
    width: 100%;
}
.product-cell-left{
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100%;
    width: 40%;
}
.product-cell-right{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100%;
    width: 60%;
}
.product-cell-item-name{
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 50%;
    width: 100%;
}
.product-cell-category{
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 50%;
    width: 100%;
}

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';

@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;
  constructor() { }
  ngOnInit() { }
}

product-merged-cell.component.html


<div class="product-merged-cell-frame">
  <div class="product-cell-left">
    {{product.rankingNo}}
  </div>
  <div class="product-cell-right">
    <div class="product-cell-item-name">
      {{product.name}}
    </div>
    <div class="product-cell-category">
      {{category.name}}
    </div>
  </div>
</div>

product-merged-cell.component.css


.product-merged-cell-frame{
  border: black solid 1px;
  border-radius: 5px;
  background-color: aquamarine;
  display: flex;
  flex-direction: row;
  min-height: 60px;
  width: 100%;
}
.product-cell-left{
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  width: 40%;
}
.product-cell-right{
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  width: 60%;
}
.product-cell-item-name{
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 50%;
  width: 100%;
}
.product-cell-category{
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 50%;
  width: 100%;
}

product-merged-cell.ts


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

@Component({
  selector: 'app-product-merged-cell',
  templateUrl: './product-merged-cell.component.html',
  styleUrls: ['./product-merged-cell.component.css']
})
export class ProductMergedCellComponent implements OnInit, CellComponent {
  @Input() products: Product[];
  @Input() category: ProductCategory;
  product = new Product();
  constructor() {
  }
  ngOnInit() {
    this.product.rankingNo = this.products[0].rankingNo;
    // merge all product names.
    this.product.name = this.products.map(p => p.name).join(',');
  }
}

cell.component.ts


import {Product} from '../../models/product';
import {ProductCategory} from '../../models/product-category';

export interface CellComponent {
  products: Product[];
  category: ProductCategory;
}


Generate data for loading components dynamically

For loading components dynamically, add entryComponents to app.module.ts

app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { TopPageComponent } from './top-page/top-page.component';
import { ApplicationSettingsService } from './application-settings.service';
import { ProductRankingComponent } from './top-page/product-ranking/product-ranking.component';
import { ProductCellComponent } from './top-page/product-ranking/product-cell/product-cell.component';
import { ProductRankingBlockComponent } from './top-page/product-ranking/product-ranking-block/product-ranking-block.component';
import { ProductMergedCellComponent } from './top-page/product-ranking/product-merged-cell/product-merged-cell.component';
...
@NgModule({
  declarations: [
    AppComponent,
    TopPageComponent,
    ProductRankingComponent,
    ProductCellComponent,
    ProductRankingBlockComponent,
    ProductMergedCellComponent,
    ProductCellDirective
  ],
  ...
  bootstrap: [AppComponent],
  entryComponents: [ProductCellComponent, ProductMergedCellComponent],
})
export class AppModule { }


And I added Directive to create entry point.
ng g directive product-cell

product-cell.directive.ts


import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[product-cell]'
})
export class ProductCellDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}


For loading the component and set data, I used a class.

product-cell-item.ts


import {Type} from '@angular/core';
import {CellComponent} from './product-ranking/cell.component';
import {Product} from '../models/product';
import {ProductCategory} from '../models/product-category';

export class ProductCellItem {
  constructor(public component: Type<CellComponent>, public products: Product[], public category: ProductCategory) {}
}


I created the service for creating "ProductCellItem".
ng g s top-page/product-ranking/product-cell

product-cell.service.ts


import {Injectable} from '@angular/core';
import {ProductCellItem} from '../product-cell-item';
import {ProductCellComponent} from './product-cell/product-cell.component';
import {Product} from '../../models/product';
import {ProductCategory} from '../../models/product-category';
import {ProductMergedCellComponent} from './product-merged-cell/product-merged-cell.component';

/** set product and category for generating ProductCellItem */
interface ProductItem {
  product: Product;
  category: ProductCategory;
}

@Injectable({
  providedIn: 'root'
})
export class ProductCellService {
  constructor() { }
  public generateProductCellItems(products: Product[], categories: ProductCategory[]): ProductCellItem[] {
    const cellItems = new Array<ProductCellItem>();
    const rankingNumbers = products.map(p => p.rankingNo);
    const rankingFrom = Math.min.apply(null, rankingNumbers);
    const rankingTo = Math.max.apply(null, rankingNumbers);
    for (let i = rankingFrom; i <= rankingTo; i++) {
      
      // group the products by ranking numbers.
      const sameRankingProducts = products.filter(p => p.rankingNo === i);
      if (sameRankingProducts.length <= 0) {
        continue;
      }

      // group the products by categories.
      const eBookProducts = new Array<ProductItem>();
      const otherProducts = new Array<ProductItem>();
      for (const product of sameRankingProducts) {
        if (product.categoryId === 2) {
          eBookProducts.push({product, category: this.getCategory(product.categoryId, categories)});
        } else {
          otherProducts.push({product, category: this.getCategory(product.categoryId, categories)});
        }
      }
      if (eBookProducts.length > 0) {
        cellItems.push(...this.generateMergedCellItems(eBookProducts));
      }
      if (otherProducts.length > 0) {
        cellItems.push(...this.generateDefaultCellItems(otherProducts));
      }
    }
    return cellItems;
  }
  /** find category */
  private getCategory(targetId: number, categories: ProductCategory[]): ProductCategory {
    for (const c of categories) {
      if (c.id === targetId) {
        return c;
      }
    }
  }
  /** generate cells for any categories exclude e-book */
  private generateDefaultCellItems(products: ProductItem[]): Array<ProductCellItem> {
    return products.map(p => new ProductCellItem(ProductCellComponent, [p.product], p.category));
  }
  /** generate cell for e-book */
  private generateMergedCellItems(products: ProductItem[]): Array<ProductCellItem> {
    const category = products[0].category;
    return [new ProductCellItem(ProductMergedCellComponent, products.map(p => p.product), category)];
  }
}


Generating ProductCellItems.

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';
...
export class ProductRankingComponent implements OnInit {
  rankingFirst: ProductCellItem[];

  constructor(private cellService: ProductCellService) {
    // create ranking data and set ProductCellItems.
    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'}
      ]
    );
  }
...

product-ranking.component.html


<div id="product-ranking-area">
    <div id="product-ranking-frame">
        <div id="product-ranking-group">
            <app-product-ranking-block [cells]="rankingFirst"></app-product-ranking-block>
        </div>
    </div>
</div>


Loading components dynamically

For using selector what had been declared by product-cell.directive.ts, I used the "ng-template" element.

product-ranking-block.component.html


<div class="product-ranking-block">
...
  <ng-template product-cell></ng-template>
</div>

product-ranking-block.component.ts


import {Component, OnInit, Input, ComponentFactoryResolver, ViewChild} 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 {
  @Input() cells: ProductCellItem[];
  @ViewChild(ProductCellDirective, {static: true}) cellHost: ProductCellDirective;
  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }

  ngOnInit() {
    this.load();
  }
  public load() {
    const viewContainerRef = this.cellHost.viewContainerRef;
    // clear the components what were added last time.
    viewContainerRef.clear();

    for (const cell of this.cells) {
      // create a component from component's type.
      const factory = this.componentFactoryResolver.resolveComponentFactory(cell.component);
      const componentRef = viewContainerRef.createComponent(factory);
      
      // get instance and set data.
      const component = componentRef.instance as CellComponent;
      component.products = cell.products;
      component.category = cell.category;
    }
    
  }
}


Finally, I could load the components:)

コメント

このブログの人気の投稿

[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