我还不知道这个叫什么

本贴最后更新于 1225 天前,其中的信息可能已经斗转星移

ng-alain

转载请注明原出处,勘误请发送电子邮件

1.什么是 ng-alain?

ng-alain 是来自中国作者 卡色 开源出的一个企业级中后台前端/设计解决方案脚手架,秉承 Ant Design 的设计价值观,让 Angular 快速落地于企业生产实践中的一个高集成性框架。

其中提供了非常丰富的功能诸如

  • Dynamic Form
  • Antv/Echarts
  • Acl
  • Auth
  • Theme
  • Cache
  • Mock
  • Util (DeepCopy/TreeToArray 等等)

假如你对 Angular 有一些使用基础,那么使用起来会相当的得心应手。

假如你对 Angular 只有一些略微了解,那么也可以通过 ng-alain 提供的 CLI 快速的建立起一个可运行 Project 用来快速上手。

2.ng-alain 解析

  • 生成一个 ng-alain 项目首先需要安装 AngularCli
  • npm install @angular/cli@12.2.0
  • 其次使用 ng new my-project --style less --routing 创建名为 my-project 的纯 Angular 项目
  • 然后使用 ng-alainCLI 生成项目
  • ng add ng-alain
  • 最后使用 npm run start 启动项目
  • 或者直接前往预览地址

3.ng-alain 项目

“没有比浏览 angular.json 配置文件更能快速了解一个 angular 项目的方法了” -詹姆斯高斯林

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "ng-alain": {
      "projectType": "application",
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "schematics": {
        "@schematics/angular:component": {
          "style": "less"
        },
        "@schematics/angular:application": {
          "strict": true
        }
      },
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "tsConfig": "tsconfig.app.json",
            "polyfills": "src/polyfills.ts",
            "assets": ["src/assets", "src/favicon.ico"],
            "styles": ["src/styles.less"],
            "scripts": [],
            "allowedCommonJsDependencies": ["ajv", "ajv-formats"]
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "outputHashing": "all",
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "6mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "ng-alain:build",
            "proxyConfig": "proxy.conf.json"
          },
          "configurations": {
            "production": {
              "browserTarget": "ng-alain:build:production"
            },
            "development": {
              "browserTarget": "ng-alain:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "ng-alain:build"
          }
        },
      ....

我们可以清晰的从 angular.json 中看出

  • build 部分
  • projects 中仅仅含有一个类型为 application 的项目 ng-alain
  • builder 编译器使用了默认的 @angular-devkit/build-angular:browser,当然还有许多其他的编译器,可以自行搜索使用。
  • builder 中的 options 指定了一些入口文件
  • 引入了 CommanJS: ajv,ajv-formatsajvJSON Schema 的一种验证器
  • build 中的 configuration 会使用 env.prod.ts 替换 env.ts
  • outputHashing 打包文件带有哈希值
  • serve 部分
  • serve 对应的命令为 ng serve,使用的 builder@angular-devkit/build-angular:dev-server,我们可以在 @angular-devikt 找到这个包,有一些配置参数和说明,刚刚 build 时使用的 browser,还有 app-shell 呦

找到了入口文件 index.html

<app-root></app-root>
  <div class="preloader">
    <div class="cs-loader">
      <div class="cs-loader-inner">
        <label> ●</label>
        <label> ●</label>
        <label> ●</label>
        <label> ●</label>
        <label> ●</label>
        <label> ●</label>
      </div>
    </div>
  </div>

比较重要的一部分

  • <app-root> ,这里是对 app.component.ts 的直接引用
  • 下面的 div 中使用 classcss 做了一个加载动画,可以理解成遮罩层,利用最先渲染 index.html,使用 css 将整个 webapp 遮盖住
  • index.html 结束

找到了入口文件 main.ts

preloaderFinished();

第九行调用了 preloaderFinished,这个函数是在 alain 的另一个基础项目 delon 中以基础包的形式出现的

export function preloaderFinished(): void {
  const body = document.querySelector('body')!;
  const preloader = document.querySelector('.preloader')!;

  body.style.overflow = 'hidden';

  function remove(): void {
    // preloader value null when running --hmr
    if (!preloader) return;
    preloader.addEventListener('transitionend', () => {
      preloader.className = 'preloader-hidden';
    });

    preloader.className += ' preloader-hidden-add preloader-hidden-add-active';
  }

  (window as NzSafeAny).appBootstrap = () => {
    setTimeout(() => {
      remove();
      body.style.overflow = '';
    }, 100);
  };
}
  • 写了之前说的遮罩层如何删除

  • 赋予 window 对象一个 appBootstrap 函数,用来进行调用 remove 删除掉 index.html 中的遮罩层

    if (environment.production) {
    enableProdMode();
    }

如果环境为 prod 模式那么开启 Prod 模式

platformBrowserDynamic()
  .bootstrapModule(AppModule, {
    defaultEncapsulation: ViewEncapsulation.Emulated,
    preserveWhitespaces: false
  })
  .then(res => {
    const win = window as NzSafeAny;
    if (win && win.appBootstrap) {
      win.appBootstrap();
    }
    return res;
  })
  .catch(err => console.error(err));

上面为 main.ts 中最后的内容

  • 引导 AppModule 启动
  • 启动完毕后调用 win 中存下的 remove 函数,删除遮罩层

@import '~@delon/theme/system/index';
@import '~@delon/abc/index';
@import '~@delon/chart/index';
@import '~@delon/theme/layout-default/style/index';
@import '~@delon/theme/layout-blank/style/index';

@import './styles/index';
@import './styles/theme';

style.less 中引入了一些 less 变量,以供全局 css 使用,这些 less 变量也是 @delon/theme 包中出现的,后续可能会看到


至此,除了 assets 静态资源目录,其余从 angular.json 找到的入口的启动过程梳理完毕,分别

  • 提供了模版
  • 提供了静态资源
  • 提供了 JS 启动模块
  • 提供了全局 CSS

3.1 项目配置

接下来看被 bootstrapAppModule

const LANG = {
  abbr: 'zh',
  ng: ngLang,
  zorro: zorroLang,
  date: dateLang,
  delon: delonLang
};
registerLocaleData(LANG.ng, LANG.abbr);
const LANG_PROVIDES = [
  { provide: LOCALE_ID, useValue: LANG.abbr },
  { provide: NZ_I18N, useValue: LANG.zorro },
  { provide: NZ_DATE_LOCALE, useValue: LANG.date },
  { provide: DELON_LOCALE, useValue: LANG.delon }
];

前面主要是本地化/i18n 的一些配置,通过 provide 键值对的方式注入到包里,使得第三方库使用 useValue 提供的值

const I18NSERVICE_PROVIDES = [{ provide: ALAIN_I18N_TOKEN, useClass: I18NService, multi: false }];

当然除了使用 useValue 的方式,还可以使用 useClass

export const ALAIN_I18N_TOKEN = new InjectionToken<AlainI18NService>('alainI18nToken', {
  providedIn: 'root',
  factory: () => new AlainI18NServiceFake()
});

上文的 ALAIN_I18N_TOKEN@delon/theme 中,接收一个 AlainI18nService类型 的 class

I18nService 继承自 AlainI18nBaseService,而 AlainI18nBaseService 又实现了 AlainI18NService 接口,所以类型匹配,这样我们就可以把自己的 class 传入第三方包中,第三方包注入 class 时调用的 function 就可以来自外部实际项目了


const GLOBAL_THIRD_MODULES: Array<Type<any>> = [BidiModule]

引入了一个左右布局切换的 Module,在 cdk 里,cdk@angular 提供的一个工具包


const INTERCEPTOR_PROVIDES = [
  { provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true }
];

再往下就是这两个拦截器

  • Simple: 验证 token
  • Default: 处理错误信息

export function StartupServiceFactory(startupService: StartupService): () => Observable<void> {
  return () => startupService.load();
}
const APPINIT_PROVIDES = [
  StartupService,
  {
    provide: APP_INITIALIZER,
    useFactory: StartupServiceFactory,
    deps: [StartupService],
    multi: true
  }
];

重点,这里我称为 实际引导项目启动 的一个函数,这里作为整个项目声明周期的 初始化 周期,使用了 Angular 内置的 provide: APP_INITIALIZER 并返回了一个 Promise 函数 load

@Injectable()
export class StartupService {
  constructor(
    iconSrv: NzIconService,
    private menuService: MenuService,
    @Inject(ALAIN_I18N_TOKEN) private i18n: I18NService,
    private settingService: SettingsService,
    private aclService: ACLService,
    private titleService: TitleService,
    private httpClient: HttpClient
  ) {
    iconSrv.addIcon(...ICONS_AUTO, ...ICONS);
  }

  load(): Observable<void> {
    const defaultLang = this.i18n.defaultLang;
    return zip(this.i18n.loadLangData(defaultLang), this.httpClient.get('assets/tmp/app-data.json')).pipe(
      // 接收其他拦截器后产生的异常消息
      catchError(res => {
        console.warn(`StartupService.load: Network request failed`, res);
        return [];
      }),
      map(([langData, appData]: [Record<string, string>, NzSafeAny]) => {
        // setting language data
        this.i18n.use(defaultLang, langData);
        // 应用信息:包括站点名、描述、年份
        this.settingService.setApp(appData.app);
        // 用户信息:包括姓名、头像、邮箱地址
        this.settingService.setUser(appData.user);
        // ACL:设置权限为全量
        this.aclService.setFull(true);
        // 初始化菜单
        this.menuService.add(appData.menu);
        // 设置页面标题的后缀
        this.titleService.default = '';
        this.titleService.suffix = appData.app.name;
      })
    );
  }
}

以上代码初始化了整体项目必备的数据


@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    GlobalConfigModule.forRoot(),
    CoreModule,
    SharedModule,
    LayoutModule,
    RoutesModule,
    STWidgetModule,
    NzNotificationModule,
    ...GLOBAL_THIRD_MODULES,
    ...FORM_MODULES
  ],
  providers: [...LANG_PROVIDES, ...INTERCEPTOR_PROVIDES, ...I18NSERVICE_PROVIDES, ...APPINIT_PROVIDES],
  bootstrap: [AppComponent]
})
export class AppModule {}

回到 app.module 中,imports 一些模块,providers 一些服务,

其中对于整体项目比较重要的模块有

  • GlobalConfigModule.forRoot() 全局配置模块,主要配置 alain 中的一些组件功能/认证/等各方面的内容
  • LayoutModule 在用户端的一个布局模块
  • SharedModule 共享模块
  • RoutesModule 总体路由模块,比如 /app/v1 路径,要渲染哪个模块的那个组件,就是这个模块决定的,在这个项目里,也称为 业务模块
  • CoreModule core 模块,刚才提到的拦截器和 i18n 和 startup 就在这个模块中

3 操作
someone66251 在 2021-08-16 11:05:15 更新了该帖
someone66251 在 2021-08-15 21:55:32 更新了该帖
someone66251 在 2021-08-15 21:53:51 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...