Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

AngularとjQuery/jQuery UIを組み合わせる

f:id:fa11enprince:20180526233100j:plain AngularとjQuery/jQuery UIを組み合わせるのは何か間違ってる気がしますが、
Angularの部品が足りなくて、どうしても使いたいことが起きることがあるかと思います(たぶん…)。
ここは意識低い系の方法を紹介します。
(意識高い系の方法は知識不足でちょっとわからなかった)。一応補足に記載。
ここで紹介する方法のデメリットはTypeScriptを使ってるのにjQueryの型がなくなってしまうことです(てかそれで別に困らない)。 こういうシチュエーションがそれなりにあると思うのに、
jQuery排他主義の人が多いのか(自分もどちらかといえばそう) 情報が錯綜しているのでまとめました。

jQueryをインストールする

これは普通にnpmからインストールします

$ npm install --save-dev jquery

jQuery UIをインストールする

公式のは1.12.1で止まっていて、いつからかnpmで提供されるのは部品ごとに細かく分かれたらしく、 Angularから使うにはwebpackをいじらないといけない模様。 ただ、jquery-ui-distっていうのを作ってくれている人がいて、それを使えばnpmからイケるのだが、 ちょっと怪しいので、フツーに公式からzipを落として配置することにする。 https://jqueryui.com/download/
から最新版をデフォルトの設定で落として、 src/assets/vendor/jquery-ui とか作ってあげてそこに以下の解凍した中身を放り込む

external/
images/
AUTHORS.txt
...
jquery-ui.theme.min.css
LICENSE.txt
package.json

angular.jsonにCSSとJSを登録する

 "projects": {
    "client": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
        ...
            "styles": [
              "src/assets/vendor/jquery-ui/jquery-ui.min.css",
              ...
            ],
            "scripts": [
              "node_modules/jquery/dist/jquery.min.js",
              "src/assets/vendor/jquery-ui/jquery-ui.min.js"
              ...
            ]
...

stylesscripts部分にJQueryとJQueryUIのCSSとJSを追加する。

使ってみる

あとはjQuery/jQuery UIを使っているところのTypeScriptで
declare var $: any;と書けばいいだけ。
この場合、import $ from 'jquery';はいりません。
ただし、型はない状態のままです。
これだけです。 どうしてもDOMを操作する場合は ngAfterViewInit()でやるとかありますが、基本はこれだけでOKです。

補足: 意識高い系の方法

おそらく、型を使う場合は、うまくいってませんが

$ npm install --save-dev @types/jquery
$ npm install --save-dev @types/jqueryui

とやってかつ

import $ from 'jquery';
import 'jqueryui';

とやればOKだと思われる...がこれだけではうまくいかない…(詳細不明)。 詳しい人教えてください。

ググると出てくる情報源

https://stackoverflow.com/questions/30623825/how-to-use-jquery-with-angular

Angular6に移行メモ

f:id:fa11enprince:20180628144634j:plain
Angular5.2からAngular6に移行したのでメモ

移行手順

https://update.angular.io/
で示されることをひたすらやっていく

  • Angular Version 5.2 -> 6.0

  • App Complexity Basic

  • ngUpgrade I use ngUpgrade no

  • Package Manager npm

Before Updating

httpモジュールからhttpClientモジュールへ

Switch from HttpModule and the Http service to HttpClientModule and the HttpClient service. HttpClient simplifies the default ergonomics (You don't need to map to json anymore) and now supports typed return values and interceptors. Read more on angular.io

→すでに実施済み

https://brianflove.com/2017/07/21/migrating-to-http-client/

                                                                                               
+import { HttpClient, HttpParams } from '@angular/common/http';                                                       
 import { Injectable } from '@angular/core';                                                                          
-import { Headers, Http, RequestOptions, Response, URLSearchParams } from '@angular/http';                            
+                                                                                                                     
 import { Observable } from 'rxjs/Observable';                                                                        
                                                                                                                      
import 'rxjs/add/observable/throw';                                                                                  
export class HogeService {                                                                                                       
                                                                                                                      
-    constructor(private http: Http) { }                                                                              
+    constructor(private httpClient: HttpClient) { }                                                                  
...                                                                                      
         locationCode: string,                                                                                        
         page: number,                                                                                                
         pageSize: number,                                                                                            
-        optionalParams: URLSearchParams,                                                                             
+        optionalParams: HttpParams,                                                                                  
     ): Observable<IPaginationPage<Hoge>> {                                                                    
-        const params = new URLSearchParams();                                                                                                                                 
-        params.set('size', `${pageSize}`);                                                                           
-        params.set('page', `${page}`);                                                                               
-        if (optionalParams != null && optionalParams.paramsMap.size !== 0) {                                         
-            params.appendAll(optionalParams);                                                                        
-        }                                                                                                            
-        const options = new RequestOptions({ search: params });                                                      
-        return this.http.get(this.hogeAllGetUrl, options).map(this.extractData)                               
+        let params: HttpParams = new HttpParams()                                                                                                                                  
+            .set('size', `${pageSize}`)                                                                              
+            .set('page', `${page}`);                                                                                 
+        optionalParams.keys().forEach((key) => {                                                                     
+            params = params.append(key, optionalParams.get(key));  // 注意:直観に反して戻り値を取らないと変わらない                        
+        });                                                                                                          
+        return this.httpClient.get<IPaginationPage<Hoge>>(this.hogeGetUrl, {params})                
+            .map(this.extractData)                                                                                   
             .catch((error) => Observable.throw(error.statusText));                                                   
     }                                                                                                                

これをやってなかったら多分これが一番大変。

Angular/CLIのアップデート

Update your Angular CLI globally and locally, and migrate the configuration to the new angular.json format by running the following:

npm install -g @angular/cli
npm install @angular/cli@latest
ng update @angular/cli

これで失敗する場合はたぶんNode.jsのバージョンを上げろと言ってきます。

Angularのcoreのアップデート

Update all of your Angular framework packages to v6, and the correct version of RxJS and TypeScript.

ng update @angular/core

Angular Materialのアップデート

After the update, TypeScript and RxJS will more accurately flow types across your application, which may expose existing errors in your application's typings

Update Angular Material to the latest version.

-> 使ってないのでやってない

After the Update

RxJSのlintを入れ替えてRxJSを新バージョンに

Remove deprecated RxJS 6 features using rxjs-tslint auto update rules. For most applications this will mean running the following two commands:

npm install -g rxjs-tslint
rxjs-5-to-6-migrate -p src/tsconfig.app.json # →失敗したが放置

ここからRxJSの直しにかかります。RxJSがバージョンアップしているので書き換えます。

 import { HttpClient, HttpParams } from '@angular/common/http';
 import { Injectable } from '@angular/core';

-import { Observable } from 'rxjs/Observable';
-
-import 'rxjs/add/observable/throw';
-import 'rxjs/add/operator/catch';
-import 'rxjs/add/operator/map';
+import { Observable, throwError } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
...
         return this.httpClient.get<IPaginationPage<Hoge>>(this.hogeGetUrl, {params})
-            .map(this.extractData)
-            .catch((error) => Observable.throw(error.statusText));
+            .pipe(
+                map(this.extractData),
+                catchError((error) => throwError(error.statusText)),
+            );
     }

Angular6へあげた効果

ビルド時間早くなった
200秒から60秒
バンドルサイズが減った
main.bundle.jsが1.1MBから800kB

以上で終わり。お疲れさまでした。

その後エラーになったことなど

AjaxでJSONをパラメータとしてPOSTする場合

これはAngular6の移行というよりhttpModuleからhttpClientModuleへ変えたときに問題になる話

npm installでnode-sassがエラーになる

node_modules\node-sass
> node scripts/install.js

module.js:549
    throw err;
    ^

Error: Cannot find module 'true-case-path'
    at Function.Module._resolveFilename (module.js:547:15)
    at Function.Module._load (module.js:474:25)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)

ngx-bootstrapがらみの問題

  • Accordion いつの間にかタイトル文字がクリック可能になってSubmit発火する→使うのやめる
  • Datepicker 下記の問題により、ヘッダの文字が崩れる

https://github.com/valor-software/ngx-bootstrap/issues/4443

// as of Angular 6 they set preserveWhitespaces to false from default.
platformBrowserDynamic().bootstrapModule(AppModule, { preserveWhitespaces: true });
(window as any).global = window;

polyfills.ts

/***************************************************************************************************
 * APPLICATION IMPORTS
 */
import 'global-shim';  // workaround
/**
 * Date, currency, decimal and percent pipes.
 * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
 */
import 'intl';  // Run `npm install --save intl`.

その他気になったこと

全然関係ないが、Angularのコンポーネントって少ないなーと思ってたんですが、 探したらそこそこあるんですね。

中でもすごいのがコレ

でもAngularって極力materialとかAngular Teamが提供しているもの以外依存しないほうがいろいろと幸せになれそうな気がする…

ちなみに僕はngx-bootstrapを使っています。

おまけ

Angular5からAngular6に移行した小さなサンプルを置きました

Angular5のmain.bundle.jsが重いのでSpring BootのGzip圧縮を試してみる

f:id:fa11enprince:20180611025013j:plain Angular6出ましたね!割と前に…
Angularを使っています。Angularは割といろいろ好きなところはあるのですが、
やはり、FullのSPAを作っているならまだしも、そうでないので、
趣味&実験でプロダクトにぶっこんだ側面もあるので、いろいろ困難があります。
まぁ、端的に言うと、作ろうとしているものに対しての選定ミス。
おとなしくVue.jsとかが向いていたと思います。
そこで今回、重大だ、という問題について書きます。

Angularのmain.bundle.のサイズでかい問題

Angularのmain.bundle.jsが重い問題。
貧弱なネットワークの中ではこれがとても問題になる。
1M超えのJSはやっぱりちょっときつい…
何とかしたい。Angular5を使っているのだがどうにもならない…。
なんででかくなるかはわかっている
jQuery, Momentを使っているからだ。
jQueryは対処しようがなく、使用しないしか対処法がない。
Momentはlocaleを絞ればよい。
それでもでかい…1MBを切るのが難しい。
いろいろ調べると--vendor-chunck=trueを付けるといいとかでてくるが、これは意味がない。
main.bundle.jsのかわりにvendor.bundle.jsのサイズがでかくなるからだ。
Angular6に実験で上げてみるも、あんまり効果ない&RxJSの書き換えがだるい…ので諦めました。
時間があったらバージョンアップしたい。

いろいろ調べてみる

やはりjQuery, Lodash, Momentあたりを使ってると部分的な対策はあるものの(特にMomentのlocaleとか)、
根本的な対策はないようだ。
そもそもそれらを使わないというのがベストなのだが、どうしようもない事情があるのも確か…。

https://github.com/angular/angular/issues/16850
によると…

元の文

" it shouldn't be expected to use gzip in order to get a realistic file size." If being lightweight is crucial to your project, you might want to consider trying out a view library like React or VueJs until angular properly supports server-side rendering and platform-server is documented. Unfortunately, since angular is a complete framework, its size is large and getting the porduction files to less than 100kb even with compression is not trivial [I've read some people experimenting with the closure compiler getting builds ~50kb in size]

" As I said, the servers I'm using do not support gzip" . The express compression middle-ware does the compression on the fly as a client requests the resources and does not need the server to support gzip files, meaning that the files you'll be uploading to your server are going to be the original files built using ng build command as-is, without being compressed.

翻訳

「(サーバがgzip圧縮に対応してないので)現実的なファイルサイズを得るためにgzipを使用することは期待できません。」 軽量であることがプロジェクトにとって重要なら、サーバサイドレンダリングやplatform-serverを適切にサポートしていることがドキュメントに書かれるまで、ReactやVueJsなどのViewライブラリを試すことを検討することをお勧めします。 残念ながら、Angularは完全なフレームワークなので、サイズは大きく、圧縮してもporductionファイルを100kb未満にすることは自明ではありません。 [私はclosureコンパイラを使って~50kbのサイズをにしたというのを読んだことがあります]

「私が言ったように、私が使用しているサーバはgzipをサポートしていません」 。 Expressのオンザフライで圧縮を行うミドルウェアがあるので、gzipファイルをサポートする必要がありません。つまり、サーバーにアップロードするファイルは圧縮されずにng buildのそのままということになります。

端的に言うと、それなりのbundleのサイズは覚悟しろってことか。

他にもSSRを使えというのがあるが、そもそもサーバサイドはExpressじゃなく、Javaなので使えない…
部分的に導入ってのもありはありだが、めんどくさい事がかなり増える。
そもそもExpressは好きじゃない(そういう問題ではない)。
JavaでSSRを行うものもGitHubに上がってるが、今後のバージョンアップに追随できるか謎なのでまっとうな方法でいきたい。しかし、このやりっぷりには感動した。
ちなみにJNIでV8をたたいてる……ものだった。
そのほかの方法としてはLazy Loadingか…。

ということで…
結論としてはGzip圧縮を試みようということになった。
幸い、使っているものはSpring Bootなので、Gzip圧縮を気軽に試せる。
もちろん、古き良きApacheにもモジュール追加でその方法は試せるが。

Gzip圧縮を使ってみる

まずはAOTビルド時のサイズ

Time: 119604ms
chunk {scripts} scripts.bundle.js (scripts) 398 kB [initial] [rendered]
chunk {0} main.bundle.js (main) 1.16 MB [initial] [rendered]
chunk {1} polyfills.bundle.js (polyfills) 169 kB [initial] [rendered]
chunk {2} styles.bundle.css (styles) 219 kB [initial] [rendered]
chunk {3} inline.bundle.js (inline) 1.36 kB [entry] [rendered]

SSDのそこそこのスペックのマシンだが11sかかってる…。そして1.16MB…
もちろん、RxJSとかのimportは極小になるように頑張っている

Spring Bootの公式サイトによると

server.compression.enabled=false # If response compression is enabled.

がデフォルトのことなので、

application.yamlを書き換える

server:
  compression:
    enabled: true
    mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css

Chromeの帯域制限で計測

に記載の方法で試してみる。 Fast3Gで計測

圧縮前
f:id:fa11enprince:20180709224402p:plain
圧縮後
f:id:fa11enprince:20180709230852p:plain
帯域を絞ると…だいぶ早くなってますね!

GZIPで返ってこないと少しハマった件

ちなみにですが、あれれ?gzipでレスポンス返ってきてない!!とおもってハマりました。 アンチウィルスのESETのせいでgzipで受け付けなかったようです。
f:id:fa11enprince:20180709230605p:plain
ここの設定をOFFにしないとダメでした(ファイヤーウォール全無効にするだけじゃダメでした)

Thunderbirdのデータがすべて消えたので復旧した

f:id:fa11enprince:20150727234208j:plain

問題発覚

WindwosでThunderbirdを使っています。 再起動とかかけてないのに、Thunderbirdをふと開くと、プロファイルから何から何まで全部消えてる… うーん何かしたか?と思い当たると、 普段使わないCClearを気まぐれで使ったのでこれが怪しい… そういえばマルウェア問題があったから更新しとこうとか思ってついつい使ってしまった。

http://note.lilish.com/digital_life/windows/ccleaner_thunderbird

ここのサイトの情報が全てでこの手順をやればOKです。

復旧手順

かならずしもこの方法で復旧できるとは限りません Explorerで%APPDATA%と打つ そうすると普通の環境であればC:\Users\[ユーザ名]\AppData\Roamingに移動します

とにもかくにも退避

Thunderbirdというフォルダがあると思うのでバックアップします。 デスクトップ等にコピーしましょう

Profileデータがあるか確認します

C:\Users\[ユーザ名]\AppData\Roaming\Thunderbird\Profiles の下にxxxxxxx.defaultのような名前でフォルダがあってその下にデータがあるか確認します

Invalidprefs.jsがあるか確認します

C:\Users\[ユーザ名]\Desktop\Thunderbird\Profiles\xxxxxxx.default Invalidprefs.jsとprefs.jsがあればだいたい復旧できる条件が整ってます。 WinMergeを使って念のため差分を見ます(※この作業は必須ではありません)

f:id:fa11enprince:20180610214813p:plain

そうすると文字化けしていて、かつ文字コードがUTF-8が正しいはずなのに、誤った認識をしていることがわかります。

Invalidprefs.jsをサクラエディタ等で開きます。

サクラエディタ等でUTF-8で開きなおします。 そうすると、日本語で書かれている部分がいくつか文字化けしているところがあるので、あきらめて消すか、 推測で適当な文字列に置換します。 私の場合 ・calendar.categories.namesは消しました ・profileの自分の名前らしきところは名前を正しく打ち直しました。

user_pref("mailnews.tags.$label1.color", "#FF0000");
user_pref("mailnews.tags.$label1.tag", "重要E);
user_pref("mailnews.tags.$label2.color", "#FF9900");
user_pref("mailnews.tags.$label2.tag", "仕亁E);
user_pref("mailnews.tags.$label3.color", "#009900");
user_pref("mailnews.tags.$label3.tag", "プライベ��EチE);

となってたところは、重要・仕事、プライベートだと思われるのでそのように書き換えました。

Invalidprefs.jsをprefs.jsにして上書き

上書きします。 そしてThunderbirdを起動します。

そうすると無事復旧できました。

その他参考にさせていただいたサイト

プロファイル消えた? と思ったら… - とりかごとなり。 CCleanerのアップデートに注意!Thunderbirdのデータが消えた原因とその対処法|くまらぼ Thunderbirdを起動したら初期化されててメールがすべて消えていました - Fioに言わせろ!

なお、本件については質問は受けません。