【2019年10月版本】使用Angular+Storybook来创建使用Material和访问Firebase的组件的Stories,可以用Component Story Format编写如下!
在使用 Angular + Firebase 开发的应用程序中,Storybook 包含了 Angular Material 组件。
近来,我连续发现了一些关于Stroybook非常有学习价值的文章。
-
- 理想的なStorybookのワークフローとは
- VueのUIテストにStorybookを使う
太棒了,真是令人感激的事情。
我手上也有一个使用Angular + Firebase构建的SPA项目,E2E测试方面我已经用Protractor解决了,但是”组件级别的单元测试”却令我有些烦恼。
组件类的单元测试可以使用Karma的ts/js代码级别的测试,但是对于作为组件的UI/UX部分的部件=功能+设计+独立性(与其他组件的分离和非依赖)的测试是不能使用Karma的,所以即使我们努力编写代码也有些困难,而且整个页面的设计和所需功能都是按页面提交的,所以作为组件的规范也是开发者自行决定的部分,即使所有在git上提交代码都可以提交到仓库中,其他人创建的组件也变得不可见了。
在这样的时刻,一束光,切取出黑暗的组成部分,啊,只暴露出组成部分在白日下,以独立无需依赖的状态来处理成员变量的那些事情。
装安了Storybook和Angular插件。
我在下面的博客文章中找到了一个关于使用Angular + Storybook的非常易懂的指南。
- Component Driven Development using StoryBook and Angular
请参考以下步骤,立即开始将 Storybook 引入 Angular 项目吧。
由于您手头已经有一个 Angular 项目,因此我们将从添加 Storybook 开始。
使用以下命令即可完成安装。
$ npx -p @storybook/cli sb init
为什么 @storybook/angular 也被安装了呢?太棒了。
所以,通过以下命令可以启动Storybook的开发服务器?
$ npm run storybook
只需要一个选项来用中文将以下内容进行释义:
如果访问 http://localhost:6006,那就可以了。
如果不喜欢6006,那请在package.json中修改”start-storybook -p 6006″的部分,那也可以!
写 Firebase 参照组件的 Stories 时需要注意的事项
在中文中,上述内容的释义如下:
这是第一个主题,关于组件方面。在组件中,要从 Firestore 中获取数据列表等,需要访问 Firebase 和其服务。但仅仅在组件中加入组件本身的代码,并不能实现对 Firebase 的访问。
import { FirestoreListComponent } from "./firestore-list.component";
export default {
title: "FirestoreList",
}
// 先頭の5件が表示されるはず・・・
export const Simple = () => ({
component: FirestoreListComponent ,
props: {
selected : ''
},
moduleMetadata: {
imports: [],
providers: []
},
});
顺便提一下,这个格式似乎是一种不使用storiesOf API来描述的形式,也就是最新的Storybook格式——Component Story Format(CSF)。 (试用最新格式的Storybook和Component Story Format(CSF))
我立刻去查看storybook页面的控制台,发现。。。
ERROR Error: StaticInjectorError(DynamicModule)[FirestoreListService -> AngularFirestore]:
StaticInjectorError(Platform: core)[FirestoreListService -> AngularFirestore]:
NullInjectorError: No provider for AngularFirestore!
FirestoreListComponent引用的FirestoreListService,以及从那里引用的AngularFirestore的提供者不存在,所以会报错!
因此,我们将在上述供应商中添加AngularFirestore。
...
moduleMetadata: {
imports: [],
providers: [AngularFirestore]
},
...
當我們查看storybook頁面的控制台時…
ERROR Error: StaticInjectorError(DynamicModule)[AngularFirestore -> InjectionToken angularfire2.app.options]:
StaticInjectorError(Platform: core)[AngularFirestore -> InjectionToken angularfire2.app.options]:
NullInjectorError: No provider for InjectionToken angularfire2.app.options!
当然了,但是该如何传递访问令牌呢?
试了各种方法后,以下设置不再被责骂。
import { environment } from "src/environments/environment";
...
moduleMetadata: {
imports: [AngularFireModule.initializeApp(environment.firebase)],
providers: [AngularFirestore]
},
...
这是在 Angular 的 App.module.ts 等文件中进行的 AngularFireModule 的初始化处理,如果将其传递给 imports 如上所述,它就会变得安静。
最后,结果如下所示。
import { FirestoreListComponent } from "./firestore-list.component";
import { AngularFireModule } from "@angular/fire";
import { AngularFirestore } from "@angular/fire/firestore";
import { environment } from "src/environments/environment";
export default {
title: "FirestoreList",
}
// 無事に先頭の5件が表示される!
export const Simple = () => ({
component: FirestoreListComponent ,
props: {
selected : ''
},
moduleMetadata: {
imports: [AngularFireModule.initializeApp(environment.firebase)],
providers: [AngularFirestore]
},
});
现在,您可以在storybook上完美地显示Firestore的测试数据!
补充一点,在添加另一个故事时也需要上述规定。如下所示。
import { FirestoreListComponent } from "./firestore-list.component";
import { AngularFireModule } from "@angular/fire";
import { AngularFirestore } from "@angular/fire/firestore";
import { environment } from "src/environments/environment";
export default {
title: "FirestoreList",
}
// 無事に先頭の5件が表示される!
export const Simple = () => ({
component: FirestoreListComponent ,
props: {
selected : ''
},
moduleMetadata: {
imports: [AngularFireModule.initializeApp(environment.firebase)],
providers: [AngularFirestore]
},
});
// `One` が選択されている。
export const WithOneSelected = () => ({
component: FirestoreListComponent ,
props: {
selected : 'One'
},
moduleMetadata: {
imports: [AngularFireModule.initializeApp(environment.firebase)],
providers: [AngularFirestore]
},
});
嗯,可能有点烦人…
本题2. 使用Angular Material组件的组件。
接下来是第二个主题,关于材料的标签也不是一马平川的。
假设有一个组件包含了 AngularMaterial。例如,如果包含了标签,那么可能会因为不知道mat-icon标签而被怒斥。
Unhandled Promise rejection: Template parse errors:
'mat-icon' is not a known element:
1. If 'mat-icon' is an Angular component, then verify that it is part of this module.
2. If 'mat-icon' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
只需传递 MatIconModule,就可以解决这个错误。
import { MyIconListComponent } from "./my-icon-list.component";
import { MatIconModule } from "@angular/material";
export default {
title: "My Icon List",
}
export const StarUserMail = () => ({
component: MyIconListComponent ,
props: {
values : ['star', 'user', 'mail']
},
moduleMetadata: {
imports: [MatIconModule],
providers: []
},
});
这样就不会出现上述的错误了。然而…
材料的 CSS 没有生效
如果保持现状,Material的主题(CSS)将无效,导致外观严重受损。如果依赖于Material主题的CSS(只更改高亮部分,或从Material的边距和填充进行进一步调整等),外观可能会完全破坏。
除了上面提到的使用StoryBook和Angular的Component Driven Development页面的内容之外,还需要参考以下示例,重点关注与Material相关的处理。
- https://github.com/amcdnl/material-storybook
我已经按照以下内容创建了.storybook/preview-head.html文件。
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
@import '@angular/material/prebuilt-themes/deeppurple-amber.css'
html, body {
margin: 0;
height: 100%;
}
storybook-dynamic-app-root {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
padding: 25px;
}
storybook-dynamic-app-root > ng-component {
margin: 0 auto;
}
似乎可以通过创建preview-head.html,在组件预览区的开头插入这个HTML。在上述博客文章中,@import ‘@angular/material/prebuilt-themes/deeppurple-amber.css’这一部分被提及。
编辑`src/styles.scss`,并导入一个主题`’@angular/material/prebuilt-themes/deeppurple-amber.css’`。
但是修改styles.scss有点麻烦…所以我在这里进行了记录。
如果以上样式起作用,Material组件应该会变得整齐漂亮。
就是这样了!
那么,我也决定核实一下现有的组件是否正确地“作为组件”。