Skip to content

Commit 0fbe8f3

Browse files
authored
Merge pull request #13 from reduxjs/injector-demo
Injector demo
2 parents bfc9695 + 66be8c7 commit 0fbe8f3

21 files changed

+239
-11
lines changed

angular.json

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
}
3535
}
3636
},
37-
"angular-redux-demo": {
37+
"angular-redux-simple-demo": {
3838
"projectType": "application",
3939
"schematics": {
4040
"@schematics/angular:component": {
@@ -64,25 +64,25 @@
6464
"skipTests": true
6565
}
6666
},
67-
"root": "projects/angular-redux-demo",
68-
"sourceRoot": "projects/angular-redux-demo/src",
67+
"root": "projects/angular-redux-simple-demo",
68+
"sourceRoot": "projects/angular-redux-simple-demo/src",
6969
"prefix": "app",
7070
"architect": {
7171
"build": {
7272
"builder": "@angular-devkit/build-angular:application",
7373
"options": {
74-
"outputPath": "dist/angular-redux-demo",
75-
"index": "projects/angular-redux-demo/src/index.html",
76-
"browser": "projects/angular-redux-demo/src/main.ts",
74+
"outputPath": "dist/angular-redux-simple-demo",
75+
"index": "projects/angular-redux-simple-demo/src/index.html",
76+
"browser": "projects/angular-redux-simple-demo/src/main.ts",
7777
"polyfills": ["zone.js"],
78-
"tsConfig": "projects/angular-redux-demo/tsconfig.app.json",
78+
"tsConfig": "projects/angular-redux-simple-demo/tsconfig.app.json",
7979
"assets": [
8080
{
8181
"glob": "**/*",
82-
"input": "projects/angular-redux-demo/public"
82+
"input": "projects/angular-redux-simple-demo/public"
8383
}
8484
],
85-
"styles": ["projects/angular-redux-demo/src/styles.css"],
85+
"styles": ["projects/angular-redux-simple-demo/src/styles.css"],
8686
"scripts": []
8787
},
8888
"configurations": {
@@ -113,10 +113,79 @@
113113
"builder": "@angular-devkit/build-angular:dev-server",
114114
"configurations": {
115115
"production": {
116-
"buildTarget": "angular-redux-demo:build:production"
116+
"buildTarget": "angular-redux-simple-demo:build:production"
117117
},
118118
"development": {
119-
"buildTarget": "angular-redux-demo:build:development"
119+
"buildTarget": "angular-redux-simple-demo:build:development"
120+
}
121+
},
122+
"defaultConfiguration": "development"
123+
},
124+
"extract-i18n": {
125+
"builder": "@angular-devkit/build-angular:extract-i18n"
126+
}
127+
}
128+
},
129+
"angular-redux-injector-demo": {
130+
"projectType": "application",
131+
"schematics": {},
132+
"root": "projects/angular-redux-injector-demo",
133+
"sourceRoot": "projects/angular-redux-injector-demo/src",
134+
"prefix": "app",
135+
"architect": {
136+
"build": {
137+
"builder": "@angular-devkit/build-angular:application",
138+
"options": {
139+
"outputPath": "dist/angular-redux-injector-demo",
140+
"index": "projects/angular-redux-injector-demo/src/index.html",
141+
"browser": "projects/angular-redux-injector-demo/src/main.ts",
142+
"polyfills": [
143+
"zone.js"
144+
],
145+
"tsConfig": "projects/angular-redux-injector-demo/tsconfig.app.json",
146+
"assets": [
147+
{
148+
"glob": "**/*",
149+
"input": "projects/angular-redux-injector-demo/public"
150+
}
151+
],
152+
"styles": [
153+
"projects/angular-redux-injector-demo/src/styles.css"
154+
],
155+
"scripts": []
156+
},
157+
"configurations": {
158+
"production": {
159+
"budgets": [
160+
{
161+
"type": "initial",
162+
"maximumWarning": "500kB",
163+
"maximumError": "1MB"
164+
},
165+
{
166+
"type": "anyComponentStyle",
167+
"maximumWarning": "2kB",
168+
"maximumError": "4kB"
169+
}
170+
],
171+
"outputHashing": "all"
172+
},
173+
"development": {
174+
"optimization": false,
175+
"extractLicenses": false,
176+
"sourceMap": true
177+
}
178+
},
179+
"defaultConfiguration": "production"
180+
},
181+
"serve": {
182+
"builder": "@angular-devkit/build-angular:dev-server",
183+
"configurations": {
184+
"production": {
185+
"buildTarget": "angular-redux-injector-demo:build:production"
186+
},
187+
"development": {
188+
"buildTarget": "angular-redux-injector-demo:build:development"
120189
}
121190
},
122191
"defaultConfiguration": "development"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, EnvironmentInjector, inject } from '@angular/core';
2+
import { injectSelector, injectDispatch } from '@reduxjs/angular-redux';
3+
import { incrementByRandomNumber } from './store/counter-slice';
4+
import { AppDispatch, RootState } from './store';
5+
6+
@Component({
7+
selector: 'app-root',
8+
standalone: true,
9+
template: `
10+
<button (click)="incrementByRandomNumber()">
11+
Increment by a random number
12+
</button>
13+
<p>{{ count() }}</p>
14+
@if (isLoading()) {
15+
<p>Loading...</p>
16+
}
17+
`,
18+
})
19+
export class AppComponent {
20+
injector = inject(EnvironmentInjector);
21+
count = injectSelector((state: RootState) => state.counter.value);
22+
isLoading = injectSelector((state: RootState) => state.counter.isLoading);
23+
dispatch = injectDispatch<AppDispatch>();
24+
incrementByRandomNumber = () => {
25+
this.dispatch(incrementByRandomNumber({ injector: this.injector }));
26+
};
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Injectable } from '@angular/core';
2+
3+
@Injectable({ providedIn: 'root' })
4+
export class RandomNumberService {
5+
getRandomNumber() {
6+
return new Promise<number>((resolve) => {
7+
setTimeout(() => {
8+
resolve(Math.floor(Math.random() * 100));
9+
}, 1000);
10+
});
11+
}
12+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
2+
import { inject } from '@angular/core';
3+
import { RandomNumberService } from '../services/random-number.service';
4+
import {
5+
asyncRunInInjectionContext,
6+
RunInInjectionContextProps,
7+
} from '../utils/async-run-in-injection-context';
8+
9+
export const incrementByRandomNumber = createAsyncThunk(
10+
'counter/incrementByAmountFromService',
11+
(arg: RunInInjectionContextProps<{}>, _thunkAPI) => {
12+
return asyncRunInInjectionContext(arg.injector, async () => {
13+
const service = inject(RandomNumberService);
14+
const newCount = await service.getRandomNumber();
15+
return newCount;
16+
});
17+
},
18+
);
19+
20+
export const counterSlice = createSlice({
21+
name: 'counter',
22+
initialState: {
23+
value: 0,
24+
isLoading: false,
25+
},
26+
reducers: {},
27+
extraReducers: (builder) => {
28+
builder.addCase(incrementByRandomNumber.fulfilled, (state, action) => {
29+
state.isLoading = false;
30+
state.value += action.payload;
31+
});
32+
builder.addCase(incrementByRandomNumber.pending, (state) => {
33+
state.isLoading = true;
34+
});
35+
},
36+
});
37+
38+
export default counterSlice.reducer;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { EnvironmentInjector, runInInjectionContext } from '@angular/core';
2+
3+
export const asyncRunInInjectionContext = <TReturn>(
4+
injector: EnvironmentInjector,
5+
fn: () => Promise<TReturn>,
6+
) => {
7+
return new Promise<TReturn>((resolve, reject) => {
8+
runInInjectionContext(injector, () => {
9+
fn()
10+
.then((value) => {
11+
resolve(value);
12+
})
13+
.catch((error) => {
14+
reject(error);
15+
});
16+
});
17+
});
18+
};
19+
20+
export type RunInInjectionContextProps<
21+
T extends object,
22+
> = T & {
23+
injector: EnvironmentInjector;
24+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>AngularReduxInjectorDemo</title>
6+
<base href="/">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<link rel="icon" type="image/x-icon" href="favicon.ico">
9+
</head>
10+
<body>
11+
<app-root></app-root>
12+
</body>
13+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { bootstrapApplication } from '@angular/platform-browser';
2+
import { appConfig } from './app/app.config';
3+
import { AppComponent } from './app/app.component';
4+
5+
bootstrapApplication(AppComponent, appConfig)
6+
.catch((err) => console.error(err));
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3+
{
4+
"extends": "../../tsconfig.json",
5+
"compilerOptions": {
6+
"outDir": "../../out-tsc/app",
7+
"types": []
8+
},
9+
"files": [
10+
"src/main.ts"
11+
],
12+
"include": [
13+
"src/**/*.d.ts"
14+
]
15+
}
Binary file not shown.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
2+
import { provideRedux } from '@reduxjs/angular-redux';
3+
import { store } from './store';
4+
5+
export const appConfig: ApplicationConfig = {
6+
providers: [
7+
provideZoneChangeDetection({ eventCoalescing: true }),
8+
provideRedux({ store }),
9+
],
10+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { configureStore } from '@reduxjs/toolkit';
2+
import counterReducer from './counter-slice';
3+
4+
export const store = configureStore({
5+
reducer: {
6+
counter: counterReducer,
7+
},
8+
});
9+
10+
// Infer the `RootState` and `AppDispatch` types from the store itself
11+
export type RootState = ReturnType<typeof store.getState>;
12+
// Inferred type: {counter: CounterState}
13+
export type AppDispatch = typeof store.dispatch;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/* You can add global styles to this file, and also import other style files */

0 commit comments

Comments
 (0)