質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Angular

Angularは、JavaScriptフレームワークです。AngularJSの後継であり、TypeScriptベースで実装されています。機能ごとに実装を分けやすく、コードの見通しが良いコンポーネント指向です。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

Q&A

解決済

2回答

4462閲覧

(Angular6)カスタムしたdirectiveを動的にタグに埋め込みたい

hiromi.a

総合スコア9

Angular

Angularは、JavaScriptフレームワークです。AngularJSの後継であり、TypeScriptベースで実装されています。機能ごとに実装を分けやすく、コードの見通しが良いコンポーネント指向です。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

0グッド

0クリップ

投稿2018/08/19 15:01

編集2018/08/21 23:57

https://ja.stackoverflow.com/questions/47643/angular-%e3%82%ab%e3%82%b9%e3%82%bf%e3%83%a0%e3%81%97%e3%81%9fdirective%e3%82%92%e5%8b%95%e7%9a%84%e3%81%ab%e3%82%bf%e3%82%b0%e3%81%ab%e5%9f%8b%e3%82%81%e8%be%bc%e3%81%bf%e3%81%9f%e3%81%84
この質問は上記のサイトでも質問しています。

前提・実現したいこと

Angular6で、複数の画面で共通のdirectiveを使おうと思っています。
複数のプログラマーがテンプレートに個々にdirectiveを埋め込むと抜け漏れが発生するので、
name="corp_name"のinputタグの場合、必ず"HanToZen"というdirectiveを埋め込む様に
ngOnInitで実装したいのですが、うまくいきません。
試したコードは下記です。
setAttributeした後にテンプレートをコンパイルしなおさないといけないのでしょうか。
その場合、テンプレートをコンパイルし直すにはどうすれば良いのでしょうか。
色々調べてはいるのですが未だに解決出来ていません。
どなたかご教授頂けるととても助かります。どうぞよろしくお願いします。

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

試したこと

directive-test.component.ts

Angular

1import { Component, OnInit, ElementRef, Renderer2 } from '@angular/core'; 2 3@Component({ 4 selector: 'app-directive-test', 5 templateUrl: './directive-test.component.html', 6 styleUrls: ['./directive-test.component.css'] 7}) 8export class DirectiveTestComponent implements OnInit { 9 10 element: HTMLElement; 11 12 constructor(private el: ElementRef,private renderer: Renderer2) { 13 this.element = el.nativeElement; 14 } 15 16 ngOnInit() { 17 // 単純にHTMLelementにZenToHanをsetしてみると、DOMは期待通りになるのですがdirectiveが動きませんでした 18 var input = this.element.getElementsByTagName("input"); 19 for (var i = 0; i < input.length; i++) { 20 if ( input[i].getAttribute("name") === "corp_name") { 21 input[i].setAttribute("HanToZen",""); 22 } 23 } 24 25 // Renderer2でcreateも試してみましたが、HTMLelementにsetAttributeした時と結果は同じでした 26 var rendInput = this.renderer.createElement('input'); 27 this.renderer.setAttribute(rendInput, 'name', 'corp_name2'); 28 this.renderer.setAttribute(rendInput, 'HanToZen', ''); 29 this.renderer.appendChild(this.element, rendInput); 30 } 31}

han-to-zen.directive.ts

Angular

1import {OnInit, Directive, ElementRef, HostListener} from '@angular/core'; 2import { convertHAN2ZEN } from "./util"; 3 4@Directive({ 5 selector: '[HanToZen]' 6}) 7export class HanToZenDirective implements OnInit { 8 9 private element: HTMLInputElement; 10 11 constructor( 12 private elementRef: ElementRef, 13 ) { 14 this.element = this.elementRef.nativeElement; 15 } 16 17 ngOnInit(){ 18 this.element.value = convertHAN2ZEN(this.element.value); 19 } 20 21 @HostListener("blur", ["$event.target.value"]) 22 onBlur(value){ 23 this.element.value = convertHAN2ZEN(value); 24 } 25}

directive-test.component.html

html

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

補足情報(FW/ツールのバージョンなど)

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

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

やろうと思えは出来なくな無いですが処理が煩雑になりますし、もはやディレクティブである必要があまりないです。
全てのngOnInit記述漏れするのとテンプレートにディレクティブを書き忘れるのとリスクで言うとそこまで大差ないように思います。
ngOnInitにディレクティブを追加する記述を書く代わりに、そこにディレクティブでやりたいことと同じことを書いたらどうでしょうか?
たとえばこのような感じで

ngOnInit() { const input = this.element.getElementsByTagName('input'); for (let i = 0; i < input.length; i++) { if (input[i].getAttribute('name') === 'corp_name') { Observable.fromEvent(input[i], 'input') .debounceTime(100) .subscribe((event: any) => { const replaceText = event.target.value.replace(/[A-Za-z0-9]/g, function (s) { return String.fromCharCode(s.charCodeAt(0) + 65248); }); this.renderer.setProperty(event.target, 'value', replaceText); }); } } }

初期値の設定とかもう少しあると思うので処理はServiceに分けて書いたらスッキリすると思います。

「追記」

コンポーネントを別の親クラスから継承させることは全く問題ありませんが
ライフサイクルメソッドのオーバーライドは予期せぬバグに遭遇する場合があるためアンチパターンになっています
https://qiita.com/okunokentaro/items/90b60fae2622f7c1f1a2

どのみち
super.ngOnInit()
と書く必要があるため作業の軽減には繋がらないかもしれませんが。

ディレクティブで実装する方法ですが、はっきり言ってオススメできませんのでちら見するくらいでお願いします。
https://stackblitz.com/edit/angular-k9n4a7
ディレクティブとして動いてかつ、通常のクラスをしても使用できるように無理やり実装しています。
見て良くわからなくても全く参考にはならないものなので。

「更に追記」
プログラマのdirectiveの実装忘れを回避することが目的であれば
TSLintにカスタムルールを作成して、directiveが無ければコンパイルエラーを出すことは出来るかもしれません。

投稿2018/08/24 02:08

編集2018/08/24 07:31
keisukeh

総合スコア657

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hiromi.a

2018/08/24 06:24 編集

keisukehさん、回答ありがとうございます!大変助かりました。 >処理が煩雑になりますし、もはやディレクティブである必要があまりないです。 なるほど。確かにその通りですね。ディレクティブで実装することにこだわっていました。 keisukehさんのやり方で私のやりたいことは実現できそうなので、このやり方でやってみます。 質問に答えて頂いて、さらに質問で申し訳ありませんがさらにいくつか確認させてください。 >全てのngOnInit記述漏れするのとテンプレートにディレクティブを書き忘れるのとリスクで言うとそこまで大差ないように思います。 そうですよね。なので親クラスを作ってすべてのComponentに継承して、そこで実装しようとしていました。 個々の実装者の労力を極力減らしたかったのでそうしようとしているのですが、Angular的にはあまりよくないやり方なのでしょうか・・。感想を聞かせて頂けるとありがたいです。 また、もし手間でなければ、「やろうと思えは出来なくな無い」ということなので、最初にやろうとしていた、ディレクティブを反映させる方法をヒントでも良いので教えてもらえないでしょうか? 色々調べてやってみたのですが、どうにもうまくいかなかったもので。 やはりなんとかテンプレートをコンパイルし直すのでしょうか・・。
keisukeh

2018/08/24 06:55

回答の方に追記しましたのでご確認ください。
hiromi.a

2018/08/24 11:00 編集

お忙しい中、追加の質問に答えて頂いて有難うございます!とても勉強になりました。 TSLintのカスタムルールというのも良さそうですね!実装コストを軽減させるのに、そういうやり方もあるというのは目からウロコでした。 TSLintを使ったコーディング品質の向上も考えてみます。 色々答えて頂き、有難うございました。
hiromi.a

2018/08/24 17:51

keisukehさんの回答を踏まえた今回の解決策を、自己回答として投稿しておきました。 もし内容に何か問題があれば、ご指摘頂けると嬉しいです。 このたびはご回答有難うございました! (ちなみに、ディレクティブで実装する方法も見てみました。こういう事もできるのですね・・・参考になりました。有難うございました。)
guest

0

keisukehさんに2パターンの回答を頂きましたので、その回答を踏まえて解決策を下記に書いておきます。

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

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

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

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

angular

1export class AppParent { 2 ngOnInit() { 3 //何もしない 4 } 5 initialize(){ 6 //Formの初期化処理 7 } 8} 9 10export class ValidateSampleComponent extends AppParent implements OnInit { 11 12 ngOnInit() { 13 this.initialize(); 14 } 15}

投稿2018/08/24 17:44

編集2018/08/24 17:53
hiromi.a

総合スコア9

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問