Struktura aplikacji Angular cz. 2
Ten wpis jest kontynuacją Struktura aplikacji Angular. Po ponad roku pracy z użyciem opisanej tam struktury projektu, chciałbym podzielić się wnioskami oraz zmianami, jakie zaszły we wcześniej przyjętej konwencji.
Co się zmieniło
Spłaszczenie drzewa katalogów
Konwencja, w której każdy komponent posiadający komponenty dzieci, również dysponuje katalogiem component
, okazał się koncepcją nadmiarową, gdy ilość zagłębień jest większa niż 1.
Wcześniej:
- article-edit
- component
- form
- form.component.
[html|scss|ts]
- form.component.
- images
- component
- image
- image.component.
[html|scss|ts]
- image.component.
- image
- images.component.
[html|scss|ts]
- component
- form
- article-edit.component.
[html|scss|ts]
- component
- articles
- component
- article
- component
- image
- image.component.
[html|scss|ts]
- image.component.
- content
- content.component.
[html|scss|ts]
- content.component.
- image
- article.component.
[html|scss|ts]
- component
- article
- articles.component.
[html|scss|ts]
- component
Taka struktura, mimo iż maksymalnie uporządkowana, sprawiała że nawigowanie po drzewie katalogów projektu było niekiedy mordęgą.
Zdecydowaliśmy się przyjąć założenie, że jeden poziom zagłębienia będzie dla nas wystarczająco czytelny.
Aktualnie:
- articles
- component
- article
- article.component.
[html|scss|ts]
- article.component.
- article-image
- article-image.component.
[html|scss|ts]
- article-image.component.
- article-content
- article-content.component.
[html|scss|ts]
- article-content.component.
- article
- articles.component.
[html|scss|ts]
- component
Budowa i walidacja formularzy w odrębnych serwisach
Proces obsługi danych przychodzących od użytkownika, powodował zdecydowanie zbyt wiele zamieszania w samych komponentach, aby mógł zapewnić uporządkowaną pracę z formularzami.
Z tego powodu zdecydowaliśmy się wydzielić kolejną warstwę serwisów o nazwie form
i form-validator
, aby pracować z formularzami w wygodny sposób.
Wcześniej:
article-edit.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
createForm(): void {
this.form = this.formBuilder.group({
name: [null, {
validators: [
Validators.required,
Validators.minLength(2),
Validators.maxLength(255)
],
asyncValidators: this.checkNameAvailability.bind(this)
}]
});
}
Jak widać, w tym przypadku oprócz samego tworzenia formularza, także dodanie metody checkNameAvailability
odbywać się musiało wewnątrz komponentu. W sytuacji, gdy mamy na prawdę rozbudowany formularz, komponenty bywały przesycone metodami, których celem nie było samo wyświetlenie jakiejś zawartości, ale też sprawdzenie danych pod kątem ich poprawności. Kod w ten sposób przepełnionych komponentów stawał się w wyniku tego mniej czytelny.
Dlatego też zdecydowaliśmy się wydzielić całą logikę związaną z tworzeniem i obsługą formularzy, do odrębnych serwisów.
Aktualnie:
article.form.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export class ArticleFormService {
private _form: FormGroup;
public article: ArticleViewModel;
constructor(
private formBuilder: FormBuilder,
private formValidator: DiscountFormValidator,
) {
}
public init(article: ArticleViewModel): void {
this.article = article;
this._form = this.formBuilder.group({
name: [model.name, [
Validators.required,
Validators.minLength(3),
Validators.maxLength(255)
]]
}, [
this.formValidator.validate(this.article)
]);
this._form
.valueChanges
.subscribe(value => {
this.article.apply(value);
});
}
}
article.form-validator.ts
1
2
3
4
5
6
7
8
9
10
11
12
export class ArticleFormValidator {
constructor() {
}
public validate(article: ArticleViewModel): ValidatorFn {
return (control: FormGroup): ValidationErrors | null => {
const errors: ValidationErrors = {};
return null;
};
}
}
article-form.component.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export class ArticleFormComponent implements OnInit {
@Input() article: ArticleViewModel;
@Output() submitEvent = new EventEmitter<ArticleViewModel>();
form: FormGroup;
constructor(
private formService: ArticleFormService
) {
}
ngOnInit() {
this.formService.init(this.article);
this.form = this.formService.form;
}
}
Jak widzisz, komponenty zawierają znikomą ilość operacji związanych z formularzem. Właściwie celem komponentu jest jedynie utworzyć instancję formularza z dostarczonego modelu danych i przypisać utworzony formularz do pola komponentu, aby wyświetlić go w html.
Cała logika związana z utworzeniem i walidacją odbywa się w przeznaczonych do tego celu serwisach.
Warstwa container
Żadko się to zdarza, ale bywają przypadki bardzo zaawansowanych edytorów, które nie są bezpośrednio związane ze ścieżkami routingu i folder page
implikujący jednak powiązanie z routingiem, nie był odpowiedniem miejscem na ich usytuowanie.
Jednocześnie poziom ich skomplikowania sugeruje umieszczenie go w innym miejscu, niż to, w którym zazwyczaj znajdują się komponenty typu dumb
.
Doskonałym przykładem jest komponent do komponowania menu restauracji, który składa się z kilku/kilkunastu mniejszych komponentów, np. do drag and drop produktów, prezentacji ich na podglądzie POS itp.
W tym celu wydzieliliśmy warstwę o nazwie container
, która przechowuje mądre komponenty, nie powiązane z routingiem.
Przykładowo:
article-create.component.html
1
2
3
<div>
<article-form [discount]="discount"></article-form>
</div>
article-edit.component.html
1
2
3
<div>
<article-form [discount]="discount"></article-form>
</div>
Aktualna struktura
Reasumując ponad rok stosowania zasad z pierwszej części tego wpisu oraz z powyższych zasad, uzyskaliśmy coś takiego:
- src
- app
- api
- interceptor
- rest
- jwt.interceptor.ts
- request.interceptor.ts
- response.interceptor.ts
- response-logger.interceptor.ts
- rest
- model
- auth.api.model.ts
- user.api.model.ts
- article.api.model.ts
- user-permission.api.model.ts
- rest
- article.rest.service.
[ts|spec.ts]
- auth.rest.service.
[ts|spec.ts]
- user.rest.service.
[ts|spec.ts]
- user-permission.rest.service.
[ts|spec.ts]
- article.rest.service.
- soap
- service
- rest.service.ts
- soap.service.ts
- api.module.ts
- interceptor
- core
- layout
- admin
- component
- footer
- footer.component.
[html|scss|spec.ts|ts]
- footer.component.
- header
- header.component.
[html|scss|spec.ts|ts]
- header.component.
- layout
- layout.component.
[html|scss|spec.ts|ts]
- layout.component.
- main
- main.component.
[html|scss|spec.ts|ts]
- main.component.
- footer
- component
- client
- component
- footer
- footer.component.
[html|scss|spec.ts|ts]
- footer.component.
- header
- header.component.
[html|scss|spec.ts|ts]
- header.component.
- layout
- layout.component.
[html|scss|spec.ts|ts]
- layout.component.
- main
- main.component.
[html|scss|spec.ts|ts]
- main.component.
- footer
- component
- admin
- core.module.ts
- core-routing.module.ts
- layout
- shared
- component
- modal
- modal.component.
[html|scss|spec.ts|ts]
- modal.component.
- button
- button.component.
[html|scss|spec.ts|ts]
- button.component.
- breadcrumb
- breadcrumb.component.
[html|scss|spec.ts|ts]
- breadcrumb.component.
- navbar
- navbar.component.
[html|scss|spec.ts|ts]
- navbar.component.
- modal
- directive
- pipe
- date-formatter.pipe.ts
- shared.module.ts
- component
- features
- auth
- component
- login-form
- login-form.component.
[html|spec.ts|ts]
- login-form.component.
- logout-button
- logout-button.component.
[html|spec.ts|ts]
- logout-button.component.
- login-form
- page
- register
- data-provider
- register.data-provider.ts
- model
- user.view.model.ts
- register.component.
[html|spec.ts|ts]
- data-provider
- register
- service
- auth.module.ts
- auth-routing.module.ts
- component
- article
- component
- article-image
- article-image.component.
[html|spec.ts|ts]
- article-image.component.
- popular
- component
- item
- item.component.
[html|spec.ts|ts]
- item.component.
- item
- popular.
[html|spec.ts|ts]
- component
- last-added
- component
- item
- item.component.
[html|spec.ts|ts]
- item.component.
- item
- last-added.component.
[html|spec.ts|ts]
- component
- article-image
- container
- form
- form.component.
[html|spec.ts|ts]
- form.component.
- form
- data-provider
- article.data-provider.ts
- form
- article.form.ts
- article.form-validator.ts
- model
- article.view.model.ts
- page
- list
- component
- item
- item.component.
[html|spec.ts|ts]
- list.component.
[html|spec.ts|ts]
- list.component.
- show
- show.component.
[html|spec.ts|ts]
- show.component.
- popular
- popular.component.
[html|spec.ts|ts]
- popular.component.
- add
- add.component.
[html|spec.ts|ts]
- add.component.
- edit
- edit.component.
[html|spec.ts|ts]
- edit.component.
- list
- component
- item
- item.component.
- service
- article.module.ts
- article-routing.module.ts
- component
- auth
- app.component.html
- app.component.ts
- app.module.ts
- app-routing.module.ts
- api
- config
- environments
- i18n
- pl
- core.json
- article.json
- en
- core.json
- article.json
- pl
- scss
- themes
- default
- _colors.scss
- dark
- _colors.scss
- default
- app