基于Angular 8和Bootstrap 4实现动态主题切换的示例代码


Posted in Javascript onFebruary 11, 2020

效果

首先看看效果:

基于Angular 8和Bootstrap 4实现动态主题切换的示例代码

本文将介绍如何基于Angular 8和Bootstrap 4来实现上面的主题切换效果。

设计

遵循Bootstrap的设计,我们会使用 bootswatch.com 提供的免费主题来实现上面的效果。Bootswatch为前端程序员提供了多达21种免费的Bootstrap主题,并且提供了API文档 和 实例页面 ,介绍如何在HTML+jQuery的环境中实现主题切换。其实,我们也可以使用Bootstrap官网提供的主题设计工具来设计自己的主题,这些自定义的主题也是可以用在本文介绍的方法里的,只需要替换相关的资源地址就可以。如果你打开Bootswatch的API,你就会看到各种主题的元数据信息,我们可以使用其中的cssMin链接来替换主页的link地址,以达到切换主题的目的。

在开工之前,还是要做一些粗略的设计。为了简单起见,我使用Bootstrap的Navbar来完成这个功能,因为Navbar的代码可以直接从Bootstrap官网拷贝过来,稍微改改就行。不同的是,我将Navbar封装在一个组件(Component)里,这样做的好处是,可以将切换主题的功能封装起来,以实现模块化的设计。下图展示了这一设计:

基于Angular 8和Bootstrap 4实现动态主题切换的示例代码

基本流程如下:

  • theme.service.ts提供从Bootswatch获取主题信息的服务
  • 主应用app.component.ts调用theme.service.ts,获取主题信息,并将主题信息绑定到nav-bar.component.ts组件
  • 第一次执行站点,站点会使用定义在environment.ts中的默认值作为默认主题,当每次切换主题时,会将所选主题绑定到nav-bar.component.ts上,用来在下拉菜单中标注已选主题,并将所选主题名称保存在LocalStorage,以便下次启动站点时直接应用已选主题
  • nav-bar.component.ts组件会在Navbar上的dropdown中列出所有的主题名称,并且标注所选主题,当用户点击某个主题名称时,就会触发themeSelectionChanged事件,app.component.ts接收到这个事件后,就会替换主页的link,完成主题设置

步骤

首先,根据Bootswatch API所返回的数据结构,定义一个数据模型:

export class ThemeDefinition {
 name: string;
 description: string;
 thumbnail: string;
 preview: string;
 css: string;
 cssMin: string;
 cssCdn: string;
 scss: string;
 scssVariables: string;
}
 
export class Themes {
 version: String;
 themes: ThemeDefinition[];
}

然后,创建theme.service.ts服务,用来调用Bootswatch API:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Themes } from '../models/themes';
 
@Injectable({
 providedIn: 'root'
})
export class ThemeService {
 
 constructor(private http: HttpClient) { }
 
 getThemes(): Observable<Themes> {
 return this.http.get<Themes>('https://bootswatch.com/api/4.json');
 }
}

接下来,创建Navbar组件,关键代码部分就是将主题的名称绑定到dropdown上,并根据选择的主题名称决定当前所显示的主题名称是否应该是active的。当然,dropdown的每个item还应该响应用户的点击事件:

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
 <a class="navbar-brand" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ><i class="fab fa-acquisitions-incorporated"></i></a>
 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
 aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
 </button>
 <div class="collapse navbar-collapse" id="navbarSupportedContent">
 <ul class="navbar-nav mr-auto">
  <li class="nav-item active">
  <a class="nav-link" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Home <span class="sr-only">(current)</span></a>
  </li>
  <li class="nav-item">
  <a class="nav-link" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >Link</a>
  </li>
  <li *ngIf="themes" class="nav-item dropdown">
  <a class="nav-link dropdown-toggle" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" id="navbarDropdown" role="button" data-toggle="dropdown"
   aria-haspopup="true" aria-expanded="false">
   主题
  </a>
  <div class="dropdown-menu" aria-labelledby="navbarDropdown">
   <a *ngFor="let theme of themes.themes"
   [className]="theme.name === selectedTheme ? 'dropdown-item active' : 'dropdown-item'" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" 
   (click)="onThemeItemSelected($event)">{{theme.name}}</a>
  </div>
  </li>
 </ul>
 </div>
</nav>

Navbar组件的代码如下:

import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
import { Themes } from 'src/app/models/themes';
import { ThemeService } from 'src/app/services/theme.service';
import { ThemeDefinition } from 'src/app/models/theme-definition';
 
@Component({
 selector: 'app-nav-bar',
 templateUrl: './nav-bar.component.html',
 styleUrls: ['./nav-bar.component.css']
})
export class NavBarComponent implements OnInit {
 
 @Input() themes: Themes;
 @Input() selectedTheme:string;
 @Output() themeSelectionChanged : EventEmitter<ThemeDefinition> = new EventEmitter();
 
 constructor(private themeService: ThemeService) { }
 
 ngOnInit() {
 }
 
 onThemeItemSelected(event: any) {
 const selectedThemeName = event.target.text;
 const selectedTheme = this.themes.themes.find(t => t.name === selectedThemeName);
 this.themeSelectionChanged.emit(selectedTheme);
 }
}

在onThemeItemSelected事件处理函数中,会读取被点击dropdown item的名称,根据该名称找到所选的主题,然后将其作为事件数据,发起themeSelectionChanged事件,然后,就是app.component.ts来处理这个事件了。在该事件处理函数中,从事件数据获取主题信息,然后调用applyTheme方法来应用主题:

import { Component, OnInit } from '@angular/core';
import { ThemeDefinition } from './models/theme-definition';
import { Themes } from './models/themes';
import { ThemeService } from './services/theme.service';
import { environment } from 'src/environments/environment';
import { StorageMap } from '@ngx-pwa/local-storage';
 
@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
 title = 'nblogger';
 themes: Themes;
 selectedTheme: string;
 
 constructor(private themeService: ThemeService,
 private storage: StorageMap) {
 
 }
 
 ngOnInit() {
 this.themeService.getThemes()
 .subscribe(data => {
  this.themes = data;
  this.storage.get('app-theme-name').subscribe(name => {
  const themeName = name ? name : environment.defaultTheme;
  const currentTheme = this.themes.themes.find(t => t.name === themeName);
  this.applyTheme(currentTheme);
  });
  
 });
 }
 
 onThemeSelectionChanged(event: ThemeDefinition) {
 this.applyTheme(event);
 }
 
 private applyTheme(def: ThemeDefinition): void {
 this.storage.set('app-theme-name', def.name).subscribe(()=>{});
 this.selectedTheme = def.name;
 const links = document.getElementsByTagName('link');
 for(let i = 0; i < links.length; i++) {
  const link = links[i];
  if (link.getAttribute('rel').indexOf('style') !== -1 &&
  link.getAttribute('type').indexOf('text') !== -1) {
   link.setAttribute('href', def.cssMin);
  }
 }
 }
}

在applyTheme方法中,首先会将所选主题名称设置到LocalStorage中,以便下次打开页面的时候能够直接应用主题;然后,从当前document中找到所需的link tag,并将其href值替换为所选主题信息的cssMin链接地址(内容可以参考Bootswatch的API结果)以此完成主题替换。

当重新打开页面时,app.component.ts中的ngOnInit初始化方法会被首先调用,它会通过theme.service.ts来读取主题信息,之后判断LocalStorage中是否有已经设置好的主题。如果有,则使用该主题,否则就从environment.ts的默认值中选择主题名称进行设置。

app.component.ts所使用的template就比较简单,主体是对Navbar组件的引用,还可以加一些额外的HTML元素进行效果测试:

<app-nav-bar [themes]="themes" [selectedTheme]="selectedTheme" (themeSelectionChanged)="onThemeSelectionChanged($event)"></app-nav-bar>
<div class="container">
 <article>
 <h1>Heading 1</h1>
 <h2>Heading 2</h2>
 <h3>Heading 3</h3>
 <h4>Heading 4</h4>
 </article>
 <div class="alert alert-primary" role="alert">
 这是一个警告框
 </div>
 <div class="alert alert-secondary" role="alert">
 A simple secondary alert—check it out!
 </div>
 <div class="alert alert-success" role="alert">
 A simple success alert—check it out!
 </div>
 <div class="alert alert-danger" role="alert">
 A simple danger alert—check it out!
 </div>
 <div class="alert alert-warning" role="alert">
 A simple warning alert—check it out!
 </div>
 <div class="alert alert-info" role="alert">
 A simple info alert—check it out!
 </div>
 <div class="alert alert-light" role="alert">
 A simple light alert—check it out!
 </div>
 <div class="alert alert-dark" role="alert">
 A simple dark alert—check it out!
 </div>
 
 <button type="button" class="btn btn-primary">Primary</button>
 <button type="button" class="btn btn-secondary">Secondary</button>
 <button type="button" class="btn btn-success">成功</button>
 <button type="button" class="btn btn-danger">失败</button>
 <button type="button" class="btn btn-warning">警告</button>
 <button type="button" class="btn btn-info">信息</button>
 <button type="button" class="btn btn-light">Light</button>
 <button type="button" class="btn btn-dark">Dark</button>
 
 <button type="button" class="btn btn-link">Link</button>
</div>

当然,记得在index.html中加入link的占位符,以便上面的applyTheme方法能够找到它:

<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <title>Nblogger</title>
 <base href="/" rel="external nofollow" >
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico" rel="external nofollow" >
 <link rel="stylesheet" type="text/css" href="#" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >
</head>
<body>
 <app-root></app-root>
</body>
</html>

总结

我们可以将Bootswatch的所有主题下载到本地,由本地服务来提供主题的API,这样切换主题会变得更快,也可以自己自定义主题然后扩展这个自制的本地API来提供更丰富的主题,根据需要来定吧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js 创建书签小工具之理论
Feb 25 Javascript
可兼容IE的获取及设置cookie的jquery.cookie函数方法
Sep 02 Javascript
jquery数组封装使用方法分享(jquery数组遍历)
Mar 25 Javascript
jQuery设置和获取HTML、文本和值示例
Jul 08 Javascript
jQuery事件绑定和委托实例
Nov 25 Javascript
jQuery统计指定子元素数量的方法
Mar 17 Javascript
angular中实现控制器之间传递参数的方式
Apr 24 Javascript
解决Angular4项目部署到服务器上刷新404的问题
Aug 31 Javascript
Angular动态绑定样式及改变UI框架样式的方法小结
Sep 03 Javascript
微信公众号获取用户地理位置并列出附近的门店的示例代码
Jul 25 Javascript
Vue.js组件props数据验证实现详解
Oct 19 Javascript
JS函数进阶之prototy用法实例分析
Jan 15 Javascript
原生js实现点击轮播切换图片
Feb 11 #Javascript
node.js中process进程的概念和child_process子进程模块的使用方法示例
Feb 11 #Javascript
小程序如何定位所在城市及发起周边搜索
Feb 11 #Javascript
JS+DIV实现拖动效果
Feb 11 #Javascript
vue项目中使用particles实现粒子背景效果及遇到的坑(按钮没有点击响应)
Feb 11 #Javascript
原生js拖拽实现图形伸缩效果
Feb 10 #Javascript
js实现单元格拖拽效果
Feb 10 #Javascript
You might like
php中require和require_once的区别说明
2014/02/27 PHP
PHP图像处理类库及演示分享
2015/05/17 PHP
PHP设计模式之工厂模式实例总结
2017/09/01 PHP
PHP Pipeline 实现中间件的示例代码
2020/04/26 PHP
风吟的小型JavaScirpt库 (FY.JS).
2010/03/09 Javascript
javascript 用函数语句和表达式定义函数的区别详解
2014/01/06 Javascript
js转化毫秒为时间格式代码
2014/04/10 Javascript
js判断手机和pc端选择不同执行事件的方法
2015/01/30 Javascript
JavaScript转换二进制编码为ASCII码的方法
2015/04/16 Javascript
JavaScript中的setUTCDate()方法使用详解
2015/06/11 Javascript
高性能JavaScript 重排与重绘(2)
2015/08/11 Javascript
浅析Bootstrap组件之面板组件
2016/05/04 Javascript
微信小程序 删除项目工程实现步骤
2016/11/10 Javascript
ES6中module模块化开发实例浅析
2017/04/06 Javascript
Vue表单验证插件Vue Validator使用方法详解
2017/04/07 Javascript
vue-cli常用设置总结
2018/02/24 Javascript
vue cli2.0单页面title修改方法
2018/06/07 Javascript
JavaScript设计模式之原型模式分析【ES5与ES6】
2018/07/26 Javascript
在Vue项目中取消ESLint代码检测的步骤讲解
2019/01/27 Javascript
Python 元类使用说明
2009/12/18 Python
Python中使用socket发送HTTP请求数据接收不完整问题解决方法
2015/02/04 Python
使用Python编写提取日志中的中文的脚本的方法
2015/04/30 Python
import的本质解析
2017/10/30 Python
python实现ID3决策树算法
2017/12/20 Python
编写多线程Python服务器 最适合基础
2018/09/14 Python
Python Opencv实现图像轮廓识别功能
2020/03/23 Python
Pytorch之保存读取模型实例
2019/12/30 Python
Anaconda使用IDLE的实现示例
2020/09/23 Python
CSS3 制作绽放的莲花采用效果叠加实现
2013/01/31 HTML / CSS
Web时代变迁及html5与html4的区别
2016/01/06 HTML / CSS
学生个人求职自荐信格式
2013/09/23 职场文书
土木工程师岗位职责
2013/11/24 职场文书
咖啡书吧创业计划书
2014/01/13 职场文书
餐厅感恩节活动策划方案
2014/10/11 职场文书
python geopandas读取、创建shapefile文件的方法
2021/06/29 Python
分布式Redis Cluster集群搭建与Redis基本用法
2022/02/24 Redis