基于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 相关文章推荐
javascript读取RSS数据
Jan 20 Javascript
js调用activeX获取u盘序列号的代码
Nov 21 Javascript
js字符编码函数区别分析
Dec 28 Javascript
Javacript实现颜色梯度变化和渐变的效果代码
May 31 Javascript
jQuery中next()方法用法实例
Jan 07 Javascript
基于cookie实现zTree树刷新后展开状态不变
Feb 28 Javascript
整理关于Bootstrap排版的慕课笔记
Mar 29 Javascript
微信小程序之绑定点击事件实例详解
Jul 07 Javascript
Vue 中的受控与非受控组件的实现
Dec 17 Javascript
Node.js API详解之 module模块用法实例分析
May 13 Javascript
如何通过vscode运行调试javascript代码
Jul 24 Javascript
JavaScript通如何过RGraph实现动态仪表盘
Oct 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中逗号与点号的区别
2013/08/05 PHP
深入解读php中关于抽象(abstract)类和抽象方法的问题分析
2014/01/03 PHP
zen_cart实现支付前生成订单的方法
2016/05/06 PHP
php解决安全问题的方法实例
2019/09/19 PHP
Use Word to Search for Files
2007/06/15 Javascript
该如何加载google-analytics(或其他第三方)的JS
2010/05/13 Javascript
jQuery UI的Dialog无法提交问题的解决方法
2011/01/11 Javascript
『jQuery』名称冲突使用noConflict方法解决
2013/04/22 Javascript
js过滤HTML标签以及空格的思路及代码
2013/05/24 Javascript
动态加载js的方法汇总
2015/02/13 Javascript
jQuery实现菜单式图片滑动切换
2015/03/14 Javascript
JS生成不重复的随机数组的简单实例
2016/07/10 Javascript
利用fecha进行JS日期处理
2016/11/21 Javascript
javascript 注释代码的几种方法总结
2017/01/04 Javascript
用React实现一个完整的TodoList的示例代码
2017/10/30 Javascript
webpack多入口多出口的实现方法
2018/08/17 Javascript
Angular 多级路由实现登录页面跳转(小白教程)
2019/11/19 Javascript
微信小程序实现同时上传多张图片
2020/02/03 Javascript
Python中类的继承代码实例
2014/10/28 Python
python的keyword模块用法实例分析
2015/06/30 Python
np.dot()函数的用法详解
2020/01/17 Python
pandas中的数据去重处理的实现方法
2020/02/10 Python
python+Selenium自动化测试——输入,点击操作
2020/03/06 Python
用python批量下载apk
2020/12/29 Python
Melissa鞋马来西亚官方网站:MDreams马来西亚
2018/04/05 全球购物
Pandora西班牙官方商店:PandoraShop.es
2020/10/05 全球购物
试用期转正鉴定评语
2014/01/27 职场文书
《兰亭集序》教学反思
2014/02/11 职场文书
细节决定成败演讲稿
2014/05/12 职场文书
综治维稳工作汇报
2014/10/27 职场文书
师范生见习报告
2014/10/31 职场文书
2014年办公室主任工作总结
2014/11/12 职场文书
简单的离婚协议书范本
2014/11/16 职场文书
小学生五一劳动节演讲稿
2015/03/18 职场文书
上学路上观后感
2015/06/16 职场文书
node.js使用express-fileupload中间件实现文件上传
2021/07/16 Javascript