• Angular4入门笔记


    AngularJS最大版本号只有1.x,2.x/4.x的版本号都是针对于全新的框架Angular。但不能说Angular和AngularJS一点关系都没有,你看名字这么像,是吧?!回忆一下AngularJS被人念念不忘的特性,双向数据绑定,MVC,指令,服务,过滤器,模块化,脏检查机制,依赖注入,Scope,路由,表单校验等等。

    看下AngularJS到Angular的过程中,哪些概念被保留下来,哪些被剔除了(所谓的取其精华,去其糟粕)。

    剔除的部分:

    • ng-controller指令:控制器主要是业务逻辑的控制部分
    • $scope概念:很强大又很复杂
    • 数据双向绑定:数据双向流通可能导致数据的震荡(故才有最多检查10次的限制,10次之后还不稳定就报错)

    保留/改善的部分:

    • 路由嵌套:AngularJS自带的路由系统是不能嵌套路由的,到了Angular你想怎么嵌套就怎么嵌套
    • 过滤器(Filter)变成管道(Pipe),概念的变化
    • 依赖注入机制:直接在构造器中注入,还有分层依赖注入的概念
    • 指令写法:
      • (事件) ng-click变成(click)
      • [属性] href = "{{}}"可以写成 [href]
      • [(ngModel)]代替以前的ng-model
      • *ngFor 代替 ng-repeat,不适用于对象,适用任何有Symbol.iterator属性的数据结构(能用for...of来访问),比如数组,集合等
      • *ngIf 代替 ng-if,去掉ng-show,ng-hide
    • 对移动端的支持
    • 模版,数据绑定,服务,模块,脏检查机制等

    新增的部分:

    • 组件化:Angular的核心所在
    • Typescript作为默认的开发语言
    • ZoneJS监听所有(可能导致数据变化)的异步事件
    • 支持服务端渲染

    模块

    Angular很重要的概念之一仍然是模块。Angular整个框架就是由很多个模块组成的,而不同的模块需要从不同的地方导入。打开package.json文件,可以看到依赖的angular包可能是这样的:

    "@angular/common": "^2.3.1",
    "@angular/compiler": "^2.3.1",
    "@angular/core": "^2.3.1",
    "@angular/forms": "^2.3.1",
    "@angular/http": "^2.3.1",
    "@angular/platform-browser": "^2.3.1",
    "@angular/platform-browser-dynamic": "^2.3.1",
    "@angular/router": "^3.3.1",
    

     

    来简单看下这些angular包中包含了哪些常用的模块(至少目前为止,我觉得常用的)。

    • @angular/core:这里包含了很多常用的模块
      • NgModule:模块定义装饰器
      • Component:组件定义装饰器
      • Directive:指令定义装饰器
      • Pipe :管道定义装饰器
      • PipeTransform:管道接口
      • Injectable:服务定义装饰器
      • ElmentRef:元素引用
      • ViewChild:获取子元素
      • Render:渲染
      • Input:接受参数输入
      • Output:事件输出
      • EventEmitter:触发自定义事件
    • @angular/common
      • CommonModule:通用模块,包含内置指令ngIf,ngFor
    • @angular/forms
      • FormsModule:定义模版驱动表单
      • ReactiveFormsModule:定义响应式表单
      • FormGroup, FormArray, FormControl, FormBuilder:响应式表单元素
      • Validators:表单校验
    • @angular/http
      • HttpModule:http请求模块
    • @angular/router
      • RouterModule 路由模块
      • Routes 路由数据结构
    • @angular/platform-browser
      • platformBrowser:AoT编译
      • BrowserModule:浏览器支持,注意该模块导入了CommonModule,然后导出去,所以引用了这个模块也就引用了CommonModule
    • @angular/platform-browser-dynamic
      • platformBrowserDynamic:JIT编译

     

    以上模块都是Angular框架中的自带模块,而我们开发的完整单元也是模块。一个应用中至少要有一个模块,也就是根模块。 一些共享的功能属性我们可以抽象出来,成为共享模块。然后就是一些特性模块了。

    模块的组成由组件,服务,指令,管道等等组成,这些概念会在下面讲到。定义模块的语法如下:

    @NgModuel({
        declarations: [],   // 用到的组件,指令,管道
        providers: [],      // 依赖注入服务 
        imports: [],        // 导入需要的模块
        exports: [],        // 导出的模块,跨模块交流
        entryComponents: [] // 需提前编译好的模块
        bootstrap: []       // 设置根组件
        
    })
    export class AppModule { }
    

      所有用到的组件,指令,管道,模块都需要事先在模块中声明好,才能在具体组件中使用。服务可以在模块,组件,指令中的providers声明,也可以直接在运行时提供(参见Trotyl Yu的例子)。

    一般情况下,在根模块的bootstrap中设置启动的根组件即可,但也可以动态处理(参见Trotyl Yu的例子)。

    那如何启动根模块呢?

    在入口脚本中,也就是Angular Cli项目中的main.ts中,启动如下:

    // 导入需要模块
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
    // 根模块
    import { AppModule } from './app/app.module';
    
    // 编译启动模块
    platformBrowserDynamic().bootstrapModule(AppModule);
    

      至此,我们对模块有所了解,也知道了模块的定义。

    组件

    自从采用组件化的React大火之后,目前市面上炙手可热的框架全都采用了组件化的理念,Angular当然也不能落后了。可以说,组件化是Angular的核心理念。按Angular在中国的布道者 @大漠穷秋的话来说,就是:

    Angular的核心概念是组件,模块化机制NgModule是为组件化服务的,实际上所有其它机制都是围绕组件化而来的。只有从组件化这个角度才能把握Angular的精神内核。

    组件通常都是由模版和业务逻辑组成,看一下如何用Angular写一个很简单的组件:

    // hello.component.ts
    
    import { Component } from '@angular/core';
    
    @Component({              
        selector: 'hello',
        template: '<p> {{greeting}} </p>',
        styles: [`p { color: red;}`]
    })
    export class HelloComponent{
        private greeting: string;
        constructor(){
            this.greeting = 'Hello, Angular2!';
        }
    }
    
    // 使用
    <hello></hello>
    
    // 渲染结果
    <hello>
        <p> Hello, Angular2! </p> 
    </hello>
    

    定义类HelloComponent的时候,加上装饰器@Component(Typescript语法),告诉Angular这个类是组件类。里面的数据称之为元数据(metadata),selector属性说明了该组件对外的使用标记,template就是组件的模版,styles是组件的样式。而HelloComponent中定义的就是该组件的业务逻辑了。

    如果模版内容太多,可以单独写在一个html文件中,用templateUrl属性引入;同理,样式文件用styleUrls引入。

    组件生命周期

    正如其他框架的组件,Angular的组件也是有生命周期这个概念。在不同的阶段不同的场景下,可以调用不同的生命周期函数钩子(hook)。

    constructor:构造器函数,一般用于注入服务

    • ngOnChanges:检测到输入数据变化,首次触发发生在ngOnInit前。注意对象的属性发生变化时监听不到
    • ngOnInit:组件初始化,通常会设置一些初始值
    • ngDoCheck:手动触发更新检查
      • ngAfterContentInit:内容初始化到组件之后
      • ngAfterContentChecked:内容变更检测之后
      • ngAfterViewInit:视图 初始化之后
      • ngAfterViewChecked:视图发生变化检测之后,这个可以用来保证用户视图的及时更新
      • ngOnDestroy:组件注销时的清理工作,通常用于移除事件监听,退订可观察对象等
      • 组件通信

        可以想像得到,组件化的页面结构最终会形成一颗组件树

      • 不可避免,我们需要考虑父子组件之间的参数传递问题。Anuglar提供的通信方式有如下几种:

        • 父组件到子组件:父组件用属性绑定将值传入,子组件通过@Input来接收。
          // 父组件
          import { Component } from '@angular/core'; 
           
          @Component({
            selector: 'hero-parent',
            template: `<h2> heroes </h2>
              <hero-child *ngFor="let hero of heroes"
                [hero]="hero" >
              </hero-child>
            `
          })
          export class HeroParentComponent {
            heroes = [{
              name: 'John'
            }, {
              name: 'Lily'
            }]; 
          }
          
          // 子组件
          import { Component, Input } from '@angular/core';
          
          import { Hero } from './hero';
           
          @Component({
            selector: 'hero-child',
            template: `
              <h3>{{hero.name}}</h3> 
            `
          })
          export class HeroChildComponent {
            @Input() hero: Hero; 
          }
          

            子组件到父组件:子组件自定义事件用@Output传出,父组件用事件绑定获取。

          // 子组件
          import { Component, EventEmitter, Output } from '@angular/core';
          
          @Component({
            selector: 'my-voter',
            template: `
              <h4>{{name}}</h4>
              <button (click)="vote(true)">Agree</button> 
            `
          })
          export class VoterComponent { 
            @Output() onVoted = new EventEmitter<boolean>(); 
           
            vote(agreed: boolean) {
              this.onVoted.emit(agreed); 
            }
          }
          
          // 父组件
          import { Component } from '@angular/core';
          
          @Component({
            selector: 'vote-taker',
            template: `
              <h2>Should mankind colonize the Universe?</h2>
              <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
              <my-voter *ngFor="let voter of voters"
                [name]="voter"
                (onVoted)="onVoted($event)">
              </my-voter>
            `
          })
          export class VoteTakerComponent {
            agreed = 0;
            disagreed = 0;
            voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
           
            onVoted(agreed: boolean) {
              agreed ? this.agreed++ : this.disagreed++;
            }
          }
          

            子组件引用:在父组件模版中添加对子组件的引用,即可通过该子组件去访问子组件的方法。

          <h3>Countdown to Liftoff (via local variable)</h3>
          <button (click)="timer.start()">Start</button>
          <button (click)="timer.stop()">Stop</button>
          <div class="seconds">{{timer.seconds}}</div>
          <countdown-timer #timer></countdown-timer>
          

            @ViewChild():类似的,也可以在脚本中用@ViewChild()来获取子组件

          import { AfterViewInit, ViewChild } from '@angular/core';
          import { Component }                from '@angular/core';
          import { CountdownTimerComponent }  from './countdown-timer.component';
           
          @Component({
            selector: 'countdown-parent-vc',
            template: `
            <h3>Countdown to Liftoff (via ViewChild)</h3>
            <button (click)="start()">Start</button>
            <button (click)="stop()">Stop</button>
            <div class="seconds">{{ seconds() }}</div>
            <countdown-timer></countdown-timer>
            `
          })
          export class CountdownViewChildParentComponent implements AfterViewInit {
           
            @ViewChild(CountdownTimerComponent)
            private timerComponent: CountdownTimerComponent;
           
            seconds() { return 0; }
           
            ngAfterViewInit() { 
              setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
            }
           
            start() { this.timerComponent.start(); }
            stop() { this.timerComponent.stop(); }
          }
          

            将数据保存在服务中

        • 模板与数据绑定

          模版说白了就是html的内容,常规的html基本都是静态内容,而模版结合了框架中的新语法使得html动态化。来看看Angular中的模版有什么便利的语法:

          • 插值绑定:双花括号{{}}

          我们可以看到上一节组件例子中的{{greeting}}就是插值绑定。不仅可以获取变量的值,还可以直接写表达式。

          • 属性(Property)绑定
            <input [value]='myData'>
            

            还有其他的,比如样式绑定

            <div [ngClass]="{special: isSpecial}"></div>

            注意点:property和attribute不一样,想要绑定attribute,你需要写成property。比如:

            <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
            

             你将会得到如下错误信息:

            Template parse errors:
            Can't bind to 'colspan' since it isn't a known native property
            

              你需要改写成这样:

            <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
            // 或者
            <tr><td attr.colspan="{{1 + 1}}">One-Two</td></tr>
            

              事件绑定

            <input (keyup)='handle($event)' >
            

              可以是原生的事件:click,change,keydown,mousemove等,也可以是自定义事件,也可以是指令事件,比如ngSubmit。

          • 双向绑定
            <input [(ngModel)] = 'data'>
            // 双向绑定的背后其实是单向绑定和事件触发,等价于下面
            <input [ngModel]="data" (ngModelChange)="data=$event">
            

              注意点:使用ngModel,需要引入FormsModule模块。

          • 还有些内置的指令:

            • 模版引用变量(# / ref-)

            可以在元素上用#或者ref-前缀来标记这个元素,然后在其他地方引用。

            <input #fax placeholder="fax number">
            ( <input ref-fax placeholder="fax number"> )
            <button (click)="callFax(fax.value)">Fax</button>
            

              *ngIf:控制内容的有无

            <div *ngIf="show"> Can you see this? </div>
            

              如果还有else部分,可以如下操作:

            <div *ngIf="show; else elseBlock"> Can you see this? </div>
            <ng-template #elseBlock>  else block   </ng-template>
            

              *ngFor:循环

            <div *ngFor="let hero of heroes; let i=index> {{i}}: {{hero.name}}</div>
            

              

            路由

            一个模块有了多个组件之后,需要用路由来配置哪个url呈现哪个组件。

            首先,我们需要在入口页面的index.html中配置根路径:

            ...
            <head>
            <base href="/">
            ...
            </head>
            ...
            

              然后创建一个路由模块:

            import { NgModule }              from '@angular/core';
            import { RouterModule, Routes }  from '@angular/router';
             
            ...
            
            // 路由配置
            const appRoutes: Routes = [
              { path: 'home', component: HomeComponent },
              { path: 'heroes', component: HeroesComponent },
              { path: '',   redirectTo: '/home', pathMatch: 'full' },
              { path: '**', component: PageNotFoundComponent }
            ];
             
            @NgModule({
              imports: [
                RouterModule.forRoot(appRoutes)
              ],
              exports: [
                RouterModule
              ]
            })
            export class AppRoutingModule {}
            

              在主模块中导入配置好的路由模块:

            import { NgModule }       from '@angular/core';
            import { BrowserModule }  from '@angular/platform-browser';
            import { FormsModule }    from '@angular/forms';
             
            ...
             
            @NgModule({
              imports: [
                BrowserModule,
                FormsModule,
                AppRoutingModule
              ],
              declarations: [
                AppComponent,
                HomeComponent,
                HeroesComponent,
                PageNotFoundComponent
              ],
              bootstrap: [ AppComponent ]
            })
            export class AppModule { }
            

              而在页面中需要一个容器<router-outlet></router-outlet>去承载:

            import { Component } from '@angular/core';
             
            @Component({
              selector: 'my-app',
              template: `
                <h1>Angular Router</h1>
                <nav>
                  <a routerLink="/home" routerLinkActive="active">Home</a>
                  <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
                </nav>
                <router-outlet></router-outlet>
              `
            })
            export class AppComponent { }
            

              

            上面代码中的routerLink定义了用户点击后的路由跳转,routerLinkActive定义该路由激活时的样式类。

            路由上还可以带上一些索引参数:

            { path: 'heroes/:id', component: HeroesComponent },
            

              获取的方式:

            import { ActivatedRoute, Params }   from '@angular/router';
            
            ...
            export class a {
                constructor( 
                  private route: ActivatedRoute 
                ) {}
                
                // 路由参数
                this.route.params
            }
            

              当模块很多,路由也很多的时候,我们可以使用模块懒加载的方式。懒加载的方式也很简单,在配置路由的时候修改如下即可:

            const routes: Routes = [
                {    // 默认转到订单管理
                    path: '',
                    redirectTo: '/order',
                    pathMatch: 'full'
                 },
                {
                    path: 'order',
                    loadChildren: './order/order.module#OrderModule'
                },
                {
                    path: 'warehouse',
                    loadChildren: './warehouse/warehouse.module#WarehouseModule' 
                },
                {
                    path: 'statistics/sales',
                    component: SalesComponent
                }
            ];
            
            // 在子模块中用RouterModule.forChild
            
            import { NgModule } from '@angular/core';
            import { RouterModule } from '@angular/router'; 
            import { OrderComponent } from './order.component';
            
            
            const orderRoutes = [
            	{
                    path:'',
                    component: OrderComponent
                }
            ];
            
            @NgModule({
                imports: [RouterModule.forChild(orderRoutes)],
                exports: [RouterModule]
            })
            
            export class OrderRoutingModule {
            }
            

            服务与依赖注入

            服务是什么概念?可以简单地认为它是一个功能模块,重要在于它是单例对象,并且可以注入到其他的地方使用。

            依赖注入是来自后端的概念,其实就是自动创建一个实例,省去每次需要手动创建的麻烦。

            在Angular中定义一个服务很简单,主要在类之前加上@Injectable装饰器的功能

            import { Injectable } from '@angular/core';  
            
            @Injectable() 
            export class Service {
                counter: number = 0;
                
                getData(){
                    return this.counter++;
                }
            }
            

              然后在模块的providers中声明:

            import { Service } from './service';
            ...
            
            @NgModule({
                imports: [
                    ...
                ],
                declarations: [
                    ...
                ],
                providers: [ Service ],  // 注入服务
                bootstrap: [...]
            })
            export class AppModule {
            }
            

              使用的时候需要在构造器中建立关联:

            import { Component } from '@angular/core'; 
            import { Service } from './service';
            ...
            
            @Component({
                selector: 'my-app',
                templateUrl: './app.component.html',
                styleUrls: ['./app.component.css']
            })
            export class AppComponent {
                constructor(public service: Service) {
                    // this.service被成功注入
                    // 相当于 this.service = new Service(); 
                    // 然后可以调用服务
                    this.service.getData();
                }
            }
            

              

            由于该服务是在模块中注入,所以该模块中的所有组件使用这个服务时,使用的都是同一个实例。

            除了在模块中声明,还可以在组件中声明。假设AppComponent下还有组件HomeComponent,此时我们在AppComponent中注入这个服务:

            import { Component } from '@angular/core'; 
            import { Service } from './service';
            ...
            
            @Component({
                selector: 'my-app',
                templateUrl: './app.component.html',
                styleUrls: ['./app.component.css'],
                providers: [ Service ],  // 注入服务
            })
            export class AppComponent {
                constructor(public service: Service) {
                    // this.service被成功注入
                    // 相当于 this.service = new Service(); 
                    // 然后可以调用服务
                    this.service.getData();
                }
            }
            

              

            如果HomeComponent也使用了这个服务,那它使用的将是同一个实例。这个可以从Service中的数据变化来看出。

            Angular还有个分层依赖注入的概念,也就是说,你可以为任一组件创建自己独立的服务。就像上面的例子,如果想要HomeComponent不和它的父组件同使用一个服务实例的话,只要在该组件中重新注入即可:

            ...
            @Component({
                selector: 'home',
                templateUrl: './home.component.html',
                styleUrls: ['./home.component.css'],
                providers: [ Service ],  // 重新注入服务
            })
            export class HomeComponent {
                ...
            }
            

              对于前后端的接口,通常会写成服务。下面说下请求后端数据这块应该怎么写。在模块这节中提过,http有专门的HttpModule模块处理请求。首先要在模块中导入HttpModule,然后引入http服务,调用相应的请求方法即可。

            import { Injectable } from '@angular/core';
            import { Http }       from '@angular/http';
              
            import 'rxjs/add/operator/toPromise';
              
             
            @Injectable()
            export class HttpService {
             
              constructor(private http: Http) {}
             
              getFromServer():any {
                return this.http.get(`/data`)
                    .toPromise()
                    .then(res => res.json())
                    .catch();
              }
            }
            

              

            由于请求返回的对象是个可观察对象,可以转成Promise对象处理。这里需要用到RxJS的toPromise操作符,然后用then去处理返回成功结果,catch处理失败情况。这样就搞定了后端数据的请求了。

            RxJS又是另外一个比较高深的话题了,有机会深入学习一下再聊。

         

    指令

    Angular的指令概念跟AngularJS的指令差不多,最重要的区别在于Angular中的组件继承指令,算是特殊的指令。我们看下用指令的方式去写组件的简单例子:

    import { Directive,Input,ElementRef } from '@angular/core';
    
    @Directive({
        selector: 'hello'
    })
    export class HelloDirective { 
        @Input() name: string;
    
        constructor(private el: ElementRef) {}
    
        public ngOnInit(): void {
            
            this.el.nativeElement.innerText = `hello ${this.name}!`;
        }
    }
    
    // 使用组件指令
    <hello name="Yecao"></hello>
    
    // 渲染结果
    <hello> hello, Yecao! </hello>
    

      

    不要忘记在使用前先在模块中声明哦,我觉得这是Angular最烦人的一点。

    除此之外,还有属性指令和结构指令,属性指令只改变元素的样式或者行为。要写成属性指令,需要在selector属性中用[]包裹起来。来看简单地例子:

    import { Directive, ElementRef, Renderer2 } from '@angular/core';  
    
    @Directive({   
      selector: '[highLight]'  
    })  
    
    export class HighLightDirective {    
      constructor(private el: ElementRef, private renderer2: Renderer2) { }    
      
      ngAfterViewInit() {   
        this.renderer2.addClass(this.el.nativeElement, 'highlight');   
      }  
    }
    
    // 使用属性指令
    <p highLight> 这一段会高亮显示 </p>
    

      结构指令就是模板中提到的*ngIf,ngFor等指令,它修改了DOM结构。举个例子,重写ngIf:

    import { Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core';   
    
    @Directive({   
      selector: '[myIf]'  
    })  
    
    export class MyIfDirective {    
    
      constructor(private templateRef: TemplateRef<any>,   
          private viewContainer: ViewContainerRef) { }   
    
      @Input() set appMyIf(condition: boolean) {   
        if (condition) {   
          this.viewContainer.createEmbeddedView(this.templateRef);   
        } else {   
          this.viewContainer.clear();   
        }   
      }  
    }  
    
    // 使用结构指令
    <p *myIf="false"> 这一段不会显示 </p>
    

      

    管道(过滤器)

    管道其实就是过滤器,就是叫法不一致而已。主要用于格式化源数据,而不改变源数据。定义和使用的方式也很简单:

    import { Pipe, PipeTransform } from '@angular/core'; 
    
    /*
     * 订单取消状态:默认为ALL表示全部,CANCEL表示已取消,NOTCANCEL表示正常
    */
    @Pipe({ name: 'cancelStatus' })
    export class CancelStatusPipe implements PipeTransform {
        transform(status:string, blank: boolean):string {
             const map = {
                 "ALL": "全部", 
                 "NOTCANCEL": "正常",
                 "CANCEL": "已取消",
                  "": "暂无",  
             }
    
             return blank? '特殊情况': map[status];
        }
    }
    

      使用前记得在模块的declarations声明,或者导到共享模块,在共享模块中导出去。使用如下:

    {{ "ALL" | cancelStatus }}  // 全部
    {{ "ALL" | cancelStatus: true }}  // 特殊情况
    

      Angular内置了一些管道:

    // 日期 DatePipe
    {{ expression | date:"MM/dd/yy" }} 
    
    // 数字 DecimalPipe,digitInfo的组成 {minIntegerDigits}.{minFractionDigits}-{maxfractionDigits}
    // minIntegerDigits:整数部分保留最小的位数,默认值为1.
    // minFractionDigits:小数部分保留最小的位数,默认值为0.
    // maxFractionDigits:小数部分保留最大的位数,默认值为3.
    {{ expression | number[:digitInfo] }}
    
    // 大写
    {{ expression | uppercase }}
    
    // 小写
    {{ expression | lowercase }}
    

      

    sometimes the hardest part isn't letting go,but rather start over
  • 相关阅读:
    解决WordPress中无法将上传的文件移动至wp-content/uploads
    nginx解析php请求为404
    centos6.5搭建lnmp环境
    springMVC 实现ajax跨域请求
    最近的一些坑
    微信开发文档与工具整理
    thymeleaf 中文乱码问题
    Python获取网页指定内容(BeautifulSoup工具的使用方法)
    查找算法的实现与分析(数据结构实验)
    二叉树的先序,中序,后序,层次的递归及非递归遍历
  • 原文地址:https://www.cnblogs.com/zhumeiming/p/7294938.html
Copyright © 2020-2023  润新知