Code Monkey home page Code Monkey logo

angular-101's Introduction

Ng101

This project was generated with Angular CLI version 11.2.7.

Log

How this workspace was generated

npx @angular/cli new ng101 --directory=angular-101 --package-manager=yarn --strict --create-application=false

How this workspace was generated

How demo app was generated

yarn ng generate application demo --routing --style=scss

How demo app was generated

What demo app initially looked like

yarn start --open

What demo app initially looked like

How Angular Material was added

yarn ng add @angular/material

How Angular Material was added

How unit tests are run

yarn test

How unit tests are run

Note: Karma was configured to run on headless Chrome. The default setup would open a new Chrome instance.

How lazy-loaded module was added

yarn ng g module todo-list --module=app --route=todo-list

How lazy-loaded module was added

Note: g is the short alias for generate.

How to generate a layout

# first create the module
yarn ng g module layouts/main-layout --module=todo-list/todo-list

# then create the component
yarn ng g component layouts/main-layout/main-layout --export --change-detection=OnPush --flat

Main layout before theming

Note 1: Configured todo list routing to have nested routes.

Note 2: Obviously, some styles as well as Material components were used to get that result.

How to customize Material theme

@import "~@angular/material/theming";

$app-primary: mat-palette($mat-blue-grey, 900);
$app-accent: mat-palette($mat-yellow, 700);
$app-warn: mat-palette($mat-red, 600);
$app-theme: mat-light-theme(
  (
    color: (
      primary: $app-primary,
      accent: $app-accent,
      warn: $app-warn,
    ),
  )
);

Main layout after theming

Note 1: Material has over 900 free icons and, when required, custom icons can be registered via a service.

Note 2: Angular protects us against XSS attacks. Custom SVG source could be registered only after explicit trust was granted.

How linter is run

yarn lint

How to run linter

Note: TS Lint is deprecated and is expected to be replaced by Angular team.

How mock library was generated

Important: This step is not needed when developing Angular apps with a backend.

yarn ng g library mock --entry-file=index --skip-package-json

How mock library was generated

How MSW and PouchDB were integrated

Important: This step is not needed when developing Angular apps with a backend.

  1. Installed dependencies.
  2. Added pouchdb to script injected by Angular when app is built.
  3. Used MSW CLI to create mockServiceWorker.js and added the generated file to assets.
  4. Created models and request handlers.
  5. Initiated msw only in development.

How mock requests work

Note: Using MSW is a personal preference. There are other mocking options such as Angular in-memory-web-api or providing mock services with dependency injection.

How AsyncPipe, ngIf, nfFor, and ngClass works

<mat-card>
  <mat-selection-list *ngIf="list$ | async as list; else spinner">
    <mat-list-option [selected]="todo.done" *ngFor="let todo of list.rows">
      <span [ngClass]="{ done: todo.done }">{{ todo.title }}</span>
    </mat-list-option>
  </mat-selection-list>

  <ng-template #spinner>
    <div class="spinner">
      <mat-spinner diameter="60" color="accent"></mat-spinner>
    </div>
  </ng-template>
</mat-card>

How AsyncPipe, ngIf, nfFor, and ngClass works

How to execute async operations before initialization

The mock DB implementation so far has an error. When site data is cleared and the page is refreshed, the first response is empty.

Clearing site data before async initializer implementation

This is due to lack of proper asynchronous initialization. APP_INITIALIZER serves that purpose.

{
  provide: APP_INITIALIZER,
  useFactory: () => {
    return async () => {
      await seedDb();
      worker.start();
    };
  },
  multi: true,
}

Clearing site data after async initializer implementation

How to update a record

CRUD operations via AJAX are probably the most common implementations in web development. Angular has an awesome HttpClient to do all sorts of HTTP requests.

@Injectable()
export class TodoService {
  constructor(private http: HttpClient) {}

  update(id: string, input: TodoUpdate) {
    return this.http.put<void>(`/api/todos/${id}`, input);
  }
}

...and in component class...

@Component(/* removed for brevity */)
export class TodoListComponent {
  private listUpdate$ = new Subject<void>();

  list$ = merge(of(0), this.listUpdate$).pipe(
    switchMap(() => this.todo.getList())
  );

  constructor(private todo: TodoService, private dialog: MatDialog) {}

  toggleDone(todo: Rec<Todo>) {
    this.todo
      .update(todo.id, { title: todo.title, done: !todo.done })
      .subscribe(() => this.listUpdate$.next());
  }
}

How to update a record

Note: Did you notice the canceled request? This is due to use of switchMap in list$.

How to delete a record

Sometimes, we need to get some confirmation before proceeding with the request. HttpClient uses RxJS observables, so that usually is quite easy.

@Component(/* removed for brevity */)
export class TodoListComponent {
  @ViewChild("deleteDialog") deleteDialog?: TemplateRef<any>;

  /* removed for brevity */

  deleteRecord(todo: Rec<Todo>) {
    this.dialog
      .open(this.deleteDialog!, { data: todo.title })
      .afterClosed()
      .pipe(
        concatMap((confirmed) =>
          confirmed ? this.todo.delete(todo.id) : EMPTY
        )
      )
      .subscribe(() => this.listUpdate$.next());
  }
}

How to delete a record

How to refactor routes

Angular modules manage their own child routes and parent modules are unaware of grand child routes. This makes it easy to refactor routes.

@NgModule({
  imports: [
    RouterModule.forChild([{ path: "", component: TodoListComponent }]),
  ],
  exports: [RouterModule],
})
export class TodoListRoutingModule {}

@NgModule({
  imports: [
    RouterModule.forChild([
      {
        path: "",
        component: MainLayoutComponent,
        children: [
          { path: "", pathMatch: "full", loadChildren: () => TodoListModule },
        ],
      },
    ]),
  ],
  exports: [RouterModule],
})
export class TodosRoutingModule {}

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: "",
        loadChildren: () =>
          import("./todos/todos.module").then((m) => m.TodosModule),
      },
    ]),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Although there is now an additional module between, nothing changes.

How to refactor routes

How to create a new record

yarn ng g module todo-create --module=todos/todos --route=create

How todo create module was generated

@Component(/* removed for brevity */)
export class TodoCreateComponent {
  form!: FormGroup;

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private todo: TodoService
  ) {
    this.buildForm();
  }

  goToListView() {
    this.router.navigate([".."]);
  }

  submitForm() {
    if (!this.form.valid) return;

    this.todo.create(this.form.value).subscribe(() => this.goToListView());
  }

  private buildForm(): void {
    this.form = this.fb.group({
      title: [null, Validators.required],
    });
  }
}

...and in template...

<!-- removed for brevity -->

<form [formGroup]="form" id="todo-form" (ngSubmit)="submitForm()">
  <mat-form-field>
    <mat-label>Todo title *</mat-label>
    <input
      matInput
      formControlName="title"
      placeholder="Become a Jedi Knight"
      maxlength="256"
      autocomplete="off"
      #title
    />
    <mat-hint align="end">{{ title.value.length }} / 256</mat-hint>
    <mat-error *ngIf="form.get('title')?.invalid">
      Sorry, this field is <strong>required</strong>.
    </mat-error>
  </mat-form-field>
</form>

<!-- removed for brevity -->

How reactive forms work

How HTTP errors were handled

yarn ng g interceptor common/error --flat

How error interceptor was generated

...then...

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(private snackBar: MatSnackBar) {}

  intercept(
    request: HttpRequest<unknown>,
    next: HttpHandler
  ): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      catchError(({ error, status }) => {
        this.snackBar.open(`${status}: ${error}`, "HTTP Error", {
          duration: 3000,
        });
        return EMPTY;
      })
    );
  }
}

...and in root module...

@NgModule({
  /* removed for brevity */

  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ErrorInterceptor,
      multi: true,
    },
  ],
})
export class AppModule {}

Note: Http interceptors can intercept outgoing requests as well.

How to get a production build of the app

yarn build --prod

How to build an Angular app

Further help

To get more help on the Angular CLI use ng help or go check out the Angular CLI Overview and Command Reference page.

angular-101's People

Contributors

armanozak avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.