Angular малоиспользуемая функциональность

Feb 10, 2019

Тонкости при работе с @Input

@Input и set

Входящие свойства можно использовать совместно с set. Этим вы сможете на лету формировать нужные вам кастомные свойства в компоненте.

<app-list-errors [errors]="errors"></app-list-errors>
export class ListErrorsComponent {
    formattedErrors:Array<string> = [];

    @Input()
    set errors(errorList:Errors) {
        this.formattedErrors = Object.keys(errorList.errors || {})
            .map(key => `${key} ${errorList.errors[key]}`);
    }

    get errorList() {
        return this.formattedErrors;
    }
}

Директивы

Более подробно про директивы можно узнать в стетье - директивы в Angular

Структурная директива и subject

Реализуем структурную директиву, назначение которой будет в том, чтобы скрывать/открывать блок с навигацией для авторизированного/неавторизированного пользователя. Обратите внимание, что в самой директиве мы подпишемся на ReplaySubject - позволяет указать сколько значений мы хотим запомнить, например, это актуально при создании новой подписки.

// user.service.ts
private isAuthenticatedSubject = new ReplaySubject<boolean>(1);
public isAuthenticated = this.isAuthenticatedSubject.asObservable();
// ...
// ex. set isAuthenticated to true
this.isAuthenticatedSubject.next(true);
<ul *appShowAuthed="false"
    class="nav navbar-nav pull-xs-right">

Реализация структурной директивы:

@Directive({selector: '[appShowAuthed]'})
export class ShowAuthedDirective implements OnInit {

    @Input() set appShowAuthed(condition:boolean) {
        this.condition = condition;
    }

    condition:boolean;

    constructor(private templateRef:TemplateRef<any>,
                private userService:UserService,
                private viewContainer:ViewContainerRef) {
    }

    ngOnInit() {
        this.userService.isAuthenticated.subscribe(
            (isAuthenticated) => {
                if (isAuthenticated && this.condition || !isAuthenticated && !this.condition) {
                    this.viewContainer.createEmbeddedView(this.templateRef);
                } else {
                    this.viewContainer.clear();
                }
            }
        );
    }
}

Резолвер

Многие игнорируют данную функциональность, например, при роутинге на странцу article нужно получить данные предварительно от бэкэнда по статье. Самый простой и используемый способ это подписаться на route в компоненте статьи, получить id статьи и сделать запрос на сервер. Все работает, но есть более удобный и специально созданный для таких случаев инструмент - резолвер. Рассмотрим пример:

Роутинг для статьи, обратите внимание на свойство resolve:

const routes:Routes = [
    {
        path: ':slug',
        component: ArticleComponent,
        resolve: {
            article: ArticleResolver
        }
    }
];
// article-resolver.service.ts
@Injectable()
export class ArticleResolver implements Resolve<Article> {
    constructor(private articlesService:ArticlesService,
                private router:Router,
                private userService:UserService) {
    }

    resolve(route:ActivatedRouteSnapshot,
            state:RouterStateSnapshot):Observable<any> {

        return this.articlesService.get(route.params['slug'])
            .pipe(catchError((err) => this.router.navigateByUrl('/')));
    }
}

Получаем статью в сервисе:

// articles.service.ts
    get(slug):Observable<Article> {
        return this.apiService.get('/articles/' + slug)
            .pipe(map(data => data.article));
    }

Получаем статью в компоненте ArticleComponent:

ngOnInit() {
    // Retreive the prefetched article
    this.route.data.subscribe(
        (data:{ article:Article }) => {
            this.article = data.article;

            // Load the comments on this article
            this.populateComments();
        }
    );

Interceptor

Например, при каждом запросе на сервер нам нужно отправлять в заголовке токен. Вместо того чтобы дублировать код в сервисах мы можем определить один перехватчик, который будет работать как прокладка (расширять параметры запроса) для всех запрососв.

// http.token.interceptor.ts
@Injectable()
export class HttpTokenInterceptor implements HttpInterceptor {
    constructor(private jwtService:JwtService) {
    }

    intercept(req:HttpRequest<any>, next:HttpHandler):Observable>HttpEvent<any>> {
        const headersConfig = {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        };

        const token = this.jwtService.getToken();

        if (token) {
            headersConfig['Authorization'] = `Token ${token}`;
        }

        const request = req.clone({setHeaders: headersConfig});
        return next.handle(request);
    }
}

Регистрируем перехватчик в корневом модуле:

// src\app\core\core.module.ts
@NgModule({
    imports: [
        CommonModule
    ],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: HttpTokenInterceptor,
            multi: true
        },
        ApiService,

Обрабатываем ошибки в сервисах

Можно создать отдельный метод в сервисе, который будет обрабатывать и отдавать Observable с ошибкой:

import {Observable, throwError} from 'rxjs';
import {catchError} from 'rxjs/operators';


    public put(path:string, body:object = {}):Observable<any> {
        return this.httpClient
            .put(BASE_URL + path, JSON.stringify(body), this.options)
            .pipe(catchError(this.formatErrors));
    }

    public formatErrors(error:any):Observable<any> {
        return throwError(error.error);
    }

ng-content

Вы вполне можете определять какую-либо верстку внутри определения компонента (в каком либо другом компоненте) и использовать тег ng-content, чтобы определить место, где вы хотите вывести ранее определенную верстку внутри самого компонента:

<!-- article.component.html -->

<app-article-meta [article]="article">

    <span [hidden]="!canModify">
        <a class="btn btn-sm btn-outline-secondary"
           [routerLink]="['/editor', article.slug]">
            <i class="ion-edit"></i> Edit Article
        </a>

        <button class="btn btn-sm btn-outline-danger"
                [ngClass]="{disabled: isDeleting}"
                (click)="deleteArticle()">
            <i class="ion-trash-a"></i> Delete Article
        </button>
    </span>

    <span [hidden]="canModify">
        <app-follow-button
                [profile]="article.author"
                (toggle)="onToggleFollowing($event)">
        </app-follow-button>

        <app-favorite-button
                [article]="article"
                (toggle)="onToggleFavorite($event)">
            {{ article.favorited ? 'Unfavorite' : 'Favorite' }} Article
            <span class="counter">({{ article.favoritesCount }})</span>
        </app-favorite-button>
    </span>

</app-article-meta>

<!-- Используем ng-content внутри шаблона компонента ArticleMetaComponent -->
<div class="article-meta">
    <a [routerLink]="['/profile', article.author.username]">
        <img [src]="article.author.image"/>
    </a>

    <div class="info">
        <a class="author"
           [routerLink]="['/profile', article.author.username]">
            {{ article.author.username }}
        </a>
        <span class="date">
          {{ article.createdAt | date: 'longDate' }}
        </span>
    </div>

    <ng-content></ng-content>
</div>

RxJS

concatMap

Допустим, у вас есть две асинхронные операции и вам нужно вторую асинхронную операцию вызвать с данными, которые получены от первой асинхронной операции. Для этого оптимальным решением будет использование оператора concatMap. Например, дожидаемся, когда придут данные от роутинга и делаем запрос на сервер, чтобы получить данные о пользователе:

ngOnInit() {
    this.route.data.pipe(
        concatMap((data:{ profile:Profile }) => {
            this.profile = data.profile;
            // Load the current user's data.
            return this.userService.currentUser.pipe(tap(
                (userData:User) => {
                    this.currentUser = userData;
                    this.isUser = (this.currentUser.username === this.profile.username);
                }
            ));
        })
    ).subscribe();
}

mergeMap vs flatMap vs concatMap vs switchMap

AsyncPipe

Иногда следует позаботиться об отписке от Observable. AsyncPipe подписывается на Observable и возвращает последнее выданное им значение. Когда компонент уничтожается, асинхронный pipe автоматически отписывается.

export class AsyncPipeCardComponent implements OnInit {
    messageSubscription: Observable<string>;
    constructor(private upperCaseService: UpperCaseService) {}

    ngOnInit() {
        this.messageSubscription = this.upperCaseService.getUpperCaseMessage();
    }
}
<h4>{{messageSubscription | async}}</h4>
Добавить комментарий
Комментарии:
qq
Oct 9, 2019
qq
qq
Oct 9, 2019
qq