DevGang
Авторизоваться

Angular: 3 простых совета

В этом посте я поделюсь самыми ценными и простыми советами и приемами Angular, которые каждый может использовать в своем проекте. Неважно, большой у вас проект, маленький, старый или молодой.

Не подписывайтесь на Observables

Реактивное программирование с RxJ — одна из самых естественных вещей в Angular. Мы все знаем, как это работает. У нас есть наблюдаемый и подписчик, который подписывается на observable, которая отправляет данные подписчику. Итак, мы подписываемся на наблюдаемый объект, распаковываем его и сохраняем полученные значения в некотором свойстве класса, чтобы мы могли использовать его в нашем шаблоне.

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit, OnDestroy {
  subscriptions = new Subscription();
  person: Person;
  address: Address;
  date: Date;

  constructor(private profile: ProfileService) { }

  ngOnDestroy(): void {
    this.subscriptions.unsubscribe();
  }

  ngOnInit(): void {
    this.subscriptions.add(
      this.profile.getPersons().subscribe(person => this.person = person)
    );
    this.subscriptions.add(
      this.profile.getAddress().subscribe(address => this.address = address)
    );
    this.subscriptions.add(
      this.profile.getDate().subscribe(date => this.date = date)
    );
  }

}

Этот код не является неправильным и работает отлично; даже очень хорошо справляется с отпиской, но в этом подходе есть один недостаток. Это плохо, потому что код раздут. Но, к счастью, Angular поставляется с так называемым асинхронным каналом из коробки, который является действительно классной функцией для использования наблюдаемых непосредственно в шаблоне. Мы можем просто привязать наблюдаемый объект и связать async pipe, который автоматически обрабатывает подписку и также отменяет подписку, когда компонент уничтожается.

<ng-container *ngIf="person$ | async as person">
  Firstname: {{ person.firstName }} <br>
  Lastname: {{ person.lastName }}
</ng-container>

<ng-container *ngIf="address$ | async as address">
  City: {{ address.city }} <br>
  Country: {{ address.country }}
</ng-container>

<ng-container *ngIf="date$ | async as date">
  Date: {{ date.birthDate | date }} <br>
</ng-container>
@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit, OnDestroy {
  subscriptions = new Subscription();
  person$: Observable<Person>;
  address$: Observable<Address>;
  date$: Observable<Date>;

  constructor(private profile: ProfileService) {}

  ngOnInit(): void {
    this.person$ = this.profile.getPerson();
    this.address$ = this.profile.getProfile();
    this.date$ = this.profile.getDate();
  }

}

Используйте BehaviourSubjects

BehaviourSubjects — это супер-набор субъектов, которые на самом деле уже достаточно эффективны, поскольку позволяют выполнять многоадресную рассылку. Вам нужно знать, что наблюдаемый объект выполняет одноадресную рассылку, так что каждая подписка получает свои собственные значения, и ни одна подписка не использует один и тот же поток данных. Субъект, с другой стороны, является многоадресным, и поэтому любая подписка будет использовать один и тот же поток данных. Это особенно полезно, когда речь идет о взаимодействии компонентов через сервисы, хранящие общее состояние. Но может быть одна проблема. Что, если ваш компонент зависит от того, что ваш субъект немедленно выдает данные, потому что в противном случае некоторые привязки данных в вашем шаблоне изначально были бы привязаны к «undefined»? Решением является BehaviourSubject, который почти эквивалентен Subjects, но с одной дополнительной функцией:

userState$ = new BehaviorSubject<UserState>({
    user: {
      firstName: 'john',
      lastName: 'doe'
    }
    // ...
  });

  userState$.subscribe() // immediately the initial value passed in the behaviour subject constructor is emitted

Используйте преобразователи маршрутов

Резолверы используются в маршрутизации для асинхронного извлечения некоторых данных и передачи их компоненту до того, как он будет создан. Возможно, вы сейчас думаете, зачем кому-то делать это вместо того, чтобы просто извлекать данные из хука жизненного цикла компонента ngOnInit, и ответ таков: ну, вы могли бы сделать это, но это не очень хорошо для UX. Что бы вы предпочли? Компонент, который отображает неправильные данные, поскольку привязки будут привязаны к undefined в самом начале, или вам придется вручную защищать все привязки с помощью ngIf, что утомительно? Или вы бы предпочли, чтобы новая страница не загружалась в течение секунды и, возможно, отображался индикатор загрузки? Вероятно, вы один из тех, кто предпочитает второй вариант.

@Injectable({ providedIn: 'root' })
export class HeroResolver implements Resolve<Hero> {
  constructor(private service: HeroService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<Hero>|Promise<Hero>|Hero {
    return this.service.getHero(route.paramMap.get('id'));
  }
}

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

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'detail/:id',
        component: HeroDetailComponent,
        resolve: {
          hero: HeroResolver
        }
      }
    ])
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Преобразователь назначается параметру данных маршрута, доступ к которому можно получить в компоненте через свойство данных ActivatedRoute.

И чтобы добавить счетчик загрузки, мы должны прослушивать события маршрутизатора и сделать if-else, чтобы решить, когда должен отображаться счетчик загрузки.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  loading = false;

  constructor(private router: Router) {}

  ngOnInit() {
   this.router.events.subscribe(ev => {
      if (ev instanceof NavigationStart) {
        this.loading = true;
      }
      if (ev instanceof NavigationEnd ||
        ev instanceof NavigationCancel ||
        ev instanceof NavigationError
      ) {
        this.loading = false;
      }
    });
  }
}
#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться