angular-base-lib 18.0.7 → 19.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -280
- package/fesm2022/angular-base-lib.mjs +24 -25
- package/fesm2022/angular-base-lib.mjs.map +1 -1
- package/lib/base/base-control-value-accessor.d.ts +1 -1
- package/lib/base/signal-base-control-value-accessor.d.ts +1 -1
- package/lib/model-component/base-model-list.component.d.ts +5 -6
- package/package.json +6 -9
- package/esm2022/angular-base-lib.mjs +0 -5
- package/esm2022/index.mjs +0 -2
- package/esm2022/lib/auth/base-auth.service.mjs +0 -47
- package/esm2022/lib/auth/guards/index.mjs +0 -3
- package/esm2022/lib/auth/guards/logged-in.guard.mjs +0 -14
- package/esm2022/lib/auth/guards/logged-out.guard.mjs +0 -14
- package/esm2022/lib/auth/index.mjs +0 -4
- package/esm2022/lib/auth/models/base-auth-state.mjs +0 -2
- package/esm2022/lib/auth/models/index.mjs +0 -4
- package/esm2022/lib/auth/models/logged-in-auth-state.mjs +0 -7
- package/esm2022/lib/auth/models/login-state.type.mjs +0 -2
- package/esm2022/lib/auth/models/not-logged-in-auth-state.mjs +0 -7
- package/esm2022/lib/auth/models/pending-auth-state.mjs +0 -7
- package/esm2022/lib/base/base-control-value-accessor.mjs +0 -45
- package/esm2022/lib/base/index.mjs +0 -3
- package/esm2022/lib/base/signal-base-control-value-accessor.mjs +0 -43
- package/esm2022/lib/dao/converted-http-base-dao.mjs +0 -37
- package/esm2022/lib/dao/converted-promise-http-base-dao.mjs +0 -37
- package/esm2022/lib/dao/converter.mjs +0 -3
- package/esm2022/lib/dao/dao-id.mjs +0 -2
- package/esm2022/lib/dao/http-base-dao-options.mjs +0 -4
- package/esm2022/lib/dao/http-base-dao.mjs +0 -22
- package/esm2022/lib/dao/index.mjs +0 -8
- package/esm2022/lib/dao/interfaces/identifiable.mjs +0 -2
- package/esm2022/lib/dao/interfaces/index.mjs +0 -2
- package/esm2022/lib/dao/internal/internal-http-base-dao.mjs +0 -44
- package/esm2022/lib/dao/promise-http-base-dao.mjs +0 -23
- package/esm2022/lib/forms/form-util.mjs +0 -53
- package/esm2022/lib/forms/index.mjs +0 -2
- package/esm2022/lib/index.mjs +0 -9
- package/esm2022/lib/model-component/action/index.mjs +0 -2
- package/esm2022/lib/model-component/action/model-action.mjs +0 -2
- package/esm2022/lib/model-component/action/model-action.type.mjs +0 -2
- package/esm2022/lib/model-component/base-model-detail.component.mjs +0 -46
- package/esm2022/lib/model-component/base-model-edit.component.mjs +0 -8
- package/esm2022/lib/model-component/base-model-list.component.mjs +0 -38
- package/esm2022/lib/model-component/base-model.component.mjs +0 -22
- package/esm2022/lib/model-component/base.component.mjs +0 -15
- package/esm2022/lib/model-component/index.mjs +0 -6
- package/esm2022/lib/navigation/back-button.directive.mjs +0 -24
- package/esm2022/lib/navigation/index.mjs +0 -3
- package/esm2022/lib/navigation/navigation.service.mjs +0 -35
- package/esm2022/lib/types/index.mjs +0 -2
- package/esm2022/lib/types/value-change-callback.type.mjs +0 -2
- package/esm2022/lib/util/index.mjs +0 -4
- package/esm2022/lib/util/is-nil.mjs +0 -2
- package/esm2022/lib/util/prepare-optional-string.mjs +0 -8
- package/esm2022/lib/util/remove-nil-values-recursively.mjs +0 -33
package/README.md
CHANGED
|
@@ -1,280 +1,280 @@
|
|
|
1
|
-
# AngularBaseDao
|
|
2
|
-
|
|
3
|
-
This library helps you to create dao-services that include all **CURDL** methods out of the box without needing to implement them by your own.
|
|
4
|
-
Complex models that are received from the REST requests can simply be **converted/mapped** using the provided **HttpBaseDao** and **IConverter**.
|
|
5
|
-
|
|
6
|
-
> **CURLD** => **C**reate **R**ead **U**pdate **D**elete **L**ist
|
|
7
|
-
|
|
8
|
-
## Basic Usage
|
|
9
|
-
|
|
10
|
-
To create a service that provides all CRUDL functions out of the box follow these steps:
|
|
11
|
-
|
|
12
|
-
**Step 1: Create an entity-class which implements the **IIdentifiable** interface**
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
export interface UserModel implements IIdentifiable {
|
|
16
|
-
id: string;
|
|
17
|
-
name: string;
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
**Step 2: Create an entity-dao-service, which extends from the NoConversionHttpBaseDao-class**
|
|
22
|
-
|
|
23
|
-
By default, the service will set **withCredentials** to true, to make use of http-only cookie authentication.
|
|
24
|
-
If this is not wanted, you can specify it in the super-constructor call.
|
|
25
|
-
|
|
26
|
-
If you prefer a different kind of authentication method, you can pass custom http-headers to the affected **CRUDL** method. (`this.dao.list(false, { ...httpOptions })`)
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
@Injectable({ providedIn: 'root' })
|
|
30
|
-
export class UserDaoService extends NoConversionHttpBaseDao<UserModel> {
|
|
31
|
-
constructor() {
|
|
32
|
-
super('https://api.net/user');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**Step 3: Use the service**
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
@Component(...)
|
|
41
|
-
export class AppComponent {
|
|
42
|
-
private readonly userDao = inject(UserDaoService);
|
|
43
|
-
|
|
44
|
-
constructor() {
|
|
45
|
-
this.userDao.create({ name: 'Max' }); // returns Promise<UserModel>
|
|
46
|
-
this.userDao.read('my-id'); // returns Promise<UserModel>
|
|
47
|
-
this.userDao.update({ id: 'max-id', name: 'Max' }); // returns Promise<UserModel>
|
|
48
|
-
this.userDao.delete({ id: 'max-id', name: 'Max' }); // returns Promise<void>
|
|
49
|
-
this.userDao.list(); // returns Promise<UserModel[]>
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Using converters to convert http body to models (optional)
|
|
55
|
-
|
|
56
|
-
**Step 1: Create the request interface that the server sends back to the client**
|
|
57
|
-
|
|
58
|
-
```ts
|
|
59
|
-
export interface UserResponse implements IIdentifiable {
|
|
60
|
-
id: string;
|
|
61
|
-
creationDateString: string; // ISO string
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
**Step 2: Create the interface that the dao-service should return**
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
export interface UserModel implements IIdentifiable {
|
|
69
|
-
id: string;
|
|
70
|
-
creationDate: Date; // Real date object
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**Step 3: Create a mapper-service that maps between those two models**
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
@Injectable({ providedIn: 'root' })
|
|
78
|
-
export class PersonConverterService implements IConverter<UserResponse, UserModel> {
|
|
79
|
-
// Converts the model that was returned by the server to the model that should be used in the applciation
|
|
80
|
-
fromJson(response: UserResponse): UserModel {
|
|
81
|
-
return {
|
|
82
|
-
id: response.id,
|
|
83
|
-
creationDate: new Date(response.creationDateString), // Converts the date-string to a date
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Prepares the object that will be sent to the server for CREATE or UPDATE
|
|
88
|
-
toJson(model: UserModel): unknown {
|
|
89
|
-
return {
|
|
90
|
-
id: model.id,
|
|
91
|
-
creationDate: model.creationDate.toISOString(),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Step 4: Create the entity-dao-service and provide the created converter-service**
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
@Injectable({ providedIn: 'root' })
|
|
101
|
-
export class UserDaoService extends HttpBaseDao<UserResponse, PersonModel> {
|
|
102
|
-
constructor() {
|
|
103
|
-
super('https://api.net/user', inject(HttpClient), inject(PersonConverterService));
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Server Requirements
|
|
109
|
-
|
|
110
|
-
- REST server
|
|
111
|
-
- HTTP methods are used to differentiate between the different CRUDL request
|
|
112
|
-
|
|
113
|
-
- **POST** (CREATE)
|
|
114
|
-
|
|
115
|
-
- url: https://api.net/entity
|
|
116
|
-
- request body: **json object**
|
|
117
|
-
- response body: **json object**
|
|
118
|
-
|
|
119
|
-
- **GET** (READ)
|
|
120
|
-
|
|
121
|
-
- url: https://api.net/entity/:id
|
|
122
|
-
- request body: -
|
|
123
|
-
- response body: **json object**
|
|
124
|
-
|
|
125
|
-
- **PUT** (UPDATE)
|
|
126
|
-
|
|
127
|
-
- url: https://api.net/entity/:id
|
|
128
|
-
- request body: **json object**
|
|
129
|
-
- response body: **json object**
|
|
130
|
-
|
|
131
|
-
- **DELETE** (DELETE)
|
|
132
|
-
|
|
133
|
-
- url: https://api.net/entity/:id
|
|
134
|
-
- request body: -
|
|
135
|
-
- response body: -
|
|
136
|
-
|
|
137
|
-
- **GET** (LIST)
|
|
138
|
-
|
|
139
|
-
- url: https://api.net/entity
|
|
140
|
-
- request body: -
|
|
141
|
-
- response body: **json array**
|
|
142
|
-
|
|
143
|
-
# Angular Base Authentication
|
|
144
|
-
This library contains an abstract auth-service and guards that can be used out of the box, by just extending the `BaseAuthService<TUSER>`.
|
|
145
|
-
|
|
146
|
-
## Usage
|
|
147
|
-
|
|
148
|
-
**Step 1: Create a user model**
|
|
149
|
-
|
|
150
|
-
```js
|
|
151
|
-
interface UserModel {
|
|
152
|
-
id: string;
|
|
153
|
-
email: string;
|
|
154
|
-
isAdmin: boolean;
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Step 2: Extend the base-auth-service using the user-model**
|
|
159
|
-
|
|
160
|
-
```js
|
|
161
|
-
@Injectable({ providedIn: 'root' })
|
|
162
|
-
export class MyOwnAuthService extends BaseAuthService<UserModel>...
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**Step 3: Set the initial login state inside the constructor**
|
|
166
|
-
|
|
167
|
-
With local auth-cookie:
|
|
168
|
-
```js
|
|
169
|
-
constructor() {
|
|
170
|
-
const user = loginUsingAuthCookie(); // Validate and check your local auth-cookie
|
|
171
|
-
if (user) {
|
|
172
|
-
this.userLoggedIn(user); // Sets the login state to logged-in
|
|
173
|
-
} else {
|
|
174
|
-
this.userLoggedOut(); // Sets the login state to logged-out
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Without local tokens:
|
|
180
|
-
```js
|
|
181
|
-
constructor() {
|
|
182
|
-
this.userLoggedOut(); // Sets the initial login state to logged-out
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
> Important: Make sure the initial login state is set right after the application is started by calling `userLoggedIn`or `userLoggedOut`. If you do NOT do this the login-state will be `pending` and the provided auth-guards be waiting for the 'readyLoginState$' forever.
|
|
187
|
-
|
|
188
|
-
****
|
|
189
|
-
|
|
190
|
-
```js
|
|
191
|
-
constructor() {
|
|
192
|
-
this.userLoggedOut(); // If you do not do this, the login-state will be pending
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**Step 4: Implement the login-logout functions and call the provided functions of the base-class**
|
|
197
|
-
|
|
198
|
-
```js
|
|
199
|
-
async login(loginData): Promise<void> {
|
|
200
|
-
try {
|
|
201
|
-
const result$ = this.http.post<UserModel>(login, loginData);
|
|
202
|
-
const user = await firstValueFrom(result$);
|
|
203
|
-
this.userLoggedIn(user); // Sets the login state to logged-in
|
|
204
|
-
} catch (ex) {
|
|
205
|
-
this.userLoggedOut(); // Sets the login state to logged-out
|
|
206
|
-
return ex;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Step 5: Provide your service implementation as `BaseAuthService`**
|
|
212
|
-
app.config.ts
|
|
213
|
-
```js
|
|
214
|
-
export const appConfig: ApplicationConfig = {
|
|
215
|
-
providers: [
|
|
216
|
-
provideHttpClient(),
|
|
217
|
-
{
|
|
218
|
-
provide: BaseAuthService,
|
|
219
|
-
useExisting: MyOwnAuthService
|
|
220
|
-
},
|
|
221
|
-
...
|
|
222
|
-
]
|
|
223
|
-
};
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
**Step 6: Use it**
|
|
227
|
-
|
|
228
|
-
Now you can use the provided route-guards without any configuration.
|
|
229
|
-
```js
|
|
230
|
-
{
|
|
231
|
-
...
|
|
232
|
-
path: 'login',
|
|
233
|
-
canActivate: [loggedOutGuardFn], //Only allow when logged-out
|
|
234
|
-
...
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
...
|
|
238
|
-
path: 'account',
|
|
239
|
-
canActivate: [loggedInGuardFn], // Only allow when logged-in
|
|
240
|
-
...
|
|
241
|
-
},
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
The `BaseAuthService` also provides a list of handy observables that represent the current auth-state like:
|
|
245
|
-
```js
|
|
246
|
-
loginState$(): Observable<LoginState>
|
|
247
|
-
readyLoginState$(): Observable<LoginState>
|
|
248
|
-
readyAuthState$(): Observable<BaseAuthState<T>>
|
|
249
|
-
user$(): Observable<T | null>
|
|
250
|
-
isLoggedIn$(): Observable<boolean>
|
|
251
|
-
loginRoute(): string
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### loginState$ vs readyLoginState$
|
|
255
|
-
|
|
256
|
-
#### loginState$
|
|
257
|
-
|
|
258
|
-
Emits all login states. Initial value will be `{ loginState: 'pending', user: null }`
|
|
259
|
-
|
|
260
|
-
#### readyLoginState$
|
|
261
|
-
|
|
262
|
-
Only emits non `pending` auth-states. Therefor handy for auth-guards.
|
|
263
|
-
|
|
264
|
-
#### Explanation
|
|
265
|
-
|
|
266
|
-
When a page is initially loaded, and the user is logged in using the stored auth-cookie. The auth state will be `pending` until the cookie is verified, until this the auth guard should not check if the route can be activated. Only if `this.userLoggedOut()` or `this.userLoggedIn(user)` was called, the auth guard will check the access. This prevents the application from blocking a page from the user because login-state is not settled jet.
|
|
267
|
-
|
|
268
|
-
# angular-base
|
|
269
|
-
|
|
270
|
-
Library with different abstract components and utilities that come in handy for simple angular-applications.
|
|
271
|
-
|
|
272
|
-
# model-component
|
|
273
|
-
|
|
274
|
-
A collection of abstract classes(components) that can reflect the different action pages(list/add/edit) for a subject(model).
|
|
275
|
-
|
|
276
|
-
# navigation-service
|
|
277
|
-
|
|
278
|
-
Keeps track of visited pages and navigates back to the previous page if 'back' is called. If back is called on the initial page, the fallback route is used.
|
|
279
|
-
For example if the user is on the route '/users/edit' and clicks on the back button, the router will navigate to '/users'.
|
|
280
|
-
These base paths can be defined in the implementation of the base-model-components.
|
|
1
|
+
# AngularBaseDao
|
|
2
|
+
|
|
3
|
+
This library helps you to create dao-services that include all **CURDL** methods out of the box without needing to implement them by your own.
|
|
4
|
+
Complex models that are received from the REST requests can simply be **converted/mapped** using the provided **HttpBaseDao** and **IConverter**.
|
|
5
|
+
|
|
6
|
+
> **CURLD** => **C**reate **R**ead **U**pdate **D**elete **L**ist
|
|
7
|
+
|
|
8
|
+
## Basic Usage
|
|
9
|
+
|
|
10
|
+
To create a service that provides all CRUDL functions out of the box follow these steps:
|
|
11
|
+
|
|
12
|
+
**Step 1: Create an entity-class which implements the **IIdentifiable** interface**
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
export interface UserModel implements IIdentifiable {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Step 2: Create an entity-dao-service, which extends from the NoConversionHttpBaseDao-class**
|
|
22
|
+
|
|
23
|
+
By default, the service will set **withCredentials** to true, to make use of http-only cookie authentication.
|
|
24
|
+
If this is not wanted, you can specify it in the super-constructor call.
|
|
25
|
+
|
|
26
|
+
If you prefer a different kind of authentication method, you can pass custom http-headers to the affected **CRUDL** method. (`this.dao.list(false, { ...httpOptions })`)
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
@Injectable({ providedIn: 'root' })
|
|
30
|
+
export class UserDaoService extends NoConversionHttpBaseDao<UserModel> {
|
|
31
|
+
constructor() {
|
|
32
|
+
super('https://api.net/user');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Step 3: Use the service**
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
@Component(...)
|
|
41
|
+
export class AppComponent {
|
|
42
|
+
private readonly userDao = inject(UserDaoService);
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
this.userDao.create({ name: 'Max' }); // returns Promise<UserModel>
|
|
46
|
+
this.userDao.read('my-id'); // returns Promise<UserModel>
|
|
47
|
+
this.userDao.update({ id: 'max-id', name: 'Max' }); // returns Promise<UserModel>
|
|
48
|
+
this.userDao.delete({ id: 'max-id', name: 'Max' }); // returns Promise<void>
|
|
49
|
+
this.userDao.list(); // returns Promise<UserModel[]>
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Using converters to convert http body to models (optional)
|
|
55
|
+
|
|
56
|
+
**Step 1: Create the request interface that the server sends back to the client**
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
export interface UserResponse implements IIdentifiable {
|
|
60
|
+
id: string;
|
|
61
|
+
creationDateString: string; // ISO string
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Step 2: Create the interface that the dao-service should return**
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
export interface UserModel implements IIdentifiable {
|
|
69
|
+
id: string;
|
|
70
|
+
creationDate: Date; // Real date object
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Step 3: Create a mapper-service that maps between those two models**
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
@Injectable({ providedIn: 'root' })
|
|
78
|
+
export class PersonConverterService implements IConverter<UserResponse, UserModel> {
|
|
79
|
+
// Converts the model that was returned by the server to the model that should be used in the applciation
|
|
80
|
+
fromJson(response: UserResponse): UserModel {
|
|
81
|
+
return {
|
|
82
|
+
id: response.id,
|
|
83
|
+
creationDate: new Date(response.creationDateString), // Converts the date-string to a date
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Prepares the object that will be sent to the server for CREATE or UPDATE
|
|
88
|
+
toJson(model: UserModel): unknown {
|
|
89
|
+
return {
|
|
90
|
+
id: model.id,
|
|
91
|
+
creationDate: model.creationDate.toISOString(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Step 4: Create the entity-dao-service and provide the created converter-service**
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
@Injectable({ providedIn: 'root' })
|
|
101
|
+
export class UserDaoService extends HttpBaseDao<UserResponse, PersonModel> {
|
|
102
|
+
constructor() {
|
|
103
|
+
super('https://api.net/user', inject(HttpClient), inject(PersonConverterService));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Server Requirements
|
|
109
|
+
|
|
110
|
+
- REST server
|
|
111
|
+
- HTTP methods are used to differentiate between the different CRUDL request
|
|
112
|
+
|
|
113
|
+
- **POST** (CREATE)
|
|
114
|
+
|
|
115
|
+
- url: https://api.net/entity
|
|
116
|
+
- request body: **json object**
|
|
117
|
+
- response body: **json object**
|
|
118
|
+
|
|
119
|
+
- **GET** (READ)
|
|
120
|
+
|
|
121
|
+
- url: https://api.net/entity/:id
|
|
122
|
+
- request body: -
|
|
123
|
+
- response body: **json object**
|
|
124
|
+
|
|
125
|
+
- **PUT** (UPDATE)
|
|
126
|
+
|
|
127
|
+
- url: https://api.net/entity/:id
|
|
128
|
+
- request body: **json object**
|
|
129
|
+
- response body: **json object**
|
|
130
|
+
|
|
131
|
+
- **DELETE** (DELETE)
|
|
132
|
+
|
|
133
|
+
- url: https://api.net/entity/:id
|
|
134
|
+
- request body: -
|
|
135
|
+
- response body: -
|
|
136
|
+
|
|
137
|
+
- **GET** (LIST)
|
|
138
|
+
|
|
139
|
+
- url: https://api.net/entity
|
|
140
|
+
- request body: -
|
|
141
|
+
- response body: **json array**
|
|
142
|
+
|
|
143
|
+
# Angular Base Authentication
|
|
144
|
+
This library contains an abstract auth-service and guards that can be used out of the box, by just extending the `BaseAuthService<TUSER>`.
|
|
145
|
+
|
|
146
|
+
## Usage
|
|
147
|
+
|
|
148
|
+
**Step 1: Create a user model**
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
interface UserModel {
|
|
152
|
+
id: string;
|
|
153
|
+
email: string;
|
|
154
|
+
isAdmin: boolean;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Step 2: Extend the base-auth-service using the user-model**
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
@Injectable({ providedIn: 'root' })
|
|
162
|
+
export class MyOwnAuthService extends BaseAuthService<UserModel>...
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Step 3: Set the initial login state inside the constructor**
|
|
166
|
+
|
|
167
|
+
With local auth-cookie:
|
|
168
|
+
```js
|
|
169
|
+
constructor() {
|
|
170
|
+
const user = loginUsingAuthCookie(); // Validate and check your local auth-cookie
|
|
171
|
+
if (user) {
|
|
172
|
+
this.userLoggedIn(user); // Sets the login state to logged-in
|
|
173
|
+
} else {
|
|
174
|
+
this.userLoggedOut(); // Sets the login state to logged-out
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Without local tokens:
|
|
180
|
+
```js
|
|
181
|
+
constructor() {
|
|
182
|
+
this.userLoggedOut(); // Sets the initial login state to logged-out
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
> Important: Make sure the initial login state is set right after the application is started by calling `userLoggedIn`or `userLoggedOut`. If you do NOT do this the login-state will be `pending` and the provided auth-guards be waiting for the 'readyLoginState$' forever.
|
|
187
|
+
|
|
188
|
+
****
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
constructor() {
|
|
192
|
+
this.userLoggedOut(); // If you do not do this, the login-state will be pending
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Step 4: Implement the login-logout functions and call the provided functions of the base-class**
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
async login(loginData): Promise<void> {
|
|
200
|
+
try {
|
|
201
|
+
const result$ = this.http.post<UserModel>(login, loginData);
|
|
202
|
+
const user = await firstValueFrom(result$);
|
|
203
|
+
this.userLoggedIn(user); // Sets the login state to logged-in
|
|
204
|
+
} catch (ex) {
|
|
205
|
+
this.userLoggedOut(); // Sets the login state to logged-out
|
|
206
|
+
return ex;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Step 5: Provide your service implementation as `BaseAuthService`**
|
|
212
|
+
app.config.ts
|
|
213
|
+
```js
|
|
214
|
+
export const appConfig: ApplicationConfig = {
|
|
215
|
+
providers: [
|
|
216
|
+
provideHttpClient(),
|
|
217
|
+
{
|
|
218
|
+
provide: BaseAuthService,
|
|
219
|
+
useExisting: MyOwnAuthService
|
|
220
|
+
},
|
|
221
|
+
...
|
|
222
|
+
]
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Step 6: Use it**
|
|
227
|
+
|
|
228
|
+
Now you can use the provided route-guards without any configuration.
|
|
229
|
+
```js
|
|
230
|
+
{
|
|
231
|
+
...
|
|
232
|
+
path: 'login',
|
|
233
|
+
canActivate: [loggedOutGuardFn], //Only allow when logged-out
|
|
234
|
+
...
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
...
|
|
238
|
+
path: 'account',
|
|
239
|
+
canActivate: [loggedInGuardFn], // Only allow when logged-in
|
|
240
|
+
...
|
|
241
|
+
},
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The `BaseAuthService` also provides a list of handy observables that represent the current auth-state like:
|
|
245
|
+
```js
|
|
246
|
+
loginState$(): Observable<LoginState>
|
|
247
|
+
readyLoginState$(): Observable<LoginState>
|
|
248
|
+
readyAuthState$(): Observable<BaseAuthState<T>>
|
|
249
|
+
user$(): Observable<T | null>
|
|
250
|
+
isLoggedIn$(): Observable<boolean>
|
|
251
|
+
loginRoute(): string
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### loginState$ vs readyLoginState$
|
|
255
|
+
|
|
256
|
+
#### loginState$
|
|
257
|
+
|
|
258
|
+
Emits all login states. Initial value will be `{ loginState: 'pending', user: null }`
|
|
259
|
+
|
|
260
|
+
#### readyLoginState$
|
|
261
|
+
|
|
262
|
+
Only emits non `pending` auth-states. Therefor handy for auth-guards.
|
|
263
|
+
|
|
264
|
+
#### Explanation
|
|
265
|
+
|
|
266
|
+
When a page is initially loaded, and the user is logged in using the stored auth-cookie. The auth state will be `pending` until the cookie is verified, until this the auth guard should not check if the route can be activated. Only if `this.userLoggedOut()` or `this.userLoggedIn(user)` was called, the auth guard will check the access. This prevents the application from blocking a page from the user because login-state is not settled jet.
|
|
267
|
+
|
|
268
|
+
# angular-base
|
|
269
|
+
|
|
270
|
+
Library with different abstract components and utilities that come in handy for simple angular-applications.
|
|
271
|
+
|
|
272
|
+
# model-component
|
|
273
|
+
|
|
274
|
+
A collection of abstract classes(components) that can reflect the different action pages(list/add/edit) for a subject(model).
|
|
275
|
+
|
|
276
|
+
# navigation-service
|
|
277
|
+
|
|
278
|
+
Keeps track of visited pages and navigates back to the previous page if 'back' is called. If back is called on the initial page, the fallback route is used.
|
|
279
|
+
For example if the user is on the route '/users/edit' and clicks on the back button, the router will navigate to '/users'.
|
|
280
|
+
These base paths can be defined in the implementation of the base-model-components.
|
|
@@ -2,7 +2,6 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, Injectable, Directive, HostListener, output, Input, model, signal } from '@angular/core';
|
|
3
3
|
import { Router, NavigationEnd } from '@angular/router';
|
|
4
4
|
import { Location } from '@angular/common';
|
|
5
|
-
import { MatTableDataSource } from '@angular/material/table';
|
|
6
5
|
import { FormGroup } from '@angular/forms';
|
|
7
6
|
import { BehaviorSubject, filter, firstValueFrom, map as map$1 } from 'rxjs';
|
|
8
7
|
import { map } from 'rxjs/operators';
|
|
@@ -31,10 +30,10 @@ class NavigationService {
|
|
|
31
30
|
this.router.navigate([fallbackRoute]);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
35
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
33
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
34
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: NavigationService, providedIn: 'root' }); }
|
|
36
35
|
}
|
|
37
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: NavigationService, decorators: [{
|
|
38
37
|
type: Injectable,
|
|
39
38
|
args: [{ providedIn: 'root' }]
|
|
40
39
|
}], ctorParameters: () => [] });
|
|
@@ -124,35 +123,35 @@ class BaseModelEditComponent extends BaseModelComponent {
|
|
|
124
123
|
class BaseModelListComponent extends BaseModelComponent {
|
|
125
124
|
constructor(baseRoute) {
|
|
126
125
|
super(baseRoute);
|
|
127
|
-
this.dataSource =
|
|
126
|
+
this.dataSource = [];
|
|
128
127
|
this.backRoute = '/';
|
|
129
128
|
}
|
|
130
|
-
handleModelAction(modelAction) {
|
|
129
|
+
async handleModelAction(modelAction) {
|
|
131
130
|
switch (modelAction.action) {
|
|
132
131
|
case 'show':
|
|
133
|
-
this.
|
|
132
|
+
await this.showDetailPage(modelAction.model.id);
|
|
134
133
|
break;
|
|
135
134
|
case 'edit':
|
|
136
|
-
this.
|
|
135
|
+
await this.editModelPage(modelAction.model.id);
|
|
137
136
|
break;
|
|
138
137
|
case 'delete':
|
|
139
|
-
this.deleteModel(modelAction.model);
|
|
138
|
+
await this.deleteModel(modelAction.model);
|
|
140
139
|
break;
|
|
141
140
|
}
|
|
142
141
|
}
|
|
143
|
-
|
|
144
|
-
this.router.navigate([this.baseRoute, id]);
|
|
142
|
+
async showDetailPage(id) {
|
|
143
|
+
await this.router.navigate([this.baseRoute, id]);
|
|
145
144
|
}
|
|
146
|
-
|
|
147
|
-
this.router.navigate([`${this.baseRoute}/edit`, id]);
|
|
145
|
+
async editModelPage(id) {
|
|
146
|
+
await this.router.navigate([`${this.baseRoute}/edit`, id]);
|
|
148
147
|
}
|
|
149
|
-
|
|
150
|
-
this.router.navigate([`${this.baseRoute}/add`]);
|
|
148
|
+
async showCreateModelPage() {
|
|
149
|
+
await this.router.navigate([`${this.baseRoute}/add`]);
|
|
151
150
|
}
|
|
152
151
|
async deleteModel(model) {
|
|
153
152
|
const [deleted, newList] = await this.handleModelDelete(model);
|
|
154
153
|
if (deleted && newList) {
|
|
155
|
-
this.dataSource
|
|
154
|
+
this.dataSource = newList;
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
157
|
}
|
|
@@ -164,10 +163,10 @@ class BackButtonDirective {
|
|
|
164
163
|
onClick() {
|
|
165
164
|
this.navigation.back();
|
|
166
165
|
}
|
|
167
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
168
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
166
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: BackButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
167
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: BackButtonDirective, isStandalone: true, selector: "[ngLibBackButton]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); }
|
|
169
168
|
}
|
|
170
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
169
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: BackButtonDirective, decorators: [{
|
|
171
170
|
type: Directive,
|
|
172
171
|
args: [{
|
|
173
172
|
selector: '[ngLibBackButton]',
|
|
@@ -550,10 +549,10 @@ class BaseControlValueAccessor {
|
|
|
550
549
|
setDisabledState(isDisabled) {
|
|
551
550
|
this.disabled = isDisabled;
|
|
552
551
|
}
|
|
553
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
554
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
552
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: BaseControlValueAccessor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
553
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.6", type: BaseControlValueAccessor, isStandalone: true, inputs: { value: "value" }, outputs: { valueChanged: "valueChanged" }, ngImport: i0 }); }
|
|
555
554
|
}
|
|
556
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: BaseControlValueAccessor, decorators: [{
|
|
557
556
|
type: Directive
|
|
558
557
|
}], propDecorators: { value: [{
|
|
559
558
|
type: Input
|
|
@@ -593,10 +592,10 @@ class SignalBaseControlValueAccessor {
|
|
|
593
592
|
setDisabledState(disabled) {
|
|
594
593
|
this.disabled.set(disabled);
|
|
595
594
|
}
|
|
596
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
597
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
595
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SignalBaseControlValueAccessor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
596
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: SignalBaseControlValueAccessor, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0 }); }
|
|
598
597
|
}
|
|
599
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
598
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SignalBaseControlValueAccessor, decorators: [{
|
|
600
599
|
type: Directive
|
|
601
600
|
}] });
|
|
602
601
|
|