壮士且慢~这里有本Angular修炼秘籍送你!
Posted Think体验设计
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了壮士且慢~这里有本Angular修炼秘籍送你!相关的知识,希望对你有一定的参考价值。
今年1月份开始接触angular的新版本Angular5,之前一直是用的angular1.x。开始了angular5的踩坑之路,话不多说进入正题吧。
初识 angular5
学习angualr5的文章很多,推荐博文(复制链接到浏览器打开)
https://segmentfault.com/a/1190000008754631
初学问题
1. 打包之后的文件运行问题
直接打开index.html 页面无法打开,控制台会报如下的错误:
是因为angular是基于npm环境搭建的,打包后的文件需要在模拟服务器上打开。
解决方案:可以全局安装npm的http-server模块。构建本地web模拟服务器。
安装方法:在cmd窗口执行命令:npm install http-server –g
使用方法:在index所在文件夹执行cmd窗口命令:hs –p 端口号 –o 即可打开运行。
2. 多级路由嵌套及懒加载问题
如果项目当中页面嵌套过多的情况,会出现路由嵌套的情况。
多级路由嵌套两种写法:
1)直接进行路由嵌套如下:
(左右/上下滑动可查看全部代码)
{
path: 'dashboard', component: DashboardComponent, canDeactivate: [CanLeaveDetail],
children: [
{ path: 'partA', component: DashboardPartAComponent },
{ path: 'partB', component: DashboardPartBComponent },
{ path: 'partC', component: DashboardPartCComponent }
]
}
2)懒加载模式:
主路由:(左右/上下滑动可查看全部代码)
{
path: 'lifeCycle',
canActivate: [AuthService],
loadChildren: './LifecycleHooks/lifeCycleHooks.module#LifeCycleHooksModule'
}
子路由:(左右/上下滑动可查看全部代码)
{
path: '', // 使用loadChildren方式此处路径为空
component: LifeCycleHooksComponent,
children: [
{ path: '', redirectTo:'componentLC', pathMatch: 'full'},
{ path: 'componentLC', component:ComponentLCComponent }
]
}
子路由全部用loadChildren加载,到这个路由的时候在加载路由下的模块及组件。
注意:子路由中的第一个path一定要为空。
开发组件期间遇到的问题
1. 使用组件的时候直接加在组件上的样式和样式类无效:
解决方案:给组件父容器上加样式或样式类,代码如下:
(左右/上下滑动可查看全部代码)
<div class="wrapper-content">
<h1>基本用法</h1>
<p-progressbar [animate]="animate" [value]="value" [maxValue]="maxValue">
</p-progressbar>
</div>
2. angular样式封装
组件内部定义的样式类不影响组件外部。
采用的策略是:angular在解析组件内部写的样式会加上_ngcontent-属性。这样不会全局污染。
打开控制台,查看DOM接口,在head标签中的style标签中会看到如下样式:
查看宿主元素也就是自定义的组件标签DOM,如下图所示会加上_nghost-属性。
导致的问题是组件内部定义的样式类在宿主元素上不生效。
为解决上述为题angualr提供了三种样式封装策略。需要引入ViewEncapsulation。
(左右/上下滑动可查看全部代码)
import { ViewEncapsulation } from'@angular/core'
具体方案:在元数据上加encapsulation属性。有下面三个值:
ViewEncapsulation. Emulated(仿真)
这是默认选项,通过Angular来模拟类似Shadow DOM的行为。不会影响全局样式。
ViewEncapsulation. Native(原生)
原先浏览器ShadowDOM行为。
ViewEncapsulation.None(无)
angular无任何封装行为,会全局污染。
3. 如何给一个DOM节点上使用2个ng-class
在angular1.x上只能有一个,并且只有一个生效。
Angular5中有个替代方案如下:
[class.className]=”expression”可以写多个。
左右/上下滑动可查看全部代码
<div class="p-progress-bar" [class.p-no-animation]="!hasAnimation"[ngClass]="progressClass">
</div>
4. 如何实现接口的双向数据绑定(Two-WayData Binding)
双向数据绑定原理图:
双向绑定是由两个单向绑定组成:
模型 -> 视图数据绑定
视图 -> 模型事件绑定
Angular 2.x及以上版本中 [] 实现了模型到视图的数据绑定,() 实现了视图到模型的事件绑
定。两个结合在一起 [()] 就实现了双向绑定。就好比[(ngModel)]。
1)在angular1.x中可以按照下面的方式实现接口的数据绑定,通过‘=’修饰符表示这个数据双向绑定
如下代码,定义接口的时候使用‘=’
scope: {
format: ‘=?’
}
2)在angular2.x以上的版本实现方式有下面两种
第一种,Input和output装饰器结合使用
实现方案:
I.定义一个Input输入属性,定义一个Output输出的事件接口,名称之间形式如下:
(左右/上下滑动可查看全部代码)
@Input() stated : boolean;
@Output() statedChange = new EventEmitter<boolean>();
II.在内部修改stated的时候把这个值,把这个值通知到外部。
private toggleSwitch(): void {
this.stated = !this.stated;
this.statedChange.emit(this.stated);
}
III.使用的时候形式如下
(左右/上下滑动可查看全部代码)
<p-switch name="state" [(stated)]="stated"></p-switch>
原理如下:
[(stated)] 可以拆分成两部分stated和statedChange,[stated]用于绑定输入属性,(statedChange)用于绑定输出属性。当 Angular 在解析模板时,遇到 [(stated)] 形式的绑定语法,它会期待这个指令中会存在一个名为 stated 的输入属性和一个名为 statedChange 的输出属性。
第二种,创建类表单组件
https://segmentfault.com/a/1190000009070500
把这个组件当中最重要的接口定义成双向数据绑定,如下方式使用:
双向数据绑定的值用[(ngModel)]=”xxx”绑定实现。
使用形式如下(就好比原生的textarea标签)
(左右/上下滑动可查看全部代码)
<p-spinner name="state" [(ngModel)]="value"></p-spinner >
5. 测试用例定义的[id]变量,在组件的ngOnInit()生命周期钩子拿不到
解决方法:在ngAfterViewInit()生命周期钩子可以拿到。
用户写宿主id三种方式:
1)给HTML属性id赋字符串
<!-- 赋字符串 -->
<input id='mycheckbox' />
<!-- 赋变量 -->
<input id='{{myId}}'/>
2)给HTML属性id赋变量
<input [attr.id]='mycheckboxid'/>
3)给DOM属性id赋变量
<input [id]='mycheckboxid'/>
组件开发读写宿主id的三种方法:
1)读HTML属性id(左右/上下滑动可查看全部代码)
constructor(@Attribute('id') public hostId ) {}
2)写HTML属性id
@HostBinding('attr.id') hostId: string;
写DOM属性id
@HostBinding('id') hostId: string;
3)读写DOM属性id(左右/上下滑动可查看全部代码)
constructor(private hostRef: ElementRef ) { }
ngAfterViewInit() {
const id = this.hostRef.nativeElement.id
}
6. 函数作为入参时候,函数尽量写成箭头函数形式
组件开发过程过需要修改或者读取组件属性或者方法时候,一定要写成箭头函数形式,不然会出现this指向问题。
比较常见的2种应用。
1)定时器:(左右/上下滑动可查看全部代码)
let timer = setInterval(() => {
startDist += motion.step * dir;
topy+= motion.step * dir;
if (Math.abs(startDist - startPoint)>= motion.distance) {
clearInterval(timer);
timer = null;
}
}, 1);
2)事件监听:(左右/上下滑动可查看全部代码)
document.addEventListener('mousemove', this.mouseMoveHandlerFn);
private mouseMoveHandlerFn = ($event) => {
const option = this.config.options;
}
7. 组件通讯之子获取父的实例
应用场景:组件嵌套,子组件需要用到父组件的属性和方法。
组件嵌套形式如下面代码:
父组件的模板代码如下,父组件嵌套了子组件app-child。
(左右/上下滑动可查看全部代码)
<div class="container">
<div *ngFor="let item in items">
<app-child [item]="item"></app-child>
</div>
</div>
方案:在constructor(构造函数)中使用@Inject()依赖注入父组件。
如下面代码:
export class TiBtnItemComponent {
constructor(
@Inject(TiBtnRadioCompontent) private btnRadio: TiBtnRadioCompontent
) {}
}
并且上面的方式是单例的(父组件下不管有几个子组件,父组件只实例化一次),但是这种方式只能注入一个组件实例。
在实际开发中,例如选择按钮组有多选按钮和单选按钮,这个时候就需要注入多个父组件。用上面同样的方式同时注入2个就会报如下错误:
原因如下:
当组件申请一个依赖时,Angular从该组件本身的注入器开始,沿着依赖注入器的树往上找,直到找到第一个符合要求的提供商。如果找不到就会抛出一个错误。
解决方案:
在@Inject()装饰前加@Optional(),如下面代码:
(左右/上下滑动可查看全部代码)
export class TiBtnItemComponent {
constructor(
@Optional()@Inject(TiBtnRadioCompontent) private btnRadio: TiBtnRadioCompontent,
@Optional() @Inject(TiBtnCheckboxCompontent) private btnRadio:TiBtnCheckboxCompontent
) {}
}
解决原因:
当Angular找不到依赖时,@Optional装饰器会告诉Angular继续执行,Angualr会把此注入参数设置为null(而不是默认的抛出错误的行为)。
8. 组件生命周期总结
如上图,为组件初始化各个生命周期执行顺序。其中紫色的生命周期只在组件初始化执行一次,绿色的会执行多次。
1)ngOnChanges频率较低,仅监听用户对@Input变量的改变。注意:监听不到组件开发者在代码中对变量更改。可以考虑使用@Input set访问器方式,监听变量,所有改变都可以监听到。
2)ngDoCheck,触发频率超高。angular自身监视[ngclass]数组变化,是放在这个生命周期。使用了IterableDiffers机制。其他什么情况适合放在这里,暂时不太清楚。
3)ngAfterContentChecked,ngAfterViewChecked相对频率低。我们目前代码监听Dom属性变化,暂时放在这里了。不知道有没有更好办法监听Dom属性变化。
注意:如果在ngAfterViewChecked里面,改变了模板中使用的变量,此变量不一定能及时更新到试图。需要强制刷新this.changeDetectorRef.detectChanges();
4)想监听数组和对象属性变化,angular内部使用了IterableDiffers和KeyValueDiffer。参考[ngClass]源码。
— END —
有什么想对作者说的吗?快来留言吧!
相关链接:
THINK体验设计
微信ID:hw-Think
长按二维码关注有惊喜
点击“阅读原文”,进入华为云官网 !
以上是关于壮士且慢~这里有本Angular修炼秘籍送你!的主要内容,如果未能解决你的问题,请参考以下文章