deepthan / blog-angular Goto Github PK
View Code? Open in Web Editor NEWAngular 笔记
Angular 笔记
在项目里面快速创建文件
为typescript和HTML添加了一些代码片段。
自动导入包和组件等。
提供Javascript的Snippet, 花一点时间记住一些片段,之后可以省下很多时间。
查看git 提交历史
查看代码里面提示每段代码是谁什么时候提交修改的
集成npm
自动补全npm下载的包
输入路径会自动提供输入建议或自动完成功能
在vs中打开浏览器
对括号进行着色
将markdowm文件转换为pdf
html中点击css名字跳转到定义的位置
格式化css和js代码
只需在css颜色上悬停光标,就可以预览色块中色彩模型的(HEX、 RGB、HSL 和 CMYK)相关信息了
可以在html里面查看ts中的变量、函数等
顶部注释模板,可定义作者、时间等信息,并会自动更新最后修改时间
让 vscode 映射 chrome 的 debug功能,静态页面都可以用 vscode 来打断点调试
编辑器中文包
import { AnimationOptions } from '@angular/animations';
interface AnimationOptions {
delay?: number|string
params?: {[name: string]: any}
}
我们知道,父子组件传递信息可以传递字符串、对象等,但是你有想过如果传递的是dom元素怎么传递吗?
这时候就得用到 ng-content了。
<div dashen class='gailun'> 盖伦 </div>
<div class='manwang'>
<ng-content select=' [dashen] ' ></ng-content>
</div>
渲染后:
<div class='manwang'>
<div dashen class='gailun'> 盖伦 </div>
</div>
class为盖伦(gailun)的div的自定义属性为 dashen
,在class为蛮王(manwang)的div里有个ng-content容器,它有个属性为 select,值为 [dashen],这样写的话就可以关联起来了。
不会 "产生" 内容,它只是投影现有的内容。你可以认为它等价于 node.appendChild(el) 或 jQuery 中的 $(node).append(el) 方法:使用这些方法,节点不被克隆,它被简单地移动到它的新位置。因此,投影内容的生命周期将被绑定到它被声明的地方,而不是显示在地方。
这种行为有两个原因:期望一致性和性能。什么 "期望的一致性" 意味着作为开发人员,可以基于应用程序的代码,猜测其行为。
子组件 heros.component.html,它ts里的selector为 'hero'
<p> 寒冰 </p>
<div class='manwang'>
<ng-content select=' [dashen] ' ></ng-content>
</div>
<p>木木</p>
父组件
<p>带我飞</p>
<hero>
<div dashen class='gailun'> 盖伦 </div>
</hero>
渲染后
<p> 寒冰 </p>
<div class='manwang'>
<div dashen class='gailun'> 盖伦 </div>
</div>
<p>木木</p>
其实和jq的$("")选取dom元素差不多。
<div dashen> 盖伦 </div>
<ng-content select=' [dashen] ' ></ng-content>
.dashen
<div class='dashen'> 盖伦 </div>
<ng-content select=' .dashen ' ></ng-content>
#dashen
<div id='dashen'> 盖伦 </div>
<ng-content select=' #dashen ' ></ng-content>
dashen
<dashen> 盖伦 </dashen>
<ng-content select=' dashen ' ></ng-content>
ts 里面定义变量
private temp;
img里
<img src="{{temp}}" alt="">
div里
{{ temp }}
ngClass里
[ngClass]="temp ? 'class1 : 'class2' "
父组件往子组件传递
[getData]="temp"
function sequence(
steps: AnimationMetadata[],
options: AnimationOptions | null = null
): AnimationSequenceMetadata;
它可以由样式或动画函数调用组成。对style()的调用将立即应用提供的样式数据,而对animate()的调用将在它延迟时间后应用它的样式数据。
// 这是一个动画序列
sequence([
style({ opacity: 0 })),
animate("1s", { opacity: 1 }))
])
它是一个动画列表[A,B,C],里面的动画挨个执行:执行完A再执行B,B执行完再执行C。
它可以应用在 group
和transition
里面,它只会在每个内部动画步骤完成后再继续执行下一条指令。
将一个数组作为动画数据传递到transition时,默认使用序列。如下面的[animate(500, style({...})), animate(500)]
就是序列。
animations: [trigger(
'demo',
[
state('void', style({...})),
state('*', style({...})),
transition(
'void => *', [animate(500, style({...})), animate(500)])
])],
都是动画列表,序列是一个一个执行,组是并行执行。
Property 'map' does not exist on type 'Observable<Response>'
或
Argument of type 'Observable<Response>' is not assignable to parameter of type 'Observable<Response>'
先把cnpm下载的rxjs删除
npm uninstall --save rxjs
再用npm下载rxjs:
npm install --save rxjs@latest
这里推荐使用 yarn替代npm下载依赖包,地址 :yarn用法
npm 的版本是4.1.2
@ angualr/cli 版本是 1.3.2
node版本是 7.7.1
在github上建立一个仓库,并且在本地新建一个ng的项目并关联起来。
下载angular-cli-ghpages用于提交Angular包
npm i -g angular-cli-ghpages
在本地项目
ng build --prod --base-href “ https://USERNAME.github.io/REPOSITORY/ ”
或
ng build --prod --base-href
ngh
github:
graph LR
进入仓库 --> setting
setting--> GitHub Pages
GitHub Pages-->Source
Source-->gh-page branch
gitee(码云):
graph LR
进入仓库 --> 管理
管理--> 默认分支
默认分支-->gn-pages
graph LR
进入仓库 --> 服务
服务--> Pages
Pages-->部署
https://用户名.github.io/仓库名/
https://用户名.gitee.io/仓库名/
码云
https://deepthan.gitee.io/poetry/
https://deepthan.gitee.io/angular-demo/home
github仓库
https://github.com/deepthan/poetry/tree/gh-pages
githubpage build的时候需要设置basehref
ng build --prod --aot --base-href “ https://USERNAME.github.io/REPOSITORY/ ”
而后者默认是以仓库为基础路径不需要设置basehref.
// 提交Angular应用到github page
https://github.com/angular-schule/angular-cli-ghpages
// 建立教程
https://jeneser.github.io/blog/2017/08/08/angular-deploying-app-github-pages/
C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:196
throw er
^
Error: EPERM: operation not permitted, unlink 'C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\gh-pages\.cache\.git\COMMIT_EDITMSG'
at Object.fs.unlinkSync (fs.js:1080:18)
at rimrafSync (C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:306:17)
at C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:342:5
at Array.forEach (native)
at rmkidsSync (C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:341:26)
at rmdirSync (C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:334:7)
at rimrafSync (C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:304:9)
at C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:342:5
at Array.forEach (native)
at rmkidsSync (C:\Program Files\nodejs\node_global\node_modules\angular-cli-ghpages\node_modules\rimraf\rimraf.js:341:26)
重新build或者全部打开的编辑器关了再开重新ngh即可.
function trigger(
name: string,
definitions: AnimationMetadata[]
):AnimationTriggerMetadata;
动画触发器名字, 如 growWidth
要执行的动画, 如
[ transition("void => *", [style({opacity: 0}), animate(600, style({ opacity: 1}))] )],
Angular官方指出:如果没有足够使用hash风格(#)的理由,还是尽量使用HTML5模式的路由风格;
如果配置了hash风格,在微信支付或是Angular的深路径依然会出404的问题;
当你需要使用GA等工具时,由于无法获取#号后的URL,导致每次路由切换都给其发送一个路径;
'#'有点丑。
有四个方法:
index.html的head里加
<base href="/">
app.module.ts
import { ROUTER_CONFIG } from './app.routes.ts';
@NgModule({
imports: [
...
RouterModule.forRoot(ROUTER_CONFIG)
// RouterModule.forRoot(ROUTER_CONFIG, { useHash: true } ) 这样写是带#的
],
})
app.routes.ts:
import { NgModule } from '@angular/core';
import { Routes } from '@angular/router';
export const ROUTER_CONFIG: Routes = [
{
...
}
];
如果只配置前端虽然会去掉'#'但是一刷新页面就404,路径解析上出错了。
Angular是单页应用,它实现了前端路由功能,后台可以不再控制路由的跳转,将原本属于后端的业务逻辑全部丢给前端。
用户刷新页面时(http://gitee.poetry/life),请求是先被提交到了WebServer后台,后台路由没有对应页面的路由管理,就会出现404的错误。
用户如果是先访问首页(http://gitee.poetry),然后再跳转到 页面(http://gitee.poetry/life),则这个跳转是由Angular前台管理的URL,访问是正常的。
那么我们让WebServer把属于Angular管理的路由URL,都转发到index.html就可以解决404的问题了,也就是后面介绍的配置信息。
思考:hash模式为什么不会404?
带'***'的是需要自己配置 nginx.conf 文件内容
server {
listen 80; #监听的端口号
server_name my_server_name; # 服务器名称 ***
root /projects/angular/myproject/dist; #相对于nginx的位置 ***
index index.html; #如果index.html存在,就结束查找过程,把这个文件附加到请求的request_uri后面,并且发起一个内部的redirect。
location / { # / 是匹配所有的uri后执行下面操作
try_files $uri $uri/ /index.html; #try_files先寻找名为 $uri 文件,没有则寻找 $uri/ 文件,再没有就寻找/index.html
}
}
try_files 详细解释:
如请求的是https://deepthan.gitee.io/poetry/life, $uri则是‘/life’,如果‘$uri’‘$uri/’都找不到,就会 fall back 到 try_files 的最后一个选项 /index.html发起一个内部 “子请求”,也就是相当于 nginx 发起一个 HTTP 请求到https://deepthan.gitee.io/poetry/index.html。这个请求会被 location ~ .php$ { ... } catch 住,也就是进入 FastCGI 的处理程序。而具体的 URI 及参数是在 REQUEST_URI 中传递给 FastCGI 和 WordPress 程序的,因此不受 URI 变化的影响。
Apache的根目录新建一个.htaccess文件
RewriteEngine On
# 如果请求的是现有资源,则按原样执行
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# 如果请求的资源不存在,则使用index.html
RewriteRule ^ /index.html
Tomcat/conf/web.xml文件上添加
<error-page>
<error-code>404</error-code>
<location>/</location>
</error-page>
对于github pages来说,我们没办法直接配置Github pages,但可以在commit时添加一个404页。简单的解决方案如下:
我们在项目的根目录新建404.html,把index.html中的内容完全复制到404.html中就可以了。这样做github pages仍然会在恰当的时候给出一个404响应,浏览器将会正确处理该页,并正常加载我们的应用。
关于这方面的hack: S(GH)PA: The Single-Page App Hack for GitHub Pages
以前路由都是后台做的,通过用户请求的url导航到具体的html页面,现在我们在前端可以利用 Angular、vue、react等通过配置文件,达到前端控制路由跳转的功能。
前端路由的实现方法:
2. HTML5的history api操作浏览器的session history实现
基于history实现的路由中不带#,就是原始的路由
angular2提供的路由策略也是基于上面两个原理实现的,可以在@NgModule中通过providers配置或RouterModule.forRoot()配置:
1) 路由中有#
@NgModule({
imports:[RouterModule.forRoot(routes,{useHash:true})]
})
或
@NgModule({
imports:[RouterModule.forRoot(routes)],
providers:[
{provide: LocationStrategy, useClass: HashLocationStrategy}
]
})
HashLocationStragegy
适用于基于锚点标记的路径,比如/#/**,后端只需要配置一个根路由即可。
2) html5路由(无#)
改用 PathLocationStrategy(angular2的默认策略,也就是HTML5路由),使用这个路由的常规路径不带#,这种策略需要后台配置支持,因为我们的应用是单页面应用,如果后台没有正确的配置,当用户在浏览器从一个路由跳往另外一个路由或者刷新时就会返回404,需要在服务端里面覆盖所有的路由情况(后端可以通过nginx或者apache等配置)。
@NgModule({
imports:[RouterModule.forRoot(routes)],
providers:[
{provide: LocationStrategy, useClass: PathLocationStrategy}
// 这一行是可选的,因为默认的LocationStrategy是PathLocationStrategy
]
})
更改index.html中的base href属性,Angular将通过这个属性来处理路由跳转
<base href="/app/">
在后端的服务器上,用下面的正则去匹配所有的页面请求导向index.html页面。
we must render the index.html file for any request coming with below pattern
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<base href="/app/">
<body>
<app-root>Loading...</app-root>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
</body>
</html>
hash方案:路径比较丑,但是兼容老的浏览器,如果应用需要兼容低版本浏览器推荐使用
纯h5方案:只支持高版本浏览器,不过ng2+目前只支持高版本浏览器...
3) 定制自己的路由策略LocationStragegy
https://angular.io/api/common/LocationStrategy
4) Angular路由复用策略
http://m.itboth.com/d/7nQFni/typescript-angular2-angular4
优点:
1.从性能和用户体验的层面来比较的话,后端路由每次访问一个新页面的时候都要向服务器发送请求,然后服务器再响应请求,这个过程肯定会有延迟。而前端路由在访问一个新页面的时候仅仅是变换了一下路径而已,没有了网络延迟,对于用户体验来说会有相当大的提升。
2.在某些场合中,用ajax请求,可以让页面无刷新,页面变了但Url没有变化,用户就不能复制到想要的地址,用前端路由做单页面网页就很好的解决了这个问题
缺点:
使用浏览器的前进,后退键的时候会重新发送请求,没有合理地利用缓存,
angular2 路由策略LocationStrategy
RouterModule
Angular 2 Remove Hash (#) from the URL
angular2 配合后端实现去除#
Nginx Angular2 html5mode PathLocationStrategy
Angular开启html5模式,配合nginx配置去除路由的#号
参考了很多例子,虽然实现了预加载但却是在本模块内容没加载完就会去加载延迟模块,这样就等于没有配置懒加载,所以得进行一些优化。
├─app.module.ts
├─app.routes.ts
├─shared
└─service
└─preview-load.ts
preview-load.ts
import { Observable } from 'rxjs/Rx';
import { PreloadingStrategy, Route } from '@angular/router';
export class PreloadModules implements PreloadingStrategy {
preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : Observable.of(null);
}
}
app.module.ts
import { ROUTER_CONFIG } from './app.routes';
import { PreloadModules } from './share/service/preview-load';
...
@NgModule({
imports: [
RouterModule.forRoot(ROUTER_CONFIG, {preloadingStrategy: PreloadModules} )
],
providers: [
PreloadModules
],
app.routes.ts
...
export const ROUTER_CONFIG: Routes = [
...
{
path: 'login',
component: RoleLoginComponent
},
{
path: 'lists',
loadChildren: './tables/tables.module#TablesModule',
data: { preload: true }
}
];
实测:
部分文件、结构和上面一样,只对preview-load.ts文件做一些更改
import { Observable } from 'rxjs/Rx';
import { PreloadingStrategy, Route } from '@angular/router';
import { Injectable, NgZone } from '@angular/core';
export function requestIdle(zone: NgZone) {
const win: any = window;
if (win.requestIdleCallback) {
return (fn) => win.requestIdleCallback(fn);
}
return (fn) => zone.runOutsideAngular(() => win.setTimeout(fn, 10));
}
@Injectable()
export class PreloadModules implements PreloadingStrategy {
constructor(
private zone: NgZone
) { }
preload(route: Route, fn: () => Observable<any>): Observable<any> {
requestIdle(this.zone)(fn);
return Observable.of(null);
}
}
看结果
<div class='old-style'></div>
.old-style{
width: 100px;
height: 100px;
background: #000;
}
我想给它改变它的样式为
.new-style{
width: 200px;
background: #069;
}
获得DOM元素的引用移除旧的class
old-style
添加classnew-style
<div class='old-style' #demo></div>
import { Component, ElementRef, ViewChild, Renderer2 } from '@angular/core';
...
export class DemoComponent implements OnInit {
constructor( private renderer2 : Renderer2) { }
@ViewChild('demo') demo: ElementRef;
ngOnInit(){
this.renderer2.removeClass(this.demo.nativeElement, "old-style");
this.renderer2.addClass(this.demo.nativeElement, "new-style");
}
}
获得DOM元素的引用设置新的 style
<div class='old-style' #demo></div>
import { Component, ElementRef, ViewChild, Renderer2 } from '@angular/core';
...
export class DemoComponent implements OnInit {
constructor( private renderer2 : Renderer2) { }
@ViewChild('demo') demo: ElementRef;
ngOnInit(){
this.renderer2.setAttribute(this.demo.nativeElement, "style","width: 200px;background: #006699;");
}
}
这里是修改多个style所以用setAttribute,如果是修改单个style则使用 this.renderer2.setStyle(ele,name,value)
定义一个布尔值,通过布尔值的改变来改变其class
ts:
private changeClass : boolean = false;
// 想要更改样式时直接更改 changeClass的值。
<div [ngClass]="changeClass ? 'new-class' : 'old-class' "></div>
当changeClass为false时 div class为old-class 为true时calss为 new-class
定义两个变量,通过变量改变来改变其style
ts:
private widthTemp : string = "100px";
private backgroundTemp : string = '#000';
// 想要更改样式时直接更改 changeClass的值。
this.widthTemp = '200px;';
this.backgroundTemp = '#069'
html
<div [ngStyle]="{'background-color':backgroundTemp,'width': widthTemp}">></div>
function group(
steps: AnimationMetadata[],
options: AnimationOptions | null = null
): AnimationGroupMetadata;
AnimationOptions
group([
animate("1s", { background: "black" }))
animate("2s", { color: "white" }))
])
组是一个并行运行的动画步骤列表。当存在很多样式在不同时间段开始或结束动画,我们需要对它统一进行管理的时候作用非常明显。利用组我们可以轻易控制它们同时开始动画或者同时结束动画。
group函数既可以在序列(sequence
)中使用,也可以在转换(transition
)中使用,它只会在所有内部动画步骤完成后继续执行下一条指令。
function group(
steps: AnimationMetadata[],
options: AnimationOptions | null = null
): AnimationGroupMetadata;
AnimationOptions
group([
animate("1s", { background: "black" }))
animate("2s", { color: "white" }))
])
组是一个并行运行的动画步骤列表。当存在很多样式在不同时间段开始或结束动画,我们需要对它统一进行管理的时候作用非常明显。利用组我们可以轻易控制它们同时开始动画或者同时结束动画。
group函数既可以在序列(sequence
)中使用,也可以在转换(transition
)中使用,它只会在所有内部动画步骤完成后继续执行下一条指令。
function query(
selector: string,
animation: AnimationMetadata | AnimationMetadata[],
options: AnimationQueryOptions | null = null
): AnimationQueryMetadata;
在处于动画序列的元素内部查找一个或多个元素,这些元素也会被加入当前动画序列中,不过一般会重新写一个数组来重新定义选取元素的动画序列。
1) 选取元素并可以限制数量
query()函数源码中使用了element.querySelectorAll
因此他可以选取多个元素,所以我们在选取元素的时候可以加上一个 limit
来限制选取的数量。
// 在class为 demo的div里找一个div,找到的是 demo1,如果 limit为2 的话找到的是 [demo1, demo2]。
template: `
<div [@queryDemo] class='demo'>
<div class='demo1'></div>
<div class='demo2'></div>
</div>
`,
animations: [
trigger('queryDemo', [
transition('void => *', [
query( 'div', animate(...), {limit: 1} )
])
]
]
2) 开/ 关报错功能
默认情况下如果选取的元素找不到则 query()函数会报错,设置optional
选项为 true 则或忽略错误。
query('.demo-not-be-there', [
animate(...),
animate(...)
], { optional: true })
query()函数里面用伪选择器可以选出特定的元素:
query(":enter")/query(":leave")
: 选取新插入 / 移除的元素query(":animating")
: 选取所有正在进行动画的元素query("@triggerName")
: 选取有特定触发器的元素query("@*")
: 选取所有具有触发器的元素query(":self")
: 把当前元素增加到动画序列中多个伪选择器可以合在一起组成选择器查询字符串:
query(':self, .record:enter, .record:leave, @subTrigger', [...])
@Component({
selector: 'inner',
template: `
<div [@queryAnimation]="exp">
<h1>Title</h1>
<div class="content">
Blah blah blah
</div>
</div>
`,
animations: [
trigger('queryAnimation', [
transition('* => goAnimate', [
// 隐藏里面的元素
query('h1', style({ opacity: 0 })),
query('.content', style({ opacity: 0 })),
// 一个一个地执行里面元素的动画
query('h1', animate(1000, style({ opacity: 1 })),
query('.content', animate(1000, style({ opacity: 1 })),
])
])
]
})
class Cmp {
exp = '';
goAnimate() {
this.exp = 'goAnimate';
}
}
<a routerLink="detail">
</a>
<a [routerLink]="['detail']">
</a>
假设当前路由为
`http://localhost:4200/#/doc/license`
/ + 路由名字
<!--跳到 http://localhost:4200/#/doc/license -->
<a [routerLink]="['/doc/demo']" >跳呀跳</a>
<!--跳到 http://localhost:4200/#/demo -->
<a [routerLink]="['/demo']" >跳呀跳</a>
那么url是
http://localhost:4200/#/doc/demo
上跳转,即 http://localhost:4200/#/
+ 你要跳转的绝对路径
。
路由名字
<a [routerLink]="['demo']" >跳呀跳</a>
那么url是http://localhost:4200/#/doc/license/(demo)
,即http://localhost:4200/#/doc/license
+ 你要跳转的绝对路径
,这时候它会给你的路由加些东西变成 /(demo)
,跳转不了。
../路由名字
<a [routerLink]="['../demo']" >跳呀跳</a>
那么url是
http://localhost:4200/#/doc/demo
,即 http://localhost:4200/#/doc
+ 你要跳转的相对路径
。它会从上一级开始寻找。
./路由名字
, 即现在的路由+你写的要跳去的路由 <a [routerLink]="['./demo']" >跳呀跳</a>
那么url是
http://localhost:4200/#/doc/license/demo
上,即 http://localhost:4200/#/doc/license
+ 你要跳转的相对路径
。它会从当前路由的下一级开始寻找此匹配的路由进行跳转。
Can't bind to 'formGroup' since it isn't a known property of 'form'
在你用表单的模块得引入ReactiveFormsModule
和 FormsModule
即可解决
...
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
...
FormsModule,
ReactiveFormsModule
]
...
})
...
webpack体系原生支持热替换功能,设置也非常简单,只须在webpack-dev-server命令下添加--hot参数:
webpack-dev-server --port=4200 --hot
加上这个hot参数会添加 HotModuleReplacementPlugin 插件到webapck配置里,并启动热替换功能。
如果你用的是 Angular-CLI 搭建,则添加 --hmr 即可开发服务器端的热替换功能:
ng serve --hmr
但是仅仅在开发服务器端开启热替换还不够,Angular 项目仍需要添加热替换处理逻辑,修改 Angular 的启动命令如下如下:
declare var module: any;
if(module.hot) { // 如果webpack启用了热替换功能
// 接受模块更新的事件,同时阻止这个事件继续冒泡
// 参考这文章:https://github.com/webpack/docs/wiki/hot-module-replacement-with-webpack
module.hot.accept();
}
platformBrowserDynamic().bootstrapModule(AppModule)
这时候修改组件代码后,页面不再刷新了,而是把整颗组件树重新渲染,替换DOM节点。
但这样随之而来的一个问题是,在组件里保存的一些数据状态也会丢失,这样的体验显然不好。一个解决办法将组件的数据状态抽取出来外界管理,在热更新渲染前保存数据,渲染后还原数据,示例代码如下:
if(module.hot) {
module.hot.accept();
restoreState(module.hot.data.state); // 还原数据状态
// 模块销毁前触发
module.hot.dispose(() => {
module.hot.data.state = getState(); // 保存数据状态
});
}
webpack的热更新功能打开后,module.hot.data 是一个对象,可以用来在旧模块和新模块之间传递数据
这种有点类似于Redux的做法,Angular世界里其实也有这样工具库,如ngrx。
还有一个问题就是,重新渲染的过程中,一些老的样式不会被清理,导致header里的style标签堆积很多,所以在在重新渲染之前可先清理原来的style标签,示例代码如下:
if(module.hot) {
module.hot.accept();
module.hot.dispose(() => {
let styles = document.head.querySelectorAll('style');
for(let i = 0, len = styles.length; i++; i < len) {
if(styles[i].innerText.indexOf('_ng') >= 0) {
styles[i].remove();
styles[i] = null;
}
}
});
}
热替换功能不依赖ngrx。
热替换后的组件内数据会丢失,用ngrx来管理组件数据可以避免这个问题。
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "seczone",
"ejected": false // 标记该应用是否已经执行过eject命令把webpack配置释放出来
},
"apps": [
{
"root": "src", // 源码根目录
"outDir": "dist", //编译后的输出目录,默认是dist
"assets": [ // 记录资源文件夹,构建时复制到 outDir 指定的目录
"assets"
],
"index": "index.html", // 指定的首页文件,默认是 'index.html'
"main": "main.ts", // 指定应用的入口文件
"polyfills": "polyfills.ts", // 指定 polyfill 文件
"test": "test.ts", // 指定测试入口文件
"tsconfig": "tsconfig.app.json", // 指定tsconfig文件
"testTsconfig": "tsconfig.spec.json", // 指定TypeScript单测脚本的tsconfig文件
"prefix": "", // 使用`ng generate`命令时,自动为selector元数据的值添加的前缀名
"deployUrl": "", // 指定站点的部署地址,该值最终会赋给webpack的output.publicPath,常用于CDN部署
"styles": [ // 引入全局样式,构建时会打包进来,常用于第三方库引入的样式
"styles.scss"
],
"scripts": [ // 引入全局脚本,构建时会打包进来,常用语第三方库引入的脚本
"../node_modules/tinymce/tinymce.js",
"../node_modules/tinymce/themes/modern/theme.js",
"../node_modules/tinymce/plugins/link/plugin.js",
"../node_modules/tinymce/plugins/paste/plugin.js",
"../node_modules/tinymce/plugins/table/plugin.js",
"../node_modules/tinymce/plugins/preview/plugin.js",
"../node_modules/tinymce/plugins/fullscreen/plugin.js",
"../node_modules/tinymce/plugins/textcolor/plugin.js",
"../node_modules/tinymce/plugins/image/plugin.js",
"../node_modules/tinymce/plugins/imagetools/plugin.js",
"../node_modules/tinymce/plugins/code/plugin.js"
],
"environmentSource": "environments/environment.ts", // 基础环境配置
"environments": { // 子环境配置文件
"dev": "environments/environment.ts",
"demo": "environments/environment.demo.ts",
"prod": "environments/environment.prod.ts",
"test": "environments/environment.test.ts",
"github": "environments/environment.github.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": { // 执行`ng generate`命令时的一些默认值
"styleExt": "scss ", // 默认生成的样式文件后缀名
"component": {
"flat": false, // 生成组件时是否新建文件夹包装组件文件,默认为false(即新建文件夹)
"inlineStyle": false, // 新建时是否使用内联样式,默认为false
"inlineTemplate": false, // 新建时是否使用内联模板,默认为false
// "viewEncapsulation": "Emulated", // 指定生成的组件的元数据viewEncapsulation的默认值
// "changeDetection": "OnPush", // 指定生成的组件的元数据changeDetection的默认值
"spec": false // 是否生成spec文件,默认为true
}
}
}
function keyframes(
steps: AnimationStyleMetadata[]
): AnimationKeyframesSequenceMetadata;
它的参数是一个style()
的数组,表示步骤。
animate("5s", keyframes([
style({ backgroundColor: "red", offset: 0 }), // 0%
style({ backgroundColor: "blue", offset: 0.2 }), // 20%
style({ backgroundColor: "orange", offset: 0.3 }), // 30%
style({ backgroundColor: "black", offset: 1 }) // 100%
]))
这里的 offset 和css3 keyframes里面的百分比一样,是时间的偏移量。
offset: 0.2表示动画在 20%的时候的样式。
此外,如果没有设置偏移量,那么偏移量将自动计算
animate("5s", keyframes([
style({ backgroundColor: "red" }) // offset = 0
style({ backgroundColor: "blue" }) // offset = 0.33
style({ backgroundColor: "orange" }) // offset = 0.66
style({ backgroundColor: "black" }) // offset = 1
]))
�[1m�[31mERROR in ./src/polyfills.ts
Module not found: Error: Can't resolve 'es5-shim' in '/root/.jenkins/workspace/Platform-Service-StaticResource/src'
@ ./src/polyfills.ts 2:0-18
@ multi ./src/polyfills.ts�[39m�[22m
�[1m�[31mERROR in ./src/polyfills.ts
Module not found: Error: Can't resolve 'es6-shim' in '/root/.jenkins/workspace/Platform-Service-StaticResource/src'
@ ./src/polyfills.ts 3:0-18
@ multi ./src/polyfills.ts�[39m�[22m
Build step 'Execute shell' marked build as failure
Finished: FAILURE
npm uninstall -g @angular/cli
npm uninstall --save-dev @angular/cli
npm cache clean
npm install -g @angular/cli@latest
npm install --save -dev @angular/cli@latest
强制清除缓存
npm cache clean --force
或者
npm cache verify
number_expression(value) | number[:digitInfo[:locale]]
{{e | number:'3.1-5'}}
{{数字 | number:'限定的范围'}}
根据给定的范围,将数字转换为符合格式的文本。
value
值是一个数字类型
digitInfo
是一个字符串形如:
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
minIntegerDigits
: 设置整数的最小位数。默认是1位。minFractionDigits
: 设置小数点后的最小位数。默认值为0。maxFractionDigits
: 设置小数点后的最大位数。默认为3。locale
是定义要使用的语言环境的字符串(默认使用当前的LOCALE_ID)。
@Component({
selector: 'number-pipe',
template: `<div>
<!-- num1 = 3.14,num2 = 2.718281828459045; -->
<!-- 没有做转换,使用默认的转换系数 ‘1.3-0’,即整数最少为1位,小数点为0-3位 -->
<p>{{num1 | number}}</p> <!-- 输出 '3.14'-->
<p>{{num2 | number}}</p> <!-- 输出 '2.718'-->
<!-- 整数最少3位,小数1到5位-->
<p> {{num1 | number:'3.1-5'}}</p> <!-- 输出 '003.14 '-->
<p> {{num2 | number:'3.1-5'}}</p> <!--输出 '002.71828'-->
<!-- 整数最少4位,小数5位-->
<p>e (4.5-5): {{num1 | number:'4.5-5'}}</p><!--输出 '0,003.14000'-->
<p>pi (3.5-5): {{num2 | number:'4.5-5'}}</p><!--输出 '0,002.71828'-->
<!-- 'fr'是 LOCALE_ID -->
<p>e (french): {{num2 | number:'4.5-5':'fr'}}</p><!--输出 '0 002,71828'-->
</div>`
})
export class NumberPipeComponent {
num1: number = 3.14;
num2: number = 2.718281828459045;
}
在打包项目的时候提前编译好应用,打包好之后可以直接启动,而不是把编译器打包在应用中用的时候再编译。生产环境使用。
浏览器中启动并编译所有的组件和模块,动态运行应用程序。开发过程中使用。
告诉Angular怎么创建或改变HTML 元素。
分为三类:
监听或修改其它 HTML 元素、特性 (attribute)、属性 (property)、组件的行为的命令,通常用作修改 HTML 属性(样式等)。
如ngClass、ngStyle。
监听或者修改元素的结构,删除或者增加dom。如ngIf这个“条件化元素”指令,ngFor这个“重复器”指令。
一个网页中一切皆可以视为组件。
一个按钮或者一个表格都可以是一个组件,其实组件就相当于汽车零件,一个零件由各种材料(html、css、js等构成),它只维护自身的逻辑。
就是把一个组件的部分文件放在一个index.ts一起抛出去供别的地方引用。
├─login
│ ├─login.component.ts
│ ├─login.service.ts
│ ├─login.directive.ts
│ ├─ index.ts
...
index.ts里面引入 login.component.ts等文件
export * from './login.component.ts';
export * from './login.service.ts';
别的地方引用
import { loginComponent, LoginService } from '../login';
直接写组件的文件夹会默认寻找下面的index.ts
Angular 模块同样可以把组件、服务指令等放在一起抛出去。
实际上,是装饰 (decoration) 的同义词。
几乎都是指的数据绑定 (data binding) 和将 HTML 对象属性绑定到数据对象属性的行为。
有时也会指在“令牌”(也称为键)和依赖提供商 (provider) 之间的依赖注入 (dependency injection) 绑定。 这种用法很少,而且一般都会在上下文中写清楚。
把后台数据展示出来,把用户操作转换为数据获取到。
You launch an Angular application by "bootstrapping" it using the application root NgModule (AppModule).
通过应用程序根 Angular 模块来启动 Angular 应用程序。 启动过程标识应用的顶级“根”组件 (component),也就是应用加载的第一个组件。 更多信息,见设置。
你可以在同一个index.html中引导多个应用,每个应用都有它自己的顶级根组件。
首字母小写,其他字母或缩写首字母大写。又叫小写驼峰式命名法 (lower camel case) 。
函数、属性和方法命名一般用在这个写法。
每个单词或缩写都以大写开头,也称大写驼峰式命名法。
类名一般用这个写法。
使用中线 (-) 分隔每个单词,也称为烤串命名法 kebab-case。
指令的选择器(例如my-app)和文件名(例如hero-list.component.ts)通常是用中线命名法来命名。
在多个词之间用下划线(_)分隔。也叫下划线命名法。
用一个不恰当的词语:人模狗样。
其实就是把一个未知的东西打扮一下让Angular知道它是什么。
@component告诉Angular它是组件,@input告诉Angular它是输入数据,@Injectable告诉Angular它是服务。
需要的东西(依赖)直接从提供者(providers)那里拿过来用,不需要就不带提供者玩。
Angular 依赖注入系统 (Dependency Injection System)中的一个对象, 它可以在自己的缓存中找到一个命名的“依赖”或者利用已注册的提供商 (provider) 创建这样一个依赖。
依赖注入系统依靠提供商来创建依赖的实例。 它把一个查找令牌和代码(有时也叫“配方”)关联到一起,以便创建依赖值。
JavaScript统称,有多个JavaScript版本。最新批准的 JavaScript 版本是ECMAScript 2016(也称“ES2016”或“ES7”)。
简写: ES6 语言
缩写: ES2015 语言
“ECMAScript 5”的简写,大部分现代浏览器使用的 JavaScript 版本。
别的组件传过来的数据,数据值会从模板表达式等号右侧的数据源流入这个属性 。它和输出属性一般用作父子组件传递信息。
别人(父组件)眼里的我:
// 等号右侧往左侧流入
<me [receiver]='别的传来的'></me>
其实我(子组件)是这样的
@Component({
selector: 'me'
...
})
..
@input() receiver : string;
ngOnChanges(){
console.log("传过来的数据",this.receiver); // 打印出来‘别的传来的’
}
通过触发父组件的事件进行传递数据。
事件流从这个属性流出到模板表达式等号的右边的接收者。
子组件ts
@Output() sendHero: EventEmitter<any> = new EventEmitter();
ngOnInit() {
this.sendHero.emit('timo');
}
父组件:
html:
<me (sendHero)="getHero($event)"></me>
ts:
getHero(hero :string){
console.log("传递过来的是哪个召唤师",hero); //传递过来的是提莫
}
把变量插入html中。
ts:
public me = '万年青桐五';
html:
<div>我是{{me}},求带飞。</div> // 我是万年青桐五,求带飞。
不同时候可以做什么事情。比如20分钟才可以打大龙。
模块是一个内聚的代码块,具有单一用途。就像前端和后端是两个模块,如果想要交流的话通过接口(Angular里面是引用)。
在接口请求的时候会用到,将异步数据转化为数据流,自身也可以生成一些自定义的数据流。它是引自的RxJS(Reactive Extensions for JavaScript),一个第三方包。
一个可以把米做成米饭的函数,管道起到一个转换的作用。
Angular内置一些管道:DatePipe、UpperCasePipe、LowerCasePipe、CurrencyPipe和PercentPipe。
也可以自定义管道。
如:
假设
ts:
birthday = 1510156800000;
html:
<p> {{ birthday | date:"yy/MM/dd" }} </p>
转换后:
<p> 2017/11/9 </p>
通过组件中代码构建 Angular 表单的一种技术。 另一种技术是模板驱动表单。
构建响应式表单时:
动态表单非常强大、灵活,它在复杂数据输入的场景下尤其好用,例如动态的生成表单控制器。
通过配置不同的路由,加载不同的组件或模块,组合成不同的页面。
一个带有路由插座 ( RouterOutlet ) 的 Angular 组件。
那什么是路由插座?其实可以理解为我们平时用的插排,每个孔都是一个路由,插上这个孔的电器是路由对应的组件。这个插座加上插上的电器才是我们想要的组件。
路由插座怎么用?里面的过程是什么样的?看下面。
文件形如:
├─list
│ ├─list.component.ts
│ ├─list.routes.ts
│ ├─lol
│ │ ├─ lol.component.ts // 里面包含了html,css等
list.component.ts
@Component({
..
template: '
<h1>拥有路由插座的组件</h1>
<router-outlet></router-outlet>
'
})
list.routes.ts
import { ListComponent } from './list.component';
import { LolComponent } from './lol/lol.component';
export const listRoutes = [{
path: 'list',
component: ListComponent,
children: [
{
path: 'lol',
component: LolComponent
}
}]
首先是匹配到list路由,这时候发现list的html里面有路由插座(router-outlet),就去找子路由,根据子路由把对应的组件插入到路由插座的位置。
是指Angular的框架源码,它根据不同功能分成不同模块的包,每个包都有一定的作用范围。以@angular开头。
分成模块的好处是不用的话就不需要引入这个包,比如我不写动画就不用引入@angular/animations这个包。
题外话:和本主题无关的其他几个包的作用:
服务用于封装不与任何特定视图相关的数据和逻辑,或者用于在组件之间共享数据和逻辑。一般用于接口请求或传递数据。
其实就是个html
@Component({
template: `<div>其实就是个html</div>`
})
html里面的表达式。Angular会 执行这个表达式得到值,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
<div [property]="1+1"></div>
把一种形式的 JavaScript(例如 TypeScript)转换成另一种形式的 JavaScript(例如 ES5)的过程。
视图是屏幕中一小块,用来显示信息并响应用户动作,例如点击、移动鼠标和按键。
父组件 ts
或者为一个字符串:
public sendData = "我是要传递过去的数据";
父组件 html
<app-demo [getData]="data"></app-demo>
// 这里 [getData]="data" 是直接写 'data'不是 '{{data}}',也不是willCheckdata="data"
子组件 ts
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-Demo',
...
})
export class DemoComponent implements OnInit {
@Input() public getData: string;
ngOnInit(){
console.log("传递过来的值",this.getData);
}
}
子元素 demo.component.ts:
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-demo',
...
})
export class DemoComponent implements OnInit {
@Output() sendVal: EventEmitter<any> = new EventEmitter();
private val : string = '我是要传过去的字符串';
ngOnInit() {
this.sendVal.emit(this.val);
}
}
父元素 html
<app-demo (sendVal)="getVal($event)"></app-demo>
父元素ts
getVal(value){
console.log("value",value);
}
首先
npm i @types/node --global
再在 tsconfig.json
文件中加上
{ "compilerOptions": { ..., "types": ["node"] } }
去除原因:
发布到生产环境可能会有console函数忘记注释,这里面可能会包含敏感数据导致不安全,所以需要在全局进行禁止打印信息。
去除方法:
在入口文件main.ts里面加个判断,把打印函数重写。
if (environment.production) {
window.console.log = function (){};
window.console.info = function (){};
window.console.warn = function (){};
window.console.error = function (){};
window.console.debug = function (){};
}
ERROR in Error: Debug Failure. False expression.
at tryReuseStructureFromOldProgram (D:\per\Angular-demo\node_modules\@angular\cli\node_modules\typescript\lib\typescript.js:70007:22)
at Object.createProgram (D:\per\Angular-demo\node_modules\@angular\cli\node_modules\typescript\lib\typescript.js:69763:34)
at _donePromise.Promise.resolve.then.then (D:\per\Angular-demo\node_modules\@angular\cli\node_modules\@ngtools\webpack\src\plugin.js:412:32)
at process._tickCallback (internal/process/next_tick.js:109:7)
像这样莫名的错误,直接重启项目即可,或者重启编辑器,再不行就重启电脑...
function animate(
timings: string | number,
styles: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata |null = null
): AnimationAnimateMetadata;
duration delay easing
animate("200 ease-in", ...)
// 被编译成 duration=200, easing=ease-out
animate("200 1000 ease-in", ...)
// 被编译成 duration=200, delay=1000, easing=ease-out
animate(timings, style({ background: "red" }))
animate(timings, keyframes([
style({ background: "blue", offset: 0.2})), // 20%
style({ background: "red", offset: 1})) // 100%
])
假设有三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。
当我们构建参数时会用到 --environment来指定应用执行环境。脚手架会根据指定的值加载对应的环境配置文件。如:ng build --env=dev 就是build开发环境的包。那么我们就从这里开始看看Angular项目里环境该怎么配置。
"environmentSource": "environments/environment.ts", // 基础环境配置
"environments": { // 子环境配置文件
"dev": "environments/environment.ts",
"test": "environments/environment.test.ts",
"prod": "environments/environment.prod.ts"
}
{
"title": "",
"question": {
"url": ""
},
"list": {
"pageSize": 10
}
}
开发环境 :
export const environment = Object.assign({}, require('./common.json'), require('./development.json'), {
production: false,
envName: 'dev'
});
{
"baseUrl": "/",
"env": "dev",
"api": {
"host": "http://localhost:4200"
}
}
测试环境:
export const environment = Object.assign({}, require('./common.json'), require('./test.json'), {
production: false,
envName: 'test'
});
{
"baseUrl": "/",
"env": "test",
"api": {
"host": "testurl"
}
}
生产环境:
export const environment = Object.assign({}, require('./common.json'), require('./production.json'), {
production: true,
envName: 'prod'
});
{
"baseUrl": "/",
"env": "prod",
"api": {
"host": "produrl"
}
}
...
@Injectable()
export class LoginService {
public Login(): Observable<any> {
return this.http.get("/login").map(
response => {
return response.json();
}
)
}
}
(完)
function transition(
stateChangeExpr: string,
steps: AnimationMetadata | AnimationMetadata[],
options: AnimationOptions | null = null
):AnimationTransitionMetadata;
A=>B,状态转换表达式,即从哪个状态切换到哪个状态。支持以下写法:
transition("void => *", animate(500))
transition("void <=> *", animate(500)),
transition("on => off, off => void", animate(500)),
animate(),动画执行步骤,即几秒执行完,执行曲线是怎样的。
如animate('100ms ease-in')
或是一个数组 [animate('100ms ease-in'),animate(600)]
可以传入动画的延迟值和动画输入参数,以更改计时数据和样式。详见 AnimationOptions
函数。
其实状态 transition("void => *", animate(500))
表示的是进入,在这里可以用 :enter
表示:
transition(":enter", animate(500))
同理 transition(" *=> void", animate(500))
离开可以这样写:
transition(":leave", animate(500))
假设个场景:做一个菜单,有的节点是点击出现子节点,有的节点是点击跳转路由
想要实现是连接则跳转,不是连接则不跳转,可以这样做。
ts
isLink : boolean = false;
html
<div
[routerLink]="isLink ? data.url : null"
routerLinkActive="node-active">
</div>
css
.node-active{
background: #006189;
}
可以正常跳转,但是路由只要是Null的话就会处于激活状态,所以还需对routerLinkActive
进行判断。
html
<div
[routerLink]="isLink ? null : data.url"
routerLinkActive="{{isLink ? 'node-active' : null }}">
</div>
npm install @angular/animations
"@angular/animations": "^4.0.3"
先定义一个动画函数:fade-in.ts
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
export const flyIn = trigger('flyIn', [
state('in', style({transform: 'translateX(0)'})),
transition('void => *', [
animate(1000, keyframes([
style({opacity: 0, transform: 'translateX(-100%)', offset: 0}),
style({opacity: 1, transform: 'translateX(25px)', offset: 0.3}),
style({opacity: 1, transform: 'translateX(0)', offset: 1.0})
]))
]),
transition('* => void', [
animate(3000, keyframes([
style({opacity: 1, transform: 'translateX(0)', offset: 0}),
style({opacity: 1, transform: 'translateX(-25px)', offset: 0.7}),
style({opacity: 0, transform: 'translateX(100%)', offset: 1.0})
]))
])
]);
再在要用的地方的ts里
import { flyIn } from "../../animations/fly-in";
@Component({
...
animations: [flyIn]
})
html里
<div [@flyIn]>
我会飞 你呢
</div>
根据布尔值在两个状态之间切换
定义一个toggle.ts
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
export const toggle = trigger('toggle', [
state('in', style({ opacity: '1'})),
state('out', style({ opacity: '0'})),
transition('in => out',
animate(1000, keyframes([
style({ opacity: '1'}),
style({ opacity : '0'})
]))
),
transition('out => in',
animate(1000, keyframes([
style({ opacity : '0' }),
style({ opacity : '1' })
])))
]);
主ts里面引用
import { toggle } from "../../../animations/fly-toggle";
@Component({
selector: '...',
templateUrl: '...',
styleUrls: ['...'],
animations: [toggle]
})
export class Deep {
isActive: boolean;
changeState() {
this.isActive = !this.isActive;
}
}
html里
<button (click)="changeState()">修改状态</button>
<div [@flyToggle]="isActive ? 'in' : 'out'">
</div>
<div [@flyToggle]="isActive ? 'out' : 'in'">
</div>
点击按钮触发 changeState()函数,函数里再进行运算给 isActive赋值,传回 [@flyToggle]中触发动画效果。
**
<div [@flyToggle]="isActive ? 'in' : 'out'"
(@expandState.start)="transitionStart($event)" (@expandState.done)="transitionDone($event)" >
</div>
@triggerName.start动画开始事件,@triggerName.done动画结束事件。
$event事件包含6个参数:
有个动画 flyIn和布尔值 showAnimation
ts
...
[flyIn]
...
private showAnimation : boolean = false;
...
// 在某个情况下改变布尔值开始动画
this.showAnimation = true;
html
<div [flyIn] *ngIf='showAnimation'>
开始动画
</div>
*代表其自适应到本来的数值
比如说我的width为80%,给它写一个动画
import { trigger, state, animate, transition, style, keyframes } from '@angular/animations';
export const autoWidth = trigger('autoWidth', [
transition('void => *', [
animate(1000, keyframes([
style({
width: 0
}),
style({
width: '*'
})
]))
])
])
它就可以自动从width为0 在1s内到 80%的宽度,而不是原宽度的 80%
html
<p [ngClass] = " class1 class2 " class = " class3 " >
css
.class1 { color : red }
.class2 { width : 10px }
.class3 { height : 10px }
html
<p [ ngClass] = [" class1", "class2 "] class = " class3 " >
css
.class1 { color : red }
.class2 { width : 10px }
.class3 { height : 10px }
html
<div (click)=changeP()''>更改p的样式</div>
<p [ ngClass] = "{ 'class1' : true , 'class2' : false , 'class3' :showThree }" >
css
.class1 { color : red }
.class2 { width : 10px }
.class3 { height : 10px }
ts
public showThree :boolean = true ;
class1为 true所以样式是显示的, class2 为false 所以样式是没有的, class3 在ts里面赋值为 true 所以也是显示的。
ts
private showDet : boolean = false;
clickTit(){
this.showDet = !this.showDet;
}
html
[ngClass]="showDet ? 'show-det' : 'hide-det'"
//多个class
[ngClass]="[showDet ? 'show-det' : 'hide-det', showtit ? 'show-tit' : 'hide-tit' ]"
谷歌于 2009 年发布的 JavaScript 框架叫做 AngularJS,官网为 angularjs.org,代码库angular/angular.js;
而 2016 年发布的 JavaScript 开发平台叫做 Angular,官网为 angular.io,代码库为 angular/angular。目前发布的angualr2.0、angualr4.0、angualr5.0都只是版本号。
关于两者名称的使用可以参考 Branding Guidelines for Angular and AngularJS。
Angular 的定位为开发平台而非 Web 框架,例如 Angular 也可用于移动端应用的开发等,可以参考 NativeScript、ionic 等。
当你在做一个后台管理系统,左边是一排路由导航,点击可以进入不同的页面,那么这个路由所在dom元素会添加上样式表示当前是位置。
但是一刷新你会发现,这个样式没了...
怎么办?
采用路由高亮:当路由被激活时允许你添加一个class在你添加路由的dom元素上,只有url变化时才会移除此样式。
当前路由被激活或者当前路由处于激活状态表示页面的url中路由和当前dom标签里的路由想匹配。
// 用法概览
@Directive({
selector: '[routerLinkActive]',
exportAs: 'routerLinkActive'
})
class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
constructor(router: Router, element: ElementRef, renderer: Renderer2, cdr: ChangeDetectorRef)
links: QueryList<RouterLink>
linksWithHrefs: QueryList<RouterLinkWithHref>
get isActive: boolean
routerLinkActiveOptions: {...}
set routerLinkActive: string[] | string
ngAfterContentInit(): void
ngOnChanges(changes: SimpleChanges): void
ngOnDestroy(): void
}
.red{
color: red;
}
<a routerLink="/user/login" routerLinkActive="red">login</a>
当url是user
或者/user/login
的时候,a标签将会被加上classred
。当url变化为别的时,class将会被移除。
<a routerLink="/user/login" routerLinkActive="class1 class2">login</a>
routerLinkActive
的两种写法
<a routerLink="/user/login" routerLinkActive="class1 class2">login</a>
<a routerLink="/user/login" [routerLinkActive]="['class1', 'class2']">login</a>
routerLinkActive
进行配置参数传递 exact: true
表示路由完全匹配时才高亮,如
<a routerLink="/user/login" routerLinkActive="red" [routerLinkActiveOptions]="{exact:
true}">login</a>
isActive
检查当前是否路由处于激活状态<a routerLink="/user/login" routerLinkActive #rla="routerLinkActive">
login {{ rla.isActive ? '激活' : '未激活'}}
</a>
如果当前路由处于激活状态,则会显示:
login 激活
非激活状态
login 未激活
上述的 rla 为routerLinkActive缩写,它可以随便定义。
你可以在使用routerLink
元素的父元素上使用RouterLinkActive指令
<div routerLinkActive="red" [routerLinkActiveOptions]="{exact: true}">
<a routerLink="/user/login">login</a>
<a routerLink="/user/reset">reset</a>
</div>
routerLinkActive
和routerLinkActiveOptions
,/user/login
或/user/reset
时其父元素div会被添加上red
样式。routerLinkActiveOptions
指完全匹配,不然会出现url为/user
时父元素也会被添加了red
样式。routerLink
的值是变量的话routerLink={{temp}}
temp
就是你的变量名
ERROR in Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 194:50 in
the original .ts file), resolving symbol NgModule in D:/plateform/project-test/node_modules/@angular/platform-browser/node_modules/@angular/core/core.d.ts, resolving symbol BrowserModule in D:/plateform/pr
oject-test/node_modules/@angular/platform-browser/platform-browser.d.ts, resolving symbol BrowserModule in D:/plateform/project-test/node_modules/@angular/platform-browser/platform-browser.d.ts
at positionalError (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler\bundles\compiler.umd.js:25266:35)
at simplifyInContext (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler\bundles\compiler.umd.js:25109:27)
at StaticReflector.simplify (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler\bundles\compiler.umd.js:25123:13)
at StaticReflector.annotations (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler\bundles\compiler.umd.js:24553:41)
at _getNgModuleMetadata (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_impl.js:138:31)
at _extractLazyRoutesFromStaticModule (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_impl.js:109:26)
at D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_impl.js:129:27
at Array.reduce (native)
at _extractLazyRoutesFromStaticModule (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_impl.js:128:10)
at Object.listLazyRoutesOfModule (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_impl.js:53:22)
at Function.NgTools_InternalApi_NG_2.listLazyRoutes (D:\plateform\project-test\node_modules\.4.4.3@@angular\compiler-cli\src\ngtools_api.js:91:39)
at AotPlugin._getLazyRoutesFromNgtools (D:\plateform\project-test\node_modules\.1.7.1@@ngtools\webpack\src\plugin.js:207:44)
at _donePromise.Promise.resolve.then.then.then.then.then (D:\plateform\project-test\node_modules\.1.7.1@@ngtools\webpack\src\plugin.js:443:24)
at process._tickCallback (internal/process/next_tick.js:109:7)
它是typescript的依赖关系问题,在 tsconfig.json文件中中加
"paths": {
"@angular/common": ["../node_modules/@angular/common"],
"@angular/compiler": ["../node_modules/@angular/compiler"],
"@angular/core": ["../node_modules/@angular/core"],
"@angular/forms": ["../node_modules/@angular/forms"],
"@angular/platform-browser": ["../node_modules/@angular/platform-browser"],
"@angular/platform-browser-dynamic": ["../node_modules/@angular/platform-browser-dynamic"],
"@angular/router": ["../node_modules/@angular/router"],
"@angular/http": ["../node_modules/@angular/http"]
}
加后形如这样:
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"mapRoot": "./",
"module": "es6",
"moduleResolution": "node",
"outDir": "../dist/out-tsc",
"sourceMap": true,
"target": "es5",
"typeRoots": [
"../node_modules/@types"
],
"paths": {
"@angular/common": ["../node_modules/@angular/common"],
"@angular/compiler": ["../node_modules/@angular/compiler"],
"@angular/core": ["../node_modules/@angular/core"],
"@angular/forms": ["../node_modules/@angular/forms"],
"@angular/platform-browser": ["../node_modules/@angular/platform-browser"],
"@angular/platform-browser-dynamic": ["../node_modules/@angular/platform-browser-dynamic"],
"@angular/router": ["../node_modules/@angular/router"],
"@angular/http": ["../node_modules/@angular/http"]
}
}
}
Argument of type 'Http' is not assignable to parameter of type 'Http'
或
Argument of type 'HttpClient' is not assignable to parameter of type 'Http'. Property '_backend' is missing in type 'HttpClient'.
解决办法:下载新版本的 @angular/http和 @ngx-translate/http-loader:
npm install @angular/http@latest --save
npm install @ngx-translate/[email protected] --save
把原来用cnpm下载的包卸载了用npm下载
把 它的zip包放在 C:\Users\SZHF012\AppData\Local\Temp\phantormJs
文件夹中
npm install phantomjs-prebuilt
符号 | 依赖 | 版本 | 更新内容 | 解释 |
---|---|---|---|---|
插入符号(^) | ^3.9.2 | 3.. | 1.向后兼容的新功能2.旧的功能被弃用,但是可以操作3.大的重构4.bug修改 | 主版本是固定的,匹配任何次要版本,匹配任何版本号。 |
波浪符号(~) | ~3.9.2 | 3.9.* | 修改bug | 主版本是固定的,小版本是固定的,匹配任何版本号 |
Angular不建议直接操作DOM元素,正如 ElementRef 函数的源码中介绍的一样:
export declare class ElementRef {
/**
* The underlying native element or `null` if direct access to native elements is not supported
* (e.g. when the application runs in a web worker).
*
* <div class="callout is-critical">
* <header>Use with caution</header>
* <p>
* Use this API as the last resort when direct access to DOM is needed. Use templating and
* data-binding provided by Angular instead. Alternatively you take a look at {@link Renderer}
* which provides API that can safely be used even when direct access to native elements is not
* supported.
* </p>
* <p>
* Relying on direct DOM access creates tight coupling between your application and rendering
* layers which will make it impossible to separate the two and deploy your application into a
* web worker.
* </p>
* </div>
* @stable
*/
nativeElement: any;
constructor(nativeElement: any);
}
export declare class ElementRef {
/**
* 如果不支持原生元素直接访问底层的本地元素或`null`
* (e.g. 当应用程序在一个网络工作者运行).
*
* <div class="callout is-critical">
* <header>请谨慎使用</header>
* <p>
* 当需要直接访问DOM时,使用此API作为最后的手段。 使用模板和
数据绑定由Angular提供。 或者,您可以查看{@link Renderer}
它提供的API即使在直接访问本地元素时也可以安全地使用
支持的。
* </p>
* <p>
* 依靠直接的DOM访问可以在应用程序和呈现之间建立紧密的耦合
* 层将使它不可能分开两个和部署你的应用程序到一个
* 网络工作者。
* </p>
* </div>
* @stable
*/
nativeElement: any;
constructor(nativeElement: any);
}
在应用层直接操作 DOM,就会造成应用层与渲染层之间强耦合,导致我们的应用无法运行在不同环境,所以不能直接操作 DOM 元素。
在浏览器中,通过 ElementRef 我们就可以封装不同平台下视图层中的 DOM 元素,最后借助于 Angular 提供的强大的依赖注入特性,我们就可以轻松地访问到 DOM 元素。
// HTML
<div class='deep'>蛮子</div>
// TS
import { Component, ElementRef, ngAfterViewInit } from '@angular/core';
export class AppComponent {
constructor(private elementRef: ElementRef) { }
ngAfterViewInit(){
let divEle = this.elementRef.nativeElement.querySelector('deep');
console.log(divEle); ->打印出 '<div class='deep'>蛮子</div>'
}
}
// HTML
<div #deep>蛮子</div>
// TS
import { Component, ElementRef, ViewChild, ngAfterViewInit } from '@angular/core';
export class AppComponent {
@ViewChild('deep') willChangeDiv: ElementRef;
constructor(private elementRef: ElementRef) {}
ngAfterViewInit(){
this.willChangeDiv.nativeElement.style.backgroundColor = 'red';
}
}
// HTML
<div #deep>蛮子</div>
// TS
import { Component, ElementRef, ViewChild, ngAfterViewInit, Renderer } from '@angular/core';
export class AppComponent {
@ViewChild('deep') willChangeDiv: ElementRef;
constructor(private elementRef: ElementRef) {}
ngAfterViewInit(){
this.renderer.setStyle(this.willChangeDiv.nativeElement, 'backgroundColor', 'red');
}
}
export declare abstract class render2 {
/**
* This field can be used to store arbitrary data on this renderer instance.
* This is useful for renderers that delegate to other renderers.
*/
readonly abstract data: {
[key: string]: any;
};
abstract destroy(): void;
abstract createElement(name: string, namespace?: string | null): any;
abstract createComment(value: string): any;
abstract createText(value: string): any;
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode: ((node: any) => void) | null;
abstract appendChild(parent: any, newChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract removeChild(parent: any, oldChild: any): void;
abstract selectRootElement(selectorOrNode: string | any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract parentNode(node: any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract nextSibling(node: any): any;
abstract setAttribute(el: any, name: string, value: string, namespace?: string | null): void;
abstract removeAttribute(el: any, name: string, namespace?: string | null): void;
abstract addClass(el: any, name: string): void;
abstract removeClass(el: any, name: string): void;
abstract setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void;
abstract removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void;
abstract setProperty(el: any, name: string, value: any): void;
abstract setValue(node: any, value: string): void;
abstract listen(target: 'window' | 'document' | 'body' | any, eventName: string, callback: (event: any) => boolean | void): () => void;
}
function state(
name: string,
styles: AnimationStyleMetadata,
options?: {params: {[name: string]: any}}
):AnimationStateMetadata;
当前状态的名字,可以有多个名字,用逗号隔开,如:'active,clicked'
。
这个name还可以用 void
和*
表示: void
代表动画执行前组件的状态; *
代表动画执行后组件的状态,即组件的默认状态。
处于这个状态时的样式,如style({width: 0})
AnimationStateMetadata
函数ERROR in ./node_modules/css-loader?{"sourceMap":false,"importLoaders":1}!./node_modules/postcss-loader?{"ident":"postcss"}!./node_modules/@angular/cli/node_modules/sass-loader/lib/loader.js?{
"sourceMap":false,"precision":8,"includePaths":[]}!./src/styles.scss
Module build failed: Error: ENOENT: no such file or directory, scandir 'D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\vendor'
at Object.fs.readdirSync (fs.js:913:18)
at Object.getInstalledBinaries (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\extensions.js:128:13)
at foundBinariesList (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\errors.js:20:15)
at foundBinaries (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\errors.js:15:5)
at Object.module.exports.missingBinary (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\errors.js:45:5)
at module.exports (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\binding.js:15:30)
at Object.<anonymous> (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\lib\index.js:14:35)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\sass-loader\lib\loader.js:3:14)
at Module._compile (module.js:571:32)
@ ./src/styles.scss 4:14-213
@ multi ./src/styles.scss
首先看开头的错误信息,
ERROR in...
在什么地方报错了,Module build failed
后面跟的是模块打包错误原因及地址,no such file or directory, scandir 'D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\vendor'
,在D:\个人\gitee-poetry\node_modules\@angular\cli\node_modules\node-sass\vendor'
中vendor文件不存在。那么我们找到这个文件位置,从node-sass官网下载一个vendor文件放进去即可。
npm rebuild node-sass
或者删除 node_modules 重新下载
模块
模块有两层含义:
首先,Angular 要能成功运行,至少需要定义一个模块,因为需要有一个模块作为应用启动的入口,这样的模块就称为根模块。
然后,我们的应用会不断的添加新的功能。这些新增的功能可以封装到一个新的模块里。这些新增加的模块在 angular 里称为特性模块。有了特性模块之后,根模块原来承载在功能逻辑也可以抽离出来,放到某个特性模块里,使根模块保持简洁。
接下来,我们添加的特性模块越来越多,他们之间可以抽出一些相似功能的组件或指令,这些公共的部分也可以封装成一个独立的模块,这样的模块在逻辑意义上不能称为特性模块,Angular 把他称为为共享模块。
最后,还有核心模块,我们知道,一个应用里总有一些全局的组件或服务等,他们只需要在应用启动时候初始化一次即可,例如,维护登录信息的服务,或者是,公共的头部和尾部组件等。虽然我们可以把他们放到根模块里,但更好的设计是把这些逻辑也抽离出来,放到一个独立的模块,这个模块即为核心模块。核心模块要求只导入到根模块里,而尽量不要导入到特性模块或者共享模块里,这是为了在协同工作时候避免出现一些不可预料的结果。
Angular 已经封装了不少常用的模块, 如:
ApplicationModule:封装一些启动相关的工具;
CommonModule:封装一些常用的内置指令和内置管道等;
BrowserModule:封装在浏览器平台运行时的一些工具库,同时将 CommonModule 和 ApplicationModule 打包导出,所以通常在使用时引入 BrowserModule 就可以了;
FormsModule 和 ReactiveFormsModule:封装表单相关的组件指令等;
RouterModule:封装路由相关的组件指令等;
HttpModule:封装网络请求相关的服务等。
所以,如果你想使用 ngIf 和 ngStyle 等这些内置指令,记得先导入 CommonModule,其他的模块使用方法一致。
组件
组件由两部分组成的: @component和 类。
如果只是定义一个类,angular不知道怎么解释这个类,当往这个类里注入组件元数据后,angular才知道把这个类解释为组件。
如果想了解元数据是如何注入到类里,可深入了解 reflect-metadata 这个 polyfill。
数据绑定适用于层级相隔不远的组件,层级太深或者不同分支的组件通讯通常采用其他方式,例如利用服务作为中介。
模板
angular模板基于HTML,普通的html亦可作为模板输入。
@Component({
template: `<p>deepthan</p>`
})
元数据
@component是装饰器,元数据主要以装饰器的函数参数指定。
selector和 template都是元数据。
装饰器实际上是一个自定义函数,angular的各种装饰器处理函数在modules/@angular/core/src/util/decorators.ts 中。
数据绑定
属性绑定和数据绑定均称为数据绑定。
<p>你好,{{data.name}}</p>
[deep] = 'data[0]'
(sendData) = getData(value)
<input [(ngModel)='value']>
[()]是实现双向绑定的语法糖,ngmodel是辅助实现双向绑定的内置指令。 input框和value之间形成数据关联, input值发生变更时会自动赋值到 value,而value值被组件类改变时也可更新input的值。
指令
指令可以通过与dom进行灵活交互,改变样式或改变布局。
<p *ngIf='bol==true'></p>
html:
<p> [ngStyle]='setStyles()' </p>
ts:
setStyles(){
return {
'color' : 'red'
}
}
服务
服务是封装单一功能的单元,类似于工具库,常被引用于组件内部,作为组件的功能拓展。它可以是一个简单的字符串或json数据,也可以是一个函数甚至是一个类,几乎所有的对象都可以封装成服务。
一个简单的日志服务:
// import statement
@Injectable()
export class LoggerService {
private level: string;
setLevel(level: string) {
this.level = level;
}
debug(msg: string) { }
warn(msg: string) { }
error(msg: string) { }
}
依赖注入
通过依赖注入机制,服务等模块可以被引入到任何一个组件(或模块或服务)中,而开发者无须关系这些模块是如何被初始化的。
可以说依赖注入是一种帮助开发者管理模块依赖的设计模式。
一个依赖注入例子:
import {LoggerService} from './logger-service';
// other import statement
@Component({
selector: 'contact',
template: '...'
providers: [LoggerService]
})
export class ContactListComponent {
constructor(logger: LoggerService) {
logger.debug('xxx');
}
}
@component 装饰器中的 providers元元数据是依赖注入操作的关键,它会为该组件创建一个注入器对象,并新建 LoggerService实例存储到这个注入器里。组件需要引入 LoggerService实例时,只需要在构造函数声明 LoggerService类型的参数即可,Angular自动地通过类型匹配,找出注入器里预先实例化好的 loggerService对象,在组件实例化时作为参数传入,这样组件便获得了 LoggerService的实例引用。
其他概念:
Angular 是以适当的时机去检验对象的值是否被改动,这个适当的时机并不是以固定某个频率去执行,而通常是在用户操作事件(如点击),setTimeout 或 XHR 回调等这些异步事件触发之后。Angular 捕获这些异步事件的工作是通过 Zones 库实现的.
import { LoginModule } from './login/login.module'
const routes: Route: [
{
path: 'role',
children: [
{ path: "login", loadChildren: () => LoginModule }
]
}
]
const routes: Route: [
{
path: 'role',
children: [
{ path: "login", () => import('./login/login.module').then((m) => m.LoginModule)}
]
}
]
function style(
tokens: '*' |
{[key: string]: string | number} |
Array<'*'|{[key: string]: string | number}>
): AnimationStyleMetadata;
style声明一个包含CSS属性/样式的键值对。
style({ width: 100, height: 0 })
Auto-styles(自适应样式): style值可以用 '*' 来表示,自动达到其原本的样式,举个例子你就明白作用了:
如果一个div它实际宽度是100px,高度为100px,让它高度从0 到100px变化
<div class='demo'></div>
...
.demo{
width: 100px;
height: 100px;
}
这时候用 '*'来写
animations: [trigger(
'autoHeight',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('void => *', animate(500))
])],
它就会在 500ms内 高度从 0 搭配100px。咦,似乎没感觉到什么作用...
在高度为动态获取的值时候就看到其强大了:data为动态获取的{ height: xxx }
<div class='demo' [ngStyle]="{'height':data.height}">
</div>
...
.demo{
width: 100px;
}
animations: [trigger(
'autoHeight',
[
state('void', style({height: '0px'})),
state('*', style({height: '*'})),
transition('void => *', animate(500))
])],
这样在 500ms 高度自动到达设定的值。
RROR Error: Uncaught (in promise): Error: Cannot find 'TableModule' in '../table/table.module'
Error: Cannot find 'TableModule' in '../table/table.module'
解决办法:
重新 ng serve 即可。
它是一个命令行界面工具,可用于初始化、开发、构建和维护 Angular 应用。
无npm先去下载nodejs
npm install -g @angular/cli
new|n指n是new的缩写,效果相同,下同。
ng new my-project
它会创建一个angular基础项目并且下载依赖运行项目,默认端口是4200。
用法形如:
ng new my-project --xxx=x
以下不特殊注明默认值均为false
参数 | 意义 |
---|---|
`--force=true | false` |
`--routing=true | false` |
`--skipInstall=true | false` |
`--skipTests=true | false` |
`--force=true | false` |
`--style=css | scss |
`--packageManager=npm | yarn |
--prefix=prefix |
指定选择器的前缀(组件、指令),如传入--prefix=dep 则组件的selector会成为dep-xxx , |
呀,创建的时候没有自定义,怎么补救呢? 直接在
angular.json
中改~
新建一个基础文件,里面有预设的代码片段。如ng generate service demo
,则会在当前文件夹新建一个demo.service.ts
。
命令 | 作用 | 简写 |
---|---|---|
ng generate module xx | 新建模块 | ng g m xx |
ng generate component xx | 新建组件 | ng g c xx |
ng generate directive xx | 新建指令 | ng g d xx |
ng generate service xx | 新建服务 | ng g s xx |
ng generate pipe xx | 新建管道 | ng g p xx |
还有个更简单的方法:
vscode 中下载Angular Files插件,搜索alexiv.vscode-angular2-files
即可找到。想在哪里创建点哪里。用起来爽歪歪。后面会写一个vscode开发angular好用的插件~
配置 | 意义 |
---|---|
--host=xx |
设置应用的主机地址,别人可以根据这个地址访问你启动的应用。xx 可以是你的ip或者0.0.0.0 |
`--open=true | false` |
--port |
设置启动的端口号,避免启动多个项目占用同一个端口启动不起来(有人知道怎么让它检测到端口被占用自动加1?) |
--proxyConfig=xx |
设置代理文件 |
`--watch=true | false` |
`--aot=true | false` |
配置 | 意义 |
---|---|
--baseHref=xx |
index.html访问其他静态资源文件的相对路径。也可以在index.html的<base href="xx"> 中配置,还可以在.angular.json 中的baseHref 配置。 |
`--aot=true | false` |
`--optimization=true | false` |
--configuration=xx |
指定打包环境的配置 |
`--prod=true | false` |
在angular.json中做了如下配置,:
{
projects: {
project-name: {
architect: {
build: {
configurations: {
production: {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
...
},
qa: {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.qa.ts"
}
],
...
},
sit: {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.sit.ts"
}
],
...
}
},
}
}
}
}
}
之后可以进行不同的环境打包:
ng build --c=qa
ng build --c=sit
ng build --c=production
--c 是 --configuration的缩写
angular半年更新一个大版本,及时更新版本是非常重要的事情,如果落后高于1个版本以上,后续可能升级会很麻烦(别问我怎么知道,再问跳楼)。
从一个主版本升级到另外一个主版本
ng update @angular/cli@^<major_version> @angular/core@^<major_version>
升级之前最好看下官方的升级指南
ng lint
: 运行代码规则检测ng test
: 运行单元测试ng e2e
: 运行端到端测试ng add
: 添加一个第三方库到项目中,并且将其自定义配置也添加到项目中。如何写一个可以用ng add添加的自定义配置的第三方包?core.es5.js:1020 ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded
RangeError: Maximum call stack size exceeded
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.preloadConfig (vendor.bundle.js:1)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5802)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.preloadConfig (vendor.bundle.js:1)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5802)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5806)
at RouterPreloader.webpackJsonp.../../../router/@angular/router.es5.js.RouterPreloader.processRoutes (router.es5.js:5798)
at resolvePromise (zone.js:824)
at zone.js:876
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:425)
at Object.onInvokeTask (core.es5.js:3881)
at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.runTask (zone.js:192)
at drainMicroTaskQueue (zone.js:602)
at <anonymous>
解决办法: 是不是路由没有在模块里面引入
import { PreviewLoadRouting } from './preview-load.routing';
@NgModule({
imports: [
...
RouterModule.forChild(PreviewLoadRouting) // 这里要引入,不然会报这个错误
]
})
开发者可以在构造时(build-time)编译angular应用。通过compiler-cli、ngc编译应用程序,应用可以从一个模块工厂直接启动,意味着不再需要把angular编译器添加到JavaScript包中,预编译的应用程序加载加速,具有更高的性能。(compiler :编译器)
编译可以让Angular应用达到更高层度的运行效率,主要是指的性能提升,但也包括电池节能和节省流量。
Angular采用了一个不同的方式。在给每个组件做渲染和变化检测的时候,它不再使用同一套逻辑,框架在运行时或者编译时会生成对js虚拟机友好的代码。这些友好的代码可以让js虚拟机在属性访问的缓存,执行变化检查,进行渲染的逻辑执行的快的多。
把组件的模板编译成一个JS类,这些类包含了在绑定的数据中检测变化和渲染UI的逻辑。
非AoT应用的开发流程大概是:
部署好,在页面打开这个app:
使用AoT模式的应用的开发流程是:
如果你对编译器的词法分析过程,解析和生成代码过程等感兴趣,你可以读一读Tobias Bosch的《Angular2编译器》一文,或者它的胶片。
《Angular2编译器》一文链接 https://www.youtube.com/watch?v=kW9cJsvcsGo
它的胶片链接 https://speakerdeck.com/mgechev/angular-toolset-support?slide=69
Angular模板编译器收到一个组件和它的上下文作为输入,并产生了如下文件:
*.ngfactory.ts
*.css.shim.ts : 样式作用范围被隔离后的css文件,根据组件所设置的ViewEncapsulation模式不同而会有不同
*.metadata.json :当前组件/模块的装饰器元数据信息,这些数据可以被想象成以json格式传递给 @component @NgModule 装饰器的信息。
'*'是一个文件名占位符,例如对于hero.component.ts这样的组件,编译器生成的文件是 hero.component.ngfactory.ts, hero.component.css.shim.ts 和 hero.component.metadata.json。*.css.shim.ts和我们讨论的主题关系不大,因此不会对它详细描述。
它包含了如下的定义:
以及下面两个函数
上述这些工厂函数只在生成的AppView实例中才存在。
detectChangesInternal中的代码是JS虚拟机友好的。
<div>{{newName}}</div>
<input type="text" [(ngModel)]="newName">
我们来看看编译后这个模板的代码,detectChangesInternal方法的代码看起来像是这样的:
// ...
var currVal_6 = this.context.newName;
if (import4.checkBinding(throwOnChange, this._expr_6, currVal_6)) {
this._NgModel_5_5.model = currVal_6;
if ((changes === null)) {
(changes = {});
}
changes['model'] = new import7.SimpleChange(this._expr_6, currVal_6);
this._expr_6 = currVal_6;
}
this.detectContentChildrenChanges(throwOnChange);
// ...
假设currVal_6的值是3,this_expr_6的值是1,我们来跟踪看看这个方法的执行。对于这样的一个调用 import4.checkBinding(1, 3),在生产环境下,checkBinding 执行的是下面的检查:
1 === 3 || typeof 1 === 'number' && typeof 3 === 'number' && isNaN(1) && isNaN(3);
上述表达式返回false,因此我们将把变化保持下来,以及直接更新 NgModel 的属性 model 的值,在这之后,detectContentChildrenChanges 方法会被调用,它将为整个模板内容的子级调用 detectChangesInternal。一旦 NgModel 指令发现了 model 属性发生了变化,它就会(几乎)直接调用渲染器来更新对应的DOM元素。
目前为止,我们还没有碰到任何特殊的,或者特别复杂的逻辑。
也许你已经注意到了在internal component内部访问了 this.context 属性。
internal component中的 context 是这个组件的控制器的实例,例如这样的一个组件:
@Component({
selector: 'hero-app',
template: '<h1>{{ hero.name }}</h1>'
})
class HeroComponent {
hero: Hero;
}
this.context 就是 new HeroComponent(),这意味着如果在 detectChangesInternal 中我们需要访问 this.context.name 的话,就带来了一个问题: 如果我们使用AoT模式编译组件的模板,由于这个模式会生成TypeScript代码,因此我们要确保在组件的模板中只访问 this.context 中的public成员。 这是为何?由于TypeScript的类属性有访问控制,强制类外部只能访问类(及其父类)中的public成员,因此在internal component内部我们无法访问 this.context 的任何私有成员。因此,下面这个组件:
@Component({
selector: 'hero-app',
template: '<h1>{{ hero.name }}</h1>'
})
class HeroComponent {
private hero: Hero;
}
以及这个组件
class Hero {
private name: string;
}
@Component({
selector: 'hero-app',
template: '<h1>{{ hero.name }}</h1>'
})
class HeroComponent {
hero: Hero;
}
在生成出来的 *.ngfactory.ts 中,都会抛出编译错误。第一个组件代码,internal component无法访问到在 HeroComponent 类中被声明为 private 的 hero 属性。第二个组件代码中,internal component无法访问到 hero.name 属性,因为它在 Hero 类中被声明为private。
在Angular的源码中,我们可以找到解决的办法,使用TypeScript的 /** @internal */ 注释声明,就能够达到既保证组件代码对AoT友好,又能够确保组件的封装良好的目的。
// component.ts
@Component({
selector: 'third-party',
template: `
{{ initials }}
`
})
class ThirdPartyComponent {
/** @internal */
initials: string;
private _name: string;
@Input()
set name(name: string) {...}
}
initials 属性仍然是public的。我们在使用 tsc 编译这个组件时,设置 --stripInternal 和 --declarations 参数,initials 属性就会从组件的类型定义文件(即 .d.ts 文件)中被删掉。这样我们就可以做到在我们的类库内部使用它,但是我们的组件使用者无法使用它。
https://mp.weixin.qq.com/s?__biz=MzIwMTYyMDEyMg%3D%3D&mid=2247483745&idx=1&sn=3fcb189b1d6b06b1a3311f3d9d532262&scene=45#wechat_redirect
本文将介绍如何基于脚手架配置 Angular 代理
写一个代理文件,将匹配的请求代理到其他地址,解决本地开发跨域问题。
proxy.config.js
--proxy-config proxy.config.js
配置介绍
const PROXY_CONFIG = [
{
context: ['/api'],
target: 'http://xxx',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
},
];
module.exports = PROXY_CONFIG;
context
: 需要匹配的pathtarget
: 代理到的地址pathRewrite
: 将请求的部分path重写,它是一个对象,键是^+要重写的path
, 值是替换的path。secure
: 安全设置changeOrigin
: 改变源如
http://localhost:4208/auth/login
想要代理到
http://www.baidu.com/news/login
可以这样配置
const PROXY_CONFIG = [
{
context: ['/auth/login'],
target: 'http://www.baidu.com',
pathRewrite: {
'^/auth/login': '/news/login',
},
},
]
module.exports = PROXY_CONFIG;
api/v1/1
,另外一个api/v1/cer/login
,我该如何将两个接口代理到不同的地址?{
context: ['api/v1/cer/login'],
target: 'xxx1',
secure: false,
changeOrigin: true,
},
{
context: ['/api'],
target: 'xxx2',
secure: false,
},
使用/api
,只要是匹配到这个的都会走它的代理,不过如果在它前面加了个更加精确的api/v1/cer/login
,会匹配到它,走这个代理。
{{ temp | json}}
//ts:
lol = {
region: '电一', // 大区
grade: '黄铜五' // 段位
}
//html:
{{lol}} => [object,Object]
{{lol | json}} => {'region': '电一', 'grade': '黄铜五'}
在需要更改的样式前面加上 :host ::ng-deep
,如有个第三方包里有个class为 deepthan的元素,要修改其:
:host ::ng-deep .deepthan{
color:#00
}
html中引入,可以直接用绝对路径
<img src="assets/images/smrz.png" alt="smrz">
css中引入,用相对路径
../../assets/images/smrz.png
我想在3201端口启动项目
ng server --port 3201
"serve": {
...
"port": {
"description": "The port the application will be served on.",
"type": "number",
"default": 3201
}
}
如果你是用脚手架自动生成的项目,并且要用jasmine做单元测试的话,还需要做一步:找到protractor.conf.js文件把‘baseUrl’也改成新端口,让测试代码在改过的端口运行。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.