Open Deft-pawN opened 6 years ago
2012 Angularjs1.6 版本的特性:模板功能强大丰富,双向数据绑定的特点,3.大胆的引入java的东西,可以重复使用代码 Angularjs的缺点,1.影响性能,脏检查的机制,随着绑定数量的增加性能会降低,但是在Angularjs4.0默认的绑定机制变成了单向的绑定,检查机制完全重写,2.Angularjs 不能解决路由的问题,只能通过 Angular 路由模块得到提升,3.作用域机制,Js无法响应原生事件,但是在Angularjs 4.表单验证的功能,js显示错误信息的时候,比较薄弱5.Javascript 没有面向对象的能力,timessript 可以在编译阶段解决更多的问题!
Angular 新的特点和能力: 1.命令行工具CLI,生成新项目的骨架,供我们调试,部署环境,使用该工具提高效率 2.服务器渲染,使得单页运用更加快速的展现在用户面前, 3.移动和桌面的兼容效果,但是在4.0版本把移动端提升到新的高度!提供新的组件
架构的角度对 v1.0 vs v4.0: v1.0 是MVC设计模式,会被视图(view)----->controller------->model 层, v4.0 是由组件树组成的,激活的组件不一样,用户可以和组件进行交互,组件可以通过一些依赖注入的方式和用户进行一些交互
React Vs 4.0 虚拟dom 技术,速度超级快,4.0在速度上是和React 上 Flux 架构的概念 React 可以在服务器渲染 缺点:react 是UI组件,不太适合单独作为一个框架使用,需要和别的框架进行使用,第三方组件也没有angularjs组件更多
Vue 框架Vs 2.0: 优点:简单 ,vue 都是中文的,难度比较简单, 灵活,不限制你怎么样构建,相比于CLI工具 尺寸小,类似于虚拟dom,速度更加快 缺点:Vue 是由个人主导的项目,但是angularjs 是由 google主导的 vue 开发 服务器端渲染,vue 只能靠第三方进行实现的,但是4,0可以通过官方的包
组件的概念是v4.0版本中基础的构建块,把组件可以理解为带有一段业务逻辑和数据的Html,组件可以有父子的关系 服务,用来封装可以重用的业务逻辑 指令:允许你向html元素添加自定义指令 模块:把上面三种东西放在一个模块中,然后可以重复调用!模块用来打包和分发的功能!
安装angularjs4.0的时候需要先安装node.js 并且版本有一些需求,
安装node.js 的方法 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash
,地址在这个,现在已经可以得到 对应的 angularjs CLI 的工具,接下来 需要继续!
安装webstorm 开发工具,
更新npm:sudo npm install -g @angular/cli
查看npm 的版本 :npm -v
创建完成CLI工具
创建项目命令:ng new auction
,auction 是项目名称,介绍项目中文件的类型和作用,e2e文件夹用于自动测试,src 源代码目录,package.json 列明你依赖的包,node_modules第三方的包放在该文件夹下,
app-我们写的代码在这个位置, environments-不同环境的配置文件写在这个位置,index.html
Angular最重要的是组件的概念,在app-文件夹下,有app.component.ts,组件中有三个要素
1.装饰器的概念,@component(),
1.输入属性(@input),用来接收外部数据的,父组件可以传递信息到子组件, 2.提供器(providers),依赖注入的作用 3.生命周期钩子(lifecycle Hooks),组件的生命周期中可以有多个钩子可以触发业务逻辑
1.生命周期钩子(lifecycle Hooks),组件的生命周期中可以有多个钩子可以触发业务逻辑 2.样式表 3.动画 4.输出属性@output
app.module.ts(模块)
模块也是一个带着装饰器的TS类 ,利用 declarations:
声明模块中有什么东西(组件,指令,管道),
imports
引入模块依赖的外部的包,providers 声明提供的服务,通常是空的,bootstrap 声明了主键的是什麽?
需要明白三个问题:启动时打开的什么页面,启动时什么脚本,脚本做了什么事,
打开.angular-cli.json
文件夹,里面是``` index
表示启动时打开的页面,main.ts
负责引导angular启动,是运行的起点:
platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.log(err));
这句话的意思是使用AppModule作为主模块进行加载,然后会分析AppModule,需要依赖什么模块,然后去app模块中查看需要什么模块,接着再去调用app模块中需要的模块,加载完所有的组件
然后是在index.html
中寻找启动模块(app module )指定的主组件(app component )对应的css选择器(app-root),angular 会用主组件app-component 指定的模板内容(templateUrl) 替换掉app-root 这个标签 ,在此之前会显示的是<app-root>
这个标签之间的内容
main.ts----->找到主组件(app-component),并且加载所有组件----->找到主组件中的css选择器(selector)和templateUrl 替换index.html 中的标签
linux 下启动 Angular的方法:
ng server --port 4201
但是现在还没有实现正常work
,jquery
,bootsrap
,需要三步骤:
1.在项目路径下安装到指定的包到本地去 :npm install jquery --save
,npm install bootstrap --save
,安装完成以后会有对应的包出现在node_modules
这个文件夹下的.angular-cli.json
文件添加对应的script
和style
文件bootstrap
和 jquery
的代码?npm install @types/jquery --save-dev
,npm install @types/boostrap--save-dev
4.分析一波,我们一共有七个组件:导航栏组件,星级评价组件,搜索组件,查询表单组件,产品信息组件,显示组件,。。。。。ng g component navbar
,ng g component footer
,ng g component carousel
,ng g component product
,ng g component stars
,每执行一个这样的代码可以生成四个相类似的文件,同时更新app.module.ts
文件,最后把生成的组件注册到模块里面,app目录下多了两个,app.module.ts 多了六个组件的名称创建app-component.html的模板 样式,使用标签先搭一个框架,
补充2-7对应的class 样式的navbar
是bootstrap
框架中表示导航条,navbar-inverse
表示是黑色的底色,
navbar-toggle=collapse
表示你在折叠时候需要对 button 这个按钮 作出的操作是什麽!
,data-target
指示要切换到哪一个元素,是收缩时候点击上面的button后展现的类型,或者展现开的时候的样式
折叠样式的div 里面 必须会有 collapse navbar-collapse
这两个属性,然后还需要一个data-target
传过来的值 !
styles.css
表示的是整体的样式调整
2.navbar.component.html表示生成样式,:<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collape" data-target=".navbar-ex1-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">在线竞拍</a>
</div>
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li><a href="#">关于我们</a></li>
<li><a href="#">联系我们</a></li>
<li><a href="#">网站地图</a></li>
</ul>
</div>
</div>
</nav>
Angular form 表单验证 机制,使用的是bootstrap
的组件
<form name="searchForm" role="form">
<div class="form-group">
<label for="productTitle">商品名称:</label>
<input type="text" id="productTitle" placeholder="商品名称" clss="form-control">
</div>
<div class="form-group">
<label for="productPrice">商品价格:</label>
<input type="text" id="productPrice" placeholder="商品价格" clss="form-control">
</div>
<div class="form-group">
<label for="productCategory">商品的类别:</label>
<select id="productCategory" class="form-control"></select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">搜索</button>
</div>
</form>
轮播图组件的编程有些复杂,其中有jquery的脚本在其中
<div class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li class="active"></li>
<li></li>
<li></li>
</ol>
<div class="carousel-inner">
<div class="item active">
<img class="slide-image" src="https://placehold.it/800*300" alt="">
</div>
<div class="item">
<img class="slide-image" src="https://placehold.it/800*300" alt="">
</div>
<div class="item">
<img class="slide-image" src="https://placehold.it/800*300" alt="">
</div>
</div>
<a class="left carousel-control" href="javascript:$('.carousel').carousel('prev')">
<span class="glyphicon glyphicon-chevron-left"></span>
</a>
<a class="right carousel-control" href="javascript:$('.carousel').carousel('next')">
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
</div>
1. 在product.component.ts
定义一个名为product 对象,它有一个构造函数contructor
,里面有各种属性
export class Product {
constructor(
public id:number,
public title:string,
public price:number,
public rating:number,
public desc:string,
public categories:Array<string>
){}
}
2. 在productComponent
控制器 里面声明一个数组,来存储页面上需要存储的信息,
private products:Array<Product>;
3.ngOnInit这个组件生命钩子中声明这个组件,在组件实例化以后会调用一次用来初始化里面的数据,感觉以后这边的数据是需要来自后台数据库的,products
是需要和product.component.html
的页面中数据保持一致
ngOnInit() {
this.products=[
new Product(1,"第一个商品",1.99,3.5,"这是第一个商品,我在学习的路上",["教育用品","硬件设施"]),
new Product(2,"第二个商品",3.99,4.5,"第二个商品,我在学习的路上",["教育用品","安全设施"]),
new Product(3,"第三个商品",4.99,3.5,"第三个商品,我在学习的路上",["教育用品","硬件设施"]),
new Product(4,"第四个商品",5.99,4.5,"第四个商品,我在学习的路上",["教育用品","硬件设施"]),
new Product(5,"第五个商品",6.99,1.5,"第五个商品,我在学习的路上",["教育用品","硬件设施"]),
new Product(6,"第六个商品",7.99,4.5,"第六个商品,我在学习的路上",["教育用品","硬件设施"])
]
}
3. 搭建产品组件的模板
第一个Angular指令,*ngFor
循环products 的属性
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
stars.component.ts
定义private stars : boolean [],(boolean 类型的数组)并且在后台初始化(onInit)的时候进行初始化
3.把前端的模板使用ng-repeat 的方式生成星星<img [src]=imgUrl>,
,<span *ngFor ="let star of stars "class="glyphicon glyphicon-star" [class.glyphicon-star-empty]="star"></span>
private rating: number =0;
,需要,需要安装装饰器(@input()) 表示 rating 这个值 是 必须由它的父组件传递给它, 然后在product的app-star
上传入对应的数据<app-stars [rating]="product.rating"></app-stars>
for(let i =1;i<5;i++){
//根据传入的值进行
this.stars.push(i>this.rating);
};
7. **小结** : 1. 属性绑定 2.根据属性绑定确定样式 3. **通过父组件中的数据传递给子组件**
SPA = singel Page Application 单页运用,只是加载一次,以后只改变了页面中部分内容的运用,商品详情页面原本需要重新加载,但是SPA 中浏览器不会跳转,只是把一部分的页面通过路由器替换而不是重新加载页面,以前是通过url进行页面跳转的,现在是通过自己设定的路由器(router)进行跳转的,浏览器的地址是不发生变化的
视图状态 => SPA运用是一组视图状态的集合 ,需要在轮播图组件和商品类表组件的区域里面中,展现不同的内容,首先我们需要把这两个组件所在的位置定义成一个插座
,然后配置路由器,不同的路由显示不同的组件,首先我们需要把上面两个组件封装成一个home-component 组件,再新添加一个商品详情组件,配置路由器进行组件之间切换
1.生成一个新的项目,ng new router --routing
这边加载了参数routing,会多生成一个文件app-routing.module.ts
,是当前应用的路由配置
2. Routing的五个对象
2.1Routes
: (路由配置),保存着哪个URL 对应展现哪个组件,已经在哪个RouterOutlet 中显示组件
2.2RouterOutlet
(插座),在HTML 中标记路由内容呈现位置的占位符指令
2.3Router
:运行时候执行路由的对象,可以进行导航到指定的标签上,和下面的routerlink
指令的功能是相似的但是Router
是在controller里面实现的,RouterLink
指令主要是放在<a>
标签内使用的
2.4RouterLink
: 在HTML 中声明由导航用的指令
2.5ActiveRoute
当前激活的路由对象
,保存当前路由的信息,如路由地址,路由参数等
3. Routing在Angular中的位置:
1.Angular 是由组件组成的:例如:AppComponent,组件A,组件B......每一个组件都会有自己的模板和控制器,我们的应用启动以后,首先会展现AppComponent 组件的模板,然后所有的组件都会被封装在一个模块里面,而路由的配置信息也是存在模块里面,Routers对象是由一组配置信息组成,每一个配置信息都包含两个属性path
属性,component
属性,RouterOutlet
指令用来确定对应的组件放置在模板中的位置,Routerlink
通过在模板上生成一个链接改变浏览器的地址,或者我们可以在组件控制器中调用router 对象的 navigate
方法 改变浏览器的地址从而改变路由的转换,最后我们通过路由在url 中传递的一些数据,可以通过ActivedRouter
对象获取携带的参数
2.多生成一个app-routing.module.ts 文件,这个文件中有上面的介绍到的routes对象 ,包含的是所有的路由设置,这个AppRoutingModule还会被引入到主模块中去
4.
4.1.生成两个组件,home 和product 组件,实现功能效果:在页面点击商品详情组件的时候会进行到对应的product 组件上去 : ng g component home
,ng g component product
,
4.2.修改两个组件对应的html
4.3. app-routing.module.ts
中添加路由,配置(Routes)对象: 配置的类型是一组Routes对象
const routes: Routes = [
{path:'',component:HomeComponent},
{path:'product',component:ProductComponent},
//path 不能用/开头
];
4.app.component.html中配置RouterOutlet
指令,
<a [routerLink]="['/product']">商品详情</a>
为什么routerLink
是一个数组而不是一个字符串,因为我们可以在路由中传递参数!
6.开始启动组件:npm run start
,ng serve -host 0.0.0.0 --disableHostCheck
7.在代码中通过router对象 进行导航,<input type="button" value="商品详情" (click)="toProductDetails()">
8.事件绑定(click)="toProductDetails()"
,表示需要使用控制器中的toProductDetails
方法,然后在App.component.ts
这个控制器中写toProductDetails
方法,其中需要使用一个路由对象(router)!这个对象需要从构造函数(contructor
)中获取router
对象,然后写方法
export class AppComponent {
title = 'app';
constructor(private router:Router){}
toProductDetails(){
//需要使用路由对象 ,拿到router对象
this.router.navigate(['/product']);//接收一个数组类型的
};
}
9.当用户输入一个不存在路径名称时,需要通配符配置 9.1 生成一个新的组件: ng g component code404 ! 9.2 在路由设置通配符的路由,这个设置需要放在路由的最后
方式有三种: 1.查询参数中,在路由时传递数据 /product?id=1&name=2
获取参数的方式:ActiveRoute.query[id]
2.路由的路径中传递参数,{path:/product/:id}
==>/product/1
,获取参数的方式:ActiveRoute.params[id]
3.在路由配置中传递参数,data数组参数:{path:/product,component:ProductComponent,data:[{isProd:true}]},
获取参数的方式:ActiveRoute.data[o0][isProd]
传递商品的id的不同方式
<a [routerLink]="['/product']" [queryParams]="{id:1}">商品详情</a>
然后在商品详情组件中获取对应的id,代码如下
export class ProductComponent implements OnInit {
private productid:number;//接收传递进来的参数
constructor(private routerInfo:ActivatedRoute) { }
ngOnInit() {
this.productid = this.routerInfo.snapshot.queryParams['id'];
}
}
2. Url中传递参数:
1.修改路由配置中的path属性,使其可以携带参数`path:"product/:id"`
2. routerlink在数组中添加值,`[routerlink]="['/product',1]"`
3.修改product 控制器中的参数,`this.product = this.routeInfo.snapshot.params['id']`
3.参数快照(`snapshot)`和参数订阅(`subscribe`)的区别和不同的处理方式
3.1 修改navigate方法,传递一个参数过去:`this.router.navigate(['/product',2])`
3.2然后出现了第二次点击product 页面时候,数据不发生变化的问题,原因是`routerInfo.snapshot.params['id'];` 处于Oninit 的方法中只能是在被第一次创建的时候赋值,snapshot 只能是在组件创建的时候被调用
3.3 解决的方式是改变获取参数的方式,采用函数订阅的方式进行获取:`this.routeInfo.params.subscrible((param:params))=>this.productId =params["id"]`
重定向:当用户访问某个特定的网址时候,把它转到另外一个网址,比如你用户保存了你的A地址,但是你的网站已经变成了B地址,这样你就可以使用重定向的方式进行跳转
需求 : 需要把之前的空地址路由变成home 路由,改动原因是有利于开发和调试,路径和模块名称应该尽量的一目了然的,但是这样子会出现问题,但我们第一次进入主页的时候会找不到页面,所以需要使用重定向的方法使得第一次访问的时候就可以显示主页!
方法: 在 routes 中添加: {path:'',redirectTo:'/home',pathMatch:'full'},
需求: 需要在组件下点击产生不同的组件
ng g component sellerInfo
,ng g component productDesc
sellerInfo
的组件中添加对应的路由信息:this.sellerID = this.routeInfo.snapshot.params['id'];
children route
): children:[ {path:'',component:ProductDescComponent}, {path:'seller/:id',component:SellerInfoComponent},
productComponent
模板 ,添加<router-outlet>
标签 用于确定位置,并且添加对应的子路由的路径<router-outlet>
其实是根据子路由形成父子关系,具有嵌套关系,并且可以一直嵌套下去this.sellerID = this.routeInfo.snapshot.params['id'];
声明辅助路由需要三步骤:
1. 组件模板上不仅有主outlet 标签,还需要声明一个带有name 属性的辅助标签 <outlet name="aux"> </outlet>
2. 在 路由上配置aux 可以显示的组件名称`{path:'xxx',component:XXXcompoent,outlet:'aux'}
**3.** 在导航上需要指定辅助路由上 需要显示的的组件名称
<a [outerLink] ="['/home'],{outlets:{aux:'xxx'}}">`,表示主
<router-outlet name="aux"></router-outlet>
ng g component chat
outlet:aux
:{path:'chat',component:ChatComponent,outlet:aux},
<a [routerLink]="[{outlets:{aux:'chat'}}]">开始聊天</a>
注意点:当点击开始聊天需要同时改变主路由的值,使其跳转到对应的路由上需要添加primary的属性:概念:只有用户满足某些条件的时候才会被允许进入或者是离开某个路由,比如 用户登录以后才可以进入路由,注册流程中只有满足前面的填写规定的时候才可以继续,当客户离开某个界面却没有进行保存的时候,三种路由守卫:
loginguard.ts
,在其中创建一个loginGuard
类继承的是CanActivate
接口,并且实现CanActivate
方法,该方法需要返回boolean
值canActivate:[]
属性,可以添加多个路由守卫,所有的守卫的都会被一一检验providers:[LoginGuard]
再次写入就可以了
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++CanDeactive
守卫:当客户需要离开页面的时候,提示用户是否保存,确保用户在保存以后才可以离开页面
步骤 providers:[LoginGuard]
再次写入就可以了使用场景::当我们进入某个页面后有时候需要向后台发送数据请求,在它返回之前页面上的数据都不会显示出来,有一个等的过程,这样的用户体验非常不好,通过 resolve守卫可以在进入某个路由前就去后台传递数据,然后带着数据进入某路由,这样子的方式更加人性化,用户体验更加好 实际案例:
product.resolve.ts
在其中声明一个类ProductResolve
,这个类需要继承Resolve
接口,还需要在该类中声明泛型Product
,这表示需要解析的数据类型 是product类型的,也就是需要返回一个product
对象product.component.ts
中声明一个类 export class Product
这个类有两个属性:constructor(public id number,public name string){}
ProductResolve
类中实现方法,resolve(route:ActivatedRouteSnapshot,state:RouterStateSnap){}
其中的ActivatedRouteSnapshot中可以拿到当前路由的参数let id:number = router.params["id"];
正常情况下是调用http
的函数navigate
函数进行跳转到别的地方:this.router.navigate(['/home'])
,路由器的需要通过构造函数(constructor
)进行注入ProductResolve
这个类需要使用装饰器@injectable
进行装饰,只有这样子才可以把路由器注入进来resolve:{product:ProductResolve}
这句话表示传入一个product
对象,这个对象是由ProductResolve
守卫得到的任务描述:点击商品详情按钮以后,轮播图组件和商品列表组件会被替换成商品详情组件 任务需求:
ng g component productDetail
组件同时在里面需要声明一个productTitle
的变量,待会需要从外部传入进来,需要在构造函数中注入对应的avtiavtedRouted对象,并且把该对象中的变量值赋值给this.productTitle = this.routerInfo.snapshot.params["prodTitle"]
ng g component home
其中的业务逻辑不需要改变,只需要改动模板(template),对两个组件进行封装,:const routeConfig:Routes = []
, 声明路由配置以后需要把对应的路由设置注入到路由配置App.component.html
,根据路由显示home组件或者商品详情组件修改成插座的形式:<router-outlet></router-outlet>
routerlink
指令的链接,导航到对应的商品详情路由上: <h4><a [routerlink]="['/product',product.title]">{{product.title}}</a></h4>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
小结: 路由基础, 传递数据, 重定向路由, 辅助路由, 子路由, 路由守卫Dependency Injection
:如果某个对象(方法)需要N个参数对象,通过第三方的机制去把这N个参数进行声明和传入进去Inversion of control
: 把依赖的控制权从代码内部转化成外部,控制权在内部的表现方式,当createShipment(product)
方法中的product 对象换成mockproduct
的时候,外部创建的对象也要进行改变,但是控制权在内部的时候,内部代码只需要负责声明,最终传入的对象是由外部决定的product service
和productComponent
组件之间原本是紧耦合的形式,如果是下次想要在别处使用productComponent
组件 但是传入不同的服务对象的时候,你需要修改productComponent
组件中 的代码 重新new 一个对象 providers: ['A',"B"]
指定需要依赖注入的对象 ,定义了一个对象被注入到一个组件和指令之前 contructor
)进行申明,表示 需要对应的token,Angular 会去带着这个token 去上面
找token对应的类是哪个,并且把这个类进行实例化注入到组件中去 : constructor(productService:ProductService)
this.product = productService.getProduct()
productComponent
组件 : 只需要其他项目的app.module
中的@NgModule
的声明providers: [{provide:ProductService,useClass:AnotherProductService}]
,组件本身是不需要修改的
2. 增加可测试性,: 比如测试登录组件已经完成 登录验证组件没有完成的时候可以先自己写一个登录逻辑进行验证1.注入器 : Angular的一个服务类,把组件需要的对象 注入组件中,比如 组件中的构造函数(contructor(private productService:ProductService){}
) 这样声明的时候,注入器就会在整个Angular中去寻找一个实例,并且把这个实例注入到对象中去
提供器(providers[ProductService]
): 提供其他三种方式提供实例providers:[{provide:ProductService,useClass:ProductService}]
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
功能需求:
3.1 创建一个新的app component :ng g component product1
3.2 创建服务,放在share 文件夹下供所有的人使用 ng g service shared/product
3.3 完成service 服务内容,在这个service 中需要接收一个Product
类型的对象,所以先需要创建对象,然后在 productService
里面申明一个getProduct
方法,原本需要使用这个方法和服务器进行链接获取对应的product
对象
3.4 模块声明,在app.module.ts
的providers
声明和引入对应的服务
3.5 改写product.component
组件:
----->3.5.1 引入并且创建 product
对象,用来接收从服务中获得的对象
----->3.5.2 在构造函数中声明一个productService
的服务,并且把对应的值赋给创建的product
对象
3.6 修改模板 ,把对应的属性值显示出来
3.7 修改主组件的模板,拉起服务
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
提供器(provider)的作用域,上面的例子中provider
声明在app.module.ts
(模块)中 ,其实也可以声明在组件(component
) 中
4.1 创建第二个组件: ng g component
和第二个服务:ng g service anotherProduct
4.2 开发anotherProduct 服务,该服务需要实现 ProductService 服务: implements ProductService
,必须实现getProduct
方法,返回一个不同的product对象
4.3 开发Provider2
组件 :只需要和Product1 大体相似,但是provider2 中也添加了providers
声明,里面的token 是相同的但是userclass是不一样的:providers[{provider:ProductService,useClass:AnotherServiceService}]
4.4 修改Prodoct2
的模板,使用的也是Product1 的模板 ,实现的样式就会和之前的是一样的
4.5 修改APP 组模板 添加组件<app-product2><app-product2>
,在 provider2组件中申明新的提供器provider,它和模块中申明的提供器拥有相同的token,但是调用的是另外一个服务
总结::Provider作用域的规则
当提供器申明在模块里的时候,表明该组件可以申明在任何一个组件中
提供器申明在组件中
申明在组件中和模块中的提供器拥有相同的token的时候,优先调用组件中的提供器
提供器 大多数申明在模块这个层级
解释 @injectable 装饰器 修饰 ProductService 服务的含义
表示 ProductService 这个服务可以通过构造函数注入别的服务
,但是是不是可以把自己注入到别的服务中去不是由@injectable 决定的 ,是由providers 决定的
6.1. 创建一个新的服务 : ng g service share/logger 服务,在这个服务中有一个logger 方法
6.2. 把这个logger
注入ProductService
服务中去: constructor(private logger:LoggerService) { }
6.3.在 模块中 引入LoggerService到providers: [ProductService,LoggerService],
6.4 在 ProductService
中使用logger 方法,因为 有@injectable 表示可以注入别的 服务,所以建议所有的service 服务都加上@injectable 避免以后出现没办法调用的情况
6.5 解释 为什么组件中没有 @injectable 装饰器 ,仍然可以注入组件 constructor(private productService:ProductService) { }
注入服务 ,因为@component
装饰器 是 @injectable 的子类 所以可以使用注入器
需求说明:之前 我们 使用 providers: [ProductService,LoggerService],
表示 :当 组件声明 ProductService
的 token
时候,我们会实例化一个 ProductService
实例,但是很多情况下我们需要根据实际情况 确定我们需要 创建哪个服务对象,需要使用工厂函数作为提供器,工厂函数通过一个随机数决定ProductService
服务还是AnotherProductService
服务
步骤: :
2.1删除 product2 组件中的providers
申明 ,现在product1 和product2 组件共用 模块中的 providers
获取数据的
2.2 修改App.module
中的声明,使用 usefactory
参数代替 userclass
参数 ,并且在usefactory
中声明一个匿名函数 ,注意,provider1 和 provider2组件 使用的是 服务都是相同的,表示工厂方法创建的是一个单例对象,工厂函数只会在创建第一个需要注入的对象时候被调用一次,并且在整个应用中所有被注入的实例都是同一个对象 :
providers: [{provide:ProductService,useFactory:()=>{
//创建userfactory的函数,使用的是匿名函数的方式
let logger = new LoggerService();//创建一个
let dev = Math.random() >0.5;//创建一个随机数,并且判定是不是大于5,返回true /false
if(dev){
return New ProductService(logger);
}else{
return New AnotherServiceService(logger);
}
}},LoggerService],
2.3 方法有两个问题 :问题1 方法内部手工实例化LoggerService
,这样的工厂方法和紧耦合是不理想的,如何 使用 声明 LoggerService
的提供器 进行松耦合的 ?
解决方式:使用第三个参数deps
:声明使用工厂方法需要依赖的参数 ,把上个例子中使用到的LoggerService在这里面 进行声明,然后通过参数的形式进行传入进去 :
问题2: 如何把变量和上面的服务一样也进行依赖注入呢?
解决方式:使用第三个提供器,{provide:"IS_DEV_ENV",useValue:fasle}
,后面的参数useValue 是一个明确的值 ,然后在deps
中添加token(IS_DEV_ENV)
,传入到工厂函数中
传入到工厂函数中的不仅仅是一个值还可以是一个对象 {provide: 'App_CONFIG',useValue:{isDev:false}}
providers: [{provide:ProductService,useFactory:(logger:LoggerService,appConfig)=>{
//创建userfactory的函数,使用的是匿名函数的方式
//let logger = new LoggerService();//创建一个
let dev = Math.random() >0.5;//创建一个随机数,并且判定是不是大于5,返回true /false
if(appConfig.isDev){
return new ProductService(logger);
}else{
return new AnotherServiceService(logger);
}
},
deps:[LoggerService,"APP_CONFIG"]
},LoggerService,{
provide:"APP_CONFIG",useValue:{isDev:true}//第三个provider,token 是一个字符串,第二个参数是usevalue,是一个明确的值
}],
总结以上的步骤:
productSerivce
的token,constructor(private productService:ProductService) { }
2, Angular 看见这个token后 去找模块下对应名称的提供器(providers找),在其中是使用工厂函数进行实例化,并且 这个工厂函数还需要使用另外一个服务(loggerService),找到loggerService 的token 然后找到对应的实例化对象,如果loggerService自己还需要别的服务,它也会根据对应的token找到服务,这就是依赖注入,把组件和服务两个分别隔离开,对于组件productSerivce
来说,以上这些都是未知的,它只知道自己得到了一个ProductService
,并不在意后面的操作是什么注入器:
ProductService
的提供器,如果没有找到就回去寻找父组件(App.component
)注入器中是否有对应的提供器,一直找到应用级别的注入器上 有符合条件的提供器(productService),然后根据该提供器的配置进行实例化实例,并且把它注入到ProductComponent
中去,如果应用级别的都是没有,那就抛出异常
private productService:ProductService
constructor(private injector:Injector ) { this.productService = injector.get(ProductService); }
ProductService
服务,在其中编写三个方法
1.2 之前在component
中的数据都转移到service
中,首先创建一个Product
对象,一个Products
的数组,还有一个getProducts
的方法,返回Products
数组,
1.3 创建一个 新的 Comment
对象 ,一个Comments
数组 ,还有一个 getCommentsForProductId
函数根据对应的id,返回所有的comment
的对象 getProducts():Product[]{
//first function ,get all the products
return this.products;
}
// secord function to get the Porduct
getProduct(id:number):Product{
//lambda,return product of product.id
return this.products.find((product) => product.id == id);
}
//forth function to get comment from id
getCommentsForProductId(id:number):comment[]{
//lambda ,return the comment of productid
return this.comments.filter((comment:comment) => comment.product_id==id);
}
}
{path:"product/:ProductId",component:ProductDeatilComponent}
3 . 注入ProductService
服务 并且使用其服务,一个有两个地方需要使用该服务,1是在列表页,需要获得所有信息,第二个地方在商品详情页获得所有的评论信息,以下是对应的步骤
3.1 . 把服务声明在providers 的属性上 ,使用服务之前必须把服务声明在模块的providers属性上
3.2 .在product 组件的 构造函数中注入一个: product Service,并且在ngOnit 中进行动态赋值: this.products=this.prodct.getProducts();//get all the product Info
3.3 .修改详情页的controller
: 首先把服务注入到构造函数中,constructor(private routerInfo:ActivatedRoute,private productService:ProductService) { }
,然后通过service 服务中的方法给prodyuct 赋值
3.4 .开始修改product-detail
模板页:
3.4 .处理评论相关的代码: 在product-detail的组件中调用 service.ts
中的getComments方法:
3.4.1 .声明comments参数 :comment ,并且拿到对应id 所得到的商品的评论
3.4.2 .页面上展现商品评论 : 使用到了 <app-stars [rating]="comment.rating"></app-stars>
通过这样的方式进行传递组件的值
绑定的形式有三种
Angularjs
中使用的是双向绑定的方式,<input (input)="OnclickEvent($Event)">
小括号表示是时间绑定,里面的input表示事件名称
,等号后面的函数表示需要调用的函数 ,$event 表示获得当前按钮的dom 属性 <img [src]="imgUrl">
,<img src="{{ imgUrl }}">
效果一样的dom
属性会发生变化,html 属性只是初始化值 ,不发生变化,但是dom属性是跟着input 中添加的值进行变化:<input value="Tom"{} (input)="doOnInput($event)">
在控制器中下面代码分别得到的是
doOnInput(event:any){
console.log(event.target.value);
console.log(evenet.target,getAttribute('value'))
}
2.1 button的 disabled
属性:<button disable="false">点我 </button >
如果写成这样子disable 属性还是生效的,在html中只要出现了disabled
的属性,无论你的赋值是什麽都会被禁用,但是你可以通过dom
属性进行修改,这也是html
和 dom
属性之间的区别
2.2 Angular 通过Dom 属性和事件来工作而不是html 属性绑定
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++greeting:string = This is for Test"
,模板上有一个<input [value]="greeting"> 当组件中的greeting
值发生变化后会通过单向绑定更新 DOM属性,从控制器的greeting
到DOM的value的值改变,但是此时html的value 值是空的,因为浏览器会保持UI和DOM 的属性保持一致,DOM 的value 的属性不改变html属性,
当用户在更新input值的时候,浏览器也不会更新UI 和HTML属性,只会更新DOM属性
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++有三种 HTML 属性绑定的方式,
1. 基本的HTML属性绑定,以attr开头: <td [attr.colspan]="tableColspan">SomeThing</td>
2. CSS类绑定 (三种方式 ):
------>2.1方括号class[class]的形式会完全替换到对应的class的值: <div class="aaa bbb" [class]="SomeExpression">something</div>
------>2.2 方括号class[class.]点的形式,后面跟着的是一个boolean
值: <div [class.special]="isSpecial">STH</div>
------>2.3 控制多个class 是否表现 <div [ngClass]={aaa:isA,bbb:isB}>
3. CSS样式(style)绑定 (两种方式 ):
<button [style.color]="isSpecial ?'red':‘green’">Red</button>
<button [ngStyle]={"isSpecial ?'red':‘green’"}>Red</button>
4. 为什么使用Html属性绑定的方式呢?
优先级肯定是DOM绑定的方式,但是有些元素没有DOM属性,比如 <table><tr><td colspan={{ 1+1 }}></td></tr></table>
会报错,
必须使用HTML属性 <table><tr><td [attr.colspan]='size'></td></tr></table>
5. HMTL属性绑定的过程分析
比如 组件上有: greeting:string ="this is for Test"; 页面上有HMT属性声明 <input [attr.value]="greeting">
当组件中的greeting 会采用单向绑定的方式更新 html 的元素 ,此时不会更新DOM节点的属性,此时因为浏览器的同步机制使得DOM的 值也发生变化:input.ElementValue==="this is for test"
,最后DOM 和UI 属性会进行同步,DOM属性会被渲染到页面上,浏览器上还是会出现对应的值
6. CSS类绑定
<div [class]="divclass"></div>
在compoent
中 divclass:string="a b c"
表示class
的值会被替换 <div class="a b" [class.c]="isBig"></div>
在compoent
中 给isBig
属性赋值 表示class
的值会被替换 同时进行类管理(ngClass的指令):<div [ngClass]="divClass">在compoent
中 给divClass
属性赋值,any 类型的值 divClass:any = {a:false,b:false,c:false}
7. 样式绑定
与CSS类的样式绑定类似,但是[class]
变成了[style]
的形式 : <div [style.color]="isDev?'red':'blue'">
---->如果样式带有单位的时候<div [style.font-size.em]="isDev?3:1">测试</div>
----> 同时设置多个内联样式的管理(ngStyle):<div [ngStyle] =“{style:expression}”>
compoent
中 给divClass
属性赋值,any 类型的值 expression:any = {color:red,b:background,}
介绍数据是使得视图和模型保持一致,之前介绍的事件绑定是单向的,视图---->控制器<input (input)="doOnInput($event)">
,属性绑定是单向的:控制器------>视图的单向绑定<input [value]="name">
,双向数据绑定 ,既要绑定属性又要绑定事件,
使用[(NgModel)]
标签简化双向绑定指令:
---->2.1 模板显示:<input [(Ngmodel)]="name"> {{ name }}
---->2.2 模板上你可以看name 属性 但是你不能看到对应的事件是哪个,这是因为 NgModel 使用在input 标签上,这时候使用的是input 事件,采用的是默认事件,使用在不同的标签上的时候会触发不同的事件,这些触发机制是由内部controller
接口管理的
----->双向绑定常用的方式: Form表单元素 机制 ,不应该用在显示元素上(div,等)
{{ birthday | date | uppercase }}
,多个管道可以连接:Lowcase,Uppercase,Date
.... 比如 Date
管道可以接收一个参数{{ birthday | date:"yyyy-MM-dd HH:mm:ss "}}
number
管道 用来格式化数字: 比如 格式化圆周率{{ pi | number:'2.1-4'}}
, 最后表示小数最少和最多的显示async
异步管道 ,处理异步流 的管道ng g pipe pipe/multiple
,模块和管道是相同的,都需要在模块的declarations
中声明的
------>5. 2 管道名称可以任意定义,类 实现了 PipeTransform
的接口,这个类中只有一个方法transform(value:any,args:any):any{
前面那个value 表示的是 传入的值,后面的arg
表示可以传入的参数使用响应式编程处理输入框的输入 事件 ,把关键字绑定在组件的一个属性上:
------>2.1 组件中声明一个属性keyword
保存当前的关键字 !:private keyword: string;
------>2.2 在应用主模块层面中引入 ReactiveFormModule
模块
------>2.3 在product组件的控制器中声明了titleFilter
字段,它的类型就是FormControl
:private titleFilter:FormControl = new FormControl();
------>2.3
前端把input的输入框中使用formControl
指令,把它和后台的字段绑定在一块,<input [formControl]="titleFilter">
表示input 输入框和控制器上类型是formControl
的 titleFilter
字段绑定在一起了,当input
这个框中的值改变的时候,titleFilter 字段就会往外发射valueChange
事件,然后我们订阅这个事件,帮且把这个值放在keyword
这个参数上
------>2.4 订阅该事件,并且把值放入到keyword参数上,抓取titleFiter的流,并且订阅这个流,然把值赋值给keyword
:
constructor(private prodct:ProductService) {
this.titleFilter.valueChanges
.debounceTime(500)
.subscribe(
value=>this.keyword =value
);
}
------> 2.5 编写过滤器(管道) : 这个过滤器有三个参数,商品列表(list
),需要过滤的字段(filterfield
),用户输入的关键字(keyword
) ,逻辑是首先判断
transform(list: any[], filterField:string,keyword:string): any {
if(!filterField||!keyword){
return list
}
return list.filter(item => {
let fieldValue = item[filterField];
return fieldValue.indexOf(keyword) >= 0;
});
}
------> 2.6 修改页面,把filter加在ngFor 中,在列表中过滤数据,并且指定对应的filterField和 keyword 这个keyword的值 是在后台取到的,只要拿到前端就可以了 * :`ngFor="let product of products|fiter:'title':keyword"` ------> 2.7 模块层引入 需管道, 每一个管道生效必须放到模块层面的 declarations里面进行声明,进行申明,否则HTML无法识别对应的管道名称,报错 找不到这个管道
@input
进行注解的属性,用来接收父组件的数据
----> 1.生成订单组件(子组件),定义stockCode
(股票代码)属性,amount
(股票数量)属性,两个都用@input
进行注解 , @input()
stockCode:string;
@input()
amount:string;
----> 2.修改对应的模板,显示对应的股票信息,这是对于子组件的编写
----> 3. 修改父组件(app.component
),给子组件赋值,先在控制器中定义一个stock ="";
属性,然后修改模板,设定一个输入框,把这个输入框和stock
进行双向数据绑定
----> 4. 在父组件上展现子组件的样式,并且把子组件中的值"stockCode","amount"这个属性的值分别都是用父组件上的stock属性,和”100“这个值进行替换
<div>
<input placeholder="请输入股票信息"[(ngModel)]="stock">
<app-order [sotckCode]="stock" [amount]="100"></app-order>
</div>
----> 5.注意点,1. 你想要子组件中的值能接收父组件中的值必须要使用@input
装饰器, 2. 输入绑定是单向的 只能从父组件到组件上,子组件数据改变没办法影响父组件
constructor() {
setInterval(() =>{
this.stockCode ="AppleCode";
}
)}
ActivateRoute
对象,然后通过这个对象的参数订阅/快照 的方式 ,获取外面传递的参数 场景: 编写组件,实施接收交易所的股票信息,同时还需要向外发送最新的数据信息,提供给别的组件进行逻辑处理
----->1.1. 创建报价组件和使用随机数代替股票信息的方式: ng g component priceQuote,
----->1.2. 在组件中 定义PriceQuote
类:
export class PriceQuote{
constructor(
public stockCode:string;
public lastPrice:number;
){
}}
----->1.3. 组件中单独声明属性,用作数据绑定在模板上 : stockCode:string="IBM";lastPrice:number;
----->1.4. 在构造函数中创建一个函数,实现 每秒股票的变化,在组件的构造函数中定义一个 priceQuote
的变量,这个变量 是PriceQuote
类型的,并且给他赋值,这个值也是PriceQuote 类型,里面的价格变化是随机的,最后把这个生成出来的LastPrice
的值赋值给刚刚声明的属性上,使得它可以显示在模板上
constructor() {
setInterval(()=>{
let priceQuote:PriceQuote = new PriceQuote(this.stockCode,100*Math.random());
this.price = priceQuote.lastPrice
},1000)
}
场景二: 把股票信息输出去,供别的组件使用,需要使用到 EventEmitter
声明发出去的是一个事件 ,同时需要一个泛型,我们需要使用 lastPrice
这个对象实现 发射事件的操作 ,所以我们还需要使用output
的装饰器进行注解 ,然后我们利用定义的Price_Last_object
对象作为发生器
,把我们最新的数据发射出去
@Output()
Price_Last_object:EventEmitter<PriceQuote> = new EventEmitter()
constructor() {
setInterval(()=>{
let priceQuote:PriceQuote = new PriceQuote(this.stockCode,100*Math.random());
this.lastPrice = priceQuote.lastPrice
this.Price_Last_object.emit(priceQuote);
},1000)
}
解释在Price_Last_object:EventEmitter<PriceQuote> = new EventEmitter()
后面的泛型 PriceQuote
, 表示你需要发射出去的数据是什么类型的
在父组件中 接收发射出来的 数据信息
----->4.1 父组件中声明一个 priceQuote
对象 用来接收 发射出来的数据 :priceQuote:PriceQuote = new PriceQuote('test',0);
----->4.2 利用事件绑定的方式,捕捉子组件发射的事件(和捕捉原生DOM事件是一样的),现在需要捕捉LastPrice(子组件上发射出去的) 事件 ,然后再把我当前的事件(event)传入到自定义 函数 priceQuoteHandler
中去
<app-price-quote (lastPrice)="priceQuoteHandler($event)"></app-price-quote>
注意 (lastPrice)
代表的是我从子组件那边传过来的名字,需要和子组件保持一致,否则就找不到叫啥了,priceQuoteHandler
是一个自定义的事件,传入的对象就是我们从子组件中传过来的事件
----->4.3 在父组件的控制器中写一个PriceQuoteHandler
方法,传入当前 事件 ,并且把 这个值 传给 父 组件本地中之前定义的 .priceQuote
priceQuoteHandler(event:PriceQuote){
this.priceQuote = event
}
price-quote
报价组件中 创建按钮,需要在某个条件可以进行下单的操作,本身 price-quote
组件并不知道怎么下单,需要通知中间人组件(app.component
)进行操作,中间人可以通知下单按钮
----->1.1 报价组件上创建一个立即购买的按钮,绑定在onclick
的事件上 <input type="button" value="立即购买" (onlick)="buyStock($event)"/>
点击完这个按钮以后我们在控制器中 定义一个EventEmitter
类型 作为 用来发射 报价信息的容器 ,同时还需要完成 buyStock
自定义函数,完成把 刚定义的 容器 发射出去的 动作
//定义一个发射的容器
@Output()
buy:EventEmitter<PriceQuote> = new EventEmitter();
//发射的动作在自定义的函数中实现
buyStock(event){
this.buy.emit(new PriceQuote(this.stockCode,this.lastPrice));
};
----->1.2 修改父组件(app.component)**,监听`buy` 事件**,这个事件buy是事件是刚刚 在上面定义的**发射容器**,并且在**中间人组件**中完成对应的`buyHandler`函数,这个`buyHandler`函数是我一旦监听到buy 事件以后触发的动作 :
` <app-price-quote (buy)="buyHandler($event)"></app-price-quote>`
buyHandler 对应的函数是 把 发射出来的值赋给 我们在 本地定义的priceQuote类型的值上
对应的代码如下:
priceQuote:PriceQuote = new PriceQuote('',0); EventEmitter(event:PriceQuote){ this.priceQuote = event; }
----->1.3 在**中间人(app.component)组件**中 中我们需要把对应的值传递给 下单组件进行下单
----->1.4 先修改**下单组件(order)**中的 值,先接受一个riceQuote 类型的数据,是一个输入属性(@Input ),得到数据后我们在对应的下单组件的模板中 展现数据就可以了
@Input() priceQuote:PriceQuote
----->1.5 最后实现中间人的操作,从**报价组件**传来的数据 通过**属性绑定**的方式传递给下单组件`[priceQuote]`表示**中间人的属性值,**"`priceQuote`"表示的是下单组件里面的**属性名称**:
`<app-order [priceQuote]="priceQuote"></app-order>,`
2. 没有中间组件的时候:现在的下单组件和报价组件之间有一个父组件(app.component)作为中间人,如果出现两者没有中间人的时候,需要使用 **可注入的中间人服务!**,需要使用**服务** !!!
3. 建议在编写之前,确定 中间人组件 !父组件 ! 的关系 !
6-5 组件生命周期 钩子概述
OnInit
接口, 上面介绍的每一个钩子都是来自于 '@angular/core'的接口!也就是说钩子的实质就是一个个接口 !,每一个接口都有一个钩子方法,并且名字都是ng+接口名字形成的; (组件------>钩子接口------->钩子方法),export class LiveComponent implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentCheck,AfterViewInit,AfterViewCheck,OnDestoryngDoCheck():void{
}
ngOnInit
函数和 constructor
两个函数的区别#1 name 属性在 contructor 里面的值是// 构造函数首先被调用 !
live.component.ts:25 #2 name 属性在 contructor 里面的值是Sam-Witwicky
live.component.ts:25 #3 ngOnInit
live.component.ts:25 #4 ngDoCheck
live.component.ts:25 #5 ngAfterContentInit
live.component.ts:25 #6 ngAfterContentCheck
live.component.ts:25 #7 ngAfterViewInit
live.component.ts:25 #8 ngAfterViewCheck
core.js:3660 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
live.component.ts:25 #9 ngDoCheck
live.component.ts:25 #10 ngAfterContentCheck
live.component.ts:25 #11 ngAfterViewCheck
var greeting = "hello";
greeting ="hello world !
var userName = {name:"Sam0Witwicky",age:"19"};
userName.name = "Deft-pawN"
@Input()
greeting:string;
@Input()
user:{name:"Tom"};
message:string = "初始化消息";
修改模板,把三个值添加到模板上去 ,其中的输入框(input)采用双向数据绑定的形式,并且添加子组件的背景色用来区分样式:
<div class="child">
<h2>我是子组件</h2>
<div>问候语:{{greeting}}</div>
<div>姓名:{{user.name}}</div>
<div>消息:<input [(ngModel)]="message"></div>
</div>
实现 控制器里实现 ngOnchanges 方法,在这个方法中输出我传入的对象
ngOnChanges(changes:SimpleChanges){
console.log(Json.stringify(changes,null,2));
}
编写父组件,声明变量用于接收子组件(Greeting和user)的两个值,
export class AppComponent {
title = 'Sam-Witwicky';
greeting:string="Hello";
user:{name:string} ={name:'Tom'}
}
把变量绑定到父组件上,同时在父组件上实现input框的双向数据绑定机制,这样就可以用来改变子组件的值, 使用 这段代码 <app-child [greeting]="greeting" [user]="user"></app-child>
可以实现父组件把值传入到子组件中去,传入的时候又可以触发子组件中的ngOnChanges
方法
<div class="parent">
<h2>我是父组件</h2>
<div>
问候语:<input type="text" [(ngModel)]="greeting">
</div>
<div>
姓名:<inpu type="text" [(ngModel)]="user.name"></inpu>
</div>
<app-child [greeting]="greeting" [user]="user"></app-child>
</div>
9.代码运行后的效果是 :
这里会出现一个 < 'ngModel' since it isn't a known property of 'input'. (">
的错误,是因为FormsModule模块没有引入,所以我们需要在app.component.ts
中 引入 FormsModule} from '@angular/forms';
父组件在初始化子组件的输入属性之前,子组件的输入属性是没有值的 ,然后Ogchanges被调用,接着子组件的输入属性被调用,这时候我们
改变父组件中输入属性的值(greetings),变更检测机制刷新 不可变对象 的值,然后 调用Ogchanges再次被调用
修改姓名属性的时候,可以改变子组件绑定的值,但是不会触发onChanges机制,用户只是改变了可变对象(user)的属性 ,user对象的本身没有发生变化的,这个是和上面 不可变对象的区别
改变子组件中Message属性时候,也不会 触发onChanges机制,因为它没有使用 @input 进行装饰,ngonchange方法只有在输入属性方法变化的时候才会被调用
所有 总结: 可以调用ngonchanges方法必须满足两个条件,一个是 Input输入属性,一个是不可变属性
zone.js
实现 的 ,主要保证 浏览器上和组件的属性上的值是一致的,浏览器上 发生的任何一个异步机制都会引发变更检测机制,比如:按钮的点击,输入数据,服务器返回数据后,在Angularjs的第一个版本中,任何原生的 事件(比如click) 都不会触发脏检查机制 ,在这个版本中,可以随意使用 原生事件 就是因为 有了这个zone.js
Angualr
有两种Default
策略 和 OnPush
策略,如果所有的组件都是用 Default 策略那就是只要有一个组件发生了变化都会把所有的组件上的组件全部检查一遍,如果某个组件声明了自己的OnPush
策略的时候,只有当这个组件的输入属性发生变化的时候才会 检测 在上面一章中出现一个问题:可变对象的属性发生变化的时候,onchange
方法不会被触发,但是下面介绍的 Docheck
钩子可以实现 这个方法
ngDoCheck():void{
if(this.user.name!==this.oldUsername){
this.changeDeteled = true;
console.log('DoCheck:user.name从'+this.oldUsername+"变成了"+this.user.name);
this.oldUsername =this.user.name;
}else{
this.noChangeCount += 1;
console.log("Docheck:user.name没有变化的时候ngDochck方法已经被调用"+this.noChangeCount);
}
this.changeDeteled = false;
}
child.component
)中实现Docheck
方接口并且重写它的方法 ngDocheck
方法必须考虑到性能的问题 ,所有的名称里面有check的钩子方法只要你的子组件实现了,每一次小的变动都会被调用 对应的方法 !所以需要小心6-8 view钩子 介绍 ngAfterViewInit 和 ngviewcheck
greeting
:greetings(name:string){
console.log("Hello"+name);
}
------>1.2 父组件的页面上声明了,分别表示两种不同的方法 :
<app-child #child1></app-child>
<app-child #child2></app-child>
----->1.3 在父组件上 声明一个@viewChild
装饰器,可以在父组件中获得子组件的引用,类型是ChildComponent,然后在父组件的ngOninit
方法中使用子组件的方法 :
@ViewChild("child1")
child1:ChildComponent
ngOninit():viod{
this.child1.greetings("Tom");
}
在 父组件的模板中调用子组件的方法: <button (click)="child2.greeting('jerry')">调用child2的greeting方法
介绍 AfterViewInit 和 AfterViewChecked 钩子 ,父组件实现 这个两个钩子,并且实现他们对应的方法,这两个方法调用的时间 是在 模板已经组装完毕,并且已经给用户看到之后被调用
ngAfterViewInit():viod {
console.log('父组件的视图初始化完毕')
}
ngAfterViewChecked():viod {
console.log('父组件的视图检测完毕')
}
子组件中同样实现这两个接钩子:
ngAfterViewInit():void{
console.log('子组件的变更检测机制初始化完毕';
}
ngAfterViewChecked():void{
console.log('子组件的视图变更检测完毕';
}
5.父组件中使用定时调用的机制去
ngOninit():viod{
setInterval(() =>{
this.child1.greetings("Tom");
},5000);
}
6. 实际运行的效果:
日志顺序 : 子组件的视图ViewInit() 函数 ----> 子组件的视图ViewInit() 函数----->
ng-content
生成一个投影点 : <div class="wrapper"><ng-content></ng-content></div>
----> 在父组件模板的子组件标签中 写 上需要投影的内容
<app-child>
<div class="wrapper">
这个div 是父组件投影到子组件上的
</div>
</app-child>
----> 在以上的两个div中都有wrapper 这个样式,然后我们对子组件和父组件分别使用不同的css 样式,就会发现 他们表示的样式是不同的,这是需要注意的
------> 一个子组件可以在它的模板中声明多个ng-content
标签 ,比如 子组件中有三个部分组成:页头,内容,页脚,其中的页头和页脚是通过投影的方式生成的 ,父 组件中的 样式 内容是在
<app-child>
<div class="header">这是页头,是通过div 从父组件投影到子组件上去的,title={{ title }}</div>
<div class="footer">这是页脚,是通过div 从父组件投影到子组件上去的</div>
</app-child>
------>子组件的样式通过 select= ""方式选择父组件中的 样式
<div class="wrapper">
<ng-content select=".header"></ng-content>
<div>这个是定义在子组件中的</div>
<ng-content select=".footer"></ng-content>
</div>
写一个插值表达式({{ }}的形式 ) ,把父组件中 的 值 投影到 子组件上 ,其中的 title 属性也会被传入到子组件中去 :
title = “这是父组件的属性 ”
需要注意的是,上面的 父组件的模板中使用的插值表达式 ,只能绑定父组件的属性 ,但是 不能使用子组件的属性,说白了还是属于 父组件的东西 !!!
使用属性绑定的方式插入Html
,这是 和上面使用ng-content
的效果类似,但是还是有所区别的方式 :
父组件的模板上 使用 `<div [innerHtml]="divContent">
以上两种方式的区别 :
------>5.1 innerHtml
只能使用在 浏览器 上使用,当你需要App软件的时候,ng-content
有更好的移植性
------>5.2 ng-content
可以 插入多个位置 的 投影点 ,更加灵活
------> 5.3 ng-content
只能绑定父组件的属性值,innerHtml 只能绑定所在的组件的属性值!
------> 5.4 我们应该首先考虑的是ng-content 的方式
1-1 Angular课程介绍
数据绑定,响应式编程,综合运用,在线竞拍 6.组件松耦合的形式,组件生命周期 7.表单处理,纯html的不足之处,模板式表单,响应式表单,高效实现表单需求 8.服务器通讯 9 构建和部署, 10.多环境支撑能力 11.课程总结 组件化应用,星级评价组件可以重用,编写好以后可以通过一行代码进行显示,评价组件可以和星级组件进行交互,同时还可以进行独立的使用! 前端开发的基础知识!,TypeScript语法,ES6是前端的发展方向
对应的blog 内容