استفاده از CSS Grid در انگولار — راهنمای کاربردی

۹۹ بازدید
آخرین به‌روزرسانی: ۱۱ شهریور ۱۴۰۲
زمان مطالعه: ۱۲ دقیقه
استفاده از CSS Grid در انگولار — راهنمای کاربردی

CSS Grid یک روش عالی برای ساختن لی‌آوت واکنش‌گرا بدون استفاده از کتابخانه CSS محسوب می‌شود. در مورد اپلیکیشن‌های انگولار افزودن لی‌آوت شبکه‌ای (Grid) با استفاده از پکیج افزودنی که لی‌آوت شبکه‌ای را به صورت یک سری دایرکتیو عرضه می‌کند بسیار آسان است.

برای تعیین یک لی‌آوت شبکه‌ای با استفاده از CSS باید چیزی مانند زیر را در کد خود قرار دهید:

1.container {
2    display: grid;
3    grid-template-areas: "header header" "side content";
4    grid-template-rows: auto auto;
5    grid-row-gap: 16px;
6    grid-column-gap: 16px;
7    grid-column: 150px calc(90vw - 150px)
8}

کد فوق تعیین می‌کند که صفحه هدری در بخش فوقانی دارد. یک div شامل نوار کناری (Sidebar) 150 پیکسلی در سمت چپ و یک کانتینر برای محتوا در سمت راست قرار دارد که باقی فضا را اشغال می‌کنند. برای تعیین یک لی‌آوت واکنش‌گرا از کوئری‌های مدیا مانند زیر استفاده می‌کنیم:

1@media screen and (min-width: 600px) {
2  .container {
3    display: grid;
4    grid-template-areas: "header header" "side content";
5    grid-template-rows: auto auto;
6    grid-row-gap: 16px;
7    grid-column-gap: 16px;
8    grid-column: 150px calc(90vw - 150px)
9  }
10}
11
12@media screen and (max-width: 599px) {
13  .container {
14    display: grid;
15    grid-template-areas: "header" "menu" "content";
16    grid-template-rows: auto auto;
17    grid-row-gap: 16px;
18    grid-column-gap: 16px;
19    grid-column: 90vw;
20  }
21}

کد موجود در بلوک media screen and (max-width: 599px)@ مشخص می‌سازد که در صفحه‌های با عرض کمتر از 600 پیکسل، همه چیز در یک ستون نمایش پیدا می‌کند.

در این مقاله یک اپلیکیشن انگولار می‌سازیم که از API نیویورک‌تایمز استفاده می‌کند. یک لی‌آوت برای موبایل و یک لی‌آوت برای دسکتاپ طراحی خواهیم کرد. نمای دسکتاپ دارای یک نوار کناری سمت چپ است که دسته‌بندی‌های اخبار دریافتی از API نیویورک‌تایمز را نمایش می‌دهد و کاربر می‌تواند روی آن‌ها کلیک کنید تا محتوا را در سمت راست مشاهده کنید. در نمای موبایل لی‌آوت تک‌ستونی است و یک منو وجود دارد که کاربر می‌تواند با استفاده از آن دسته‌بندی را انتخاب کرده و محتوا را ببیند. برای ساختن این لی‌آوت از کتابخانه angular/flex-layout@ استفاده می‌کنیم.

نیویورک‌تایمز یک API عالی برای توسعه‌دهندگان جهت دریافت، جستجو و نمایش داده‌های خبری‌اش ارائه کرده است. مستندات این API را در این لینک (+) می‌توانید ببینید. API نیویورک‌تایمز از CROS پشتیبانی می‌کند. از این رو اپلیکیشن‌های فرانت‌اند از دامنه‌هایی خارج از nytimes.com می‌توانند به API آن دسترسی داشته باشند. این بدان معنی است که می‌توانیم یک اپلیکیشن انگولار با آن بسازیم. برای ساخت اپلیکیشن خودمان باید به وب‌سایت نیویورک‌تایمز برویم و یک کلید API ثبت کنیم که رایگان است. کار خود را با نصب Angular CLI آغاز می‌کنیم. به این منظور دستور زیر را اجرا می‌کنیم:

npm i -g @angular/cli

زمانی که نصب شد، می‌توانیم دستور ساختن اپلیکیشن به نام nyt را به صورت زیر اجرا کنیم:

ng new nyt

مطمئن شوید که در زمان اجرای دستور فوق مسیریابی را انتخاب کرده‌اید و از SCSS برای استایل‌بندی استفاده می‌کنید. در اپلیکیشن خود یک صفحه home ایجاد می‌کنیم تا عناوین خبری را از بخش‌های مختلف نمایش دهیم. یک صفحه جستجو نیز برای گشتن به دنبال مقالات با کلیدواژه و تاریخ‌های آغاز و پایان طراحی می‌کنیم. به این منظور دستورهای زیر را اجرا کنید:

$ ng g component homePage
$ ng g component articleSearchPage
$ ng g component articleSearchResults
$ ng g component toolBar
$ ng g pipe capitalizeCategory

دستورهای فوق کامپوننت‌هایی که به کاربر نمایش خواهند یافت را ایجاد می‌کنند. سپس باید برخی کتابخانه‌هایی که برای ساخت ظاهر و کارکرد مناسب اپلیکیشن نیاز داریم را اضافه کنیم. دستورهای زیر را اجرا کنید:

npm install --save @angular/material @angular/cdk @angular/animations @angular/flex-layout

دستور فوق Angular Material را نصب می‌کند تا ظاهر اپلیکیشن را بهتر سازیم. سپس دستور زیر را اجرا می‌کنیم:

npm install @ngrx/store moment

دستورهای فوق به ترتیب Flux store را ایجاد می‌کند و امکان دستکاری تاریخ را فراهم می‌سازد. اینک می‌توانیم ماژول‌های کتابخانه را در ماژول main-app قرار دهیم. به این منظور کد زیر را در فایل app.module.ts قرار دهید:

1import { BrowserModule } from '@angular/platform-browser';
2import { NgModule } from '@angular/core';
3import { FormsModule } from '@angular/forms';
4import { AppRoutingModule } from './app-routing.module';
5import { AppComponent } from './app.component';
6import { HomePageComponent } from './home-page/home-page.component';
7import { ArticleSearchPageComponent } from './article-search-page/article-search-page.component';
8import { ArticleSearchResultsComponent } from './article-search-results/article-search-results.component';
9import { StoreModule } from '@ngrx/store';
10import { reducers } from './reducers';
11import { NytService } from './nyt.service';
12import { MatSidenavModule } from '@angular/material/sidenav';
13import { MatToolbarModule } from '@angular/material/toolbar';
14import { MatInputModule } from '@angular/material/input';
15import { MatFormFieldModule } from '@angular/material/form-field';
16import { MatDatepickerModule } from '@angular/material/datepicker';
17import { ToolBarComponent } from './tool-bar/tool-bar.component';
18import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
19import { MatButtonModule } from '@angular/material/button';
20import { MatMomentDateModule } from '@angular/material-moment-adapter';
21import { HttpClientModule } from '@angular/common/http';
22import { MatSelectModule } from '@angular/material/select';
23import { MatCardModule } from '@angular/material/card';
24import { MatListModule } from '@angular/material/list';
25import { MatMenuModule } from '@angular/material/menu';
26import { MatIconModule } from '@angular/material/icon';
27import { MatGridListModule } from '@angular/material/grid-list';
28import { FlexLayoutModule } from '@angular/flex-layout';
29import { CapitalizeCategoryPipe } from './capitalize-category.pipe';
30import { TitleCasePipe } from '@angular/common';@NgModule({
31  declarations: [
32    AppComponent,
33    HomePageComponent,
34    ArticleSearchPageComponent,
35    ArticleSearchResultsComponent,
36    ToolBarComponent,
37    CapitalizeCategoryPipe
38  ],
39  imports: [
40    BrowserModule,
41    AppRoutingModule,
42    StoreModule.forRoot(reducers),
43    FormsModule,
44    MatSidenavModule,
45    MatToolbarModule,
46    MatInputModule,
47    MatFormFieldModule,
48    MatDatepickerModule,
49    BrowserAnimationsModule,
50    MatButtonModule,
51    MatMomentDateModule,
52    HttpClientModule,
53    MatSelectModule,
54    MatCardModule,
55    MatListModule,
56    MatMenuModule,
57    MatIconModule,
58    MatGridListModule,
59    FlexLayoutModule
60  ],
61  providers: [
62    NytService,
63    TitleCasePipe
64  ],
65  bootstrap: [AppComponent]
66})
67export class AppModule { }

اغلب ماژول‌ها در آرایه import ماژول‌های انگولار متریال هستند. از آن‌ها در سراسر این اپلیکیشن استفاده خواهیم کرد. کد زیر را در فایل capitalize-category.pipe. قرار دهید:

1import { Pipe, PipeTransform } from '@angular/core';
2import { TitleCasePipe } from '@angular/common';@Pipe({
3  name: 'capitalizeCategory'
4})
5export class CapitalizeCategoryPipe implements PipeTransform {
6  constructor(private titlecasePipe: TitleCasePipe) { }transform(value: any, ...args: any[]): any {
7    if (typeof value == 'string') {
8      if (value.toLowerCase() == 'nyregion') {
9        return 'NY Region';
10      }
11      if (value.toLowerCase() == 'realestate') {
12        return 'Real Estate';
13      }
14      else if (value.toLowerCase() == 'sundayreview') {
15        return 'Sunday Review';
16      }
17      else if (value.toLowerCase() == 'tmagazine') {
18        return 'T Magazine';
19      }
20      return this.titlecasePipe.transform(value);
21    }
22    return value;  }}

کدهای فوق دسته‌بندی‌های مختلف را از نظر بزرگی و کوچکی حروف به طرز صحیحی نمایش می‌دهند. از یک pipe به نام titlecase برای اغلب رشته‌ها استفاده می‌کنیم، مگر این که چندین کلمه به هم الحاق یافته باشند. اینک باید بخشی از اپلیکیشن را بسازیم که مسئول دریافت و ذخیره‌سازی داده‌ها است. به این منظور دستور زیر را اجرا می‌کنیم:

$ ng g service nyt

این همان جایی است که فراخوانی‌های HTTP را به API نیویورک‌تایمز انجام می‌دهیم. اکنون باید یک فایل به نام nyt.service.ts داشته باشیم. کدهای زیر را در آن قرار می‌دهیم:

1import { Injectable } from '@angular/core';
2import { HttpClient, HttpParams } from '@angular/common/http';
3import { environment } from 'src/environments/environment';@Injectable({
4  providedIn: 'root'
5})
6export class NytService {constructor(
7    private http: HttpClient
8  ) { }search(data) {
9    let params: HttpParams = new HttpParams();
10    params = params.set('api-key', environment.apikey);
11    if (data.q !== undefined) {
12      params = params.set('q', data.q);
13    }
14    if (data.begin_date !== undefined) {
15      params = params.set('begin_date', data.begin_date);
16    }
17    if (data.end_date !== undefined) {
18      params = params.set('end_date', data.end_date);
19    }
20    if (data.sort !== undefined) {
21      params = params.set('sort', data.sort);
22    }
23    return this.http.get(`${environment.apiUrl}/search/v2/articlesearch.json`, { params });
24  }getArticles(section: string = 'home') {
25    let params: HttpParams = new HttpParams();
26    params = params.set('api-key', environment.apikey);return this.http.get(`${environment.apiUrl}/topstories/v2/${section}.json`, { params });
27  }
28}

تابع search اقدام به دریافت data می‌کند که ارسال خواهد شد و اگر تعریف شده باشد، در رشته کوئری درخواست GET قرار می‌گیرد. پارامتر دوم در تابع this.http.get چندین گزینه می‌گیرد که شامل هدرها و پارامترهای کوئری است. زمانی که این کد اجرا شود، اشیای HttpParams به رشته‌های کوئری تبدیل می‌شوند. getArticles کاری مشابه تابع search انجام می‌دهد، به جز این که URL متفاوتی دارد. سپس کدهای زیر را در فایل environment.ts قرار می‌دهیم:

1export const environment = {
2  production: false,
3  apikey: 'your api key',
4  apiUrl: 'https://api.nytimes.com/svc'
5};

بدین ترتیب در فایل سرویس، ارجاعی به URL و API key خواهیم داشت. سپس یک Flux data store به حالت منو و نتایج جستجو اضافه می‌کنیم. ابتدا باید دستور زیر را اجرا کنیم:

$ ng add @ngrx/store

این دستور کد آماده‌ای را به Flux store اضافه می‌کند. سپس دستور زیر را اجرا می‌کنیم:

$ ng g class menuReducer
$ ng g class searchResultsReducer

دستور فوق را در پوشه src\app\reducers اجرا می‌کنیم که پس از اجرای دستور قبلی ایجاد شده است و فایل‌های مورد نظر reducer-ها را ساخته است. سپس کدهای زیر را در فایل menu-reducer.ts قرار می‌دهیم:

1export const SET_MENU_STATE = 'SET_MENU_STATE';export function MenuReducer(state: boolean, action) {
2    switch (action.type) {
3        case SET_MENU_STATE:
4            return action.payload;default:
5            return state;
6    }
7}

کدهای زیر را نیز در فایل search-result-reducer.ts قرار می‌دهیم:

1export const SET_MENU_STATE = 'SET_MENU_STATE';export function MenuReducer(state: boolean, action) {
2    switch (action.type) {
3        case SET_MENU_STATE:
4            return action.payload;default:
5            return state;
6    }
7}

این دو قطعه کد به منو و نتایج جستجو اجازه می‌دهند که در حافظه ذخیره شوند و به کامپوننت‌هایی که در داده‌های آن اشتراک دارند، انتشار یابند. سپس کدهای زیر را در فایل src\app\reducers\index.ts قرار می‌دهیم:

1export const SET_SEARCH_RESULT = 'SET_SEARCH_RESULT';export function SearchResultReducer(state, action) {
2    switch (action.type) {
3        case SET_SEARCH_RESULT:
4            return action.payload;default:
5            return state;
6    }
7}

بدین ترتیب ماژول ما می‌تواند به reducer-ها دسترسی پیدا کند زیرا StoreModule.forRoot(reducers) را در فایل app.module.ts داریم. اکنون روی نوار ابزار اپلیکیشن کار می‌کنیم. برای ایجاد نوار ابزار کدهای زیر را در فایل tool-bar.component.ts قرار می‌دهیم:

1import { Component, OnInit } from '@angular/core';
2import { Store, select } from '@ngrx/store';
3import { SET_MENU_STATE } from '../reducers/menu-reducer';@Component({
4  selector: 'app-tool-bar',
5  templateUrl: './tool-bar.component.html',
6  styleUrls: ['./tool-bar.component.scss']
7})
8export class ToolBarComponent implements OnInit {
9  menuOpen: boolean;constructor(
10    private store: Store<any>
11  ) {
12    store.pipe(select('menuState'))
13      .subscribe(menuOpen => {
14        this.menuOpen = menuOpen;
15      })
16  }ngOnInit() {
17  }toggleMenu() {
18    this.store.dispatch({ type: SET_MENU_STATE, payload: !this.menuOpen });
19  }}this.store.dispatch({ type: SET_MENU_STATE, payload: !this.menuOpen });

این کد حالت و منو را به باقی اپلیکیشن ارسال می‌کند:

1This sends the state of the menu to the rest of the app.
2store.pipe(select('menuState'))
3  .subscribe(menuOpen => {
4    this.menuOpen = menuOpen;
5  })

کد فوق حالت منو را می‌گیرد و برای نمایش و پنهان‌سازی حالت منو استفاده می‌شود. کدهای زیر را در قالب مربوطه به نام tool-bar.component.html قرار می‌دهیم:

1<mat-toolbar>
2    <a (click)='toggleMenu()' class="menu-button">
3        <i class="material-icons">
4            menu
5        </i>
6    </a>
7    New York Times App
8</mat-toolbar>

کدهای زیر را در فایل tool-bar.component.scss اضافه می‌کنیم:

1.menu-button {
2  margin-top: 6px;
3  margin-right: 10px;
4  cursor: pointer;
5}.mat-toolbar {
6  background: #009688;
7  color: white;
8}

کدهای زیر را در فایل app.component.scss قرار می‌دهیم:

1#content {
2  padding: 20px;
3  min-height: 100vh;
4}ul {
5  list-style-type: none;
6  margin: 0;
7  li {
8    padding: 20px 5px;
9  }
10}

بدین ترتیب رنگ نوار ابزار تغییر می‌یابد. سپس کدهای زیر را به فایل app.component.ts اضافه می‌کنیم:

1import { Component, HostListener } from '@angular/core';
2import { SET_MENU_STATE } from './reducers/menu-reducer';
3import { Store, select } from '@ngrx/store';@Component({
4  selector: 'app-root',
5  templateUrl: './app.component.html',
6  styleUrls: ['./app.component.scss']
7})
8export class AppComponent {
9  menuOpen: boolean;constructor(
10    private store: Store<any>,
11  ) {
12    store.pipe(select('menuState'))
13      .subscribe(menuOpen => {
14        this.menuOpen = menuOpen;
15      })
16  }@HostListener('document:click', ['$event'])
17  public onClick(event) {
18    const isOutside = !event.target.className.includes("menu-button") &&
19      !event.target.className.includes("material-icons") &&
20      !event.target.className.includes("mat-drawer-inner-container")
21    if (isOutside) {
22      this.menuOpen = false;
23      this.store.dispatch({ type: SET_MENU_STATE, payload: this.menuOpen });
24    }
25  }

بدین ترتیب زمانی که خارج از نوار کناری سمت چپ کلیک کنیم، بسته می‌شود. کدهای زیر را به فایل app.component.html اضافه می‌کنیم:

1<mat-sidenav-container class="example-container">
2    <mat-sidenav mode="side" [opened]='menuOpen'>
3        <ul>
4            <li>
5                <b>
6                    New York Times
7                </b>
8            </li>
9            <li>
10                <a routerLink='/'>Home</a>
11            </li>
12            <li>
13                <a routerLink='/search'>Search</a>
14            </li>
15        </ul></mat-sidenav>
16    <mat-sidenav-content>
17        <app-tool-bar></app-tool-bar>
18        <div id='content'>
19            <router-outlet></router-outlet>
20        </div>
21    </mat-sidenav-content>
22</mat-sidenav-container>

بدین ترتیب منوی سمت چپ و مسیرها نمایش می‌یابند. استایل های زیر را نیز به فایل style.scss اضافه می‌کنیم:

1/* You can add global styles to this file, and also import other style files */
2@import "~@angular/material/prebuilt-themes/indigo-pink.css";body {
3  font-family: "Roboto", sans-serif;
4  margin: 0;
5}form {
6  mat-form-field {
7    width: 95vw;
8    margin: 0 auto;
9  }
10}.center {
11  text-align: center;
12}

بدین ترتیب استایل های طراحی متریال ایمپورت شده و عرض فرم‌ها تنظیم می‌شود. سپس کدهای زیر را در فایل app-routing.module.ts اضافه می‌کنیم، به طوری که می‌توانیم ببینیم صفحه‌های مختلف با رفتن به URL-های مربوطه ایجاد می‌شوند:

1import { NgModule } from '@angular/core';
2import { Routes, RouterModule } from '@angular/router';
3import { HomePageComponent } from './home-page/home-page.component';
4import { ArticleSearchPageComponent } from './article-search-page/article-search-page.component';const routes: Routes = [
5  { path: '', component: HomePageComponent },
6  { path: 'search', component: ArticleSearchPageComponent }
7];@NgModule({
8  imports: [RouterModule.forRoot(routes)],
9  exports: [RouterModule]
10})
11export class AppRoutingModule { }

کدهای زیر را به فایل index.html اضافه می‌کنیم:

1<!doctype html>
2<html lang="en"><head>
3  <meta charset="utf-8">
4  <title>New York Time</title>
5  <base href="/">
6  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
7  <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
8  <meta name="viewport" content="width=device-width, initial-scale=1">
9  <link rel="icon" type="image/x-icon" href="favicon.ico">
10</head><body>
11  <app-root></app-root>
12</body></html>

کد فوق شامل فونت Roboto است که به طور معمول در طراحی متریال استفاده می‌شود و همچنین آیکون‌های متریال را نیز دارد. اکنون منطق دو صفحه اپلیکیشن خود را طراحی می‌کنیم. ابتدا از صفحه Home آغاز می‌کنیم. کدهای زیر را در فایل home-page.component.html قرار دهید:

1import { Component, OnInit } from '@angular/core';
2import { NytService } from '../nyt.service';@Component({
3  selector: 'app-home-page',
4  templateUrl: './home-page.component.html',
5  styleUrls: ['./home-page.component.scss']
6})
7export class HomePageComponent implements OnInit {
8  sections: string[] =
9    `arts, automobiles, books, business, fashion, food, health,
10    home, insider, magazine, movies, national, nyregion, obituaries,
11    opinion, politics, realestate, science, sports, sundayreview,
12    technology, theater, tmagazine, travel, upshot, world`
13      .replace(/ /g, '')
14      .split(',');
15  results: any[] = [];
16  selectedSection: string = 'home';constructor(
17    private nytService: NytService
18  ) { }ngOnInit() {
19    this.getArticles();
20  }getArticles() {
21    this.nytService.getArticles(this.selectedSection)
22      .subscribe(res => {
23        this.results = (res as any).results;
24      })
25  }
26}

ما مقالات را در بارگذاری نخست با تابع ngOnInit دریافت می‌کنیم و زمانی که صفحه بارگذاری شود، می‌توانیم انتخاب کنیم که کدام بخش بارگذاری شود. کدهای زیر را به فایل home-page.component.html اضافه کنید:

1<div gdAreas="header header | side content" gdColumns="150px calc(90vw - 150px)" gdGap="16px"
2    gdAreas.lt-md="header | menu | content" gdRows.lt-md="auto auto auto auto" gdColumns.lt-md="90vw">
3    <div gdArea="header">
4        <div class="center">
5            <h1>{{selectedSection | capitalizeCategory }}</h1>
6        </div>
7    </div>
8    <div gdArea='menu'>
9        <button mat-raised-button [matMenuTriggerFor]="appMenu">
10            Sections
11        </button>
12        <mat-menu #appMenu="matMenu">
13            <button mat-menu-item *ngFor='let s of sections'
14                (click)='selectedSection = s; getArticles()'>{{s | capitalizeCategory }}
15            </button>
16        </mat-menu>
17    </div>
18    <div gdArea="side">
19        <mat-list role="list">
20            <mat-list-item role="listitem" *ngFor='let s of sections'>
21                <a class="sidebar-links" (click)='selectedSection = s; getArticles()'>{{s | capitalizeCategory }}</a>
22            </mat-list-item>
23        </mat-list>
24    </div>
25    <div gdArea="content">
26        <mat-card *ngFor='let r of results'>
27            <mat-list role="list">
28                <mat-list-item>
29                    <mat-card-title>
30                        {{r.title}}
31                    </mat-card-title>
32                </mat-list-item>
33            </mat-list>
34            <mat-card-subtitle>
35                <mat-list role="list">
36                    <mat-list-item>Published Date: {{r.published_date | date: 'full' }}</mat-list-item>
37                    <mat-list-item><a href='{{r.url}}'>Link</a></mat-list-item>
38                    <mat-list-item *ngIf='r.byline'>{{r.byline}}</mat-list-item>
39                </mat-list>
40            </mat-card-subtitle>
41            <mat-card-content>
42                <mat-list role="list">
43                    <mat-list-item>{{r.abstract}}</mat-list-item>
44                </mat-list>
45                <img *ngIf='r.multimedia[r.multimedia.length - 1]?.url'
46                    [src]='r.multimedia[r.multimedia.length - 1]?.url'
47                    [alt]='r.multimedia[r.multimedia.length - 1]?.caption' class="image">
48            </mat-card-content>
49        </mat-card>
50    </div>
51</div>

این همان جایی است که نتایج دریافتی از API نیویورک‌تایمز را که شامل عناوین اخبار، تصاویر، تاریخ انتشار و داده‌های دیگر است نمایش می‌دهیم. capitalizeCategory یک pipe نامیده می‌شود. capitalizeCategory با فراخوانی تابع سمت راست pipe، شیء را به سمت چپ نماد pipe نگاشت می‌کند. از angular/flex-layout@ برای ساخت لی‌آوت grid خود استفاده می‌کنیم. لی‌آوت دسکتاپ را با دستور زیر تعیین می‌کنیم:

1gdAreas=”header header | side content” gdColumns=150px calc(90vw — 150px)

header header در بخش فوقانی صحنه نمایش می‌یابد و side content در زیر هدر نمایش خواهد یافت. دستور زیر عرض‌های ستون هر یک از آیتم‌ها را تعیین می‌کند:

1gdColumns=150px calc(90vw — 150px)

هر چیزی در سمت چپ 150 پیکسل عرض دارد و هر چیزی در سمت راست 90 درصد عرض صفحه را با عرض دست‌کم 150 پیکسل اشغال می‌کند. علامت | ردیف‌ها را از هم جدا می‌کند. دستور زیر لی‌آوت موبایل را توصیف می‌کند:

1gdAreas.lt-md=”header | menu | content” gdRows.lt-md=”auto auto auto auto” gdColumns.lt-md=90vw”

همه چیز در یک ردیف قرار دارد. هر آیتم 90 درصد عرض صفحه را اشغال می‌کند. کد ابتدای فایل جایی است که می‌توانیم به کاربران امکان انتخاب بخشی که می‌خواهند بارگذاری شود را بدهیم:

1<div class="center">
2    <h1>{{selectedSection | capitalizeCategory }}</h1>
3    <mat-menu #appMenu="matMenu">
4        <button mat-menu-item *ngFor='let s of sections' (click)='selectedSection = s; getArticles()'>{{s | capitalizeCategory}}
5        </button>
6    </mat-menu>    <button mat-raised-button [matMenuTriggerFor]="appMenu">
7        Sections
8    </button>
9</div>
10<br>

این بلوک کد، رشته را به آرایه‌ای از رشته‌ها افزار می‌کند که برحسب نام‌ها و فواصل خالی صورت می‌گیرد:

1sections: string[] =
2    `arts, automobiles, books, business, fashion, food, health,
3    home, insider, magazine, movies, national, nyregion, obituaries,
4    opinion, politics, realestate, science, sports, sundayreview,
5    technology, theater, tmagazine, travel, upshot, world`
6      .replace(/ /g, '')
7      .split(',');

سپس کدهای زیر را به فایل home-page.component.scss اضافه می‌کنیم:

1.image {
2  width: 100%;
3  margin-top: 30px;
4}

کد فوق تصاویر را استایل‌بندی می‌کند. سپس صفحه‌ای برای جستجوی مقالات می‌سازیم. این صفحه یک فرم و یک فاصله برای نمایش نتایج دارد. کدهای زیر را به فایل article-search.component.ts اضافه کنید.

1import { Component, OnInit } from '@angular/core';
2import { SearchData } from '../search-data';
3import { NgForm } from '@angular/forms';
4import { NytService } from '../nyt.service';
5import * as moment from 'moment';
6import { Store } from '@ngrx/store';
7import { SET_SEARCH_RESULT } from '../reducers/search-results-reducer';@Component({
8  selector: 'app-article-search-page',
9  templateUrl: './article-search-page.component.html',
10  styleUrls: ['./article-search-page.component.scss']
11})
12export class ArticleSearchPageComponent implements OnInit {
13  searchData: SearchData = <SearchData>{
14    sort: 'newest'
15  };
16  today: Date = new Date();constructor(
17    private nytService: NytService,
18    private store: Store<any>
19  ) {}ngOnInit() {
20  }search(searchForm: NgForm) {
21    if (searchForm.invalid) {
22      return;
23    }
24    const data: any = {
25      begin_date: moment(this.searchData.begin_date).format('YYYYMMDD'),
26      end_date: moment(this.searchData.end_date).format('YYYYMMDD'),
27      q: this.searchData.q
28    }
29    this.nytService.search(data)
30      .subscribe(res => {
31        this.store.dispatch({ type: SET_SEARCH_RESULT, payload: (res as any).response.docs });
32      })
33  }}

این کد هنگامی که کاربر روی جستجو کلیک می‌کند، داده‌ها را دریافت کرده و نتایج را در Flux store قرار می‌دهد که برای نمایش داده‌ها در نهایت مورد استفاده قرار خواهد گرفت. کدهای زیر را به فایل article-search.component.html اضافه کنید:

1<div class="center">
2    <h1>Search</h1>
3</div>
4<br>
5<form #searchForm='ngForm' (ngSubmit)='search(searchForm)'>
6    <mat-form-field>
7        <input matInput placeholder="Keyword" required #keyword='ngModel' name='keyword' [(ngModel)]='searchData.q'>
8        <mat-error *ngIf="keyword.invalid && (keyword.dirty || keyword.touched)">
9            <div *ngIf="keyword.errors.required">
10                Keyword is required.
11            </div>
12        </mat-error>
13    </mat-form-field>
14    <br>
15    <mat-form-field>
16        <input matInput [matDatepicker]="startDatePicker" placeholder="Start Date" [max]="today" #startDate='ngModel'
17            name='startDate' [(ngModel)]='searchData.begin_date'>
18        <mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
19        <mat-datepicker #startDatePicker></mat-datepicker>
20    </mat-form-field>
21    <br>
22    <mat-form-field>
23        <input matInput [matDatepicker]="endDatePicker" placeholder="End Date" [max]="today" #endDate='ngModel'
24            name='endDate' [(ngModel)]='searchData.end_date'>
25        <mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
26        <mat-datepicker #endDatePicker></mat-datepicker>
27    </mat-form-field>
28    <br>
29    <mat-form-field>
30        <mat-label>Sort By</mat-label>
31        <mat-select required [(value)]="searchData.sort">
32            <mat-option value="newest">Newest</mat-option>
33            <mat-option value="oldest">Oldest</mat-option>
34            <mat-option value="relevance">Relevance</mat-option>
35        </mat-select>
36    </mat-form-field>
37    <br>
38    <button mat-raised-button type='submit'>Search</button>
39</form>
40<br>
41<app-article-search-results></app-article-search-results>

این فرم جستجو برای مقالات است. در آن یک فیلد کلیدواژه، انتخابگر تاریخ ابتدا و انتهای بازه‌ی جستجو و یک منوی بازشدنی برای انتخاب روش مرتب‌سازی نتایج وجود دارد. این موارد کامپوننت‌های انگولار متریال هستند. کامپوننت زیر برای جستجوی مقالات استفاده می‌شود که تولید شده اما هنوز build نشده است:

1<app-article-search-results></app-article-search-results>

توجه کنید [( در [(ngModel)] نشان می‌دهد که اتصال داده دوطرفه بین کامپوننت و دایرکتیو وجود دارد و کامپوننت کنونی و [ روش اتصال یک‌طرفه کامپوننت کنونی به دایرکتیو یا کامپوننت را نمایش می‌دهد. کدهای زیر را به فایل article-search.results.ts اضافه کنید:

1import { Component, OnInit } from '@angular/core';
2import { Store, select } from '@ngrx/store';@Component({
3  selector: 'app-article-search-results',
4  templateUrl: './article-search-results.component.html',
5  styleUrls: ['./article-search-results.component.scss']
6})
7export class ArticleSearchResultsComponent implements OnInit {
8  searchResults: any[] = [];constructor(
9    private store: Store<any>
10  ) {
11    store.pipe(select('searchResults'))
12      .subscribe(searchResults => {
13        this.searchResults = searchResults;
14      })
15  }ngOnInit() {
16  }}

بلوک کد فوق نتایج جستجوی مقالات را گرفته و در یک Flux store ذخیره می‌کند و برای نمایش در قالب مربوطه ارسال خواهد کرد:

1store.pipe(select('searchResults'))
2      .subscribe(searchResults => {
3        this.searchResults = searchResults;
4      })

کدهای زیر را به فایل article-search.results.html اضافه کنید:

1<mat-card *ngFor='let s of searchResults'>
2    <mat-list role="list">
3        <mat-list-item>
4            <mat-card-title>
5                {{s.headline.main}}
6            </mat-card-title>
7        </mat-list-item>
8    </mat-list>
9    <mat-card-subtitle>
10        <mat-list role="list">
11            <mat-list-item>Date: {{s.pub_date | date: 'full' }}</mat-list-item>
12            <mat-list-item><a href='{{s.web_url}}'>Link</a></mat-list-item>
13            <mat-list-item *ngIf='s.byline.original'>{{s.byline.original}}</mat-list-item>
14        </mat-list>
15    </mat-card-subtitle>
16    <mat-card-content>
17        <div class="content">
18            <p>{{s.lead_paragraph}}</p>
19            <p>{{s.snippet}}</p>
20        </div>
21    </mat-card-content>
22</mat-card>

کد فوق نتایج را از store گرفته و نمایش می‌دهد. کدهای زیر را به فایل article-search-results.component.scss اضافه می‌کنیم:

1.content {
2  padding: 0px 15px;
3}

بدین ترتیب مقداری فاصله‌گذاری به پاراگراف‌ها اضافه می‌شود. در نهایت نتیجه زیر به دست آمده است:

بدین ترتیب به پایان این مقاله می‌رسیم.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
swlh
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *