Deft-pawN / sam-for-test

This is just for Test in JLL
1 stars 0 forks source link

Angularjs4.0 版本视频 #20

Open Deft-pawN opened 6 years ago

Deft-pawN commented 6 years ago

1-1 Angular课程介绍

数据绑定,响应式编程,综合运用,在线竞拍 6.组件松耦合的形式,组件生命周期 7.表单处理,纯html的不足之处,模板式表单,响应式表单,高效实现表单需求 8.服务器通讯 9 构建和部署, 10.多环境支撑能力 11.课程总结 组件化应用,星级评价组件可以重用,编写好以后可以通过一行代码进行显示,评价组件可以和星级组件进行交互,同时还可以进行独立的使用! 前端开发的基础知识!,TypeScript语法,ES6是前端的发展方向

对应的blog 内容

Deft-pawN commented 6 years ago

1-2 Angular简介

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可以通过官方的包

Deft-pawN commented 6 years ago

2-2 Angular程序架构

组件的概念是v4.0版本中基础的构建块,把组件可以理解为带有一段业务逻辑和数据的Html,组件可以有父子的关系 服务,用来封装可以重用的业务逻辑 指令:允许你向html元素添加自定义指令 模块:把上面三种东西放在一个模块中,然后可以重复调用!模块用来打包和分发的功能!

Deft-pawN commented 6 years ago

安装angularjs4.0的时候需要先安装node.js 并且版本有一些需求, 安装node.js 的方法 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash地址在这个,现在已经可以得到 对应的 angularjs CLI 的工具,接下来 需要继续!

Deft-pawN commented 6 years ago

2-3环境搭建

安装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. 模板(template)的概念,它是以html的形式存在,告诉angualr怎么渲染组件 3.控制器(controller)包含大部分的组件和方法,页面逻辑都是在控制器中的,控制器还和模板进行数据绑定,模板展现数据,控制器处理逻辑 组件元数据装饰器,如何把Ts 类创建成,styleUrls 表示, Controller是一个被 component装饰的Ts 类,里面的属性最后会被显示在页面上 组件的第四个概念:数据绑定,把模板和控制器的各个部分相互作用的机制 组件非必要素分为可选的可注入对象,可选的输出对象两种:

1.输入属性(@input),用来接收外部数据的,父组件可以传递信息到子组件, 2.提供器(providers),依赖注入的作用 3.生命周期钩子(lifecycle Hooks),组件的生命周期中可以有多个钩子可以触发业务逻辑

1.生命周期钩子(lifecycle Hooks),组件的生命周期中可以有多个钩子可以触发业务逻辑 2.样式表 3.动画 4.输出属性@output

app.module.ts(模块) 模块也是一个带着装饰器的TS类 ,利用 declarations:声明模块中有什么东西(组件,指令,管道), imports引入模块依赖的外部的包,providers 声明提供的服务,通常是空的,bootstrap 声明了主键的是什麽?

Deft-pawN commented 6 years ago

2-4 启动Angular过程介绍

需要明白三个问题:启动时打开的什么页面,启动时什么脚本,脚本做了什么事, 打开.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

Deft-pawN commented 6 years ago

2-5 开发准备

  1. 引入第三方库到项目中去,jquery,bootsrap,需要三步骤: 1.在项目路径下安装到指定的包到本地去 :npm install jquery --save,npm install bootstrap --save,安装完成以后会有对应的包出现在node_modules这个文件夹下的
  2. 把对应的库引入到你的项目中去:修改.angular-cli.json文件添加对应的scriptstyle 文件
  3. 需要安装jquery 对应的 类型描述文件,目地是为了让typescript 的代码去认识bootstrapjquery的代码?npm install @types/jquery --save-devnpm install @types/boostrap--save-dev 4.分析一波,我们一共有七个组件:导航栏组件,星级评价组件,搜索组件,查询表单组件,产品信息组件,显示组件,。。。。。
  4. 生成组件的代码,: ng g component navbarng g component footerng g component carouselng g component product,ng g component stars,每执行一个这样的代码可以生成四个相类似的文件,同时更新app.module.ts文件,最后把生成的组件注册到模块里面,app目录下多了两个,app.module.ts 多了六个组件的名称
Deft-pawN commented 6 years ago

2-6 开发app组件

创建app-component.html的模板 样式,使用标签先搭一个框架, 补充2-7对应的class 样式的navbarbootstrap框架中表示导航条,navbar-inverse表示是黑色的底色, navbar-toggle=collapse表示你在折叠时候需要对 button 这个按钮 作出的操作是什麽! ,data-target指示要切换到哪一个元素,是收缩时候点击上面的button后展现的类型,或者展现开的时候的样式 折叠样式的div 里面 必须会有 collapse navbar-collapse这两个属性,然后还需要一个data-target传过来的值 !

Deft-pawN commented 6 years ago

2-7 开发navbar和footer组件

  1. 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>
Deft-pawN commented 6 years ago

2-8 开发search和carouseI组件

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>
Deft-pawN commented 6 years ago

2-9 开发product组件

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 的属性 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2-10 开发stars组件

  1. 使用 bootsrap 中的星星样式 ,如何显示空的星星,
  2. 如何显示五个星星(循环输入),需要在stars.component.ts定义private stars : boolean [],(boolean 类型的数组)并且在后台初始化(onInit)的时候进行初始化 3.把前端的模板使用ng-repeat 的方式生成星星
  3. 如何让实星和空星的星星同时出现,,首先提出一个概念叫做属性绑定:最常见就是插值表达式(大括号的方式),属性绑定的就是html中的某个属性值和后台控制器上的数据进行绑定,举例<img [src]=imgUrl>, ,<span *ngFor ="let star of stars "class="glyphicon glyphicon-star" [class.glyphicon-star-empty]="star"></span>
  4. 把商品中的值(数值类型的)传递给星级评价组件:需要使用 输入属性: private rating: number =0; ,需要,需要安装装饰器(@input()) 表示 rating 这个值 是 必须由它的父组件传递给它, 然后在product的app-star 上传入对应的数据<app-stars [rating]="product.rating"></app-stars>
  5. 根据商品的等级去决定星星是空心还是实心的:
    
    for(let i =1;i<5;i++){
      //根据传入的值进行
      this.stars.push(i>this.rating);   
    }; 

7.  **小结** : 1. 属性绑定 2.根据属性绑定确定样式 3. **通过父组件中的数据传递给子组件**
Deft-pawN commented 6 years ago

3-1路由内容介绍

SPA = singel Page Application 单页运用,只是加载一次,以后只改变了页面中部分内容的运用,商品详情页面原本需要重新加载,但是SPA 中浏览器不会跳转,只是把一部分的页面通过路由器替换而不是重新加载页面,以前是通过url进行页面跳转的,现在是通过自己设定的路由器(router)进行跳转的,浏览器的地址是不发生变化的 视图状态 => SPA运用是一组视图状态的集合 ,需要在轮播图组件和商品类表组件的区域里面中,展现不同的内容,首先我们需要把这两个组件所在的位置定义成一个插座,然后配置路由器,不同的路由显示不同的组件,首先我们需要把上面两个组件封装成一个home-component 组件,再新添加一个商品详情组件,配置路由器进行组件之间切换

Deft-pawN commented 6 years ago

3-2路由基础

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指令,指令表示在页面的位置

  1. RouterLink 指令,<a [routerLink]="['/product']">商品详情</a>为什么routerLink是一个数组而不是一个字符串,因为我们可以在路由中传递参数! 6.开始启动组件:npm run startng 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 在路由设置通配符的路由,这个设置需要放在路由的最后

Deft-pawN commented 6 years ago

3-3 在路由时传递数据

方式有三种: 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的不同方式

  1. <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"]`
Deft-pawN commented 6 years ago

3-4 重定向路由

重定向:当用户访问某个特定的网址时候,把它转到另外一个网址,比如你用户保存了你的A地址,但是你的网站已经变成了B地址,这样你就可以使用重定向的方式进行跳转 需求 : 需要把之前的空地址路由变成home 路由,改动原因是有利于开发和调试,路径和模块名称应该尽量的一目了然的,但是这样子会出现问题,但我们第一次进入主页的时候会找不到页面,所以需要使用重定向的方法使得第一次访问的时候就可以显示主页! 方法: 在 routes 中添加: {path:'',redirectTo:'/home',pathMatch:'full'},

Deft-pawN commented 6 years ago

3-5 子路由

需求: 需要在组件下点击产生不同的组件

  1. 添加两个销售员组件商品信息组件:ng g component sellerInfo,ng g component productDesc
  2. 在销售员组件中添加对应的ID并且在sellerInfo的组件中添加对应的路由信息:this.sellerID = this.routeInfo.snapshot.params['id'];
  3. 修改路由配置,给商品组件添加子路由中(children route): children:[ {path:'',component:ProductDescComponent}, {path:'seller/:id',component:SellerInfoComponent},
  4. 修改productComponent 模板 ,添加<router-outlet>标签 用于确定位置,并且添加对应的子路由的路径
  5. 注意点1 <router-outlet> 其实是根据子路由形成父子关系,具有嵌套关系,并且可以一直嵌套下去
  6. 注意点2所有this.sellerID = this.routeInfo.snapshot.params['id'];
Deft-pawN commented 6 years ago

3-6 辅助路由(多个outlet标签)

声明辅助路由需要三步骤: 1. 组件模板上不仅有主outlet 标签,还需要声明一个带有name 属性的辅助标签 <outlet name="aux"> </outlet> 2. 在 路由上配置aux 可以显示的组件名称`{path:'xxx',component:XXXcompoent,outlet:'aux'}

**3.** 在导航上需要指定辅助路由上 需要显示的的组件名称<a [outerLink] ="['/home'],{outlets:{aux:'xxx'}}">`,表示主上显示['/home'],副上显示的是 xxx组件 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 开发聊天室功能的思路 要求:聊天室出现在右边,并不和其他组件一起产生,只有点击关闭聊天按钮才会消失

  1. app模板组件上定义一个插件来显示聊天页面<router-outlet name="aux"></router-outlet>
  2. 单独开发一个聊天组件,只显示在新定义的插座上:ng g component chat
  3. 通过路由设置控制新插件,是不是可以显示聊天面板,多加一个参数上outlet:aux:{path:'chat',component:ChatComponent,outlet:aux},
  4. 在app.html的模板上设置对应的链接控制它是否显示 :<a [routerLink]="[{outlets:{aux:'chat'}}]">开始聊天</a> 注意点:当点击开始聊天需要同时改变主路由的值,使其跳转到对应的路由上需要添加primary的属性:
Deft-pawN commented 6 years ago

3-7 路由守卫(钩子)

概念:只有用户满足某些条件的时候才会被允许进入或者是离开某个路由,比如 用户登录以后才可以进入路由,注册流程中只有满足前面的填写规定的时候才可以继续,当客户离开某个界面却没有进行保存的时候,三种路由守卫:

  1. CanActivate守卫 处理导航到某个路由时的情况:满足要求时不能访问某个守卫
  2. CanDeactivate守卫 处理从当前路由离开时候的情况,不满足就不能离开
  3. Resolve守卫 在进入路由前获得路由信息 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 需求:只有登录的客户才可以看到页面 步骤:
  4. 建立创建目录“guard”,在其中建立loginguard.ts,在其中创建一个loginGuard类继承的是CanActivate接口,并且实现CanActivate方法,该方法需要返回boolean
  5. 建立逻辑关系,创建一个一个是否登录的逻辑,返回值
  6. 添加验证机制在路由中,添加canActivate:[]属性,可以添加多个路由守卫,所有的守卫的都会被一一检验
  7. ,在上面只是声明了守卫的类型是LoginGuard类,但是我们需要实例化该类需要通过 依赖注入的机制,后面会讲但是,这边只是需要在providers:[LoginGuard]再次写入就可以了 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++CanDeactive守卫:当客户需要离开页面的时候,提示用户是否保存,确保用户在保存以后才可以离开页面 步骤
  8. 创建Unsaveguard 类继承CanDeactivate接口,并且该接口需要传入一个泛型,表示你需要保护的组件是什麽(productComponent):实现的方法中需要实现
  9. 在路由中添加canActivate 属性表示进行声明: 接着在providers:[LoginGuard]再次写入就可以了
Deft-pawN commented 6 years ago

3-8 resolve守卫

使用场景::当我们进入某个页面后有时候需要向后台发送数据请求,在它返回之前页面上的数据都不会显示出来,有一个等的过程,这样的用户体验非常不好,通过 resolve守卫可以在进入某个路由前就去后台传递数据,然后带着数据进入某路由,这样子的方式更加人性化,用户体验更加好 实际案例:

  1. 首先声明一个product.resolve.ts 在其中声明一个类ProductResolve,这个类需要继承Resolve接口,还需要在该类中声明泛型Product,这表示需要解析的数据类型 是product类型的,也就是需要返回一个product 对象
  2. 为了得到product 对象,需要在product.component.ts中声明一个类 export class Product这个类有两个属性:constructor(public id number,public name string){}
  3. ProductResolve类中实现方法,resolve(route:ActivatedRouteSnapshot,state:RouterStateSnap){}其中的ActivatedRouteSnapshot中可以拿到当前路由的参数
  4. 然后取得传进来的id值:let id:number = router.params["id"];正常情况下是调用http的函数
  5. 进行逻辑判定,如果不正确需要通过路由的navigate函数进行跳转到别的地方:this.router.navigate(['/home']),路由器的需要通过构造函数(constructor)进行注入
  6. ProductResolve 这个类需要使用装饰器@injectable 进行装饰,只有这样子才可以把路由器注入进来
  7. 把resolve 守卫添加到路由中去:resolve:{product:ProductResolve}这句话表示传入一个product对象,这个对象是由ProductResolve守卫得到的
Deft-pawN commented 6 years ago

3-9 改造在线竞拍应用

任务描述:点击商品详情按钮以后,轮播图组件商品列表组件会被替换成商品详情组件 任务需求:

  1. 创建商品详情组件,ng g component productDetail组件同时在里面需要声明一个productTitle 的变量,待会需要从外部传入进来,需要在构造函数中注入对应的avtiavtedRouted对象,并且把该对象中的变量值赋值给this.productTitle = this.routerInfo.snapshot.params["prodTitle"]
    1. 重构代码把 轮播图组件和列表组件封装在新的home组件中:ng g component home其中的业务逻辑不需要改变,只需要改动模板(template),对两个组件进行封装,
    2. 修改对应的路由设置, 在app-module中设置路由:const routeConfig:Routes = [], 声明路由配置以后需要把对应的路由设置注入到路由配置
    3. 修改App.component.html,根据路由显示home组件或者商品详情组件修改成插座的形式:<router-outlet></router-outlet>
    4. 修改商品列表租金,给商品标题添加routerlink指令的链接,导航到对应的商品详情路由上: <h4><a [routerlink]="['/product',product.title]">{{product.title}}</a></h4> +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 小结: 路由基础, 传递数据, 重定向路由, 辅助路由, 子路由, 路由守卫
Deft-pawN commented 6 years ago

4-1 依赖注入内容

  1. 定义:任何一个Angular程序都是一个组件指令,和一堆彼此依赖的类的集合,依赖注入是一种设定模式。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    4-2 依赖注入的好处

  2. 依赖注入(DI): Dependency Injection :如果某个对象(方法)需要N个参数对象,通过第三方的机制去把这N个参数进行声明和传入进去
  3. 控制反转(IOC):Inversion of control : 把依赖的控制权从代码内部转化成外部,控制权在内部的表现方式,当createShipment(product)方法中的product 对象换成mockproduct的时候,外部创建的对象也要进行改变,但是控制权在内部的时候,内部代码只需要负责声明,最终传入的对象是由外部决定的
  4. IOC 容器 : 依赖注入(DI):控制反转(IOC) 说的是同一个事情的两个方面,依赖注入强调的是方式如何实现反转模式,使用的是依赖注入的方式,控制反转强调的是目地,实现了控制反转模式的容器就叫做IOC 容器 ,Angular 就是该容器 ---=====-------========-----------========----------=======----------========--=-=-=-=-
  5. 依赖注入的优势 1. 松耦合,可重用性: product serviceproductComponent 组件之间原本是紧耦合的形式,如果是下次想要在别处使用productComponent组件 但是传入不同的服务对象的时候,你需要修改productComponent组件中 的代码 重新new 一个对象
  6. 依赖注入 的方式 是通过在 @ NgModule 模块中的 providers: ['A',"B"]指定需要依赖注入的对象 ,定义了一个对象被注入到一个组件和指令之前
  7. 组件使用构造函数(contructor)进行申明,表示 需要对应的token,Angular 会去带着这个token 去上面 找token对应的类是哪个,并且把这个类进行实例化注入到组件中去 : constructor(productService:ProductService)
  8. 组件(export class )本身 不需要明确传进来的类的类型,也不需要实例化这个对象,只需要使用以下这种方式去调用该对象:this.product = productService.getProduct()
  9. 如果你想在别的项目中进行重用productComponent组件 : 只需要其他项目的app.module中的@NgModule的声明providers: [{provide:ProductService,useClass:AnotherProductService}] ,组件本身是不需要修改的 2. 增加可测试性,: 比如测试登录组件已经完成 登录验证组件没有完成的时候可以先自己写一个登录逻辑进行验证
Deft-pawN commented 6 years ago

4-3 提供器入门

1.注入器 : Angular的一个服务类,把组件需要的对象 注入组件中,比如 组件中的构造函数(contructor(private productService:ProductService){}) 这样声明的时候,注入器就会在整个Angular中去寻找一个实例,并且把这个实例注入到对象中去

  1. 提供器(providers[ProductService]): 提供其他三种方式提供实例providers:[{provide:ProductService,useClass:ProductService}] +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

  2. 功能需求: 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.tsproviders声明和引入对应的服务 3.5 改写product.component 组件: ----->3.5.1 引入并且创建 product对象,用来接收从服务中获得的对象 ----->3.5.2 在构造函数中声明一个productService的服务,并且把对应的值赋给创建的product对象 3.6 修改模板 ,把对应的属性值显示出来 3.7 修改主组件的模板,拉起服务

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

  3. 提供器(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,但是调用的是另外一个服务

  4. 总结::Provider作用域的规则

  5. 当提供器申明在模块里的时候,表明该组件可以申明在任何一个组件中

  6. 提供器申明在组件中

  7. 申明在组件中和模块中的提供器拥有相同的token的时候,优先调用组件中的提供器

  8. 提供器 大多数申明在模块这个层级

解释 @injectable 装饰器 修饰 ProductService 服务的含义 表示 ProductService 这个服务可以通过构造函数注入别的服务,但是是不是可以把自己注入到别的服务中去不是由@injectable 决定的 ,是由providers 决定的

  1. 演示如何把服务之间相互注入的功能(给所有的服务 @injectable 添加的重要性 )

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 的子类 所以可以使用注入器

Deft-pawN commented 6 years ago

4-4 使用工厂和值 定义 提供器

  1. 需求说明:之前 我们 使用 providers: [ProductService,LoggerService],表示 :当 组件声明 ProductServicetoken 时候,我们会实例化一个 ProductService 实例,但是很多情况下我们需要根据实际情况 确定我们需要 创建哪个服务对象,需要使用工厂函数作为提供器,工厂函数通过一个随机数决定ProductService 服务还是AnotherProductService 服务

  2. 步骤: : 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,是一个明确的值 
  }],

总结以上的步骤:

  1. 首先在 组件的构造函数中申明了productSerivce 的token,constructor(private productService:ProductService) { } 2, Angular 看见这个token后 去找模块下对应名称的提供器(providers找),在其中是使用工厂函数进行实例化,并且 这个工厂函数还需要使用另外一个服务(loggerService),找到loggerService 的token 然后找到对应的实例化对象,如果loggerService自己还需要别的服务,它也会根据对应的token找到服务,这就是依赖注入,把组件和服务两个分别隔离开,对于组件productSerivce来说,以上这些都是未知的,它只知道自己得到了一个ProductService,并不在意后面的操作是什么
Deft-pawN commented 6 years ago

4-5 注入器及其层级关系

注入器:

  1. 把实例化的对象注入到需要的组件中去,应用启动时 会生成一个应用级别的注入器,把模块中声明的提供器都注册到注册器中,
  2. Angular创建主组件(app.component),同时应用级别的注入器会创建一个主组件级别的注入器 ,并且将组件中声明的提供器注入到组件级别的注册器
  3. 当子组件创建时,父组件注入器会为子组件创建一个注入器
  4. 最后,应用中会形成一组注入器,并且会 形成 和 组件一样的层级关系 比如 : ProductComponent 这个组件的注入器首先会 检查 自身是否注册了token 是ProductService提供器,如果没有找到就回去寻找父组件(App.component)注入器中是否有对应的提供器,一直找到应用级别的注入器上 有符合条件的提供器(productService),然后根据该提供器的配置进行实例化实例,并且把它注入到ProductComponent中去,如果应用级别的都是没有,那就抛出异常
    1. 一般情况下 我们不需要通过编码来调用注入器的方法,通过构造函数的参数,自动注入所需要的依赖,Angular 的组件只有一个注入点,如果Angular的构造函数中没有任何参数就表明该组件没有被注入任何东西 !
    2. 为了加强理解注入器原理,我们进行手工实现注入器 3.1 删除 构造参数,直接在上方定义一个ProductService 对象:
      
      private productService:ProductService

constructor(private injector:Injector ) { this.productService = injector.get(ProductService); }

Deft-pawN commented 6 years ago

4-6 改造Auction

  1. 编写 ProductService : 1.1 创建一个新的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);
  }
}
  1. 修改路由设置,在商品列表进入商品详情时候,传递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> 通过这样的方式进行传递组件的值

Deft-pawN commented 6 years ago

5-1 整章内容介绍

  1. 数据绑定 2. 响应式编程 3. 管道

    5-2 事件绑定

    绑定的形式有三种

    1. 数值绑定{{ object.Name}}的形式,2.属性绑定<img [src]="imgUrl"> 3.方法绑定的 <button (click)="clickbutton()">商品详情<.button>
  2. Angualr数据绑定的默认方式是单向绑定 ,在Angularjs 中使用的是双向绑定的方式,
  3. 事件绑定 3.1 <input (input)="OnclickEvent($Event)">小括号表示是时间绑定,里面的input表示事件名称,等号后面的函数表示需要调用的函数 ,$event 表示获得当前按钮的dom 属性

5-3 Dom属性绑定

  1. 插值表达式属性绑定是一样的: <img [src]="imgUrl">,<img src="{{ imgUrl }}">效果一样的
  2. html 属性dom属性的区别: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 属性进行修改,这也是htmldom 属性之间的区别 2.2 Angular 通过Dom 属性和事件来工作而不是html 属性绑定 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  3. Dom 属性绑定 的过程 组件中有一个greeting:string = This is for Test",模板上有一个<input [value]="greeting"> 当组件中的greeting值发生变化后会通过单向绑定更新 DOM属性,从控制器的greetingDOM的value的值改变,但是此时html的value 值是空的,因为浏览器会保持UI和DOM 的属性保持一致,DOM 的value 的属性不改变html属性, 当用户在更新input值的时候,浏览器也不会更新UI 和HTML属性,只会更新DOM属性 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Deft-pawN commented 6 years ago

5-4 HTML属性绑定

有三种 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类绑定

  1. 替换所有样式的方式: <div [class]="divclass"></div>compoentdivclass:string="a b c" 表示class的值会被替换
  2. 替换部分样式: <div class="a b" [class.c]="isBig"></div>compoent中 给isBig 属性赋值 表示class的值会被替换
  3. 同时进行类管理(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,}

Deft-pawN commented 6 years ago

5-5 双向绑定

  1. 介绍数据是使得视图和模型保持一致,之前介绍的事件绑定是单向的,视图---->控制器<input (input)="doOnInput($event)">,属性绑定是单向的:控制器------>视图的单向绑定<input [value]="name">,双向数据绑定 ,既要绑定属性又要绑定事件,

  2. 使用[(NgModel)]标签简化双向绑定指令: ---->2.1 模板显示:<input [(Ngmodel)]="name"> {{ name }} ---->2.2 模板上你可以看name 属性 但是你不能看到对应的事件是哪个,这是因为 NgModel 使用在input 标签上,这时候使用的是input 事件,采用的是默认事件,使用在不同的标签上的时候会触发不同的事件,这些触发机制是由内部controller 接口管理的 ----->双向绑定常用的方式: Form表单元素 机制 ,不应该用在显示元素上(div,等)

Deft-pawN commented 6 years ago

5-7 管道

  1. 管道负责原始值到显示值之间的转换(不可能在页面上显示时间戳这样的方式),把数据作为输入把期望的数据作为输出{{ birthday | date | uppercase }} ,多个管道可以连接
  2. 内部管道的介绍:Lowcase,Uppercase,Date .... 比如 Date 管道可以接收一个参数{{ birthday | date:"yyyy-MM-dd HH:mm:ss "}}
  3. number 管道 用来格式化数字: 比如 格式化圆周率{{ pi | number:'2.1-4'}}, 最后表示小数最少和最多的显示
  4. async 异步管道 ,处理异步流 的管道
  5. 自定义管道 : ------>5. 1. 生成一个管道 : ng g pipe pipe/multiple,模块和管道是相同的,都需要在模块的declarations 中声明的 ------>5. 2 管道名称可以任意定义,类 实现了 PipeTransform的接口,这个类中只有一个方法transform(value:any,args:any):any{ 前面那个value 表示的是 传入的值,后面的arg 表示可以传入的参数
Deft-pawN commented 6 years ago

5-8 实战 (添加搜索框进行搜索)

  1. 运用到的知识: 数据绑定|响应式编程|管道,实现功能: 添加关键字查询,搜素对应的商品信息
  2. 修改product 的页面,添加对应的搜索框
  3. 使用响应式编程处理输入框的输入 事件 ,把关键字绑定在组件的一个属性上: ------>2.1 组件中声明一个属性keyword保存当前的关键字 !:private keyword: string; ------>2.2 在应用主模块层面中引入 ReactiveFormModule 模块 ------>2.3 在product组件的控制器中声明了titleFilter 字段,它的类型就是FormControlprivate titleFilter:FormControl = new FormControl(); ------>2.3前端把input的输入框中使用formControl指令,把它和后台的字段绑定在一块,<input [formControl]="titleFilter">表示input 输入框和控制器上类型是formControltitleFilter 字段绑定在一起了,当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无法识别对应的管道名称,报错 找不到这个管道

Deft-pawN commented 6 years ago

6-1 组件间通讯内容介绍

  1. 尽量保证组件之间实现 松耦合的 形式,使用依赖注入的方式可以实现 松耦合,现在需要学习的是组件之间的通讯方式,实现高重用的组件 2.组件的输入输出属性 ,实现父子组件之间的相互通讯
  2. 使用中间人模式传递数据
  3. 组件生命周期以及Angular的变化发现机制
Deft-pawN commented 6 years ago

6-2 输入属性

  1. 把组件设计成黑盒模型,组件 使用输入属性声明自己需要的东西,使用输出属性发射事件,输入属性是从父组件传数据到子组件上,单向
  2. 输入属性的定义: 被装饰器@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. 输入绑定是单向的 只能从父组件到组件上,子组件数据改变没办法影响父组件

  1. 现在证明在子组件中改变值,并不能改变父组件中的显示值 ---->3.1 在子组件的 构造函数中 使用延迟函数(setInterval)实现过几秒钟以后修改子组件中的值,在模板页面中只有子组件的值发生了变化,对应父组件的Input 值并没有因为子组件的值发生变化,这就证明了单向性
    constructor() { 
    setInterval(() =>{
      this.stockCode ="AppleCode";
    }
      )}
  2. 现在我们接触到的组件之间传递数据的方式有两种:
    ------> 4.1 输入属性传值,只能通过父子关系的组件之间,由父组件传递给子组件才可以通过输入属性进行传递 ------> 4.2 通过路由参数属性,从组件中传值,通过构造函数中注入ActivateRoute对象,然后通过这个对象的参数订阅/快照 的方式 ,获取外面传递的参数
Deft-pawN commented 6 years ago

6-3 输出属性

  1. 场景: 编写组件,实施接收交易所的股票信息,同时还需要向外发送最新的数据信息,提供给别的组件进行逻辑处理 ----->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)
    }
  2. 场景二: 把股票信息输出去,供别的组件使用,需要使用到 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)
    
    }
  3. 解释在Price_Last_object:EventEmitter<PriceQuote> = new EventEmitter() 后面的泛型 PriceQuote, 表示你需要发射出去的数据是什么类型的

  4. 在父组件中 接收发射出来的 数据信息 ----->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
    }
Deft-pawN commented 6 years ago

6-4 中间人模式

  1. 场景: 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. 建议在编写之前,确定 中间人组件 !父组件 ! 的关系 !
Deft-pawN commented 6 years ago

6-5 组件生命周期 钩子概述

  1. 组件创建开始,Angular的变更机制 就开始检测组件,然后组件被加到DOM 树上 ,被浏览器上渲染出来 ,被用户看见.组件发生变化,会导致组件被重新渲染,最后被销毁 !
  2. 组件声明周期钩子 ,使用钩子 可以在特定的组件生命周期事件 发生时候 执行你需要的业务逻辑 ,相当于给你的组件开了一个缺口,然后通过这个缺口进行逻辑操作,有一些钩子只会被调用一次,有些钩子可以被反复的调用,这些钩子分别会 分布在 组件的初始化-----> 变更检测阶段------>消亡阶段的,初始化以后用户会看到组件,变更检测机制 会确保页面保持一致,如果由于路由等操作,组件被从DOM树上被移除以后,Angular 会执 行销毁阶段
  3. 生成一个组件 ,发现本身已经继承了 OnInit 接口, 上面介绍的每一个钩子都是来自于 '@angular/core'的接口!也就是说钩子的实质就是一个个接口 !,每一个接口都有一个钩子方法并且名字都是ng+接口名字形成的; (组件------>钩子接口------->钩子方法),
  4. 实现所有 的钩子接口,并且实现 对应的钩子方法,代码如下:
export class LiveComponent implements OnInit,OnChanges,DoCheck,AfterContentInit,AfterContentCheck,AfterViewInit,AfterViewCheck,OnDestoryngDoCheck():void{

  } 
  1. 定义一个输入属性的名称为 Name的变量,为了说明ngOnInit 函数和 constructor 两个函数的区别
  2. 定义LogIt 函数,为了说明不同钩子的运行顺序 ,下面是输出的结果:
    #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
Deft-pawN commented 6 years ago

对应的参考地址 !!!

Deft-pawN commented 6 years ago

6-6 OnChanges钩子

  1. 使用的场景是:当一个父组件初始化或者修改一个子组件的输入属性的时候被调用
  2. 可变对象vs 不可变对象: 字符串对象 是不可变对象,下面这个例子中 首先在 内存中创建了一个字符串,然后再创建了第二个字符串,内存中有两个字符串,对于greeting 来说 指向的内存地址发生了变化:
    var greeting = "hello";
    greeting ="hello world !
  3. 相对于字符串,对象类型是可变的 ,创建后,即使改变了对象的内容,内存地址也不会发生变化!比如 下面的 例子中 ,userName 的内存不发生变化,内存会创建一个新的内存地址放置Deft-pawN,然后 userName.name 去指向这个地址
    var userName = {name:"Sam0Witwicky",age:"19"};
    userName.name = "Deft-pawN"
  4. 创建 child 组件,声明2个具有输入属性的对象和字符串,一个普通的变量:
  @Input()
  greeting:string;

  @Input()
  user:{name:"Tom"};

  message:string = "初始化消息";
  1. 修改模板,把三个值添加到模板上去 ,其中的输入框(input)采用双向数据绑定的形式,并且添加子组件的背景色用来区分样式:

    <div class="child">
    <h2>我是子组件</h2>
    <div>问候语:{{greeting}}</div>
    <div>姓名:{{user.name}}</div>
    <div>消息:<input [(ngModel)]="message"></div>
    </div>
  2. 实现 控制器里实现 ngOnchanges 方法,在这个方法中输出我传入的对象

    ngOnChanges(changes:SimpleChanges){
    console.log(Json.stringify(changes,null,2));
    }
  3. 编写父组件,声明变量用于接收子组件(Greeting和user)的两个值,

    export class AppComponent {
     title = 'Sam-Witwicky';
     greeting:string="Hello";
     user:{name:string} ={name:'Tom'} 
    }
  4. 把变量绑定到父组件上,同时在父组件上实现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.代码运行后的效果是 :

  5. 这里会出现一个 < 'ngModel' since it isn't a known property of 'input'. (">的错误,是因为FormsModule模块没有引入,所以我们需要在app.component.ts中 引入 FormsModule} from '@angular/forms';

  6. 父组件在初始化子组件的输入属性之前,子组件的输入属性是没有值的 ,然后Ogchanges被调用,接着子组件的输入属性被调用,这时候我们

  7. 改变父组件中输入属性的值(greetings),变更检测机制刷新 不可变对象 的值,然后 调用Ogchanges再次被调用

  8. 修改姓名属性的时候,可以改变子组件绑定的值,但是不会触发onChanges机制,用户只是改变了可变对象(user)的属性 ,user对象的本身没有发生变化的,这个是和上面 不可变对象的区别

  9. 改变子组件中Message属性时候,也不会 触发onChanges机制,因为它没有使用 @input 进行装饰,ngonchange方法只有在输入属性方法变化的时候才会被调用

  10. 所有 总结: 可以调用ngonchanges方法必须满足两个条件,一个是 Input输入属性,一个是不可变属性

Deft-pawN commented 6 years ago

6-7 变更检测和DoCheck钩子

  1. 简介: 变更检测机制是通过 zone.js 实现 的 ,主要保证 浏览器上和组件的属性上的值是一致的,浏览器上 发生的任何一个异步机制都会引发变更检测机制,比如:按钮的点击,输入数据,服务器返回数据后,在Angularjs的第一个版本中,任何原生的 事件(比如click) 都不会触发脏检查机制 ,在这个版本中,可以随意使用 原生事件 就是因为 有了这个zone.js
  2. 变更检测策略: 主组件和任意的子组件都会生成一个变更检测器 ,任何一个变更检测器发生变化的时候,都会根据变更检查策略Angualr有两种Default 策略 和 OnPush 策略,如果所有的组件都是用 Default 策略那就是只要有一个组件发生了变化都会把所有的组件上的组件全部检查一遍,如果某个组件声明了自己的OnPush策略的时候,只有当这个组件的输入属性发生变化的时候才会 检测
  3. 在上面一章中出现一个问题:可变对象的属性发生变化的时候,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;
    }
  4. 在子组件(child.component)中实现Docheck 方接口并且重写它的方法
  5. ngDocheck接口会被非常频繁的调用 ,在输入框中频繁的点击并没有改变值的时候,都会触发ngDocheck方法 ,点击事件会触发变更检测机制,所有组件上的 ngDoCheck方法被调用 ,然后我们改变输入框中的值,也会被捕捉到,其中 ngDocheck 调用的次数很多,所以我们实现ngDocheck方法必须考虑到性能的问题 ,所有的名称里面有check的钩子方法只要你的子组件实现了,每一次小的变动都会被调用 对应的方法 !所以需要小心
    事件的变化(click ...) ------> 变更检测机制触发------> 实现了check接口的组件中 方法 就会被调用
Deft-pawN commented 6 years ago

6-8 view钩子 介绍 ngAfterViewInit 和 ngviewcheck

  1. 介绍功能:从 父组件中调用子组件的API (之前都是介绍传递数据)使用两种方式进行调用 ,一个在timescript??? 代码 另外一种是 用模板的变量 是
    ----->1.1 在子组件中声明一个函数 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");
}
  1. 父组件的模板中调用子组件的方法: <button (click)="child2.greeting('jerry')">调用child2的greeting方法

  2. 介绍 AfterViewInit 和 AfterViewChecked 钩子 ,父组件实现 这个两个钩子,并且实现他们对应的方法,这两个方法调用的时间 是在 模板已经组装完毕,并且已经给用户看到之后被调用

    ngAfterViewInit():viod {
    console.log('父组件的视图初始化完毕')
    }
    ngAfterViewChecked():viod {
    console.log('父组件的视图检测完毕')
    }
  3. 子组件中同样实现这两个接钩子:

    
    ngAfterViewInit():void{
    console.log('子组件的变更检测机制初始化完毕';
    }
    ngAfterViewChecked():void{
    
    console.log('子组件的视图变更检测完毕';
    }
5.父组件中使用定时调用的机制去 

ngOninit():viod{ setInterval(() =>{ this.child1.greetings("Tom"); },5000);
}


6. 实际运行的效果: 
日志顺序 : 子组件的视图ViewInit() 函数 ----> 子组件的视图ViewInit() 函数-----> 
Deft-pawN commented 6 years ago

6-9 ngContent指令 ngAfterContentInit,ngAfterContentChecked

  1. 投影的概念: 动态改变组件的内容 :
    中根据不同的参数,显示不同的内容 ,使用 ngcontent 把 父组件的 ----> 在子组件中使用 ng-content生成一个投影点 : <div class="wrapper"><ng-content></ng-content></div> ----> 在父组件模板的子组件标签中 写 上需要投影的内容
    <app-child>
    <div class="wrapper">
      这个div 是父组件投影到子组件上的
    </div>
    </app-child>

    ----> 在以上的两个div中都有wrapper 这个样式,然后我们对子组件和父组件分别使用不同的css 样式,就会发现 他们表示的样式是不同的,这是需要注意的

------> 一个子组件可以在它的模板中声明多个ng-content标签 ,比如 子组件中有三个部分组成:页头,内容,页脚,其中的页头和页脚是通过投影的方式生成的 ,父 组件中的 样式 内容是在中 的 ,分别定义class 的名称,子组件通过class名称进行选择

<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 = “这是父组件的属性 ”

  1. 需要注意的是,上面的 父组件的模板中使用的插值表达式 ,只能绑定父组件的属性 ,但是 不能使用子组件的属性,说白了还是属于 父组件的东西 !!!

  2. 使用属性绑定的方式插入Html ,这是 和上面使用ng-content的效果类似,但是还是有所区别的方式 : 父组件的模板上 使用 `<div [innerHtml]="divContent">

,在controller 中定义: divContent ="
这是属性绑定的例子
"

  • 以上两种方式的区别 : ------>5.1 innerHtml 只能使用在 浏览器 上使用,当你需要App软件的时候,ng-content有更好的移植性 ------>5.2 ng-content 可以 插入多个位置 的 投影点 ,更加灵活 ------> 5.3 ng-content只能绑定父组件的属性值,innerHtml 只能绑定所在的组件的属性值! ------> 5.4 我们应该首先考虑的是ng-content 的方式