Angular-cli 构建应用的一些配置
Angular-cli 构建应用
的一些配置
标签(空格分隔): Angular
- 直接使用
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。
如果碰到 *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
中。比如我把routes
和modules
分离,这样组件将会在xx-routing.module.ts
中被import,那么这个CommonModule
就得在xx-routing.module.ts
中被import,在xx.module.ts
引用是不行的。
首次项目实践问题记录
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
来解决。参见stackoverflow。
2. 想要在整个应用初始化时候,在路由导航之前就请求数据,可以通过APP_INITIALIZER
实现。
app.module.ts
export function loadToken(tokenService: InitDataService) {
return () => tokenService.tokenAndTime();
}
providers: [
...
{
provide: APP_INITIALIZER,
useFactory: loadToken,
deps: [InitDataService],
multi: true
},
...
],
- *值得一提的是,目前只实现了同步数据获取,如果异步,并不能在路由渲染完毕之前获取完成。有待研究。 *
3. 关于querySelector()
选择器,默认是返回Element
,这时候就不能在其后用.style
了。
需要将选择到的Element转为HTMLElement(参见):
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;
}
}
5. 如果想要使组件样式可以应用到子组件,可以通过
@Component({
encapsulation: ViewEncapsulation.None,
...
})
这时样式将不再局限于当前组件。
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) ...。
google了半天都是说没写@Injectable()
或者少@或者(),然而检查了半天并不是。最后在GitHub的一个Issues中找到了答案,需要取消注释import 'core-js/es7/reflect';
即可解决。原因暂且未去探究。
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-if
和v-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);
}
}
在控制台,我们可以看到:
第二次触发并没有被打印。是因为调用了取消订阅事件。将取消订阅事件注释掉,可以看到第二次触发打印:
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
Angular-cli 构建应用的一些配置的更多相关文章
- @vue/cli 构建得项目eslint配置
如下:package.json // package.json { "name": "ecommerce-mall-front", "version& ...
- @vue/cli 构建得项目eslint配置2
使用ESLint+Prettier来统一前端代码风格 加分号还是不加分号?tab还是空格?你还在为代码风格与同事争论得面红耳赤吗? 正文之前,先看个段子放松一下: 去死吧!你这个异教徒! 想起自己刚入 ...
- [转]使用 Angular CLI 和 ng-packagr 构建一个标准的 Angular 组件库
使用 Angular CLI 构建 Angular 应用程序是最方便的方式之一. 项目目标 现在,我们一起创建一个简单的组件库. 首先,我们需要创建一个 header 组件.这没什么特别的,当然接下来 ...
- 使用Angular CLI生成 Angular 5项目
如果您正在使用angular, 但是没有好好利用angular cli的话, 那么可以看看本文. Angular CLI 官网: https://github.com/angular/angular- ...
- angular4.0 安装最新版本的nodejs、npm、@angular/cli的方法
在使用ng项目的ui框架时,比如ng-zorro.angular Material,需要安装最新版本的@angular/cli: 配置ng-zorro框架 ng-zorro官网:https://ng. ...
- 使用Angular CLI进行Build (构建) 和 Serve
第一篇文章是: "使用angular cli生成angular5项目" : http://www.cnblogs.com/cgzl/p/8594571.html 第二篇文章是: & ...
- Angular4---起步----环境配置安装@angular/cli
学习angular,首先要搭建起angular的手脚架@angular/cli.首先需要NodeJS环境. 1.安装NodeJS 首先检查电脑是否安装了NodeJS环境,打开cmd命令行,运行node ...
- @angular/cli项目构建--modal
环境准备: cnpm install ngx-bootstrap-modal --save-dev impoerts: [BootstrapModalModule.forRoot({container ...
- @angular/cli项目构建--组件
环境:nodeJS,git,angular/cli npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm instal ...
- 迈向angularjs2系列(8):angular cli和angular2种子项目
文章目录 1.angular cli快速搭建项目 2.angular2-seed 3.手动配置 题外话:如何更好的阅读本篇文章 一: angular cli的安装 Angular-cli(命令行界面, ...
随机推荐
- UESTC - 1137 数位DP
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #i ...
- Codeforces - 912B 位运算
求[1,n]内k的值的异或和使其值最大 k=1是肯定是n k>1,设pos是n的最高位,那答案就是(1ll<<(pos+1))-1 这里用到一个性质是当S=2^i-1时,a xor ...
- [转] 运维小技巧:使用ss命令代替 netstat,和netstat说再见
[From] https://blog.csdn.net/fenglailea/article/details/51810977 ss是Socket Statistics的缩写. 顾名思义,ss命令可 ...
- 地牢逃脱----DFS搜索最优解
https://www.nowcoder.com/practice/0385945b7d834a99bc0010e67f892e38?tpId=85&tqId=29831&tPage= ...
- http请求报头和响应报头(1)
1.web端不可避免的http缓存机制,要理解缓存机制,先来了解下http的请求报文和响应报文的内容 2.请求报文 2.1请求行 请求行三部分组成:请求方法.URL以及版本协议 请求的方法有G ...
- html中的flv视频播放器
项目中要播放flv视屏,第一时间想到html5的<video>标签,只是很可惜<video>兼容性差也就算了,居然还对格式有明确限制,也就是说只支持Ogg.MPEG4.WebM ...
- 2019.03.27 读书笔记 关于GC垃圾回收
在介绍GC前,有必要对.net中CLR管理内存区域做简要介绍: 1. 堆栈:用于分配值类型实例.堆栈主要操作系统管理,而不受垃圾收集器的控制,当值类型实例所在方法结束时,其存储单位自动释放.栈的执行效 ...
- 解决ios、微信移动端的position: fixed; 支持性不好的问题 && 禁用下拉暴露黑底的功能
解决ios.微信移动端的position: fixed; 支持性不好的问题 在chrome中的多个部分使用了position: fixed之后,都可以正常的布局,但是放在微信上却出现了不能正常显示的问 ...
- express遇到的问题
1. 如何引入express? cnpm install express --save 其中--save可以保存到依赖项中. 接着 var express = require("expres ...
- 15019:Only the instance admin may alter the PermSize attribute
15019:Only the instance admin may alter the PermSize attribute TimesTen提示空间不足,增加空间重启后提示15019:Only th ...