Angular4 问题表学习笔记
脚手架
npm install -g @angular/cli
下载工具
ng set --global packageManager=cnpm
新建工程
ng new my-app
下载bootstrap等工具
cnpm install bootstrap@4.0.0-alpha.6 jquery tether --save
启动项目
ng serve
之后我们可以在http://localhost:4200/访问它
配置bootstrap
在.angular-cli.json文件中styles和scripts中引入bootstrap.css/js以及jQuery.js和tether.js的路径
创建组件
ng g c components/组件名(这里的components是组件的文件夹新建的)
使用脚手架创建的组件会自动在app.model.ts中引用
在这里我们创建两个组件navbar(存放导航)、question-list(存放问题列表)
在app.component.html中调用创建的组件
只需要将组件中ts文件下的selector调用(我们在相应组件的ts文件下将selector拷贝一下并在app.component.html中调用)
就像这样
<app-navbar></app-navbar>
<app-question-list></app-question-list>
每一个组件都包含四个文件.html文件、.css文件、.ts文件、.spec.ts文件(测试用的一般用不到)
导航搭建
首先我们来搭建导航,这里我们需要用到bootstrap所以来打开官网bootstrap中文网
点击v4找到合适的实例复制源代码,将nav部分拷贝带我们的html文件中
删除一下不必要的内容得到如下代码
<nav class="navbar navbar-expand-md navbar-inverse bg-inverse">
<div class="container">
<a href="#" class="navbar-brand">问答记录表</a>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
</ul>
</div>
</div>
</nav>
问题显示表
要想显示数据,首先我们应该拥有数据,我们在question-list.component.ts文件中export下定义一个数组来存放数据、之后我们就可以在constructor下定义数据了
就像这样
export class QuestionListComponent implements OnInit {
questions: Object[];
constructor() {
this.questions = [
{
text : 'what`s your name',
answer : '梦'
},
{
text : 'what`s your name',
answer : '第二梦'
},
{
text : 'what`s your name',
answer : '追梦人'
},
];
}
在这里我们先将数据写死,来体验数据的遍历
数据存在之后我们就可以在question-list.component.html下遍历数据
遍历数据我们需要用到*ngFor = "let 数据 of 数组"
之后就可以通过{{}}配合数据.数据项来调用数据
就像这样
<div class="question" *ngFor = "let question of questions">
<div class="card">
<div class="card-header">
{{question.text}}
</div>
<div class="card-block">
<p class="card-text">
{{question.answer}}
</p>
</div>
</div>
</div>
此时的样式并不是很美观,我们在app.component.html中为显示表加一个容器,就像这样
<app-navbar></app-navbar>
<div class="container">
<app-question-list></app-question-list>
</div>
之后在navbar.component.html下加两个br换行将导航与显示表间隔一些距离,得到以下效果
代码分离
在angular中数据应该进行模块化,如果想处理数据不应该在我们组件里,那我们数据应该在哪里呢,数据应该是存放在服务里面,那我们现在先将服务抽离出去放在服务里面,然后将服务和组件进行一个对接
那么我们接下来创建一个服务
ng g s services/data(会新建出一个文件夹来存放服务)
现在我们已经成功创建服务,然后我们需要做的是手动将创建出来的data.service.ts在app.module.ts中引用
import from 后将DataService放入providers
此时我们的服务就可以和组件正常关联了
接下来我们将组件中的数据进行抽离
我们将question-list.component.ts中constructor下写死的questions数据剪切到data.service.ts的constructor下
此时我们发现没有定义questions,所以我们在之前定义给他一个类型Question,然后在下方通过interface将Question声明一下,定义好数据类型
就像这样
import { Injectable } from '@angular/core';
@Injectable()
export class DataService {
questions:Question[];
constructor() {
this.questions = [
{
text : 'what`s your name',
answer : '梦'
},
{
text : 'what`s your name',
answer : '第二梦'
},
{
text : 'what`s your name',
answer : '追梦人'
},
];
}
}
interface Question {
text: string,
answer: string
}
此时我们还可以继续抽离一下,那就是将定义的类型进行抽离
我们在app文件夹先新创建一个models文件夹,然后新建一个Question.ts文件用来存放类型规则
接下来我们将之前定义的Question规则剪切到Question.ts文件夹下并且暴露它
export interface Question {
text: string,
answer: string
}
那么我们如果想引用它就在data.service.ts文件中import一下
import { Question } from '../models/Question';
那么我们如何将服务和组件建立连接呢
我们在data.service.ts下定义一个方法getQuestion(){}
return一个数据 这个数据就是我们的this.questions
就像这样
import { Injectable } from '@angular/core';
import { Question } from '../models/Question';
@Injectable()
export class DataService {
questions: Question[];
constructor() {
this.questions = [
{
text : 'what`s your name',
answer : '梦'
},
{
text : 'what`s your name',
answer : '第二梦'
},
{
text : 'what`s your name',
answer : '追梦人'
},
];
}
getQuestions() { return this.questions;
}
}
然后我们在question-list.component.ts中来引用他
首先我们需要import下data.service.ts
import { DataService } from '../../services/data.service';
然后在constructor下定义一个对象dataService让他归属于引入的DataService
那么我们只要有这个对象就可以调用之前在dataservice.ts中定义的方法
export class QuestionListComponent implements OnInit {
questions: Object[];
constructor(public dataService: DataService) {
}
ngOnInit() {
}
}
之后我们在ngOnInit中通过this.dataService.getQuestions()调用我们定义的方法,他会给我们返回一个数组,那么我们将返回的数组给定义好的用来存放数据的questions即可
export class QuestionListComponent implements OnInit {
questions: Object[];
constructor(public dataService: DataService) {
}
ngOnInit() {
this.questions = this.dataService.getQuestions();
}
}
此时数据已经可以正常显示出来,接下来我们就可以将之前定义的questions的object类型转换为分离出来的Question类型 首先我们在question-list.component.ts中引用一下
import { Question } from '../../models/Question';
之后就将类型修改一下即可
questions: Question[];
此时的完整代码如下
import { Component, OnInit } from '@angular/core';
import { DataService } from '../../services/data.service';
import { Question } from '../../models/Question';
@Component({
selector: 'app-question-list',
templateUrl: './question-list.component.html',
styleUrls: ['./question-list.component.css']
})
export class QuestionListComponent implements OnInit {
questions: Question[];
constructor(public dataService: DataService) {
}
ngOnInit() {
this.questions = this.dataService.getQuestions();
}
}
接下来我们为列表添加显示和隐藏按钮
首先在question-list.component.html中添加一个a标签
<div class="card-header">
{{question.text}}
<a href="#" class="btn btn-link">显示</a>
</div>
这里我们希望点击a标签实现显示和隐藏,那么我们先在数据中给他一个值
在data.service.ts中
this.questions = [
{
text : 'what`s your name',
answer : '梦',
hide : true
},
{
text : 'what`s your name',
answer : '第二梦',
hide : true
},
{
text : 'what`s your name',
answer : '追梦人',
hide : true
},
];
既然这里的数据进行了修改,那么我们在Question.ts中也应该更新
export interface Question {
text: string,
answer: string,
hide: boolean
}
接下来我们为a标签添加click事件通过控制hide属性来实现答案的显示和隐藏
<a href="#" (click)="question.hide = !question.hide" class="btn btn-link">显示</a>
那么我们添加完click事件后就需要为答案添加一个属性
<div class="card-block" [hidden]="question.hide">
<p class="card-text">{{question.answer}}</p>
</div>
此时完整代码如下
<div class="question" *ngFor = "let question of questions">
<div class="card">
<div class="card-header">
{{question.text}}
<a href="#" (click)="question.hide = !question.hide" class="btn btn-link">显示</a>
</div>
<div class="card-block" [hidden]="question.hide">
<p class="card-text">{{question.answer}}</p>
</div>
</div>
<br>
</div>
组件嵌套、父组件向子组件传值
我们现在要做到的事情是创建一个子组件让它来实现question-list.component.html的功能(遍历了question数据并将其显示出来)然后在question-list.component.html中调用它使我们的question能够被其他组件调用
首先我们来创建一个组件ng g c components/question这样我们就创建好了一个组件
首先我们将question-list.component.html文件内容复制给刚创建好的question.component.html并且删除遍历信息
就像这样
<div class="question">
<div class="card">
<div class="card-header">
{{question.text}}
<a href="#" (click)="question.hide = !question.hide" class="btn btn-link">显示</a>
</div>
<div class="card-block" [hidden]="question.hide">
<p class="card-text">{{question.answer}}</p>
</div>
</div>
<br>
</div>
之后我们在question-list.component.html中实现组件嵌套
通过调用question.component.ts中的selector: 'app-question,可以拿到数据并进行遍历操作
就像这样
<app-question *ngFor = "let question of questions"></app-question>
但是此时我们刷新页面会发现,浏览器报错,因为在question.component.ts中无法拿到text、hide、answer等数据
那么我们如何才能让它拿到这些数据呢
我们需要在question-list.component.ts中将数据传递过去
我们通过什么方式来传递过去呢
只需要在question-list.component.html中为遍历出来的数据绑定一个属性
就像这样
<app-question *ngFor = "let question of questions" [question] = "question"></app-question>
接下来我们只需要在新定义的子级组件中拿到父级组件传递过来的属性就可以了
为了顺利的拿到属性,我们需要借助input这个方法
那么我们首先在question.component.ts引入这个方法然后调用它
就像这样
import { Component, OnInit, Input } from '@angular/core';
export class QuestionComponent implements OnInit {
@Input("question") question: Question;
constructor() { }
ngOnInit() {
}
}
说明一下:首先第一行引入input这个方法 第三行调用这个方法,传入需要拿到的属性(也就是之前定义的) 然后我们给它定义一个名字并赋予一个类型,但是在这里我们没有定义,所以需要调用之前写好的Question.ts
import { Question } from '../../models/Question';
此时就能正常的拿到属性值了 答案和问题也能正常显示了
此时的完整代码如下
import { Component, OnInit, Input } from '@angular/core';
import { Question } from '../../models/Question';
@Component({
selector: 'app-question',
templateUrl: './question.component.html',
styleUrls: ['./question.component.css']
})
export class QuestionComponent implements OnInit {
@Input("question") question: Question;
constructor() { }
ngOnInit() {
}
}
事件传值(添加问题)
此时已经实现从父组件向子组件传值,那么如何从子组件向父组件传值呢,
首先,我们来新建一个组件
ng g c components/addQuestion
此时组件已经创建好了,我们首先利用bootstrap来设计一下
既然要添加问题就需要上传表单,也就需要一个submit事件,同时定义一个方法
比如这样
<div class="card">
<div class="card-hesder">
添加问题
</div>
<div class="card-block">
<form (submit) = "addQuestion()">
<div class="form-group">
<label for="text">问题</label>
<input type="text" class="form-control" [(ngModel)] = "text" name="text">
</div>
<div class="form-group">
<label for="text">答案</label>
<input type="text" class="form-control" [(ngModel)] = "answer" name="answer">
</div>
<input type="submit" value="提交" class="btn btn-pramiry">
</form>
</div>
</div>
此时我们已经有表单了,那么展示数据是在question-list.component.html中展示的所以我们来调用它
我们首先在add-question.component.ts中找到这个组件的名字selector: 'app-add-question',,然后就可以在question-list.component.html中来调用它
<app-add-question></app-add-question>
此时如果运行浏览器会报错,因为我们还没有引入formsModule
我们先在app.module.ts中来引入他
import { FormsModule } from '@angular/forms';
然后在imports中来使用它
imports: [
BrowserModule,
FormsModule
],
此时完整代码如下
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { QuestionListComponent } from './components/question-list/question-list.component';
import { DataService } from './services/data.service';
import { QuestionComponent } from './components/question/question.component';
import { AddQuestionComponent } from './components/add-question/add-question.component';
@NgModule({
declarations: [
AppComponent,
NavbarComponent,
QuestionListComponent,
QuestionComponent,
AddQuestionComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [DataService],
bootstrap: [AppComponent]
})
export class AppModule { }
此时当前效果就出现了,
接下来我们需要考虑的事情是,当我们点击表单提交的时候,表单中的数据在哪里(add-question.component.ts)我想让它传递到哪里(question-list.component.ts)去,最终的数据又存储在哪里(data.service.ts)
所以我们应该在(add-question.component.ts)中触发一个方法将数据存储到(data.service.ts)中去,所以我们就要找一个中间组件(question-list.component.ts 它与两个组件都有关系),在(add-question.component.ts)中通过触发我们写好的addQuestion()方法再触发(question-list.component.ts)中的一个方法最终将数据存储到(data.service.ts)中去
首先我们需要将方法在add-question.component.ts的oninit中写好
addQuestion() { this.questionAdded.emit({text: this.text, answer: this.answer, hide: true});
这里的emit相当于自定义事件,此时我们没有text那么我们就在上面定义它
text: string;
answer: string;
这里需要说明一下的是
子组件如果想向父组件传递数据,我们就需要使用output,那么如果想使用output就必须注册自定义事件
首先数据的起源在(add-question.component.ts)数据的根源在(data.service.ts)那么如果想传递就需要借助(question-list.component.ts)
// 数据起源:add-question -> ql -> 数据根源:data.service.ts
// output : 子组件需要传递数据到父组件,要使用output,就需要注册自定义事件
那么此时我们已经将addQuestion()方法写好了,接下来我们就要引入一下output和eventemitter
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
那么我们现在就来使用output方法 给他起一个名字(就是我们写好的方法名)然后new一个eventemitter来注册它
@Output() questionAdded = new EventEmitter
这时候我们应该给它一个类型,所以我们先来引入之前的Question
import { Question } from '../../models/Question';
然后就可以在事件中使用类型了
@Output() questionAdded = new EventEmitter<Question>();
这样这个自定义事件就注册好了 其中的emit就会将属性传递过去,也就能在父组件中进行调用了
呢么究竟该如何开始调用呢
我们只需要在(question-list.component.html)中调用一下这个事件,那么这个事件什么时候会被执行呢,很明显当form表单提交的时候就会触发,然后我们在这个事件触发后再给它一个事件
<app-add-question (questionAdded) = "addQuestion($event)"></app-add-question>
这里的$event是先接收form表单传递过来的对象 然后再通过addQuestion方法传递出去
那么接下来 我们在(question-list.component.ts)中实现一下这个方法
addQuestion(question: Question){
this.dataService.addQuestion(question);
}
调用dataService来传递数据
但是我们的(data.service.ts)中没有这个方法
那么我们来实现一下
addQuestion(question: Question) {
this.questions.push(question);
}
调用push来将数据传递进去
此时我们可以试验一下效果 当我们提交表单的时候 会在所有列表最下方添加一个标签
那么我们现在想让他添加到头部所以我们来调整一下代码
addQuestion(question: Question) {
this.questions.unshift(question);
}
删除和存储数据
接下来我们需要添加一个删除按钮实现数据的删除,同时实现将数据存储到localhost中
首先我们在(question.component.html)中添加一个删除按钮 同时将他的click事件替换成对应的方法
<a href="#" (click)="removeQuestion(question)" class="btn btn-link">删除</a>
然后来到我们的(question.component.ts)中,如果想要删除数据肯定是要操作data.service 因为数据就在他里面 所以我们先在(question.component.ts)中来引入(data.service.ts)
import { DataService } from '../../services/data.service';
然后在constructor下声明一下
constructor(
public dataService: DataService
) { }
此时就已经又有DataServices这个对象了
那么我们现在来实现一下刚才写好的click事件方法
removeQuestion(question) {
this.dataService.removeQuestion(question);
}
那么方法中的removeQuestion是什么呢,它应该是data.service.ts中的一个方法
那么我们来到(data.service.ts)来实现一下它
removeQuestion(question: Question) {
for (let i = 0; i < this.questions.length; i++) {
if (this.questions[i] === question) {
this.questions.splice(i, 1);
}
}
}
首先给它一个类型
我们需要的是for循环整个数组 和我们传过来的数据进行匹配,如果匹配上就删除它
splice 从i这个位置删掉 删除为1
此时我们已经实现了数据的删除 但是现在仍然存在问题 当我们刷新页面的时候 已经删除的数据仍然存在
那么我们应该怎么做呢
首先在data.service.ts中我们将之前写死的数据注释掉 然后在getQuestions的方法下为他添加新内容
getQuestions() {
if (localStorage.getItem("questions") == null) {
this.questions = [];
}else {
this.questions = JSON.parse(localStorage.getItem("question"));
}
return this.questions;
}
首先我们需要考虑的是数据是从localstorage中来,那么如果localstorage没有存储过question这个数据 那么就为questions赋值一个空的数组
否则就将存储的数据赋值给questions 但是这里我们通过localStorage.getItem("question")拿到的数据并不是数组或者对象 那么我们通过JSON.parse来转换它
接下来我们需要更新addQuestion方法(和getQuestions很类似)
addQuestion(question: Question) {
this.questions.unshift(question);
// 定义一个数组
let questions;
if (localStorage.getItem("question") == null) {
questions = [];
questions.unshift(question);
localStorage.setItem("questions", JSON.stringify(question));
}else {
questions = JSON.parse(localStorage.getItem("questions"));
questions.unshift(question);
// 重新设置localstorage
localStorage.setItem("questions", JSON.stringify(questions));
}
}
首先定义一个数组用来临时存储添加的数据
然后判断一下 如果localstorage没有存储过question这个数据 那么就为questions赋值一个空的数组,然后将questions传递过去,之后我们需要改变localstorage的内容 此时我们需要传递的就是字符串了
否则就将存储的数据赋值给questions 然后来修改localstorage的内容
接下来我们来修改removeQuestion方法
removeQuestion(question: Question) {
for (let i = 0; i < this.questions.length; i++) {
if (this.questions[i] === question) {
this.questions.splice(i, 1);
localStorage.setItem("questions", JSON.stringify(this.questions));
}
}
}
只需要删除之后更新localstorage中的数据
上传GitHub服务器
此时我们已经完成案例的全部内容,接下来就可以打包上传到GitHub服务器了
值得一提的是我们可以通过修改index.html中的base来让软件自动在根路径下创建文件夹存储文件
也可以通过手动复制粘贴
浙公网安备 33010602011771号