Skip to content

Commit 884042a

Browse files
feat: restyled & separated async user cards
1 parent 225b431 commit 884042a

19 files changed

+206
-130
lines changed

README.md

+37-64
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Angular API Project
1+
# :zap: Angular API Project
22

3-
* App using a DataService with httpClient to get json data from an external API.
3+
* App using a DataService with httpClient to get a JSON Observable data stream from an API and display it using the Angular async pipe.
44
* App also submits a simple Contact form.
55
* **Note:** to open web links in a new window use: _ctrl+click on link_
66

@@ -9,99 +9,72 @@
99
![GitHub Repo stars](https://img.shields.io/github/stars/AndrewJBateman/angular-api-project?style=plastic)
1010
![GitHub last commit](https://img.shields.io/github/last-commit/AndrewJBateman/angular-api-project?style=plastic)
1111

12-
## Table of contents
12+
## :page_facing_up: Table of contents
1313

14-
* [Angular API Project](#angular-api-project)
15-
* [Table of contents](#table-of-contents)
16-
* [General info](#general-info)
17-
* [Screenshots](#screenshots)
18-
* [Technologies](#technologies)
19-
* [Setup](#setup)
20-
* [Code Examples](#code-examples)
21-
* [Features](#features)
22-
* [Status & To-Do List](#status--to-do-list)
23-
* [Inspiration](#inspiration)
24-
* [Contact](#contact)
14+
* [:zap: Angular API Project](#:zap-angular-api-project)
15+
* [:page_facing_up: Table of contents](#page_facing_up-table-of-contents)
16+
* [:books: General info](#books-general-info)
17+
* [:camera: Screenshots](#camera-screenshots)
18+
* [:signal_strength: Technologies](#signal_strength-technologies)
19+
* [:floppy_disk: Setup](#floppy_disk-setup)
20+
* [:computer: Code Examples](#code-examples)
21+
* [:cool: Features](#features)
22+
* [:clipboard: Status & To-Do List](#status--to-do-list)
23+
* [:clap: Inspiration](#inspiration)
24+
* [:file_folder: License](#file_folder-license)
25+
* [:envelope: Contact](#contact)
2526

26-
## General info
27+
## :books: General info
2728

2829
* Routing module allows user to navigate between Home, About and Contact pages.
29-
* API json/image data displayed: firstname, lastname and avatar.
30+
* API json/image data displayed: firstname, lastname, email and avatar.
3031
* Angular FormBuilder used to allow user to submit a form with name and message. Form uses validation.
31-
* Simple app.
32+
* Styling is pure SCSS
3233

33-
## Screenshots
34+
## :camera: Screenshots
3435

35-
![Example screenshot](./img/api-info.png).
36-
![Example screenshot](./img/contact-form.png).
36+
![Example screenshot](./imgs/home.png).
37+
![Example screenshot](./imgs/contact-form.png).
3738

38-
## Technologies
39+
## :signal_strength: Technologies
3940

40-
* [Angular v12](https://angular.io/)
41-
* [RxJS Library v6](https://angular.io/guide/rx-library) used to [subscribe](http://reactivex.io/documentation/operators/subscribe.html) to the API data [observable](http://reactivex.io/documentation/observable.html).
41+
* [Angular v13](https://angular.io/)
42+
* [RxJS Library v7](https://angular.io/guide/rx-library) used to [subscribe](http://reactivex.io/documentation/operators/subscribe.html) to the API data [observable](http://reactivex.io/documentation/observable.html).
4243
* [The HttpClient in @angular/common/http](https://angular.io/guide/http) offers a simplified client HTTP API for Angular applications that rests on the XMLHttpRequest interface exposed by browsers.
4344

44-
## Setup
45+
## :floppy_disk: Setup
4546

4647
* Run `npm i` to install dependencies
4748
* Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
4849

49-
## Code Examples
50+
## :computer: Code Examples
5051

51-
* `home.component.ts`
52+
* `data.service.ts` ES6 arrow function to return observable from API using `apiResponse` interface
5253

5354
```typescript
54-
import { Component, OnInit } from "@angular/core";
55-
import { DataService } from "../data.service";
56-
57-
@Component({
58-
selector: "app-home",
59-
templateUrl: "./home.component.html",
60-
styleUrls: ["./home.component.scss"]
61-
})
62-
export class HomeComponent implements OnInit {
63-
users: Object;
64-
65-
constructor(private data: DataService) {}
66-
67-
// on init the Dataservice getUsers() function supplies a user array object.
68-
ngOnInit() {
69-
this.data.getUsers().subscribe(data => {
70-
this.users = data;
71-
console.log(this.users);
72-
});
73-
}
55+
getUsers = (): Observable<apiResponse> => {
56+
return this.http.get<apiResponse>("https://reqres.in/api/users");
7457
}
7558
```
7659

77-
* `data.service.ts`
60+
* `home.component.ts` ng init. function to get observable data for the template async pipe - note: using an ES6 arrow function here would result in nothing being displayed, due the use of 'this'
7861

7962
```typescript
80-
import { Injectable } from "@angular/core";
81-
import { HttpClient } from "@angular/common/http";
82-
83-
@Injectable({
84-
providedIn: "root"
85-
})
86-
export class DataService {
87-
constructor(private http: HttpClient) {}
88-
89-
getUsers() {
90-
return this.http.get("https://reqres.in/api/users");
91-
}
92-
}
63+
ngOnInit () {
64+
this.users$ = this.data.getUsers();
65+
};
9366
```
9467

95-
## Features
68+
## :cool: Features
9669

9770
* API web link could be changed to get different and more complex data.
9871

99-
## Status & To-Do List
72+
## :clipboard: Status & To-Do List
10073

101-
* Status: Working. Updated may 2021.
74+
* Status: Working.
10275
* To-Do: Nothing.
10376

104-
## Inspiration
77+
## :clap: Inspiration
10578

10679
* [Gary Simon of Coursetro Tutorial: Angular 7 Tutorial - Learn Angular 7 by Example](https://coursetro.com/posts/code/171/Angular-7-Tutorial---Learn-Angular-7-by-Example)
10780

img/api-info.png

-149 KB
Binary file not shown.
File renamed without changes.

imgs/home.png

301 KB
Loading

src/app/app.component.html

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
1-
<!--selector app-nav references nav.component-->
21
<app-nav></app-nav>
3-
4-
<section>
5-
<router-outlet></router-outlet>
6-
</section>
2+
<router-outlet></router-outlet>

src/app/app.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { NavComponent } from "./nav/nav.component";
99
import { AboutComponent } from "./about/about.component";
1010
import { ContactComponent } from "./contact/contact.component";
1111
import { HomeComponent } from "./home/home.component";
12+
import { CardComponent } from './home/card/card.component';
1213

1314
@NgModule({
1415
declarations: [
@@ -17,6 +18,7 @@ import { HomeComponent } from "./home/home.component";
1718
AboutComponent,
1819
ContactComponent,
1920
HomeComponent,
21+
CardComponent,
2022
],
2123
imports: [
2224
BrowserModule,

src/app/home/card/card.component.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class="container">
2+
<div class="card">
3+
<div class="card-header">
4+
<div class="avatar">
5+
<div class="user-online-indicator"></div>
6+
<img
7+
[src]="user.avatar"
8+
alt="user-image of {{user.email}}"
9+
/>
10+
</div>
11+
<div class="profile-name"><h1>{{ user.first_name }} {{ user.last_name }}</h1></div>
12+
<div class="profile-role">{{user.email}}</div>
13+
</div>
14+
</div>
15+
</div>

src/app/home/card/card.component.scss

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
* {
2+
margin: 0;
3+
padding: 0;
4+
// box-sizing: border-box;
5+
}
6+
7+
.container {
8+
width: 100%;
9+
// height: 100vh;
10+
display: flex;
11+
align-items: center;
12+
justify-content: center;
13+
background-color: #bacad9;
14+
15+
.card {
16+
width: 360px;
17+
height: 180px;
18+
background-color: #fff;
19+
display: flex;
20+
flex-direction: column;
21+
border-radius: 10px;
22+
padding-top: 25px;
23+
cursor: pointer;
24+
justify-content: space-between;
25+
margin: 8px;
26+
27+
28+
.card-header {
29+
display: flex;
30+
flex-direction: column;
31+
align-items: center;
32+
33+
.avatar {
34+
width: 100px;
35+
height: 100px;
36+
position: relative;
37+
}
38+
39+
.user-online-indicator {
40+
width: 15px;
41+
height: 15px;
42+
background-color: #53f45a;
43+
position: absolute;
44+
bottom: 10px;
45+
right: 8px;
46+
border-radius: 50%;
47+
z-index: 4;
48+
}
49+
50+
.avatar img {
51+
width: 100%;
52+
height: 100%;
53+
object-fit: cover;
54+
border-radius: 50%;
55+
}
56+
57+
.profile-name h1 {
58+
font-size: 28px;
59+
font-weight: 500;
60+
}
61+
.profile-role {
62+
color: #6d6d6d;
63+
}
64+
}
65+
}
66+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { CardComponent } from './card.component';
4+
5+
describe('CardComponent', () => {
6+
let component: CardComponent;
7+
let fixture: ComponentFixture<CardComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ CardComponent ]
12+
})
13+
.compileComponents();
14+
});
15+
16+
beforeEach(() => {
17+
fixture = TestBed.createComponent(CardComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
});
21+
22+
it('should create', () => {
23+
expect(component).toBeTruthy();
24+
});
25+
});

src/app/home/card/card.component.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Component, Input, OnInit } from "@angular/core";
2+
import { Data } from "../data.model";
3+
4+
@Component({
5+
selector: "app-card",
6+
templateUrl: "./card.component.html",
7+
styleUrls: ["./card.component.scss"],
8+
})
9+
export class CardComponent implements OnInit {
10+
@Input() user: Data;
11+
12+
constructor() {}
13+
14+
ngOnInit(): void {}
15+
}

src/app/home/data.model.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export interface apiResponse {
2+
page: number;
3+
per_page: number;
4+
total: number;
5+
total_pages: number;
6+
data: Data[];
7+
support: {
8+
url: string;
9+
text: string;
10+
};
11+
}
12+
13+
export interface Data {
14+
id: number;
15+
email: string;
16+
first_name: string;
17+
last_name: string;
18+
avatar: string
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { Injectable } from "@angular/core";
22
import { HttpClient } from "@angular/common/http";
3+
import { apiResponse } from "./data.model";
4+
import { Observable } from "rxjs";
35

46
@Injectable({
57
providedIn: "root",
68
})
79
export class DataService {
810
constructor(private http: HttpClient) {}
911

10-
getUsers() {
11-
return this.http.get("https://reqres.in/api/users");
12+
getUsers = (): Observable<apiResponse> => {
13+
return this.http.get<apiResponse>("https://reqres.in/api/users");
1214
}
1315
}

src/app/home/home.component.html

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
<h1>Users</h1>
2-
3-
<ul *ngIf="users">
4-
<li *ngFor="let user of users['data']">
5-
<img [src]="user.avatar">
6-
<p>{{ user.first_name }} {{ user.last_name }}</p>
7-
</li>
8-
</ul>
1+
<app-card
2+
*ngFor="let user of (users$ | async)?.data; index as i"
3+
[user]="user"
4+
></app-card>

src/app/home/home.component.scss

-24
Original file line numberDiff line numberDiff line change
@@ -1,24 +0,0 @@
1-
ul {
2-
list-style-type: none;
3-
margin: 0;
4-
padding: 0;
5-
6-
li {
7-
background: rgb(238, 238, 238);
8-
padding: 2em;
9-
border-radius: 4px;
10-
margin-bottom: 7px;
11-
display: grid;
12-
grid-template-columns: 60px auto;
13-
14-
p {
15-
font-weight: bold;
16-
margin-left: 20px;
17-
}
18-
19-
img {
20-
border-radius: 50%;
21-
width: 100%;
22-
}
23-
}
24-
}

0 commit comments

Comments
 (0)