Ionic3项目实践记录

时间:2021-10-26 08:50:57

Ionic3首次项目实践记录

标签(空格分隔): Angular Ionic

Ionic3踩坑

1. 路由懒加载(lazy load)

如果设置了懒加载,就必须全部懒加载(包括TabsPage),否则会出现路由跳转后tabs消失的情况。

2. 通过ts来返回tabs的首页:

注意必须通过this.app.getRootNav().setRoot('tabs');,不能到home,否则,tabs会消失。

参见* | Issues

import { App } from "ionic-angular";

@IonicPage({ name: [page-name] })
@Component({
  ...
})

export class DemoPage {
  constructor(
    private app: App
  ) {  }

  goBack() {
    this.app.getRootNav().setRoot('tabs');
  }
}

3. 隐藏子路由里面的tabs,可以通过配置app.module.tas里面的 tabsHideOnSubPages: true实现:

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    ...
    IonicModule.forRoot(MyApp, {
      tabsHideOnSubPages: true
    }),
    ...
  ],
  ...
})

4. 自定义表单验证

不用angular@Directive()装饰器的方法,直接定义一个类,定义静态方法:

import {FormControl} from "@angular/forms";
import {G} from "../services/data-store.service";

export class MyValidators {
  private static isEmptyInputValue(value) {
    // we don't check for string here so it also works with arrays
    return value == null || value.length === 0;
  }

  /**
   * 与指定值相等
   * @param {string} equalCtrl 指定FormControl健名
   * @returns {(ctrl: FormControl) => {equalTo: {valid: boolean}}}
   */
  static equalTo(equalCtrl: string) {
    return (ctrl: FormControl) => {
      if (this.isEmptyInputValue(ctrl.value)) return null;

      const _equalCtrl = ctrl.root.get(equalCtrl);
      const valid = (_equalCtrl && (ctrl.value === _equalCtrl.value));

      return valid ? null : {
        equalTo: {
          valid: false
        }
      }
    }
  }
}

调用方法(FormBui;der):

import {Component, EventEmitter, Output} from "@angular/core";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {G} from "../../../services/data-store.service";
import {AlertController} from "ionic-angular";
import {StaticDataService} from "../../../services/common/static-data.service";
import {MyValidators} from "../../../directives/my-validators.directive";

@Component({
  selector: 'page-sign-up',
  templateUrl: 'sign-up.html'
})

export class SignUpPage {
  public form: FormGroup;
  public TYPES = G.ACCOUNT_TYPES;

  @Output() onShow = new EventEmitter<number>();

  constructor(
    private formBuilder: FormBuilder,
    private alertCtrl: AlertController,
    private staticService: StaticDataService
  ) {
    this.form = this.formBuilder.group({
      username: ['', Validators.compose([
        Validators.required,
        MyValidators.mobile()
      ])],
      password: ['', Validators.compose([
        Validators.required,
        Validators.minLength(6)
      ])],
      passwordConfirm: ['', Validators.compose([
        Validators.required,
        MyValidators.equalTo('password')
      ])],
      recommend: ['']
    });
  }

}

html:

...
    <ion-item>
      <ion-label floating>请输入您的密码</ion-label>
      <ion-input type="password" formControlName="password" name="password" class="form-control"></ion-input>
    </ion-item>
    <div class="error-wrap" [hidden]="form.get('password').valid || form.get('password').pristine">
      <small [hidden]="!form.get('password').hasError('required')" class="error">
        请输入密码
      </small>
      <small [hidden]="!form.get('password').hasError('minlength')" class="error">
        请输入不少于6位的密码
      </small>
    </div>

    <ion-item>
      <ion-label floating>请再次确认密码</ion-label>
      <ion-input type="password" formControlName="passwordConfirm" name="passwordConfirm" class="form-control"></ion-input>
    </ion-item>
    <div class="error-wrap" [hidden]="form.get('passwordConfirm').valid || form.get('passwordConfirm').pristine">
      <small [hidden]="!form.get('passwordConfirm').hasError('required')" class="error">
        请确认密码
      </small>
      <small [hidden]="!form.get('passwordConfirm').hasError('equalTo')" class="error">
        两次密码输入不一致
      </small>
    </div>
...

5. 父子页面通信

主要是通过NavControllerNavParams来实现。

父向子直接通过push([path], [param], [options])的第二个参数实现数据通信,在子页面中,通过NavParams获取到对应的数据。

子向父pop([options])方法却没有参数传递选项。可以通过Promise实现:

父页:

goToChild() {
    new Promise((resolve, reject) => {
        this.navCtrl.push('coupon', {
            // 将resolve方法传递到子页中
            resolve: resolve
        });
    }).then((data: Datas) => {
        // 从子页获取到数据 赋值到当前类的属性中
        this.CouponId = data.id;
        this.CouponType = data.type;
        this.CouponValue = data.value;
    });
}

子页:

constructor(
    private navParam: NavParams,
    private navCtrl: NavController
) {
    this.callback = this.navParam.data.resolve;
}

...

goBack() {
    // 为resolve传值
    this.callback({ id: this.selectCoupon, type: this.selectCouponType, value: this.selectCouponValue });
    this.navCtrl.pop();
}

6. 在Http通信中,参数中的+号被替换为空格的问题

参见

/**
 * 解决http请求字符串中+号被替换为空格的问题
 */
export class CustomQueryEncoderHelper implements HttpParameterCodec {
  encodeKey(k: string): string {
    return encodeURIComponent(k);
  }

  encodeValue(v: string): string {
    return encodeURIComponent(v);
  }

  decodeKey(k: string): string {
    return decodeURIComponent(k);
  }

  decodeValue(v: string): string {
    return decodeURIComponent(v);
  }
}
    // 处理undefined
    for (let i in this._datas) {
      if (this._datas.hasOwnProperty(i)) {
        if (this._datas[i] === undefined) {
          delete this._datas[i];
        }
      }
    }

    Object.assign(datas, this._datas);

    if (!this.isGet) {
      const _date = new Date(Date.parse(new Date().toString()) + CONF.ApiData.DiffTime);
      datas.TimeSpan = Tools.dateTimeFormat(_date, 'yyyy-MM-dd hh:mm:ss');
    }

    let preparedParams = new HttpParams({
      encoder: new CustomQueryEncoderHelper(),
      fromObject: datas
    });

    if (this.isGet) {
      this.datas = { params: preparedParams };
    } else {
      this.datas = preparedParams;
    }

7. Events事件订阅的使用

(个人)开发中最经典的用例,就是在app.component.ts中的ion-menu做页面跳转。由于整个项目是懒加载的,如果直接使用@ViewChild(Nav) nav: Nav;,然后通过this.nav.pus([page])会导致没有tabs,页面一刷新就不能返回了。

经过各种试验,最后发现使用Events事件订阅可以轻松解决。

首先在app.component.ts中发布事件:

import ...
@Component({
  templateUrl: 'app.html'
})
export class MyApp {
    rootPage:any = 'tabs';
    
    constructor(
        platform: Platform,
        statusBar: StatusBar,
        splashScreen: SplashScreen,
        private events: Events
    ) {
        
    }
    
    // 通知从home页面跳转
    get navTo(): Datas {
        // 关闭抽屉菜单
        this.menuCtrl.close('mainMenu');
        return {
            publish: name => {
                this.events.publish('nav:to', name);
            }
        }
    }
    
    // 抽屉菜单打开
    menuOpened() {
        if (CONF.UserData.IsLogin) {
            // 已经登录
            this.isLogin = true;

            this.getUserInfo(); // 获取用户信息
            this.getUserFund(); // 获取用户资金账户信息

            return;
        }

        // 未登录
        this.isLogin = false;
    }
    
    // 前往设置
    goToSetting() {
        if (!this.checkLogin()) return;

        this.navTo.publish('setting');
    }
    
    ...

}

然后在home.ts中订阅事件,做跳转操作,这样就相当于在home中跳转,不会出现tabs丢失的情况:

import ...
@IonicPage({ name: 'home' })
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
    constructor(private events: Events) {
        this.events.subscribe('nav:to', name => {
            this.navCtrl.push(name);
        });
    }
    
    ...
}

未完待续...    Last updated by: Jehorn, August 14, 2018, 04:35 PM