นี่เป็นบทความติดตามผลเกี่ยวกับ "แอปมือถือไฮบริด: การแชร์ตรรกะระหว่างแอป Angular2 และ Ionic2" ในปี 2559 นี่เป็นความพยายามครั้งแรกของเราในการแบ่งปันตรรกะทางธุรกิจที่ใช้ Ngrx ระหว่างแอป Angular2 และแอปมือถือ Ionic2 แต่ใช้ npm link “แฮ็ก” ที่ค่อนข้างไม่เสถียร โดยมีโปรเจ็กต์ Typescript npm แยกกันสามโปรเจ็กต์

ด้วย CLI ล่าสุดของ Angular8 และ Ionic4 ขณะนี้มีวิธีที่สวยงามมากในการบรรลุเป้าหมายนี้ ต้องขอบคุณโครงสร้างโปรเจ็กต์แบบ mono-repo

เป้าหมายยังคงเหมือนเดิม:

  • สรุปตรรกะทางธุรกิจทั้งหมด (ตาม @ngrx/store) ในโมดูลหลัก และ
  • รักษาเค้าโครงมุมมอง มาร์กอัป และตรรกะการนำทางเฉพาะในโครงการแอปมือถือและเว็บ

หมายเหตุ: @ngrx/store คือการจัดการสถานะที่ขับเคลื่อนด้วย RxJS ที่ได้รับแรงบันดาลใจจาก Redux สำหรับแอป Angular ปัจจุบันเป็นวิธีที่ได้รับความนิยมมากที่สุดในการจัดโครงสร้างตรรกะทางธุรกิจที่ซับซ้อนในแอป Angular

แอปสาธิตและ repo Github

นี่คือ repo ของการพิสูจน์แนวคิดของเรา:



repo ใช้โครงสร้าง mono-repo Nx Workspace (พร้อมการรวม Ionic แบบกำหนดเองซึ่งมีรายละเอียดในภายหลัง):

  • / รูทโปรเจ็กต์ตาม Angular (เวอร์ชัน 8.1.2) และ @ngrx/store (เวอร์ชัน 8.1.0) จัดการโดย Angular CLI
  • /libs/coreโมดูลที่ใช้ร่วมกันพร้อมสถานะ ตัวลด และตรรกะการดำเนินการ
  • /apps/web เว็บแอป
  • /apps/mobile แอปมือถือที่ใช้ Ionic (เวอร์ชัน 4.7.1) จัดการโดย Ionic CLI

สำหรับการสาธิต เรากำลังใช้โค้ดตัวอย่างตัวนับ (การกระทำและตัวลด) จาก @ngrx/store

โมดูล หลัก ให้:

  • อินเทอร์เฟซ CounterState โมเดลสถานะตัวนับ
  • counterReducer ตัวลด การจัดการสถานะตัวนับตามการดำเนินการที่จัดส่ง
  • การเพิ่มขึ้น, การลดลง และ รีเซ็ต การดำเนินการตอบโต้

ในแอปบนอุปกรณ์เคลื่อนที่หรือเว็บ เราใช้เนื้อหาเหล่านั้นเพื่อสร้างโมเดลสถานะแอป

import {CounterState} from ‘@ngrx-demo/core’;
export interface AppState {
  counter: CounterState,
  // Add other states here
}

และจัดเตรียมการดำเนินการ บริการ และตัวลดระหว่างการบูตแอป

import {ActionReducerMap} from '@ngrx/store';
import {counterReducer} from '@ngrx-demo/core';
import {AppState} from './app.state';
const reducers: ActionReducerMap<AppState> = {
    counter: counterReducer
};
@NgModule({
  imports: [
    BrowserModule,
    StoreModule.forRoot(reducers)
  ]
  bootstrap: [
    AppComponent
  ],
  declarations: [
    AppComponent
  ],
  
})
export class AppModule { }

ตอนนี้เราสามารถใช้เนื้อหาเหล่านั้นในองค์ประกอบมุมมองใดก็ได้ของแอป

// HTML component template
<h2>
  {{counter$ | async}}
</h2>
<p>
  <button (click)=”increment()”> +1 </button>
  <button (click)=”decrement()”> -1 </button>
</p>
<p>
  <button (click)=”reset()”> Reset </button>
</p>

ด้วยส่วนประกอบที่สอดคล้องกัน:

// Typescript angular component
import {CounterActions} from '@ngrx-demo/core';
import {AppState} from './app.state';
@Component({
  templateUrl: 'some.component.html'
})
export class SomeComponent {
  counter$: Observable<number>;
  constructor(private store: Store<AppState>) {
    this.counter$ = this.store.select(s => s.counter.total);
  }
  decrement() {
    this.store.dispatch(new CounterActions.DecrementAction());
  }
  increment() {
    this.store.dispatch(new CounterActions.IncrementAction());
  }
  reset() {
    this.store.dispatch(new CounterActions.ResetAction());
  }
}

ตรรกะทางธุรกิจทั้งหมดถูกสรุปไว้ในโมดูลที่ใช้ร่วมกัน ตัวอย่างเช่น เราสามารถเพิ่ม @ngrx/effects ลงในมิกซ์ เพื่อจัดการกับการเรียก API async

มุมมองเท่านั้นที่ "รู้" เกี่ยวกับการดำเนินการที่มีอยู่เพื่อจัดส่งและโมเดลสถานะที่สามารถสมัครรับข้อมูลได้

การรวมแอป Ionic เข้ากับ Nx Workspace

ตามค่าเริ่มต้น Nx Workspaces ไม่รองรับแอป Ionic เนื่องจาก Ionic ใช้ CLI ของตัวเองและการพึ่งพาในการสร้าง ทดสอบ และเรียกใช้แอป /app/mobile จึงมี /app/mobile/package.json ของตัวเอง (ซึ่งเป็นอิสระ จากพื้นที่ทำงานหลัก /package.json) และดังนั้นจึงเป็น /app/mobile/node_modules ของตัวเอง

การกำหนดค่าแบบกำหนดเองของอิออน

ใน package.json Ionic มี hook หลายตัวเพื่อปรับแต่งการกำหนดค่าเริ่มต้น เรากำลังใช้ตัวเลือกการกำหนดค่า ionic_watch และ ionic_webpack และไฟล์การกำหนดค่าแบบกำหนดเอง 2 ไฟล์ที่จัดเก็บไว้ใน /app/mobile/config

// app/mobile/package.json
{
  ...
  "description": “An Ionic project”,
  "config": {
    "ionic_watch": "./config/watch.config.js",
    "ionic_webpack": "./config/webpack.config.js"
   },
  ...
}

ความละเอียดโมดูล typescript

เพื่อให้สามารถใช้โมดูลที่ใช้ร่วมกันของเราและได้รับ Typescript ความละเอียดของโมดูล ที่ถูกต้อง เราจะใช้คุณสมบัติ การแมปเส้นทาง ที่กำหนดเองใน /app/mobile/tsconfig.json ในแอปของเรา เราใช้ @ngrx-demo แต่คุณสามารถใช้ @local เป็นตัวอย่างได้

// app/mobile/tsconfig.json
{
 "compilerOptions": {
 ...
   "baseUrl": ".",
   "paths": {
     "@ngrx-demo/*": ["../../libs/*"]
   }
 ...
}

และ ปลั๊กอิน TSConfig paths webpack ในการกำหนดค่า Webpack แบบกำหนดเองของเรา เพื่อให้ Webpack ทราบถึงการแมปพาธแบบกำหนดเองเหล่านั้น

// app/mobile/config/webpack.config.js
const TsconfigPathsPlugin = require(‘tsconfig-paths-webpack-plugin’);
const resolveConfig = {
 extensions: [‘.ts’, ‘.js’, ‘.json’],
 modules: [path.resolve(‘node_modules’)],
 plugins: [
   new TsconfigPathsPlugin({})
 ]
};
// Default config update
const webpackConfig = require(‘../node_modules/@ionic/app-scripts/config/webpack.config’); 
webpackConfig.dev.resolve = resolveConfig;
webpackConfig.prod.resolve = resolveConfig;

โหลดสดครับ

การได้ประโยชน์จากการโหลดซ้ำแบบสดในระหว่างการพัฒนาถือเป็นเรื่องดี: เมื่อรัน ionic serve หากไฟล์ได้รับการอัปเดตใน /libs/* ระบบจะตรวจพบการเปลี่ยนแปลงและแอปมือถือจะถูกโหลดซ้ำโดยอัตโนมัติในเบราว์เซอร์ของคุณ เราใช้การกำหนดค่านาฬิกาแบบกำหนดเองเพื่อ:

// app/mobile/config/watch.config.js
// Default config update
const watchConfig = require(‘../node_modules/@ionic/app-scripts/config/watch.config’);
watchConfig.srcFiles.paths = [
 ‘{{SRC}}/**/*.(ts|html|s(c|a)ss)’,
 ‘../../libs/**/*.(ts|html|s(c|a)ss)’
];

เครื่องมือ Dev ของร้านค้า Ngrx

และสุดท้ายไม่ท้ายสุด ในระหว่างการพัฒนา เครื่องมือ Ngrx Store Dev เป็นสิ่งจำเป็นในการดีบักแอปของคุณ แต่จะต้องปิดใช้งานในสภาพแวดล้อมการใช้งานจริง เราใช้การแมปเส้นทาง @app/env แบบกำหนดเองสำหรับสิ่งนั้น (เพื่อเลียนแบบตัวแปรสภาพแวดล้อมในแอป Angular) ต้องขอบคุณ https://github.com/gshigeto/ionic-environment-variables

// app/mobile/tsconfig.json
{
 "compilerOptions": {
 ...
    "baseUrl": ".",
    "paths": {
      "@app/env": ["src/environments/environment"],
      "@ngrx-demo/*": ["../../libs/*"] 
    }
 ...
}

ด้วยการแมปนามแฝงในการกำหนดค่า Webpack แบบกำหนดเองของเรา

// app/mobile/config/webpack/config.json
...
webpackConfig.dev.resolve.alias = {
 "@app/env": path.resolve('../src/environments/environment.ts')
};
webpackConfig.prod.resolve.alias = {
 "@app/env": path.resolve('../src/environments/environment.prod.ts')
};

จากนั้นเราก็สามารถนำเข้าตัวแปรสภาพแวดล้อมของเราในแอป Ionic ของเราได้!

import {StoreModule} from '@ngrx/store';
import {StoreDevtoolsModule} from '@ngrx/store-devtools';
...
import {environment} from '@app/env';
@NgModule({
  ...
  imports: [
    BrowserModule,
    IonicModule.forRoot(AppComponent),
    DemoCoreModule,
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([]),
    !environment.production ? StoreDevtoolsModule.instrument({maxAge: 25}) : []
  ],
  ...
})
export class AppModule { }

ข้อดีของพื้นที่ทำงานและ mono-repo

แค่นั้นแหละ! อย่างที่คุณเห็น โครงสร้างโปรเจ็กต์ใหม่นี้สะอาดกว่าโซลูชันเริ่มต้นปี 2559 มาก

มันได้รับประโยชน์จากข้อดีทั้งหมดที่อธิบายไว้ในเอกสาร Nx ทำไมถึงเป็นพื้นที่ทำงาน (ยกเว้นการจัดการการพึ่งพาแบบรวมเนื่องจากแอป Ionic ต้องการการพึ่งพาของตัวเอง):

เวอร์ชันแบบรวม

ทุกสิ่งในคอมมิตปัจจุบันนั้นทำงานร่วมกัน

ฉลากหรือสาขาสามารถจับภาพได้เหมือนกัน

ส่งเสริมการใช้รหัสร่วมกันและการใช้ซ้ำ

ง่ายต่อการแยกโค้ดออกเป็นโมดูล lib

ง่ายต่อการใช้งาน/ปรับใช้โค้ดนั้นและการเปลี่ยนแปลงล่าสุด

คุณประโยชน์ในการปรับโครงสร้างใหม่

โปรแกรมแก้ไขโค้ดและ IDE ต่างตระหนักถึง “พื้นที่ทำงาน”

สามารถมีคอมมิตเดียวสำหรับรีแฟกเตอร์ที่ครอบคลุมแอปพลิเคชันในโดเมน

ประสบการณ์ของนักพัฒนาซอฟต์แวร์ที่สอดคล้องกัน

ตรวจสอบให้แน่ใจว่าโค้ดที่ต้องพึ่งพาที่จำเป็นทั้งหมดพร้อมใช้งาน

หากคุณมีคำถามหรือข้อเสนอแนะเพื่อปรับปรุงแอปสาธิต อย่าลังเลที่จะส่งปัญหาหรือขอดึง!

หมายเหตุ:เรากำลังรับสมัครงาน! คุณเป็นนักพัฒนา fullstack หรือ front-end ที่ต้องการทำงานบน Angular, Java หรือ Groovy หรือไม่? คุณต้องติดต่อฉันเพื่อเข้าร่วมทีมในฝันของเราในปารีส!

หากคุณชอบบทความนี้ โปรดกดปุ่ม ❤ เพื่อแนะนำ สิ่งนี้จะช่วยให้ผู้ใช้สื่อรายอื่นค้นพบสิ่งนี้ได้ง่ายขึ้น