Angular-cli 構建應用的一些配置

Angular-cli 構建應用

的一些配置

標籤(空格分隔): Angularjavascript



  • 直接使用 ng build --prod --build-optimizer --base-href=/ 來發布
  • base-href能夠設置服務器上的某個子路徑,使用 ng build --base-href=/my/path/
  • 若是打包靜態文件(js和css)不放在和index.html同一路徑下,能夠在.angular-cli.json配置文件apps屬性下增長deployUrl,等同於webpack的publicPath

如遇刷新找不到頁面(404)的狀況,須要在服務器配置重定向到index.html。以nginx爲例,能夠在location添加try_files $uri $uri/ /index.html?$query_string;來重定向到index.html。php

若是碰到 *ngIf *ngFor用不了得狀況,好比拋出 Property binding ngForOf not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations".的錯誤,一般是由於沒有importCommonModule,並且這個import必須在組件被引用的module中。好比我把routesmodules分離,這樣組件將會在xx-routing.module.ts中被import,那麼這個CommonModule就得在xx-routing.module.ts中被import,在xx.module.ts引用是不行的。css



首次項目實踐問題記錄

1. node版本升級(v6.x.x -> v8.11.1)後,原來項目ng serve拋出錯誤:Node Sass could not find a binding for your current environment

此時須要執行npm rebuild node-sass來解決。參見stackoverflowhtml

2. 想要在整個應用初始化時候,在路由導航以前就請求數據,能夠經過APP_INITIALIZER實現。

app.module.tsvue

export function loadToken(tokenService: InitDataService) {
  return () => tokenService.tokenAndTime();
}

providers: [
    ...
    {
      provide: APP_INITIALIZER,
      useFactory: loadToken,
      deps: [InitDataService],
      multi: true
    },
    ...
  ],
  • 值得一提的是,目前只實現了同步數據獲取,若是異步,並不能在路由渲染完畢以前獲取完成。有待研究。

3. 關於querySelector()選擇器,默認是返回Element,這時候就不能在其後用.style了。

須要將選擇到的Element轉爲HTMLElement(參見):java

let overlay = <HTMLElement>document.querySelector(`#${this.ID}`);
overlay.style.display = 'none';

4. 關於angualr的HttpClient,post請求默認將body中的數據序列化爲json,若是後臺只接收urlencoded格式的數據,就不能直接傳對象了:

private init() {
    this.url = apiData.ServiceUrl + this.path;

    const datas: Datas = {
      ClientType: apiData.ClientType,
      Token: this.tokenDatasService.token
    };
    Object.assign(datas, this._datas);

    // 將參數對象序列化爲 [key1]=[value1]&[key2]=[value2]的字符串
    let params = new HttpParams();
    if (!this.isGet) {
      datas.Timespan = this.tokenDatasService.timespanFormat;
    }
    for (let key in datas) {
      params = params.set(key, datas[key]);
    }

    if (this.isGet) {
      this.datas = { params: params };
    } else {
      this.datas = params;
    }
}

參見node

5. 若是想要使組件樣式能夠應用到子組件,能夠經過

@Component({
    encapsulation: ViewEncapsulation.None,
    ...
})

這時樣式將再也不侷限於當前組件。webpack

6. 若是想要當前根路徑(子根路徑)導航到未匹配路由時,好比設置404,能夠在路由數組的末尾添加

const ROUTES: Routes = [
    ...
    { path: '**', component: NotFoundComponent}
];

7. 關於polyfills.ts

以前沒有取消註釋這個文件中的引用,在IE下打開發現報錯,取消註釋第一塊引用後,發現全部瀏覽器都出現自定義DI拋出錯誤Uncaught Error: Can't resolve all parameters for ApiService: (?). at syntaxError (compiler.es5.js:1694) ...css3

google了半天都是說沒寫@Injectable()或者少@或者(),然而檢查了半天並非。最後在GitHub的一個Issues中找到了答案,須要取消註釋import 'core-js/es7/reflect';便可解決。緣由暫且未去探究。nginx

8. 關於再ng中使用canvas

使用@ViewChild('[name]') canvasRef: ElementRef來選擇canvas畫布。

9. 關於資源路徑,使用絕對路徑,好比css中獲取logo圖片:

background: url("/assets/img/shared/logo.png") no-repeat center/100%;

10. 父子路由能夠經過服務來通訊。

父級提供服務支持(providers),父級在constructor方法中訂閱(subscribe),子路由在ngOnInit方法或者其餘自定義事件中賦值(next)。

11. 經過方括號綁定的routerLink屬性,值是異步獲取的(Observable)。這時候subscribe的時候拋出 ExpressionChangedAfterItHasBeenCheckedError

能夠經過setTimeout([callback], 0)異步處理結果實現參見GitHub Issues :

this.accountService.titles$.subscribe(titles => setTimeout(() => {
      this.title = titles.title;
      this.titleLink = titles.titleLink.link;
      this.titleLinkName = titles.titleLink.name;
}, 0));

12. 在開發環境(ng serve)中,各個路由刷新頁面正常顯示,可是打包部署到服務器後,在子路由中刷新頁面會出現404。能夠經過配置服務器來修復這一問題 :

以nginx爲例:

location / {
    root   C:\Web\Site;
    index  index.html;
    ry_files $uri $uri/ /index.html?$query_string;
}

13. vue中習慣使用v-ifv-else,ng中也有這樣的模板語法:

注意必須使用ng-template

<h2 class="nick-name" *ngIf="isLogin; else notLogin">{{ userInfo.Name }}</h2>
<ng-template #notLogin>
    <a href="javascript: void(0);" class="nick-name">當即登陸</a>
</ng-template>

14. 使用Subject實現組件之間的通訊

ionic中有一個Events服務,能夠經過publish發佈事件,在其餘組件中subscribe事件。

在Angular項目中,咱們能夠經過Subject來建立一個服務實現相似的效果。類同本文(# 10)所述。

  • 首先建立一個公共服務:
import {Injectable} from '@angular/core';
import {Datas} from '../models/datas.model';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';
import {Subscriber} from 'rxjs/Subscriber';

@Injectable()
export class EventsService {
  private events: Datas = {};
  public eventsName = [];

  constructor() { }

  /**
   * 發佈
   * @param {string} topic  事件名稱
   * @param {Datas}  params 參數(對象)
   */
  public publish(topic: string, params: Datas = {}) {
    const event = this.getEvent(topic);

    Object.assign(params, { EVENT_TOPIC_NAME: topic });
    event.next(params);
  }

  /**
   * 訂閱事件
   * @param  {string}     topic 事件名稱
   * @return {Observable}
   */
  public subscribe(topic: string) {
    return this.getEvent(topic).asObservable();
  }

  /**
   * 取消訂閱事件
   * @param {Subscriber} subscriber 訂閱事件對象
   */
  public unsubscribe(subscriber: Subscriber<any>) {
    subscriber.unsubscribe();
  }

  private getEvent(topic: string) {
    this.eventsName.push(topic);
    this.eventsName = Array.from(new Set(this.eventsName));

    let _event;

    for (const i in this.events) {
      // 判斷是否已有事件
      if (this.events.hasOwnProperty(i) && i === topic) {
        _event = this.events[i];
        break;
      }
    }

    if (!_event) {
      // 沒有事件 建立一個
      _event = new Subject<Datas>();
      const eventObj = { [topic]: _event };
      Object.assign(this.events, eventObj);
    }

    return _event;
  }
}
  • 而後在某組件中訂閱事件
...
constructor(private eventsService: EventsService) { }

ngOnInit() {
    const a = this.eventsService.subscribe('setHeader').subscribe(v => {
        console.log(v);
        // 取消訂閱
        this.eventsService.unsubscribe(a);
    });
}
...
  • 在某組件中發佈事件(觸發或許更爲貼切)
...
export class IndexComponent implements OnInit {

  constructor(private eventsService: EventsService) { }

  ngOnInit() {
    // 第一次觸發
    this.eventsService.publish('setHeader', { a: 1, b: 2 });

    setTimeout(() => {
      // 第二次觸發
      this.eventsService.publish('setHeader', { c: 3 });
    }, 5000);
  }

}

在控制檯,咱們能夠看到:

1

第二次觸發並無被打印。是由於調用了取消訂閱事件。將取消訂閱事件註釋掉,能夠看到第二次觸發打印:

2

15. 監聽路由跳轉

常常會用到路由跳轉後執行一些操做。經過Route來進行操做。

import {NavigationEnd, Router} from '@angular/router';
...
constructor(private router: Router) { }
...
// 導航
navWatch() {
    this.router.events.subscribe(e => {
        if (e instanceof NavigationEnd) {
            // TODO 路由跳轉完畢
        }
    });
}
...

16. 爲組件添加事件

使用@Output() [eventName] = new EventEmitter<T>();,而後在組件內部經過this[eventName].emit([params])來觸發事件、傳遞參數。組件外部經過圓括號<my-component (eventName)="watchEvent($event)"></my-component>。其中$event就是傳遞過來的參數。

17. 監聽宿主事件

能夠經過宿主監聽器@HostListener([event]: string, [args]: string[])來操做。

好比監聽window滾動事件:

...
@HostListener('window:scroll', [])
onWindowScroll() {
    // TODO 滾動事件
    // this.scrollEvent().subscribe(obj => {
    //     this.scrollStyle(obj.offset, obj.direction);
    // });
}
...

18. 自定義表單驗證器

如何實現兩次輸入密碼一致(兩個輸入框值相等)的自定義驗證器。

詳見

19. 給元素綁定data-*等屬性

直接使用方括號你會發現拋出錯誤。這時候能夠加個attr來解決:

<img [attr.data-src]="value">

20. 關於css3 rem的使用

咱們習慣使用 html { font-size: 62.5%; }來做爲根大小(10px),可是Chrome並不支持12px如下的大小,這將致使Chrome與其餘瀏覽器顯示不一樣。

搜索解決方案。

  • 設置body { font-size: 1.4em; },經試驗不起做用(至少在個人項目中)。
  • 使用-webkit-transform: scale(.8, .8);,不是很滿意。
  • 使用html { font-size: 625%; },至關於100px。

我更偏向於第三種。








若是想要手動配置webpack來打包項目:(非必要)

使用ng new my-app初始化的項目並不包含webpack配置文件,須要ng eject命令來加入webpack.config.js配置文件。

注意此時不能再用 ng build 之類的命令了,開發環境是npm start,打包命令是npm run build

這時候webpack缺乏一些原來的配置。

1. uglifyjs-webpack-plugin js壓縮插件

將js文件壓縮,減少打包後文件的體積。

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

...

new UglifyJsPlugin({
  "test": /\.js$/i,
  "extractComments": false,
  "sourceMap": true,
  "cache": false,
  "parallel": false,
  "uglifyOptions": {
    "output": {
      "ascii_only": true,
      "comments": false
    },
    "ecma": 5,
    "warnings": false,
    "ie8": false,
    "mangle": {
      properties: {
      regex: /^my_[^_]{1}/,
      reserved: ["$", "_"]
      }
    },
    "compress": {}
  }
})

2. compression-webpack-plugin 生成gzip文件插件

進一步減少打包文件體積。

const CompressionWebpackPlugin = require('compression-webpack-plugin');

...

new CompressionWebpackPlugin()

這個須要服務器開啓gzip on;,以nginx爲例,須要爲服務器進行如下配置:

conf/nginx.conf:

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    # 開啓gzip
    gzip  on;
    gzip_static on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    gzip_vary on;
    gzip_disable "MSIE [1-6]\.";

    server {
        listen       8088;
        server_name  localhost;

        location / {
            root   website/angular;
            index  index.html;
        }
    }
}

3. clean-webpack-plugin 清除打包文件工具

每次npm run build後都會生成新的打包文件(文件名添加hash),這個插件能夠在打包後刪除以前舊的文件。

const CleanWebpackPlugin = require('clean-webpack-plugin');

...

new CleanWebpackPlugin(['dist'], {
    root: projectRoot,
    verbose:  true,
    dry:      false
})

4. CopyWebpackPlugin 配置修改

src/assets/文件夾下的靜態資源以及favicon.ico文件也須要打包,這時須要修改一下自動生成的配置代碼:

new CopyWebpackPlugin([
  {
    "context": "src",
    "to": "assets/",
    "from": "assets"
  },
  {
    "context": "src",
    "to": "",
    "from": {
      "glob": "favicon.ico",
      "dot": true
    }
  }
], {
  "ignore": [
    ".gitkeep",
    "**/.DS_Store",
    "**/Thumbs.db"
  ],
  "debug": "warning"
}),

5. Extract Text Plugin 的使用(存在問題)

若是須要分離css單獨打包,可使用 extract-text-webpack-plugin

可能會有解決方案,暫時不作深刻探究。仍是推薦直接使用ng-cli。

注意,分離css後,angular的特殊選擇器將失效,好比:host {}選擇器,使用正常的css方法實現來替代。

注意,樣式的引用就須要經過import './xx.scss';的方式來引用樣式文件,不然會拋出Expected 'styles' to be an array of strings.的錯誤。
也有經過"use": ['to-string-loader'].concat(ExtractTextPlugin.extract(<options>))的方法來實現。
由於不經過@Component({ styleUrls: '' })的方式,樣式的scope做用將消失。

webpack.config.js:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

const extractCSS = new ExtractTextPlugin('[name].[contenthash:8].css');
const extractSCSS = new ExtractTextPlugin('[name].[contenthash:8].css');

module.exports = {
    ...
    
    "entry": {
        ...
        "styles": [
            "./src/app.scss"
        ]
    },
    "module": {
        "rules": [
            {
                "test": /\.css$/,
                "use": extractCSS.extract({
                    "fallback": "style-loader",
                    "use": [
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": false,
                            "import": false
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "ident": "postcss",
                            "plugins": postcssPlugins,
                            "sourceMap": false
                        }
                    }]
                })
            },
            {
                "test": /\.scss$|\.sass$/,
                "use": extractSCSS.extract({
                    "fallback": "style-loader",
                    "use": [
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": false,
                            "import": false
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "ident": "postcss",
                            "plugins": postcssPlugins,
                            "sourceMap": false
                        }
                    },
                    {
                        "loader": "sass-loader",
                        "options": {
                            "sourceMap": false,
                            "precision": 8,
                            "includePaths": []
                        }
                    }]
                })
            },
        ],
        "plugins": [
            ...
            extractCSS,
            extractSCSS
        ]
    }
    
    ...
    
}

app.component.ts

import './app.component.scss';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})

6. publicPath

  • webpack 有一個publicPath 屬性,能夠設置資源引用路徑,須要寫在output屬性下:
module.exports = {
    ...
    "output": {
        "publicPath": '/',
        "path": path.join(process.cwd(), "dist"),
        "filename": "[name].bundle.[chunkhash:8].js",
        "chunkFilename": "[id].chunk.[chunkhash:8].js",
        "crossOriginLoading": false
    },
    ...
}

若是使用ng-cli,能夠在apps屬性下設置deployUrl,等同於publicPath。


個人環境
Angular CLI: 1.6.7 (e)
Node: 8.11.1
OS: win32 x64
Angular: 5.2.3
***
demo源碼
參考文章: angular-cli issues | style-loader issues | stackoverflow | copy-webpack-plugin拷貝資源插件

The end...    Last updated by: Jehorn, Sep 17, 2018, 04:29 PM

相關文章
相關標籤/搜索