47794


この質問はhttps://teratail.com/questions/142007にも投稿しています。  
質問に回答がつかなかった為、こちらにも投稿させてもらいました。質問内容は下記です。

Angular6で、複数の画面で共通のdirectiveを使おうと思っています。  
複数のプログラマーがテンプレートに個々にdirectiveを埋め込むと抜け漏れが発生するので、  
name="corp_name"のinputタグの場合、必ず"HanToZen"というdirectiveを埋め込む様に  
ngOnInitで実装したいのですが、うまくいきません。

もしかすると、setAttributeした後にテンプレートをコンパイルしなおさないといけないのかと思い、現在はその方向で調べています。  
どなたかご教授頂けるととても助かります。どうぞよろしくお願いします。

※directiveで呼んでいるconvertHAN2ZENは受け取った文字列の半角文字を全角に変換するだけの関数です。  
directiveが有効になる方法が分かれば、処理の内容は何でもいいので省略させて頂きました。

試したコードは下記です。

directive-test.component.ts

[code]

    import { Component, OnInit, ElementRef, Renderer2 } from '@angular/core';
    
    @Component({
      selector: 'app-directive-test',
      templateUrl: './directive-test.component.html',
      styleUrls: ['./directive-test.component.css']
    })
    export class DirectiveTestComponent implements OnInit {
    
      element: HTMLElement;
    
      constructor(private el: ElementRef,private renderer: Renderer2) {
        this.element = el.nativeElement;
       }
    
      ngOnInit() {
        // 単純にHTMLelementにZenToHanをsetしてみると、DOMは期待通りになるのですがdirectiveが動きませんでした
        var input = this.element.getElementsByTagName("input");
        for (var i = 0; i < input.length; i++) { 
          if ( input[i].getAttribute("name")  === "corp_name") { 
            input[i].setAttribute("HanToZen","");
          }
        }
    
        // Renderer2でcreateも試してみましたが、HTMLelementにsetAttributeした時と結果は同じでした
        var rendInput = this.renderer.createElement('input');
        this.renderer.setAttribute(rendInput, 'name', 'corp_name2');
        this.renderer.setAttribute(rendInput, 'HanToZen', '');
        this.renderer.appendChild(this.element, rendInput);
      }
    }
    
[/code]

han-to-zen.directive.ts

[code]

    import {OnInit, Directive, ElementRef, HostListener} from '@angular/core';
    import { convertHAN2ZEN } from "./util";
    
    @Directive({
      selector: '[HanToZen]'
    })
    export class HanToZenDirective implements OnInit {
    
      private element: HTMLInputElement;
    
      constructor(
        private elementRef: ElementRef,
      ) {
        this.element = this.elementRef.nativeElement;
      }
    
      ngOnInit(){
        this.element.value = convertHAN2ZEN(this.element.value);
      }
    
      @HostListener("blur", ["$event.target.value"])
      onBlur(value){
        this.element.value = convertHAN2ZEN(value);
      }
    }
    
[/code]

directive-test.component.html

[code]

    <h2>Directive Test</h2>  
    <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
      <!-- firstのHanToZenは問題なく動作する -->
      <input name="first" [(ngModel)]="first" HanToZen>
      <!-- corp_nameにsetAttributeしたHanToZenは動作しない -->
      <input name="corp_name" value='100'  [(ngModel)]="corp_name" >
      <button>Submit</button>
    </form>
    
[/code]

＜動作環境＞

[code]

    Angular CLI: 6.1.3
    Node: 8.11.3
    OS: win32 x64
    Angular: 6.0.3
    ... animations, common, compiler, compiler-cli, core, forms
    ... http, language-service, platform-browser
    ... platform-browser-dynamic, router
    
    Package                           Version
    -----------------------------------------------------------
    @angular-devkit/architect         0.6.5
    @angular-devkit/build-angular     0.6.5
    @angular-devkit/build-optimizer   0.6.5
    @angular-devkit/core              0.6.5
    @angular-devkit/schematics        0.7.3 (cli-only)
    @angular/cli                      6.1.3
    @ngtools/webpack                  6.0.5
    @schematics/angular               0.7.3 (cli-only)
    @schematics/update                0.7.3 (cli-only)
    rxjs                              6.2.0
    typescript                        2.7.2
    webpack                           4.8.3
    
[/code]


CC BY-SA 4.0
2018-08-20T03:06:24.847
47643
2018-08-24T23:18:51.847
2018-08-20T20:36:15.307
29784
29784
question
typescript angular5
Angular：カスタムしたdirectiveを動的にタグに埋め込みたい


この質問はhttps://teratail.com/questions/142007にも投稿し、有用な回答を得られたので、こちらにも記載し回答済みとします。  
（下記の内容はteratailユーザー：keisukehさんの回答を元にしています）

質問に対する回答は２つです。

> **1.ngOnInitでDOMにdirectiveを追加する処理を書くのではなく、directiveで実装したいことを直接ngOnInit内に書く**  
>  ＜コーディング例＞※Util.convertHAN2ZENは自作の関数です。import文などは省略しています。
[code]

>      element: HTMLElement;
>  
>      constructor(private el: ElementRef,private renderer: Renderer2) {
>        this.element = this.el.nativeElement;
>      }
>  
>      ngOnInit() {
>        const input = this.element.getElementsByTagName('input');
>         for (let i = 0; i < input.length; i++) {
>            if (input[i].getAttribute('name') === 'corp_name') {
>                fromEvent(input[i], 'blur')
>                    .subscribe((event: any) => {
>                        const convertStr =
> Util.convertHAN2ZEN(event.target.value);
>                        this.renderer.setProperty(event.target, 'value',
> convertStr);
>                    });
>            }
>        }
>      }
>  
[/code]

>
> **2.TSLintのカスタムルールを作る**  
>
> 「指定のname属性の時に、指定のdirectiveが無ければコンパイルエラーを出す」様なカスタムルールをTSLintに作成すれば、directiveの実装漏れを防ぐ事が出来るのではないか。

確かにdirectiveの実装漏れを防ぐのが目的であれば、TSLintで良さそうです。  
今回は「directiveの実装漏れを防ぐ」＋「実装者のコーディング量を少しでも減らす」＋「実装者がコントローラに対する装飾ルールを意識しないで済む仕組み」というのが目的だったので、1の案を採用することにしました。

ただ、name属性が必ずコントローラに記載されていなければならないので、そこはTSLintのカスタムルールを作って実装漏れが無い様にしたいと思っています。

また実際は、全てのコンポーネントのngOnInitに1の実装をするのではなく親クラスを作ってすべてのコンポーネントに継承させます。  
必ず継承するルールにしたいので、ここもTSLintで制御したいです。

teratailユーザー：keisukehさんからも指摘がありましたが、親クラスのngOnInitをオーバーライドさせるのはアンチパターン(<https://qiita.com/okunokentaro/items/90b60fae2622f7c1f1a2>)となっているので、親クラスのメソッドを全てのコンポーネントのngOnInitで呼ぶつもりです。  
(このやり方に問題がある様なら指摘頂けると嬉しいです。)

[code]

    export class AppParent  {
        ngOnInit() {
            //何もしない
        }
        initialize(){
            //Formの初期化処理
        }
    }
    
    export class SampleComponent extends AppParent  implements OnInit {
    
    ngOnInit() {
        this.initialize();
    }
    
[/code]


CC BY-SA 4.0
2018-08-24T16:44:57.153
47794
2018-08-24T23:18:51.847
2018-08-24T23:18:51.847
29784
29784
47643
answer