自學Angular5

簡介

本人是2017年開始接觸angular2+版本的,一路走過來也看着angular越變越好,自學的道路上也走了很多的彎路,建議在學angular的時候最好掌握一些其它必要的知識。這裏寫這篇文章也是想分享下我是如何學習angular的,給大家剛學習angular的同窗們鋪鋪路,若是寫的看的不明白的同窗歡迎留言。

什麼是Angular

Angular 是一個開發平臺。它能幫你更輕鬆的構建 Web 應用。Angular 集聲明式模板、依賴注入、端到端工具和一些最佳實踐於一身,爲你解決開發方面的各類挑戰。Angular 爲開發者提高構建 Web、手機或桌面應用的能力。(引用官網簡介)
這篇文章主要講述了我平時工做上遇到的問題。寫的比較粗糙,想起什麼就寫什麼,沒有統一的規劃,後續若有機會能夠寫一篇進階版。

須要哪些技術?

Angular CLI

Angular CLI是一個命令行界面工具,它能夠建立項目、添加文件以及執行一大堆開發任務,好比測試、打包和發佈。(引用官網簡介)
  1. npm install -g @angular/cli    
  2. ng new my-app --routing --style less  (--routing 帶有路由模塊,--style less 樣式表爲less)
  3. ng serve -p 4800 --aot --host 0.0.0.0 --disable-host-check true  (-p 啓動端口4800 --disable-host-check true 開啓端口檢查 --aot aot編譯)
  4. ng build --prod -nc  (--prod 成產模式 -nc chunks以模塊名命名)

Angular組建

運行 ng g c index,自動在app文件下建立組建index。
@Component({
    selector: 'app-index',//選擇器
    encapsulation: ViewEncapsulation.None,//Emulated 默認 Native 私有沒法訪問 None 不處理
    styleUrls: ['./index.component.less'],//樣式表連接
    styles: ['.primary {color: red}'],
    viewProviders:[],//依賴模塊
    providers:[],//依賴服務
    exportAs:'appIndex',//模塊導出標識
    //templateUrl: './index.component.html',//模板連接
    template:"<div>{{msg}}</div>",
    host:{
        '[class.test]': 'true',
        '[attr.id]': ''test'',
    }
})
export class IndexComponent{
    constructor() {
    }

    @ViewChild('movieplayer') movieplayerRef:ElementRef//獲取當前dom實例。

    msg:string = 'hello angular';
    clickTest(){}
    list:Array<any> = [1,2,3,4,5];
    show:boolean = true;
    word:string
    width:number = 100;
    showTest:boolean = true;

    @Output() myEvent = new EventEmitter<any>();
    @Input() myProperty;
    _my2Property
    @Input() set my2Property(val){
        this._my2Property = val;
    };
    get my2Property(){
        return this._my2Property;
    }
}複製代碼

<app-index [myProperty]="{a:1}" (myEvent)="$event.stopPropagation()" #indexRef="appIndex"></app-index>複製代碼

Angular模板語法

  1. []  屬性綁定
  2. ()  事件綁定
  3. *ngFor  列表渲染
  4. *ngIf  條件渲染
  5. [(ngModel)]  雙向綁定
  6. #xxxx  局部變量
<div 
    [title]="msg" 
    (click)="clickTest($event)" 
    [style.width.px]="width"
    [ngStyle]="{'width.%':width}"
    [ngClass]="{'class-test':showTest}"
    [class.class-test]="showTest"
    *ngFor="let li of list;let i = index;trackBy: trackById" 
    *ngIf="show">
    <input type="text" [(ngModel)]="word">
    {{word}}
</div>

<video #movieplayer></video>
<button (click)="movieplayer.play()">

<ng-template ngFor [ngForOf]="list" let-li let-i="index" let-odd="odd" [ngForTrackBy]="trackById"></ng-template>

<ng-template [ngIf]="show"></ng-template>

<div [ngSwitch]="conditionExpression">
    <template [ngSwitchCase]="case1Exp"></template>
    <template ngSwitchCase="case2LiteralString"></template>
    <template ngSwitchDefault></template>
</div>複製代碼

路由與導航

  • routing.module.ts
const routes: Routes = [
    {
         path:'',
         component:IndexComponent,
         resolve:{resolve:ResolverService},
         canActivate:[AuthGuard],
         data:{},
    }
]

@NgModule({
    imports: [RouterModule.forRoot(routes,{useHash: true})],
    exports: [RouterModule]
})
export class IndexRoutingModule {
    
}複製代碼
  • resolver-service.ts
@Injectable()
export class CurrentResolverService implements Resolve<any> {
    constructor(private router: Router, private userService_: UserService) {
    }

    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
        //1.
        return Observable.create({})
        //2.
        return new Promise((resolve, reject) => {
            setTimeout(()=> {
                resolve({})
            },2000)
        })
        //3.
        return {}
    }
}



複製代碼
  • auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {

    constructor() {

    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        //1.
        return Observable.create(false)
        //2.
        return new Promise((resolve, reject) => {
            setTimeout(()=> {
                resolve(false)
            },2000)
        })
        //3.
        return false
    }

    //子路由守護
    canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.canActivate(childRoute, state);
    }

    //異步加載組件
    canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean {
        return null;
    }
}複製代碼
  • 位置標記
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>複製代碼
  • 路由跳轉
<a routerLink="/path" [routerLinkActive]="'active'">
<a [routerLink]="[ '/path', routeParam ]">
<a [routerLink]="[ '/path', { matrixParam: 'value' } ]">
<a [routerLink]="[ '/path' ]" [queryParams]="{ page: 1 }">
<a [routerLink]="[ '/path' ]" fragment="anchor">複製代碼

生命週期

  • ngOnChanges  當組建綁定數據發生改變將會觸發今生命週期
//OnChanges
ngOnChanges(changes: SimpleChanges): void {
    let value = changes.test && changes.test.currentValue
}複製代碼
  • ngOnInit  在調用完構造函數、初始化完全部輸入屬性
//OnInit
ngOnInit(): void{
    
}複製代碼
  • ngDoCheck  每當對組件或指令的輸入屬性進行變動檢測時就會調用。能夠用它來擴展變動檢測邏輯,執行自定義的檢測邏輯。
//DoCheck
ngDoCheck(): void {
    console.info('ok')
}複製代碼
  • ngOnDestroy  只在實例被銷燬前調用一次。
//OnDestroy
ngOnDestroy(): void {
    console.info('ok')
}複製代碼

服務

爲了避免再把相同的代碼複製一遍又一遍,咱們要建立一個單一的可複用的數據服務,而且把它注入到須要它的那些組件中。 使用單獨的服務能夠保持組件精簡,使其集中精力爲視圖提供支持,而且,藉助模擬(Mock)服務,能夠更容易的對組件進行單元測試。(引用官網)
@Injectable()
export class TestService {
    constructor() {
    }

    private data = {num:1};

    get num(){
        return this.data.num
    }

    add(){
        ++this.data.num
    }

    minus(){
        --this.data.num
    }
}複製代碼

如何使用服務

  • 若是怎個項目已單例的模式,直接注入到 app.module.ts 元數據  providers 中便可
@NgModule({
    providers:[
        TestService
    ]
})複製代碼
  • 非單例模式直接引入到自身的組件或模塊的 providers中 便可。
@Component({
    providers:[
        TestService
    ]
})複製代碼

依賴注入

依賴注入是重要的程序設計模式。 Angular 有本身的依賴注入框架,離開了它,幾乎無法構建 Angular 應用。 它使用得很是普遍,以致於幾乎每一個人都會把它簡稱爲 DI。(引用官網)

經過構造函數注入 TestService 實例,並把它存到名爲 _testService 的私有屬性中
export class IndexComponent{
    constructor(private _testService:TestService) {
    }
}複製代碼
替代providers
@Injectable()
export class Test2Service extend TestService{
    constructor() {
        super();
    }
}

@Component({
    providers:[
        { provide: TestService, useClass: Test2Service}
    ],//依賴服務
})
export class IndexComponent{
    constructor(private _testService:TestService) {
    }
}複製代碼

HTTP 服務

版本的跟新由以前的 HttpModule  模塊 變動爲  HttpClientModule  。
建議在app.module.ts 引入  HttpClientModule 
@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        BrowserModule,
        CommonModule,
        FormsModule,
        HttpClientModule,
    ],
    providers: [
        
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
} 複製代碼
這樣配置以後就能夠任意模塊或組件中使用 HttpClient  服務。
下面是幾個比較經常使用的方法。
request(method: string, url: string, options: {
    body?: any;
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: 'body';
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType: 'any';
    withCredentials?: boolean;
}): Observable<any>;

get(url: string, options: {
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: 'body';
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType: 'any';
    withCredentials?: boolean;
}): Observable<any>;

post(url: string, body: any | null, options: {
    headers?: HttpHeaders | {
        [header: string]: string | string[];
    };
    observe?: 'body';
    params?: HttpParams | {
        [param: string]: string | string[];
    };
    reportProgress?: boolean;
    responseType: 'any';
    withCredentials?: boolean;
}): Observable<any>;複製代碼
這樣用寫,處理一些一樣的操做時就不太方便,也很差維護,這邊個人作法是用類統一封裝一下,全部的接口調用都經過這個類處理。

//定義接口返回的參數字段 通常接口返回的參數都是固定的
export interface Result<T> {
    result?: any
    success?: any
    message?: any
}複製代碼

export class ConfigService {
    static baseUrl    = 'http://127.0.0.1:8080';
    static uploadPath = 'http://127.0.0.1:8080';
    constructor(private _http: HttpClient) {

    }

    configForm() {
        return new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
    }

    /**
     * @deprecated 直接經過FormData處理
     */
    configFormData() {
        return new HttpHeaders().set('Content-Type', 'multipart/form-data');//;charset=UTF-8
    }

    configJson() {
        return new HttpHeaders().set('Content-Type', 'application/json;charset=UTF-8');
    }

    postForm<T>(url, body = {}, config = {}): Observable<Result<T>> {
        return this._http.post<T>(ConfigService.baseUrl + url, qs.stringify(body), {headers: this.configForm(), ...config})
    }

    postFormData<T>(url, body = {}, config = {}): Observable<Result<T>> {
        const f = new FormData();
        for (let i in body) {
            f.append(i, body[i]);
        }
        return this._http.post<T>(ConfigService.baseUrl + url, f, {...config})
    }

    postFormDataUpload<T>(url, body = {}, config = {}): Observable<Result<T>> {
        const f = new FormData();
        for (let i in body) {
            if(body.hasOwnProperty(i)) f.append(i, body[i]);
        }
        return this._http.post<T>(ConfigService.uploadPath + url, f, {...config})
    }

    postJson<T>(url, body = {}, config = {}): Observable<Result<T>> {
        return this._http.post<T>(ConfigService.baseUrl + url, body, {headers: this.configJson(), ...config})
    }

    get<T>(url, body: any = {}, config = {}): Observable<Result<T>> {
        return this._http.get<T>(`${ConfigService.baseUrl + url}?${qs.stringify(body)}`, config)
    }
}複製代碼
經過這樣封裝以後,無論什麼方法,最終的參數傳遞都是  url, body, config 。方便管理。若是項目龐大,能夠多加幾個服務,按照模塊劃分,不一樣的模塊由不一樣的 service 服務。

@Injectable()
export class UserService extends ConfigService {
    constructor(_http: HttpClient) {
        super(_http);
    }

    /**
     * 退出
     * @returns {Observable<Result<any>>}
     */
    quit() {
        return this.get(`url`)
    }

    /**
     * 登陸
     * @returns {Observable<Result<any>>}
     */
    login(body = {},config = {}) {
        return this.postJson(`url`, body, config)
    }

     /**
     * 註冊
     * @returns {Observable<Result<any>>}
     */
    register(body = {},config = {}){
        return this.postForm('url', body, config);
    }
}


複製代碼
這樣寫的好處是結構清晰,協同開發比較友好,各自負責本身的模塊。

HTTP攔截器

全部的接口請求都會經過攔截器,由它決定是否繼續請求。相似 window.addEventListener('fetch',(event)=> {}) 這個監聽事件。官網文檔 移步
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
    constructor() {
    }
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        req = req.clone({headers: req.headers});
        let xhr = next.handle(req);
        return xhr
            .do(response => {
                if (response instanceof HttpResponse) {
                    if (response.type == 4) {
                        if (response.status == 200) {
                            //...統一的邏輯處理,好比彈幕提示
                        } else if (response.status == 500) {
                            //...統一的錯誤處理
                        }
                    }
                }
            }).catch(error => {
                return Observable.throw(error || "Server Error");
            })
    }
}複製代碼
使用方法以下,這樣全部的接口請求都會通過 NoopInterceptor類。
@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        BrowserModule,
        CommonModule,
        FormsModule,
        HttpClientModule,
    ],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: NoopInterceptor,
            multi: true,
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
} 複製代碼
若是想看angular的理論知識能夠 移步

如何編寫管道(Pipe)

Angular內置了一些管道,好比 DatePipeUpperCasePipeLowerCasePipeCurrencyPipePercentPipe。 它們全均可以直接用在任何模板中。(引用官網)
@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
    /**
    * @value 預處理的值
    * @args  參數數組 {{value | keys:xxx:xxxx}} xxx、xxxx就是參數
    * @return 必需要有返回值
    */
    transform(value, args: string[]): any {
        let keys = [];
        for (let key in value) {
            keys.push({key: key, value: value[key]});
        }
        return keys;
    }
}複製代碼
純(pure)管道與非純(impure)管道
//默認是純Pipe
@Pipe({
    name: 'keys',
    pure: true,
})
//非純Pipe
@Pipe({
    name: 'keys',
    pure: false,
})複製代碼
他們的區別在於變動檢測,若是數值將來將不會改變,則用純管道,否者用非純管道。

如何編寫動畫

在如今的版本當中,動畫模塊已經獨立出去了,在嚮應用程序添加動畫以前,須要將一些動畫特定的模塊和函數導入到根應用程序模塊 BrowserAnimationsModule
@NgModule({
  imports: [ BrowserModule, BrowserAnimationsModule ],
})
export class AppModule { }複製代碼

import {animate, style, transition, trigger} from '@angular/animations';

export const fadeIn = trigger('fadeIn', [
    transition('void => *', [
        style({opacity: 0}),
        animate(150, style({opacity: 1}))
    ]),
    transition('* => void', [
        animate(150, style({opacity: 0}))
    ])
]);

/**
* 使用方式
*/
@Component({
    animations:[fadeIn],
    template: `
       <div [@fadeIn]></div>
    `,
})
export class IndexComponent{
    constructor() {
    }
}複製代碼
我這裏只演示最簡單的用法,建議去看官網文檔,請 移步

路由異步加載

異步組件最小單位是模塊 NgModule ,直接上代碼。
  • user-list.component.ts
@Component({
    selector: 'app-user-list',
    templateUrl: './user-list.component.html',
    encapsulation: ViewEncapsulation.None,//html不作任何處理
    styleUrls: ['./user-list.component.less']
})
export class UserListComponent implements OnInit {
    
}複製代碼
  • user-routing.module.ts
const routes = [
    {
        path: '',
        children:[
            {
                path: 'userList',
                component: UserListComponent
            },
        ]
    }
]
@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})
export class UserRoutingModule {
}複製代碼
  • user.module.ts
@NgModule({
    declarations: [
        UserListComponent,
    ],
    imports: [
        UserRoutingModule
    ],
    providers: [],
})
export class UserModule {
}複製代碼
  • app-routing.module.ts
const routes = [
    {
        path: '',
        component: IndexComponent,
        children:[
            {
                path: 'user',
                loadChildren: 'app/user/user.module#UserModule',
            },
        ]
    }
]
@NgModule({
    imports: [RouterModule.forRoot(routes, {useHash: true})],
    exports: [RouterModule]
})
export class AppRoutingModule {
}複製代碼

如何編寫一個業務組件

先分析這個組件須要一個什麼樣的結果,須要什麼必要的參數,結構是怎麼樣子的,樣子能夠變幻無窮,可最終結果就只有一個。
以下圖,作一個相似省市聯動的組件,需求是獲取被選中的省市ID集合,若是父級是勾中狀態,則排除子集,若是父級是非全選狀態也須要排除掉。
需求大概知道了之後,須要思考,這個組件未來可能用於其餘的需求,在寫組件的時候,儘可能把功能細分。
  • directional-select.component.html
<p class="selected-text">
    當前已選擇:
    <span>{{result().length}}</span>個
</p>
<div class="tags-box">
    <ul class="clearfix data-tags">
        <li *ngFor="let rl of resultList;let index = index">
            {{rl.name}}
            <i class="yc-icon icon" (click)="removeResultList(rl)">X</i>
        </li>
    </ul>
</div>
<div class="select-list-inner">
    <div class="scope" *ngFor="let list of cacheList;let index1 = index" [ngStyle]="{'width.%':100.0 / cacheList.length}">
        <ul class="list-with-select">
            <li class="spaui" *ngFor="let l of list;let index2 = index" (click)="pushCache(index1,index2,list)">
                <app-checkbox [(ngModel)]="l.selected" (eventChange)="areaItemChange(l)" [label]="l.name" [checkState]="l.checkState" [not_allow_selected]="l[hasCheckbox]"></app-checkbox>
                <i *ngIf="l[child]?.length > 0" class="icon yc-icon">&#xe664;</i>
            </li>
        </ul>
    </div>
</div>複製代碼
這個組件依賴 app-checkbox徹底能夠用原生checkbox代替。
  • directional-select.component.less
.select-list-inner {
    background: #fff;
    width: 100%;
    border: 1px solid #ddd;
    max-height: 272px;
    overflow: auto;
    display: flex;
    .scope {
        &:first-child {
            border-left: none;
        }
        flex: auto;
        width: 0;
        overflow: auto;
        max-height: 270px;
        transition: width .2s;
        border-left: 1px solid #dfe1e7;
        .list-with-select {
            margin: 10px 0;
            .spaui {
                &:hover {
                    background: #f8f9fa;
                }
                height: 40px;
                line-height: 40px;
                padding-left: 20px;
                width: 100%;
                cursor: pointer;
                font-size: 15px;
                position: relative;
                > i {
                    float: right;
                    padding-right: 20px;
                }
            }
        }
    }
}

.tags-box {
    position: relative;
    border-top: 1px solid transparent;
    border-bottom: 1px solid transparent;
    margin-bottom: 10px;
    overflow-y: auto;
    max-height: 202px;
    overflow-x: hidden;
    transition: height .2s ease-in-out;
    ul.data-tags {
        width: 100%;
        position: relative;
        margin-bottom: 10px;
        li {
            float: left;
            margin-right: 5px;
            border: 1px solid #dfe1e7;
            border-radius: 20px;
            background: #f0f3f6;
            height: 34px;
            line-height: 32px;
            padding-left: 24px;
            padding-right: 8px;
            margin-top: 10px;
            font-size: 14px;
            transition: padding .2s ease-in-out;
            .icon {
                opacity: 0;
                transition: opacity .2s;
            }
            &:hover {
                background: #e1e7f1;
                padding-left: 16px;
                padding-right: 16px;
                transition: padding .2s ease-in-out;
                .icon {
                    opacity: 1;
                    transition: opacity .2s;
                    cursor: pointer;
                }
            }
        }
    }
}

.selected-text {
    float: left;
    line-height: 55px;
    padding-right: 10px;
    > span {
        font-size: 16px;
        font-weight: 700;
        color: #ff5e5e;
    }
}

複製代碼
  • directional-select.component.ts
import {Component, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/forms";

const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DirectionalSelectComponent),
    multi: true
};

@Component({
    selector: 'directional-area-select',
    exportAs: 'directionalAreaSelect',
    templateUrl: './directional-select.component.html',
    styleUrls: ['./directional-select.component.less'],
    providers: [
        CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR
    ]
})
export class DirectionalSelectComponent implements OnInit, ControlValueAccessor {

    constructor() {
    }
    
    onChange = (value: any) => {
    };

    writeValue(obj: any): void {
        if (obj instanceof Array && obj.length > 0) {
            this.filterFill(obj);
            this.resultList = this.result(3);
        }
    }

    registerOnChange(fn: any): void {
        this.onChange = fn;
    }

    registerOnTouched(fn: any): void {
    }

    ngOnInit() {

    }

    resultList;
    cacheList: any[] = [];
    private list: any[] = [];

    inputListChange() {
        if (this._inputList instanceof Array && this._inputList.length > 0) {
            this.list = this._inputList.map(d => {
                this.recursionChild(d);
                return d;
            });
            this.cacheList.length = 0;
            this.cacheList.push(this.list);
        }
    }

    _inputList;
    @Input('firstNotSelect') firstNotSelect = false;

    @Input('inputList') set inputList(value) {
        this._inputList = value;
        this.inputListChange();
    }

    @Input('value') value;
    @Input('hasCheckbox') hasCheckbox = 'not_allow_selected';

    @Input('child') child = 'options';
    @Output('eventChange') eventChange = new EventEmitter<any>();

    /**
     * 顯示對應的子集數據列表
     * @param index1 當前層數下標
     * @param index2 當前層數列表的下標
     * @param list 當前層的列表數據
     */
    pushCache(index1, index2, list) {
        //日後選擇
        let cl = this.cacheList[index1 + 1];
        let child = list[index2][this.child];
        if (child instanceof Array && child.length > 0) {
            if (!cl) {
                this.cacheList.push(child);
            } else {
                if (cl !== child) {
                    this.cacheList.splice(index1 + 1, 1, child)
                }
            }
        } else {
            if (cl !== child && !(child instanceof Array)) {
                this.cacheList.pop();
            }
        }

        //往前選擇
        if (child && child.length > 0) {
            while (this.cacheList.length > index1 + 2) {
                this.cacheList.pop();
            }
        }
    }

    removeResultList(data) {
        data.selected = false;
        this.areaItemChange(data);
    }

    //選中有幾個狀態 對於父節點有 1所有選中 2部分選中 3所有取消 checkState 1 2 3
    areaItemChange(data) {
        if (data[this.hasCheckbox]) return;

        let child = data[this.child];

        if (data.selected) {
            data.checkState = 1
        } else {
            data.checkState = 3
        }

        //向下尋找
        if (child && child.length > 0) {
            this.recursionChildCheck(child)
        }
        //向上尋找
        this.recursionParentCheck(data);

        this.resultList = this.result(3);
        if (this.resultList instanceof Array && this.resultList.length > 0) {
            this.onChange(this.resultList.map(r => r.id));
        } else {
            this.onChange(null);
        }

        this.eventChange.next();
    }

    /**
     * 判斷當前對象的父級中的子集被選中的個數和checkState == 2的個數來肯定父級的當前狀態
     * 遞歸
     * @param data
     */
    private recursionParentCheck(data) {
        let parent = data.parent;
        if (parent) {
            let l = parent[this.child];
            let length = l.reduce((previousValue, currentValue) => {
                return previousValue + ((currentValue.selected) ? 1 : 0)
            }, 0);
            let length2 = l.reduce((previousValue, currentValue) => {
                return previousValue + ((currentValue.checkState == 2) ? 1 : 0)
            }, 0);
            if (length == l.length) {
                parent.checkState = 1;
                parent.selected = true;
            } else if (length == 0 && length2 == 0) {
                parent.checkState = 3
            } else {
                parent.checkState = 2;
                parent.selected = false;
            }
            this.recursionParentCheck(parent);
        }
    }

    /**
     * 同步子集和父級的狀態
     * 遞歸
     * @param list
     */
    private recursionChildCheck(list) {
        if (list && list.length > 0) {
            list.forEach(data => {
                let checked = data.parent.selected;
                data.selected = checked;
                if (checked) {
                    data.checkState = 1;
                    data.selected = true;
                } else {
                    data.checkState = 3;
                    data.selected = false;
                }
                let l = data[this.child];
                this.recursionChildCheck(l)
            })
        }
    }

    /**
     * 子集包含父級對象
     * 遞歸
     */
    private recursionChild(target) {
        let list = target[this.child];
        if (list && list.length > 0) {
            list.forEach(data => {
                data.parent = target;
                this.recursionChild(data)
            })
        }
    }

    /**
     * type 1 獲取id集合 2獲取key value 3獲取對象引用
     * @param {number} type
     * @param {any[]} result
     * @returns {any[] | undefined}
     */
    result(type = 1, result = []) {
        if (this.firstNotSelect) {
            return this.recursionResult2(this.list, result, type);
        }
        return this.recursionResult(this.list, result, type);
    }

    /**
     * 只獲取最後一層的值
     * @param list
     * @param {any[]} result
     * @param {number} type
     * @returns {any[] | undefined}
     */
    private recursionResult2(list, result = [], type = 1) {
        if (list && list.length > 0) {
            list.forEach(data => {
                let child = data[this.child];
                if (child && child.length > 0) {
                    this.recursionResult2(child, result, type);
                } else if (data.checkState == 1) {
                    switch (type) {
                        case 1:
                            result.push(data.id);
                            break;
                        case 2:
                            result.push({
                                id: data.id,
                                name: data.name,
                            });
                            break;
                        case 3:
                            result.push(data);
                            break;
                    }
                }
            })
        }
        return result;
    }

    private recursionResult(list, result = [], type = 1) {
        if (list && list.length > 0) {
            list.forEach(data => {
                //所有選中而且父級沒有複選框
                if ((data[this.hasCheckbox] && data.checkState == 1) || data.checkState == 2) {
                    let child = data[this.child];
                    if (child && child.length > 0) {
                        this.recursionResult(child, result, type);
                    }
                    //所有選中而且父級有複選框 結果不須要包含子集
                } else if (data.checkState == 1 && !data[this.hasCheckbox]) {
                    switch (type) {
                        case 1:
                            result.push(data.id);
                            break;
                        case 2:
                            result.push({
                                id: data.id,
                                name: data.name,
                            });
                            break;
                        case 3:
                            result.push(data);
                            break;
                    }
                }
            })
        }
        return result;
    }

    filterFill(result) {
        //運用數據特性 判斷時候選擇了國外的選項
        this.cacheList.length = 1;
        let bo = result.find(n => {
            if (n == 1156 || String(n).length >= 6) return n
        });
        if (result instanceof Array && result.length > 0 && bo) {
            let child = this.list[0][this.child];
            while (child instanceof Array && child.length > 0) {
                this.cacheList.push(child);
                child = child[0][this.child];
            }
        } else {
            let child = this.list[1][this.child];
            while (child instanceof Array && child.length > 0) {
                this.cacheList.push(child);
                child = child[0][this.child];
            }
        }
        this.recursionFilter(result, this.list)
    }

    //遞歸過濾知足條件對象
    private recursionFilter(target, list, result = []) {
        if (target instanceof Array && target.length > 0) {
            list.forEach((data) => {
                let child = data[this.child];
                let bo = target.find((d => {
                    if (d == data.id) {
                        return d;
                    }
                }));
                if (bo) {
                    data.selected = true;
                    data.checkState = 1;
                    this.recursionChildCheck(child);
                    this.recursionParentCheck(data);
                }
                if (child instanceof Array && child.length > 0) {
                    this.recursionFilter(target, child);
                }
            })
        }
    }
}

複製代碼
初始化狀態是隻包含頂層父級的列表,點擊後將出現對應的子集列表,日後一直類推。
因而可知,能夠聲明一個變量 cacheList來儲存頂層父級數據做爲第一層,能夠查看 inputListChange方法。 recursionChild方法是將全部的子集包含本身的父級引用。
pushCache顯示對應的子集數據列表
areaItemChange checkbox發生改變的時候就會觸發這個方法。這個方法完成的任務有向上尋找父級改變其狀態,向下尋找子集改變其狀態,將結果以標籤的形式羅列出來,對應的方法能夠查看 recursionChildCheck recursionParentCheck result(3)
此類實現了 ControlValueAccessor類 ,可使用雙向綁定來獲取值。

路由守衛權限管理

上文也講到了路由守衛 auth.guard.ts  ,這個類繼承了 CanActivate  、CanActivateChild  類。javascript

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
    constructor() {

    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        //1.能夠將當前的訪問路由請求後臺訪問當前用戶是否有權限,或者能夠將權限存入本地緩存再進行判斷。
        return Observable.create(false)
        //2.
        return new Promise((resolve, reject) => {
            setTimeout(()=> {
                resolve(false)
            },2000)
        })
        //3.經過本地緩存判斷。
        return false
    }

    //子路由守護 父路由啓用了子路由保護,進入自路由將會觸發這個回調函數。
    canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.canActivate(childRoute, state);
    }複製代碼
  • 路由配置 app-routing.module.ts  

const routes = [
    {
        path: '',
        component: IndexComponent,
        canActivate: [AuthGuard],
        children:[
            {
                path: 'user',
                loadChildren: 'app/user/user.module#UserModule',
            },
        ]
    }
]
@NgModule({
    imports: [RouterModule.forRoot(routes, {useHash: true})],
    exports: [RouterModule]
})
export class AppRoutingModule {
}複製代碼

  •  user-routing.module.ts  

const routes: Routes = [
    {
        path: '',
        canActivateChild: [AuthGuard],//守護子路由
        children: [
            {
                path: 'userlist',
                component: UserListComponent,
                resolve: {jurisdiction: CurrentResolverService},
                data: {current: limitRole.userlist}
            }
        ]
    }
];
@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})
export class UserRoutingModule {
}複製代碼
相關文章
相關標籤/搜索