Materiały na warsztat #4 2020-12-03

Na dzisiejszych warsztatach zapoznamy się z upiększaniem Twojej aplikacji. Do styli użyjemy biblioteki Angular Material plus Flex Layout.

Zaczynajmy.

Instalacja Angular Material

ng new zrobmy-quizz

? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS
cd zrobmy-quizz
code .

https://material.angular.io/guide/getting-started

ng add @angular/material

? Choose a prebuilt theme name, or "custom" for a custom theme: Deep Purple/Amber
? Set up global Angular Material typography styles? Yes
? Set up browser animations for Angular Material? Yes

✔ Packages installed successfully.
UPDATE src/app/app.module.ts 
UPDATE angular.json 
UPDATE src/index.html 
UPDATE src/styles.scss 

app.module.ts

import { MatSliderModule } from '@angular/material/slider';

....

  imports: [
....
    BrowserAnimationsModule,
    MatSliderModule
  ],

app.component.html

<mat-slider min="1" max="100" step="1" value="1"></mat-slider>

<router-outlet></router-outlet>
ng serve -o

Kontrolki (Komponenty UI)

https://material.angular.io/components/categories

app.component.html

<mat-card class="example-card">
  <mat-card-header>
    <div mat-card-avatar class="example-header-image"></div>
    <mat-card-title>Shiba Inu</mat-card-title>
    <mat-card-subtitle>Dog Breed</mat-card-subtitle>
  </mat-card-header>
  <img mat-card-image src="https://material.angular.io/assets/img/examples/shiba2.jpg" alt="Photo of a Shiba Inu">
  <mat-card-content>
    <p>
      The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan.
      A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally
      bred for hunting.
    </p>
  </mat-card-content>
  <mat-card-actions>
    <button mat-button>LIKE</button>
    <button mat-button>SHARE</button>
  </mat-card-actions>
</mat-card>

app.component.scss

.example-card {
  max-width: 400px;
}

.example-header-image {
  background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg');
  background-size: cover;
}

Tworzenie modułu

cd src/app
ng g m material

material.module.ts

import { MatSliderModule } from '@angular/material/slider';
import { MatCardModule } from '@angular/material/card';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    MatSliderModule,
    MatCardModule,
  ],
  exports: [
    MatSliderModule,
    MatCardModule,
  ]
})

app.module.ts

import { MaterialModule } from './material/material.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MaterialModule
  ],

Inne kontrolki

material.module.ts

import { MatCardModule } from '@angular/material/card';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatNativeDateModule } from '@angular/material/core';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    MatCardModule,
    MatDatepickerModule,
    MatNativeDateModule,
    MatFormFieldModule,
    MatInputModule,
  ],
  exports: [
    MatCardModule,
    MatDatepickerModule,
    MatNativeDateModule,
    MatFormFieldModule,
    MatInputModule,
  ]
})

app.component.html

<mat-form-field appearance="fill">
  <mat-label>Choose a date</mat-label>
  <input matInput [matDatepicker]="picker">
  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  <mat-datepicker #picker></mat-datepicker>
</mat-form-field>

<router-outlet></router-outlet>

Materiał Schematics

https://material.angular.io/guide/schematics

ng generate @angular/material:navigation navigation

material.module.ts

import { MatCardModule } from '@angular/material/card';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatNativeDateModule } from '@angular/material/core';

import { LayoutModule } from '@angular/cdk/layout';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    MatCardModule,
    MatDatepickerModule,
    MatNativeDateModule,
    MatFormFieldModule,
    MatInputModule,
    LayoutModule,
    MatToolbarModule,
    MatButtonModule,
    MatSidenavModule,
    MatIconModule,
    MatListModule
  ],
  exports: [
    MatCardModule,
    MatDatepickerModule,
    MatNativeDateModule,
    MatFormFieldModule,
    MatInputModule,
    LayoutModule,
    MatToolbarModule,
    MatButtonModule,
    MatSidenavModule,
    MatIconModule,
    MatListModule
  ]
})

app.module.ts

import { MaterialModule } from './material/material.module';
import { NavigationComponent } from './navigation/navigation.component';

@NgModule({
  declarations: [
    AppComponent,
    NavigationComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MaterialModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})

app.component.html

<app-navigation></app-navigation>

navigation.component.html

.....

      <span>Zróbmy quiz</span>
    </mat-toolbar>

    <router-outlet></router-outlet>

  </mat-sidenav-content>
</mat-sidenav-container>

Let’s do the quiz

ng g c quiz1

app-routing.module.ts

const routes: Routes = [
  { path: '', component: Quiz1Component },
  { path: 'quiz1', component: Quiz1Component },
];

navigation.component.html

    <mat-nav-list>
      <a mat-list-item [routerLink]="['/quiz1']">Quiz 1</a>
      <a mat-list-item href="#">Link 2</a>
      <a mat-list-item href="#">Link 3</a>
    </mat-nav-list>

https://material.angular.io/components/stepper/api

material.module.ts

import { MatStepperModule } from '@angular/material/stepper';
import { MatRadioModule } from '@angular/material/radio';

app.module.ts

import { ReactiveFormsModule } from '@angular/forms';
...

  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MaterialModule,
    ReactiveFormsModule,
  ],

quiz1.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-quiz1',
  templateUrl: './quiz1.component.html',
  styleUrls: ['./quiz1.component.scss']
})
export class Quiz1Component implements OnInit {

  isLinear = false;

  pytania = [
    {
      pytanie: 'Jakie mleko daje czarna krowa w kropki bordo?',
      mozliwosci: ['Białe', 'Czarne', 'Bordowe'],
      odpowiedz: ''
    },
    {
      pytanie: 'Jakiego koloru są biedroneczki w kropeczki?',
      mozliwosci: ['Czerwone', 'Czarne', 'Czarne w czerwone kropki', 'Czerwone w czarne kropki'],
      odpowiedz: ''
    },
  ];

  constructor(private _formBuilder: FormBuilder) { }

  ngOnInit(): void {
  }

}

quiz1.component.html

<mat-vertical-stepper [linear]="isLinear" #stepper>
  <ng-container *ngFor="let pytanie of pytania; let i = index">
    <mat-step [stepControl]="i">
      <!-- <form [formGroup]="i"> -->
        <ng-template matStepLabel>{{ pytanie.pytanie }}</ng-template>
        <!-- <mat-form-field> -->
        <mat-radio-group>
          <mat-radio-button *ngFor="let mozliwosc of pytanie.mozliwosci" [value]="mozliwosc">
            {{ mozliwosc }}
          </mat-radio-button>
        </mat-radio-group>
        <!-- </mat-form-field> -->
        <div>
          <button mat-button matStepperNext>Następne pytanie</button>
        </div>
      <!-- </form> -->
    </mat-step>
  </ng-container>

  <mat-step>
    <ng-template matStepLabel>Wynik</ng-template>
    <p>Skończone.</p>
    <div>
      <button mat-button matStepperPrevious>Cofnij</button>
      <button mat-button (click)="stepper.reset()">Od nowa</button>
    </div>
  </mat-step>
</mat-vertical-stepper>

quiz1.component.scss

.mat-stepper-horizontal {
  margin-top: 8px;
}

.mat-form-field {
  margin-top: 16px;
}

mat-radio-group {
  display: flex;
  flex-direction: column;
  margin: 15px 0;
}

mat-radio-button {
  margin: 5px;
}

Instalacja Flex Layout

https://github.com/angular/flex-layout

ng g c quiz2

npm i -s @angular/flex-layout

app.module.ts

import { FlexLayoutModule } from '@angular/flex-layout';
...

@NgModule({
    ...
    imports: [ FlexLayoutModule ],
    ...
});

app-routing.module.ts

const routes: Routes = [
  { path: '', component: Quiz1Component },
  { path: 'quiz1', component: Quiz1Component },
  { path: 'quiz2', component: Quiz2Component },
];

navigation.component.html

    <mat-nav-list>
      <a mat-list-item [routerLink]="['/quiz1']">Quiz 1</a>
      <a mat-list-item [routerLink]="['/quiz2']">Quiz 2</a>
      <a mat-list-item href="#">Link 3</a>
    </mat-nav-list>

quiz2.component.html

<div class="zakres">
  <div class="content" fxLayout="row" fxLayout.xs="column" fxFlexFill>
    <div fxFlex="15" class="pierwszy" fxFlex.xs="55">Pierwszy</div>
    <div fxFlex="30" class="drugi">Drugi</div>
    <div fxFlex="55" class="trzeci" fxFlex.xs="15">Trzeci</div>
  </div>
</div>

quiz2.component.scss

/* Add application styles & imports to this file! */

.zakres {
  background-color: #ddd;
  height: 500px;
}

.pierwszy {
  background: red;
  color: white;
  text-transform: uppercase;
}

.drugi {
  background: yellow;
  color: blue;
}

.trzeci {
  background: blue;
  color: white;
  text-transform: uppercase;
}

.pierwszy,
.drugi,
.trzeci {
  padding-top: 20px;
  text-align: center;
}

.content {
  min-width: 300px;
  /*height: 400px;*/
}

https://tburleson-layouts-demos.firebaseapp.com/#/docs

Rozmiary punktów responsywności https://github.com/angular/flex-layout/wiki/Responsive-API

Szafrański Michał
Architekt oprogramowania, bujający w chmurach obliczeniowych.