Angular 入门(三) 手机端开发
上篇总结:
- 管道 pipe
相当于vue中的过滤器. 使用时: {{值 | 管道名:参数:参数...}}
angular默认官方提供了一些常用管道, 可以直接使用
也提供了自定义的方式: ng g p 管道名
- 指令 directive
生成命令: ng g d 指令名, 用于操作元素的 DOM 属性
- 生命周期
初始化 ngOnInit -> 挂载时 init -> 更新时 checked -> 销毁 destroy
- 组件间的参数传递
– 父子: 子中用 @Input() 关键词声明属性即可
– 子父: 子中要声明事件, 向外传递数据 @Output(), 父通过事件方式 传递一个 自身方法 给子, 子触发传入的方法 向方法中传递数据
– 兄弟: 子1 -> 父 -> 子2 或 服务进行全局状态管理.
- 服务: 同vue 的 Vuex
生成服务命令: ng g s 服务名
使用时, 要通过 依赖注入 机制来完成.
– 依赖注入: 在构造函数的参数 中声明类型, 组件在使用时 就必须传递对应的类型的值.
– 其中: 我们负责声明依赖即可; 注入操作由系统自动完成;
语法糖用法: constructor(权限词 参数名: 服务类型)
- 网络服务: 官方提供了很多强大的服务, 网络服务是其中一种
– 网络模块默认未加载, 必须到 app.module.ts 文件中加载才可以
话不多说直入主题 ;接下来介绍手机端的开发
Ionic_________________
Vue 有 mintUI, 制作手机端的界面
Angular 有 ionic, 制作手机端的 UI库
脚手架的安装:
npm install -g @ionic/cli --force
生成项目包
ionic start ionicApp blank // 生成 包名 类型((blank/sidemenu/tabs) /** *- blank: 基础包 *- tabs: 带有标签栏导航的包 *- sidemenu: 带有侧边栏导航的包 /
包的运行:
ionic s
vscode插件:

主要容器:
- ion-app: 根容器
- ion-header: 头部导航栏容器
- ion-content: 主体内容部分
- ion-footer: 底部容器

更多组件详细介绍请点这里
button按钮组件
<!-- 按钮组件 --> <!-- https://ionicframework.com/docs/api/button --> <ion-app> <ion-header> <ion-toolbar> <ion-title>按钮</ion-title> </ion-toolbar> </ion-header> <ion-content> <!-- 组件的风格, 默认会自动适配所在操作系统 --> <ion-button>我是按钮</ion-button> <!-- mode: 指定风格 --> <ion-button mode="ios">ios</ion-button> <ion-button mode="md">android</ion-button> <!-- 官方自带很多主题 theme --> <br /> <ion-button color="primary">primary</ion-button> <ion-button color="secondary">secondary</ion-button> <ion-button color="tertiary">tertiary</ion-button> <ion-button color="success">success</ion-button> <ion-button color="warning">warning</ion-button> <ion-button color="danger">danger</ion-button> <ion-button color="light">light</ion-button> <ion-button color="medium">medium</ion-button> <ion-button color="dark">dark</ion-button> <!-- 大小 --> <br /> <ion-button size="small">small</ion-button> <ion-button size="default">default</ion-button> <ion-button size="large">large</ion-button> <!-- 填充方式 --> <br /> <ion-button fill="outline">outline</ion-button> <ion-button fill="solid">solid</ion-button> <ion-button fill="clear">clear</ion-button> <!-- 扩展方式 --> <br /> <ion-button expand="block">block</ion-button> <ion-button expand="full">full</ion-button> <!-- 不可用 --> <br /> <ion-button disabled>不可用</ion-button> </ion-content> </ion-app>
bdge徽章
<!-- 徽章组件 badge --> <!-- https://ionicframework.com/docs/api/badge --> <ion-app> <ion-header> <ion-toolbar> <ion-title>badge徽章</ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-badge>42</ion-badge> <ion-badge color="success">success</ion-badge> <ion-badge mode="ios" color="danger">ios</ion-badge> </ion-content> </ion-app>
icon图标
<!-- 图标库 --> <!-- https://ionicons.com/ --> <ion-app> <ion-header> <ion-toolbar> <ion-title>icon</ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-icon name="add" style="font-size: 30px"></ion-icon> <ion-icon name="logo-alipay" style="font-size: 50px" color="secondary"></ion-icon> <!-- 可以搭配按钮使用 --> <ion-button> <ion-icon name="add"></ion-icon> </ion-button> </ion-content> </ion-app>
card卡片组件
<!-- 卡片组件 --> <!-- https://ionicframework.com/docs/api/card --> <ion-app> <ion-header> <ion-toolbar> <ion-title>卡片</ion-title> </ion-toolbar> </ion-header> <ion-content> <!-- i-card-full --> <ion-card> <img src="http://101.96.128.94:9999/mfresh/news_imgs/01.jpg" alt="" /> <ion-card-header> <ion-card-subtitle>此净化器功能优秀, 节能减排, 价格优美.</ion-card-subtitle> <ion-card-title>净美仕净化器</ion-card-title> </ion-card-header> <ion-card-content> 打掉的的阿达的看了看.马卡龙文件但大家拿健康的宽带连接我已大奖等你 </ion-card-content> </ion-card> </ion-content> </ion-app>
<!-- 横向滚动展示 --> <!-- https://ionicframework.com/docs/api/slides --> <ion-app> <ion-header> <ion-toolbar> <ion-title>横向滚动展示</ion-title> </ion-toolbar> </ion-header> <ion-content> <!-- slides: 容器; slide: 项目 --> <!-- pager: 页数指示 --> <!-- loop: 循环滚动 --> <!-- autoplay: 自动滚动 --> <!-- disableOnInteraction: 用户触摸之后 让自动滚动失效; 默认为true --> <ion-slides pager [options]="{ loop: true, autoplay: { disableOnInteraction: false } }" > <!-- 在ts文件先定义好图片路径的数组 --> <ion-slide *ngFor="let item of images"> <img [src]="item" alt="" /> </ion-slide> </ion-slides> </ion-content> </ion-app>
grid栅格
<!-- 栅格布局 --> <!-- https://ionicframework.com/docs/api/grid --> <ion-app> <ion-header> <ion-toolbar> <ion-title>栅格布局</ion-title> </ion-toolbar> </ion-header> <ion-content> <!-- i-grid --> <!-- 在scss中定义样式grid-lianxi --> <ion-grid fixed class="grid-lianxi"> <ion-row> <!-- 每行最多12列, size代表列数 --> <ion-col size="6">6</ion-col> <ion-col size="6">6</ion-col> </ion-row> <ion-row> <!-- 超出会换行 --> <ion-col size="4">4</ion-col> <ion-col size="5">5</ion-col> <ion-col size="6">6</ion-col> <ion-col size="7">7</ion-col> </ion-row> <ion-row> <ion-col size="3">3</ion-col> <!-- 不写会自动填充剩余份数 --> <ion-col>不写份数</ion-col> </ion-row> </ion-grid> </ion-content> </ion-app>
综合这些组件的一个例子
html:
<!-- 新闻 --> <ion-app> <ion-header> <ion-toolbar color="danger"> <ion-title>网易新闻</ion-title> </ion-toolbar> </ion-header> <!-- scrollEvents: 允许监听滚动事件, 默认false --> <!-- ionScroll: 滚动事件 --> <ion-content *ngIf="news" #content [scrollEvents]="true" (ionScroll)="onScroll($event)" > <!-- 下拉刷新 --> <ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)"> <ion-refresher-content></ion-refresher-content> </ion-refresher> <ion-grid fixed class="news-grid"> <ion-row> <ion-col size="6" *ngFor="let item of news.result"> <ion-card> <img [src]="item.image" alt="" /> <div class="content"> <div>{{ item.title }}</div> <div>{{ item.passtime }}</div> </div> </ion-card> </ion-col> </ion-row> </ion-grid> <!-- 加载更多 --> <!-- threshold: 触底阈值. 10% 代表剩余未显示区域的高度 是 显示区域高度 10% 时触发触底操作 --> <ion-infinite-scroll threshold="10%" position="bottom" (ionInfinite)="loadData($event)" > <ion-infinite-scroll-content loadingSpinner="dots" loadingText="加载中..." > </ion-infinite-scroll-content> </ion-infinite-scroll> <!-- 悬浮按钮: 可以悬浮在已有内容的上方 --> <!-- vertical: 竖向, 可选值 top/center/bottom 对应 上/中/下 --> <!-- horizontal: 横向, 可选值 start/center/end --> <ion-fab [hidden]="!showGoTop" vertical="bottom" horizontal="end" slot="fixed" > <ion-fab-button (click)="goTop()"> <ion-icon name="arrow-up-outline"></ion-icon> </ion-fab-button> </ion-fab> </ion-content> </ion-app>
scss:
.grid-lianxi { border: 1px solid red; ion-row { border: 2px solid orange; ion-col { border: 1px solid blue; } } } .news-grid { padding: 3px; img { width: 100%; } ion-col { padding: 3px; } ion-card { margin: 0; .content { padding: 4px; div:first-child { color: black; text-overflow: -o-ellipsis-lastline; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; font-size: 1.1em; margin-bottom: 3px; } } } }
ts:
import { HttpClient } from '@angular/common/http';
import { Component, ViewChild } from '@angular/core';
import { IonContent } from '@ionic/angular';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent {
// 掌控子元素, 绑定变量 content 到 ion-content 组件
@ViewChild('content') content: IonContent;
showGoTop = false; //是否要显示 回到顶部 按钮
// 绑定到 ion-conent 的滚动监听事件, 当滚动时自动触发
onScroll(event) {
console.log(event);
const y = event.detail.currentY; //当前竖向滚动的 y
//如果 y > 1200 就显示回到头部按钮
this.showGoTop = y > 1200;
}
goTop() {
// scrollToTop(动画时长); 单位毫秒
this.content.scrollToTop(250);
}
constructor(public http: HttpClient) {}
getUrl(pno: number) {
return 'https://api.apiopen.top/getWangYiNews?page=' + pno;
}
news: News;
ngOnInit(): void {
const url = this.getUrl(1);
this.http.get(url).subscribe((res: News) => {
console.log(res);
this.news = res;
});
}
page = 1;
//下拉刷新
doRefresh(event) {
console.log('下拉刷新触发!');
const url = this.getUrl(1);
this.http.get(url).subscribe((res: News) => {
console.log(res);
this.news = res;
this.page = 1; //重置为 第一页
event.target.complete(); //结束下拉刷新动画的显示
});
}
// 触底时触发
loadData(event) {
// console.log(event);
const url = this.getUrl(this.page + 1);
this.http.get(url).subscribe((res: News) => {
console.log(res);
// 合并新旧数据中的数组
res.result = this.news.result.concat(res.result);
this.news = res;
this.page++; //成功后, 页数+1
//通知组件此次加载操作已完成; 这样组件才会监听下一次
event.target.complete();
});
}
images = [
'http://101.96.128.94:9999/mfresh/images/banner_01.jpg',
'http://101.96.128.94:9999/mfresh/images/banner_02.jpg',
'http://101.96.128.94:9999/mfresh/images/banner_03.jpg',
'http://101.96.128.94:9999/mfresh/images/banner_04.jpg',
];
}
/////////////////////////////////////////
///////// 返回值结构声明
interface News {
code: number;
message: string;
result: NewsResult[];
}
interface NewsResult {
image: string;
passtime: string;
path: string;
title: string;
}
item组件
<!-- item组件 --> <!-- https://ionicframework.com/docs/api/item --> <ion-app> <ion-header> <ion-toolbar> <ion-title>item</ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-item> <ion-label>Awesome Label</ion-label> </ion-item> <!-- button: 可以点击 --> <!-- detail: 右侧出现 箭头图标 --> <ion-item button detail> <ion-label>Click me</ion-label> </ion-item> <ion-item> <!-- slot: 插槽; item具有左右两个插槽, start 和 end --> <ion-badge slot="end">42</ion-badge> <ion-label>Item Avatar</ion-label> </ion-item> <ion-item> <!-- avatar: 把内部图片变圆形 --> <ion-avatar slot="start"> <img src="http://www.gx8899.com/uploads/allimg/2017112908/pc2e3vft403.jpg" /> </ion-avatar> <ion-label> <h3>重庆天花板</h3> <p>今天的面试题超级简单, 妥妥的~</p> </ion-label> <ion-badge color="danger" slot="end">42</ion-badge> </ion-item> <ion-item> <ion-icon name="settings" slot="start"></ion-icon> <ion-label>设置</ion-label> </ion-item> <ion-item lines="none"> <ion-label>没有分割线</ion-label> </ion-item> <ion-item lines="full"> <ion-label>分割线充满</ion-label> </ion-item> <ion-item> <ion-label>用户名:</ion-label> <ion-input placeholder="账号/手机号/邮箱"></ion-input> </ion-item> <ion-item> <ion-label>密码:</ion-label> <ion-input placeholder="密码" type="password"></ion-input> </ion-item> <!-- 分组 --> <ion-item-group> <ion-item-divider> <ion-label>A</ion-label> </ion-item-divider> <ion-item> <ion-label>阿宝</ion-label> </ion-item> <ion-item> <ion-label>阿花</ion-label> </ion-item> <ion-item-divider> <ion-label>B</ion-label> </ion-item-divider> <ion-item> <ion-label>宝蓝</ion-label> </ion-item> <ion-item> <ion-label>boner</ion-label> </ion-item> </ion-item-group> <!-- i-item-sliding: 通过滑动 显示隐藏操作 --> </ion-content> </ion-app>
例:网络模块+组件+跨域
html:
<ion-app> <ion-header> <ion-toolbar color="warning"> <ion-title>直播列表</ion-title> </ion-toolbar> </ion-header> <ion-content *ngIf="res" #content [scrollEvents]="true" (ionScroll)="onScroll($event)"> <ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)"> <ion-refresher-content></ion-refresher-content> </ion-refresher> <ion-grid fixed class="douyu-grid"> <ion-row> <ion-col size="6" *ngFor="let item of res.data.list"> <ion-card> <div class="card-header"> <img [src]="item.roomSrc" alt="" /> <div class="hn">{{ item.hn }}</div> <div class="nick"> <ion-avatar> <img [src]="item.avatar" /> </ion-avatar> <span>{{ item.nickname }}</span> </div> </div> <div class="room-name">{{ item.roomName }}</div> </ion-card> </ion-col> </ion-row> </ion-grid> <!-- i-infinite-scroll --> <ion-infinite-scroll threshold="10%" position="bottom" (ionInfinite)="loadData($event)"> <ion-infinite-scroll-content loadingSpinner="bubbles" loadingText="加载中..."> </ion-infinite-scroll-content> </ion-infinite-scroll> <!-- 悬浮按钮 --> <ion-fab [hidden]="!showGoTop" vertical="bottom" horizontal="end" slot="fixed"> <ion-fab-button (click)="goTop()"> <ion-icon name="arrow-up-outline"></ion-icon> </ion-fab-button> </ion-fab> </ion-content> </ion-app>
ts:(注意在app.module.ts中引入HttpClientModule模块)
import { HttpClient } from '@angular/common/http';
import { Component, ViewChild } from '@angular/core';
import { IonContent } from '@ionic/angular';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent {
@ViewChild('content') content: IonContent;
showGoTop = false;
onScroll(e) {
this.showGoTop = e.detail.currentY > 1000;
}
goTop() {
this.content.scrollToTop(200);
}
getUrl(page: number) {
// 请求地址以 /douyu 开头, 代理就会进行解析
return `/douyu/api/room/list?page=${page}&type=yz`;
}
// 依赖注入机制: 声明依赖
constructor(public http: HttpClient) { }
res: Douyu;
// 下拉刷新
doRefresh(e) {
const url = this.getUrl(1);
this.http.get(url).subscribe((res: Douyu) => {
this.res = res;
e.target.complete(); //终止下拉刷新动画
});
}
// 加载更多
loadData(e) {
const url = this.getUrl(this.res.data.nowPage + 1);
this.http.get(url).subscribe((res: Douyu) => {
//合并新旧数据数组, 然后把新的数据 替换旧的
res.data.list = this.res.data.list.concat(res.data.list);
this.res = res;
e.target.complete(); // 声明本次加载更多操作已完成, 准备下一次
});
}
ngOnInit(): void {
this.http.get(this.getUrl(1)).subscribe((res: Douyu) => {
console.log(res);
this.res = res;
// 默认数据量8条: 不足以充满整个页面, 就会导致没有触底操作
// 再请求第二页数据 与第一页合并在一起
this.http.get(this.getUrl(2)).subscribe((res: Douyu) => {
res.data.list = this.res.data.list.concat(res.data.list);
this.res = res;
});
});
}
}
/////////////////////////////////////////////
///// 返回值的数据结构声明
interface Douyu {
code: number;
data: DouyuData;
}
interface DouyuData {
cate2Id: number;
list: DouyuDataList[];
nowPage: number;
pageCount: number;
}
interface DouyuDataList {
avatar: string;
cate1Id: number;
cate2Id: number;
hn: string;
isLive: number;
isVertical: number;
liveCity: string;
nickname: string;
rid: number;
roomName: string;
roomSrc: string;
verticalSrc: string;
vipId: number;
}
scss:
.douyu-grid { padding: 3px; ion-col { padding: 3px; ion-card { margin: 0; img { width: 100%; } } } } .card-header { position: relative; .hn { position: absolute; top: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); color: white; padding: 4px; } .nick { position: absolute; bottom: 0; left: 0; display: flex; background-color: rgba(0, 0, 0, 0.5); align-items: center; color: white; right: 0; ion-avatar { width: 20px; height: 20px; margin: 3px; } } img { display: block; } } .room-name { padding: 4px; color: black; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
创建src目录下proxy.config.json文件跨域 在angular.json中引入

浙公网安备 33010602011771号