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:
Leave a comment