Angular脚手架开发

简介写一份自定义的angular脚手架吧写之前我们先解析一下antd的脚手架前提先把 Angular Schematic这篇文章读一遍,确保了解了collection等基础antd脚手架克隆项目git clone https://github.com/NG-ZORRO/ng-zorro-antd.git开始打开项目在schematics下的collection.json为入口,查看内容一共定了了4个schematic,每个schema分别指向了各文件夹的子schema.json,factory指向了函数入口,index.tsng-add/schema.json{ // 指定schema.json的验证模式 “$schema”: “http://json-schema.org/schema", “id”: “nz-ng-add”, “title”: “Ant Design of Angular(NG-ZORRO) ng-add schematic”, “type”: “object”, // 包含的属性 “properties”: { “project”: { “type”: “string”, “description”: “Name of the project.”, “$default”: { “$source”: “projectName” } }, // 是否跳过package.json的安装属性 “skipPackageJson”: { // 类型为布尔 “type”: “boolean”, // 默认值为false “default”: false, // 这是个描述,可以看到,如果在ng add ng-zorro-antd时不希望自动安装可以加入–skipPackageJson配置项 “description”: “Do not add ng-zorro-antd dependencies to package.json (e.g., –skipPackageJson)” }, // 开始页面 “bootPage”: { // 布尔 “type”: “boolean”, // 默认为true “default”: true, // 不指定–bootPage=false的话,你的app.html将会被覆盖成antd的图标页 “description”: “Set up boot page.” }, // 图标配置 “dynamicIcon”: { “type”: “boolean”, “default”: false, “description”: “Whether icon assets should be add.”, “x-prompt”: “Add icon assets [ Detail: https://ng.ant.design/components/icon/en ]” }, // 主题配置 “theme”: { “type”: “boolean”, “default”: false, “description”: “Whether custom theme file should be set up.”, “x-prompt”: “Set up custom theme file [ Detail: https://ng.ant.design/docs/customize-theme/en ]” }, // i18n配置,当你ng add ng-antd-zorro 的时候有没有让你选择这个选项呢? “i18n”: { “type”: “string”, “default”: “en_US”, “enum”: [ “ar_EG”, “bg_BG”, “ca_ES”, “cs_CZ”, “da_DK”, “de_DE”, “el_GR”, “en_GB”, “en_US”, “es_ES”, “et_EE”, “fa_IR”, “fi_FI”, “fr_BE”, “fr_FR”, “is_IS”, “it_IT”, “ja_JP”, “ko_KR”, “nb_NO”, “nl_BE”, “nl_NL”, “pl_PL”, “pt_BR”, “pt_PT”, “sk_SK”, “sr_RS”, “sv_SE”, “th_TH”, “tr_TR”, “ru_RU”, “uk_UA”, “vi_VN”, “zh_CN”, “zh_TW” ], “description”: “add locale code to module (e.g., –locale=en_US)” }, “locale”: { “type”: “string”, “description”: “Add locale code to module (e.g., –locale=en_US)”, “default”: “en_US”, “x-prompt”: { “message”: “Choose your locale code:”, “type”: “list”, “items”: [ “en_US”, “zh_CN”, “ar_EG”, “bg_BG”, “ca_ES”, “cs_CZ”, “de_DE”, “el_GR”, “en_GB”, “es_ES”, “et_EE”, “fa_IR”, “fi_FI”, “fr_BE”, “fr_FR”, “is_IS”, “it_IT”, “ja_JP”, “ko_KR”, “nb_NO”, “nl_BE”, “nl_NL”, “pl_PL”, “pt_BR”, “pt_PT”, “sk_SK”, “sr_RS”, “sv_SE”, “th_TH”, “tr_TR”, “ru_RU”, “uk_UA”, “vi_VN”, “zh_TW” ] } }, “gestures”: { “type”: “boolean”, “default”: false, “description”: “Whether gesture support should be set up.” }, “animations”: { “type”: “boolean”, “default”: true, “description”: “Whether Angular browser animations should be set up.” } }, “required”: []}schema.ts当你进入index.ts时首先看到的是一个带options:Schema的函数,options指向的类型是Schema interface,而这个interface 恰好是schema.json中的properties,也就是cli的传入参数类.我们可以通过自定义传入参数类来完成我们需要的操作.export type Locale = | ‘ar_EG’ | ‘bg_BG’ | ‘ca_ES’ | ‘cs_CZ’ | ‘da_DK’ | ‘de_DE’ | ’el_GR’ | ’en_GB’ | ’en_US’ | ’es_ES’ | ’et_EE’ | ‘fa_IR’ | ‘fi_FI’ | ‘fr_BE’ | ‘fr_FR’ | ‘is_IS’ | ‘it_IT’ | ‘ja_JP’ | ‘ko_KR’ | ’nb_NO’ | ’nl_BE’ | ’nl_NL’ | ‘pl_PL’ | ‘pt_BR’ | ‘pt_PT’ | ‘sk_SK’ | ‘sr_RS’ | ‘sv_SE’ | ’th_TH’ | ’tr_TR’ | ‘ru_RU’ | ‘uk_UA’ | ‘vi_VN’ | ‘zh_CN’ | ‘zh_TW’;export interface Schema { bootPage?: boolean; /** Name of the project to target. / project?: string; /* Whether to skip package.json install. */ skipPackageJson?: boolean; dynamicIcon?: boolean; theme?: boolean; gestures?: boolean; animations?: boolean; locale?: Locale; i18n?: Locale;}ng-add/index.tsimport { Rule, SchematicContext, Tree } from ‘@angular-devkit/schematics’;import { NodePackageInstallTask, RunSchematicTask } from ‘@angular-devkit/schematics/tasks’;import { addPackageToPackageJson } from ‘../utils/package-config’;import { hammerjsVersion, zorroVersion } from ‘../utils/version-names’;import { Schema } from ‘./schema’;// factory指向的index.ts必须实现这个函数,一行一行看代码// 我们的函数是一个更高阶的函数,这意味着它接受或返回一个函数引用。// 在这种情况下,我们的函数返回一个接受Tree和SchematicContext对象的函数。// options:Schema上面提到了export default function(options: Schema): Rule {// tree:虚拟文件系统:用于更改的暂存区域,包含原始文件系统以及要应用于其的更改列表。// rule:A Rule是一个将动作应用于Tree给定的函数SchematicContext。 return (host: Tree, context: SchematicContext) => { // 如果需要安装包,也就是–skipPackageJson=false if (!options.skipPackageJson) { // 调用addPackageToPackageJson,传入,tree文件树,包名,包版本 addPackageToPackageJson(host, ’ng-zorro-antd’, zorroVersion); // hmr模式包 if (options.gestures) { addPackageToPackageJson(host, ‘hammerjs’, hammerjsVersion); } } const installTaskId = context.addTask(new NodePackageInstallTask()); context.addTask(new RunSchematicTask(’ng-add-setup-project’, options), [installTaskId]); if (options.bootPage) { context.addTask(new RunSchematicTask(‘boot-page’, options)); } };}addPackageToPackageJson// 看function名字就知道这是下载依赖的函数// @host:Tree 文件树// @pkg:string 包名// @vserion:string 包版本// @return Tree 返回了一个修改完成后的文件树export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { // 如果文件树里包含package.json文件 if (host.exists(‘package.json’)) { // 读取package.json的内容用utf-8编码 const sourceText = host.read(‘package.json’).toString(‘utf-8’); // 然后把package.json转化为对象,转为对象,转为对象 const json = JSON.parse(sourceText); // 如果package.json对象里没有dependencies属性 if (!json.dependencies) { // 给package对象加入dependencies属性 json.dependencies = {}; } // 如果package对象中没有 pkg(包名),也就是说:如果当前项目没有安装antd if (!json.dependencies[pkg]) { // 那么package的dependencies属性中加入 antd:version json.dependencies[pkg] = version; // 排个序 json.dependencies = sortObjectByKeys(json.dependencies); } // 重写tree下的package.json内容为(刚才不是有package.json对象吗,现在在转回去) host.overwrite(‘package.json’, JSON.stringify(json, null, 2)); } // 把操作好的tree返回给上一级函数 return host;}现在在回过头去看 ng-add/index.ts// 给context对象增加一个安装包的任务,然后拿到了任务idconst installTaskId = context.addTask(new NodePackageInstallTask());// context增加另一个任务,然后传入了一个RunSchematicTask对象,和一个id集合 context.addTask(new RunSchematicTask(’ng-add-setup-project’, options), [installTaskId]);RunSchematicTask(’ng-add-setup-project’)任务ng-add-setup-project定义在了schematic最外层的collection.json里,记住如下4个schematic,后文不再提及{ “$schema”: “./node_modules/@angular-devkit/schematics/collection-schema.json”, “schematics”: { “ng-add”: { “description”: “add NG-ZORRO”, “factory”: “./ng-add/index”, “schema”: “./ng-add/schema.json” }, // 在这里 “ng-add-setup-project”: { “description”: “Sets up the specified project after the ng-add dependencies have been installed.”, “private”: true, // 这个任务的函数指向 “factory”: “./ng-add/setup-project/index”, // 任务配置项 “schema”: “./ng-add/schema.json” }, “boot-page”: { “description”: “Set up boot page”, “private”: true, “factory”: “./ng-generate/boot-page/index”, “schema”: “./ng-generate/boot-page/schema.json” }, “add-icon-assets”: { “description”: “Add icon assets into CLI config”, “factory”: “./ng-add/setup-project/add-icon-assets#addIconToAssets”, “schema”: “./ng-generate/boot-page/schema.json”, “aliases”: [“fix-icon”] } }}ng-add/setup-project// 刚才的index一样,实现了一个函数export default function (options: Schema): Rule { // 这里其实就是调用各种函数的一个集合.options是上面的index.ts中传过来的,配置项在上文有提及 return chain([ addRequiredModules(options), addAnimationsModule(options), registerLocale(options), addThemeToAppStyles(options), options.dynamicIcon ? addIconToAssets(options) : noop(), options.gestures ? hammerjsImport(options) : noop() ]);}addRequiredModules// 模块字典const modulesMap = { NgZorroAntdModule: ’ng-zorro-antd’, FormsModule : ‘@angular/forms’, HttpClientModule : ‘@angular/common/http’};// 加入必须依赖模块export function addRequiredModules(options: Schema): Rule { return (host: Tree) => { // 获取tree下的工作目录 const workspace = getWorkspace(host); // 获取项目 const project = getProjectFromWorkspace(workspace, options.project); // 获取app.module的路径 const appModulePath = getAppModulePath(host, getProjectMainFile(project)); // 循环字典 for (const module in modulesMap) { // 调用下面的函数,意思就是:给appModule引一些模块,好吧,传入了tree,字典key(模块名称),字典value(模块所在包),project对象,appModule的路径,Schema配置项 addModuleImportToApptModule(host, module, modulesMap[ module ], project, appModulePath, options); } // 将构建好的tree返回给上层函数 return host; };}function addModuleImportToApptModule(host: Tree, moduleName: string, src: string, project: WorkspaceProject, appModulePath: string, options: Schema): void { if (hasNgModuleImport(host, appModulePath, moduleName)) { console.log(chalk.yellow(Could not set up "${chalk.blue(moduleName)}" + because "${chalk.blue(moduleName)}" is already imported. Please manually + check "${chalk.blue(appModulePath)}" file.)); return; } addModuleImportToRootModule(host, moduleName, src, project);}未完待续 ...

April 8, 2019 · 4 min · jiezi

Angular/Ionic安装配置汇总

一、NodeJSAngular和Cordova/Ionic都使用cli工具开发,因此必须先安装最新版本的NodeJS。NodeJS里面包含npm包管理器,而Angular/Ionic本身和程序依赖的库和框架都是npm包,要进行Angular/Ionic开发首先需要安装npm包管理器。NodeJS的官网下载页:https://nodejs.org/en/download/安装NodeJS的时候把npm一起安装就可以。也可以安装yarn,下载依赖包的速度和稳定性会提升很多。可以参考 https://yarn.bootcss.com/二、使用npm的淘宝镜像直接下载npm库会很慢,很多时候会不成功。国内用户可以使用淘宝的npmjs.org镜像,加快下载速度。淘宝镜像的官方使用说明在:https://npm.taobao.org/官方的建议是安装cnpm代替npm,或者添加一个叫cnpm的别名。另一个方法是不使用cnpm,而是修改npm的默认镜像:npm config set registry http://registry.npm.taobao.org/如果想修改回默认的版本,可以这样:npm config set registry https://registry.npmjs.org/用 npm get registry 可以查看现在使用的是哪个镜像。如果安装了yarn,可以这样设置镜像:yarn config set registry http://registry.npm.taobao.org/三、Angular安装npm install -g @angular/cli可参考官网:https://angular.cn/guide/quic…cli命令可以参考:https://angular.cn/cli四、Ionic4安装安装cli: npm install -g ionicIonic cli的文档:https://ionicframework.com/do…Ionic Native提供使用Angular风格调用原生组件的方法。Ionic native的文档:https://ionicframework.com/do…五、Ionic3安装1.安装Ionic和Cordova的CLInpm install -g ionic cordovanpm可以先安装或者配置淘宝镜像。2.新建项目在命令行中,进入项目目录的上级目录,然后执行 以下命令:ionic start TestIonic tabs其中,TestIonic是项目名,也是目录名,命令执行成功后,会自动在当前目录下创建一个名叫TestIonic的子目录,就是新建项目的目录。后面的tabs表示新建项目的模板,tabs模板3个tab的布局,也可以使用其他官方模板:tabs : a simple 3 tab layoutsidemenu: a layout with a swipable menu on the sideblank: a bare starter with a single pagesuper: starter project with over 14 ready to use page designstutorial: a guided starter project此命令可能会出现网络连接错误:[ERROR] Network connectivity error occurred, are you offline? If you are behind a firewall and need to configure proxy settings, see: https://ionicframework.com/docs/cli/configuring.html#using-a-proxy解决方案,执行以下命令配置npm代理:npm config set proxy= https://registry.npm.taobao.orgnpm config set https_proxy=https://registry.npm.taobao.org然后设置ionic的代理:npm install -g @ionic/cli-plugin-proxy添加环境变量 IONIC_HTTP_PROXY 值为代理服务器地址,比如https://registry.npm.taobao.org3.运行项目ionic serve复制地址到谷歌浏览器,然后按下F12,按左上角第二个按钮切换到手机模式,可以调试项目。4.打包打包android app,需要先安装gradle(网上说法如此,但实际编译过程中没有看到ionic使用了安装的gradle,反而自己下载了一个gradle)和android sdk,可参考https://blog.csdn.net/qq_2026…。ionic cordova build android –release –prod上面命令可以打包成为android apk,并且在输出提示中显示apk文件的位置。ionic cordova run android上面命令生成apk文件并且直接安装到安卓手机上,但安卓手机必须用usb连接PC,而且进入开发者模式,启用usb调试,并且安装的时候一般需要在手机上做确认,否则安装失败。ionic cordova emulate android上面命令生成apk文件并且安装到安卓模拟器上运行,最好先启动安卓模拟器,如果没有启动安卓模拟器,ionic会自动启动缺省的安卓模拟器。另外,上面两个命令也可以用cordova版本例如:cordova run androidcordova emulate android效率更高,不过事先必须先调用过ionic cordova build。另外,以上ionic命令都可以加上选项-lc:ionic cordova emulate android -lc选项-lc有两个作用,一个是把console.info等输出信息输出到命令行上;另一个是可以使配置文件ionic.config.json中的proxies生效。注意,-lc选项虽然可以让proxies生效,但只能用于调试,正式安装运行是无效的,需要用环境变量来控制。5.android签名运行ionic cordova build android –release –prod之后,在命令行输入以下命令:keytool -genkey -v -keystore testionic.keystore -alias testionic.keystore -keyalg RSA -validity 36500jarsigner -verbose -keystore testionic.keystore -signedjar tionic.apk app-release-unsigned.apk testionic.keystorezipalign -v 4 txx.apk txx_aligned.apk其中keytool和jarsigner是jdk的工具,需要配置jdk的path。zipalign是android sdk的工具,需要在android sdk下搜索其具体路径,但这个命令不一定需要执行,只是优化。也可以直接在cordova build中直接签名ionic cordova build android –release –prod – – –minSdkVersion=22 –keystore=testionic.keystore –alias=testionic.keystore –storePassword=123456 –password=123456 ...

March 18, 2019 · 1 min · jiezi

ng5 兼容IE之 object doesn't support property or method 'xxxxxx'

用angular5练习,偶然发现,在Chrome等浏览器跑飞了的代码,居然在IE面前就是战五渣,第一个页面都加载不出来,决心解决下。项目环境就是ng new命令生产的默认结构,没有其他特殊设置。F12开始看后台,一个错误都没有。气急败坏的反复刷新,错误总算出来了(个人猜测这个状况可能是因为我的电脑有问题,理论上应该第一次打开就能看到错误)。object doesn’t support property or method ‘startsWith’随便一搜,这个错误很普遍,总结起来就是IE的脚步落后了,有些语法不支持,需要polyfill的辅助才行。解决方案也很简单,就是重写polyfill的对应方法。由于不清楚到底把代码该写在哪里,进项目下查看一番,发现在src文件夹下,有一个叫polyfill.js的文件。文件打开一看,问题基本上解决了。看下文件内和本次相关的内容:/** IE9, IE10 and IE11 requires all of the following polyfills. /import ‘core-js/es6/symbol’;import ‘core-js/es6/object’;import ‘core-js/es6/function’;import ‘core-js/es6/parse-int’;import ‘core-js/es6/parse-float’;import ‘core-js/es6/number’;import ‘core-js/es6/math’;import ‘core-js/es6/string’;import ‘core-js/es6/date’;import ‘core-js/es6/array’;import ‘core-js/es6/regexp’;import ‘core-js/es6/map’;import ‘core-js/es6/weak-map’;import ‘core-js/es6/set’;/ IE10 and IE11 requires the following for NgClass support on SVG elements /import ‘classlist.js’; // Run npm install --save classlist.js./* IE10 and IE11 requires the following for the Reflect API. */import ‘core-js/es6/reflect’;简单明了,以上代码原来都是注释掉的,相信大家都懂了,打开注释,问题解决了。这部分,相信在其他框架里也有对应的处理,大家细心找一下就ok。第一步错误处理搞定,本以为万事大吉,结果又提示了相似的错误,这次是找不到方法readAsBinaryString(练习中用涉及到了文件读取)。有了前边的经验,搜索起来驾轻就熟,原因还是IE的步伐慢,暂时还没支持readAsBinaryString,解决方案是readAsArrayBuffer方法代替。走着,先判断下浏览器是否支持readAsBinaryString,然后再决定用哪个方法!readFile(file: any) { const reader = new FileReader(); if (!FileReader.prototype.readAsBinaryString) { reader.onload = (event: any) => { let binary = ‘’; const bytes = new Uint8Array(event.target.result); const length = bytes.byteLength; for (let i = 0; i < length; i++) { binary += String.fromCharCode(bytes[i]); } this.readFileContent(binary, file.name); }; reader.readAsArrayBuffer(file); //IE浏览器 } else { reader.onload = (event: any) => { const data = event.target.result; this.readFileContent(data, file.name); }; reader.readAsBinaryString(file); //其他浏览器 }}跑一下,完美解决!IE真是程序员的噩梦呀,遇到噩梦,翻个身再睡! ...

March 15, 2019 · 1 min · jiezi

问道Angular——Angular刷新当前页面

onSameUrlNavigation 从angular5.1起提供onSameUrlNavigation来支持路由重新加载。、 有两个值’reload’和’ignore’。默认为’ignore’ 定义当路由器收到一个导航到当前 URL 的请求时应该怎么做。 默认情况下,路由器将会忽略这次导航。但这样会阻止类似于 “刷新” 按钮的特性。 使用该选项可以配置导航到当前 URL 时的行为。使用配置onSameUrlNavigation@NgModule({ imports: [RouterModule.forRoot( routes, { onSameUrlNavigation: ‘reload’ } )], exports: [RouterModule]}) reload实际上不会重新加载路由,只是重新出发挂载在路由器上的事件。配置runGuardsAndResolvers runGuardsAndResolvers有三个值:paramsChange: 仅在路由参数更改时触发。如/reports/:id 中id更改paramsOrQueryParamsChange: 当路由参数更改或参训参数更改时触发。如/reports/:id/list?page=23中的id或page属性更改always :始终触发const routes: Routes = [ { path: ‘’, children: [ { path: ‘report-list’, component: ReportListComponent }, { path: ‘detail/:id’, component: ReportDetailComponent, runGuardsAndResolvers: ‘always’ }, { path: ‘’, redirectTo: ‘report-list’, pathMatch: ‘full’ } ] }];组件监听router.eventsimport {Component, OnDestroy, OnInit} from ‘@angular/core’;import {Observable} from ‘rxjs’;import {Report} from ‘@models/report’;import {ReportService} from ‘@services/report.service’;import {ActivatedRoute, NavigationEnd, Router} from ‘@angular/router’;@Component({ selector: ‘app-report-detail’, templateUrl: ‘./report-detail.component.html’, styleUrls: [’./report-detail.component.scss’]})export class ReportDetailComponent implements OnInit, OnDestroy { report$: Observable<Report>; navigationSubscription; constructor( private reportService: ReportService, private router: Router, private route: ActivatedRoute ) { this.navigationSubscription = this.router.events.subscribe((event: any) => { if (event instanceof NavigationEnd) { this.initLoad(event); } }); } ngOnInit() { const id = +this.route.snapshot.paramMap.get(‘id’); this.report$ = this.reportService.getReport(id); } ngOnDestroy(): void { // 销毁navigationSubscription,避免内存泄漏 if (this.navigationSubscription) { this.navigationSubscription.unsubscribe(); } } initLoad(e) { window.scrollTo(0, 0); console.log(e); }}☞☞☞问道Angular系列☜☜☜ ...

November 20, 2018 · 1 min · jiezi