Wprowadzenie do Angular Material

Wprowadzenie do Angular Material

Angular material to podejście do projektowania graficznego interfejsu użytkownika które określa specyfikacja https://material.io/design. Artykuł ten bazuje na wpisie https://www.positronx.io/create-angular-material-8-custom-theme/.

Tworzymy nowy projekt Angulara:

ng new angular-material-app-theme

wybieramy (dodajemy routing oraz SCSS z ang. Syntactically Awesome Style Sheets – rozszerzona składnia CSS) :

? Would you like to add Angular routing? yes
? Which stylesheet format would you like to use? SCSS

pakiety są instalowane:

\ Installing packages...

instalujemy teraz Angular material:

ng add @angular/material

wybieramy:

? Choose a prebuilt theme name, or "custom" for a custom theme: (Use arrow keys)
> Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ]
  Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ]
  Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ]
  Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]
  Custom

dodajemy pakiety typography oraz animations:

? Set up global Angular Material typography styles? Yes
? Set up browser animations for Angular Material? Yes

Tworzymy nowy plik – angular-material.module.ts w katalogu app:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { OverlayModule } from '@angular/cdk/overlay';
import { CdkTreeModule } from '@angular/cdk/tree';
import { PortalModule } from '@angular/cdk/portal';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatRippleModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTreeModule } from '@angular/material/tree';
import { MatBadgeModule } from '@angular/material/badge';
import { MatGridListModule } from '@angular/material/grid-list';
import { MatRadioModule } from '@angular/material/radio';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatTooltipModule } from '@angular/material/tooltip';
 
 
const materialModules = [
  CdkTreeModule,
  MatAutocompleteModule,
  MatButtonModule,
  MatCardModule,
  MatCheckboxModule,
  MatChipsModule,
  MatDividerModule,
  MatExpansionModule,
  MatIconModule,
  MatInputModule,
  MatListModule,
  MatMenuModule,
  MatProgressSpinnerModule,
  MatPaginatorModule,
  MatRippleModule,
  MatSelectModule,
  MatSidenavModule,
  MatSnackBarModule,
  MatSortModule,
  MatTableModule,
  MatTabsModule,
  MatToolbarModule,
  MatFormFieldModule,
  MatButtonToggleModule,
  MatTreeModule,
  OverlayModule,
  PortalModule,
  MatBadgeModule,
  MatGridListModule,
  MatRadioModule,
  MatDatepickerModule,
  MatTooltipModule
];
 
@NgModule({
  imports: [
    CommonModule,
    ...materialModules
  ],
  exports: [
    ...materialModules
  ],
})
 
export class AngularMaterialModule { }

… to tzw. spread operator np.:

function foo(x, y, z) { }
var args = [0, 1, 2];
foo(...args);

operator ten pozwala przypisać argumenty funkcji do elementów tablicy.

Plik app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
 
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
 
/* Angular material */
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AngularMaterialModule } from './angular-material.module';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    AngularMaterialModule,
    BrowserAnimationsModule
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

CUSTOM_ELEMENTS_SCHEMA – co pozwoli nam na używanie w tym module komponentów nie-angularowych, zapisanych z użyciem “-”, oraz argumentów komponentów zapisanych w ten sam sposób. W skrócie, pozwala nam to na używanie Custom Elements w Angularze.

Edycja pliku app.component.html:

<!-- Toolbar -->
<mat-toolbar color="primary" class="header">
  <div>Material Theme</div>
  <span class="nav-tool-items">
    <mat-icon (click)="sidenav.toggle()" class="hamburger">menu</mat-icon>
  </span>
</mat-toolbar>
 
<mat-sidenav-container>
  <!-- Sidenav -->
  <mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'over' : 'side'" [(opened)]="opened" [fixedInViewport]="true"
               [fixedTopGap]>
    <mat-nav-list>
      <a mat-list-item>
        <mat-icon>dashboard</mat-icon> Dashboard
      </a>
      <a mat-list-item>
        <mat-icon>person</mat-icon> User Profile
      </a>
      <a mat-list-item>
        <mat-icon>content_paste</mat-icon> Table List
      </a>
      <a mat-list-item>
        <mat-icon>library_books</mat-icon> Typography
      </a>
      <a mat-list-item>
        <mat-icon>location_on</mat-icon> Maps
      </a>
      <a mat-list-item>
        <mat-icon>calendar_today</mat-icon> Calendar
      </a>
    </mat-nav-list>
  </mat-sidenav>
 
  <!-- Main content -->
  <mat-sidenav-content>
 
    <!-- Applying the mat-tyography class adds styles for native elements. -->
    <section class="mat-typography title-group">
      <h1>Heading Goes Here</h1>
      <mat-divider></mat-divider>
    </section>
 
    <!-- Angular material cards -->
    <div class="productCards">
      <mat-grid-list cols="4" rowHeight="200px">
        <mat-grid-tile [colspan]="3" [rowspan]="1">1
        </mat-grid-tile>
        <mat-grid-tile [colspan]="1" [rowspan]="2">2
        </mat-grid-tile>
        <mat-grid-tile [colspan]="1" [rowspan]="1">3
        </mat-grid-tile>
        <mat-grid-tile [colspan]="2" [rowspan]="1">4
        </mat-grid-tile>
      </mat-grid-list>
    </div>
 
  </mat-sidenav-content>
</mat-sidenav-container>

Edycja pliku app.component.ts:

import { Component, ViewChild, HostListener } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
 
export class AppComponent {
  opened = true;
  @ViewChild('sidenav', { static: true }) sidenav: MatSidenav;
 
  ngOnInit() {
    console.log(window.innerWidth)
    if (window.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55;
      this.opened = true;
    }
  }
 
  @HostListener('window:resize', ['$event'])
  onResize(event) {
    if (event.target.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55
      this.opened = true;
    }
  }
 
  isBiggerScreen() {
    const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    if (width < 768) {
      return true;
    } else {
      return false;
    }
  }
}

Plik styles.scss:

html,
body {
  height: 100%;
}
 
body {
  margin: 0;
  font-family: 'Roboto', sans-serif;
}
 
.header {
  justify-content: space-between;
}
 
.mat-sidenav-container {
  height: 100%;
  display: flex;
  flex: 1 1 auto;
}
 
.mat-nav-list .mat-list-item {
  font-size: 15px;
}
 
.nav-tool-items {
  display: inline-block;
  margin-right: 13px;
}
 
.mat-sidenav,
.mat-sidenav-content {
  padding: 15px;
}
 
.mat-list-item.active {
  background: rgba(0, 0, 0, .04);
}
 
.mat-sidenav-content {
  padding: 25px 40px 0;
}
 
.mat-sidenav {
  background-color: #F2F2F2;
  width: 250px;
}
 
.header {
  position: sticky;
  position: -webkit-sticky;
  top: 0;
  z-index: 1000;
}
 
mat-sidenav mat-icon {
  margin-right: 12px;
}
 
.hamburger {
  margin-top: 5px;
  cursor: pointer;
}
 
.mat-radio-button,
.mat-radio-group {
  margin-right: 25px;
}
 
.controlers-wrapper>* {
  width: 100%;
  padding: 0;
}
 
.misc-bottom-padding {
  margin: 8px 0 10px;
}
 
.misc-bottom-padding mat-label {
  margin-right: 15px;
}
 
mat-radio-group mat-radio-button {
  margin-left: 5px;
}
 
.button-wrapper button {
  margin-right: 5px;
}
 
table.mat-table,
table {
  width: 100%;
}
 
body .mat-list-item {
  margin-bottom: 10px;
}
 
.inner-wrapper {
  padding: 15px 0 130px;
  width: 100%;
}
 
.inner-wrapper mat-card {
  display: inline-block;
  margin: 0 6% 0 0;
  vertical-align: top;
  width: 44%;
}
 
.full-wrapper {
  width: 100%;
}
 
.multiple-items {
  position: relative;
}
 
.multiple-items .tooltip-info {
  right: 0;
  top: 7px;
  cursor: pointer;
  color: #a1a7c7;
  position: absolute;
  font-size: 20px;
}
 
body .push-right {
  margin-right: 10px;
}
 
.no-data {
  text-align: center;
  padding-top: 30px;
  color: #6c75a9;
}
 
.button-wrapper {
  margin: 20px 0 0 0;
}
 
.example-card {
  max-width: 400px;
}
 
.example-header-image {
  background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg');
  background-size: cover;
}
 
.title-group {
  margin-bottom: 25px;
}
 
.card-deck-container {
  width: 100%;
  max-width: 1200px;
  position: relative;
  border-radius: 2px;
  padding: 10px 10px 30px;
  margin: 10px 10px 10px 10px;
  background-color: #f5f5f5;
}
 
.card-item {
  padding: 3px 3px 3px 3px;
}
 
mat-grid-tile {
  background: lightblue;
}
 
.my-alternate-theme button {
  margin-right: 10px;
}
 
@media (max-width:1024px) {
  .inner-wrapper mat-card {
    width: 100%;
  }
 
  .mat-sidenav-content {
    padding: 20px 20px 0;
  }
 
  .misc-bottom-padding mat-label {
    display: block;
    padding-bottom: 10px;
  }
 
  .mat-sidenav {
    width: 230px;
  }
 
  .mat-nav-list .mat-list-item {
    font-size: 14px;
  }
}
 
@media (max-width:767px) {
  .nav-tool-items {
    margin-right: 0;
  }
 
  .hamburger {
    visibility: visible !important;
  }
}

Uruchamiamy aplikację:

ng serve --open

wynik:

Tworzymy własny szablon!

Plik theme.scss:

@import '~@angular/material/theming';
@include mat-core();
 
/* ======== angular material custom theme ======== */
$my-custom-primary: mat-palette($mat-deep-purple);
$my-custom-accent: mat-palette($mat-pink, 100, 500, A100);
$my-custom-warn: mat-palette($mat-lime);
 
// Light theme
$my-custom-theme: mat-light-theme($my-custom-primary, $my-custom-accent, $my-custom-warn);
 
// Dark theme
$my-custom-theme: mat-dark-theme($my-custom-primary, $my-custom-accent, $my-custom-warn);
 
// Main theme defination
@include angular-material-theme($my-custom-theme);
 
// Alternate Angular Material Theme
.my-alternate-theme {
  $my-alternate-primary: mat-palette($mat-red);
  $my-alternate-accent: mat-palette($mat-green, 400);
  $my-alternate-warn: mat-palette($mat-grey);
 
  $my-alternate-theme: mat-light-theme($my-alternate-primary, $my-alternate-accent, $my-alternate-warn);
 
  @include angular-material-theme($my-alternate-theme);
}

edycja angular.json:

"styles": [
           "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
           "src/styles.scss",
           "src/theme.scss"
         ],

pamiętajmy aby zrestartować serwer developerski po zmianach w plikach *.json. Zweryfikujmy szablon – w pliku app.component.html dodajemy:

<mat-card class="my-alternate-theme">
  My Alternate Themes:
  <button mat-raised-button color="primary">Primary</button>
  <button mat-raised-button color="accent">Accent</button>
  <button mat-raised-button color="warn">Warning</button>
</mat-card>

w wyniku otrzymujemy:

Zobacz kod na GitHubie i zapisz się na bezpłatny newsletter!

.

Leave a comment

Your email address will not be published.


*