Skip to content

Latest commit

 

History

History
1071 lines (818 loc) · 29.2 KB

7.-angular.md

File metadata and controls

1071 lines (818 loc) · 29.2 KB

4. Angular

{% embed url="https://github.com/PatrickJS/awesome-angular" %}

Basics

  • Angular is a Javascript Framework which allows you to create reactive Single-Page-Applications (SPAs)
  • Angular 1 (now referred as Angular JS it's totally different) Obsolete now it has been rewritten
  • Angular 2 -> Angular 4 -> ... -> Angular 9 -> Angular 11 (Nothing changed since Angular 2)
  • Install Angular CLI using npm
  • create new project using ng new my-first-project
  • It's advanced version of Javascript. Simple Janilla Javascript lacks types, classes, Interfaces
  • Typescript gets compiled to Javascript then run in browser but this compilation is very fast

Setting up Bootstrap

  • npm install --save bootstrap
  • Then in angular.json in styles add path "node_modules/bootstrap/dist/css/bootstrap.min.css"Before default styles.css

Create Component

  • Angular uses bunch of modules, default is app module which has declaration of all components within that module, imports are external modules which are imported.
  • App Component is then called, each component has seperate css, ts, html and .spec.ts (for testing)
  • You can manually create a new component, put in new folder take care of naming convention and add it to module or type ng generate component servers
  • by default it will create css, ts, html and .spec.ts file for every component use ng g c comp_name --inlineTemplate --inlineStyle to create inline component. You can set it as default behaviour by going to angular.json file and edit schematics property

  • --flat flag don't make a new folder

Data Binding

  • Shows data from typescript to html
export class ServerComponent implements OnInit {
    serverId: number = 10;
    serverStatus: string = 'offline';

    constructor() { }
    ngOnInit() { }
}

// In HTML
<p>Server with {{ serverId }} is {{ serverStatus }}</p>

Property & Event Binding

  • Change some property of Element/Component (uses [...])
  • Do some event (uses (...))
export class ServersComponent implements OnInit {
  allowNewServer = false;

  constructor() {
    setTimeout(() => {
      this.allowNewServer = true;
    }, 2000);
  }
  ngOnInit(): void {
  }
}

// In HTML

<button
  class="btn btn-primary"
  [disabled]="!allowNewServer"
  (click)="onClickAddServer($event)"
>
  Add Server
</button>

Two Way Binding

  • Two way bind data say for textbox, here it uses a special directive ng-model
// HTML
<input type="text" class="form-control" [(ngModel)]="serverName" />

// Make sure to import this in App module
import { FormsModule } from '@angular/forms';
// And put this inside imports of App module
FormsModule

Built-in Directives

  • Directives are instructions in the DOM

#noServer in below is a reference to an HTML component which can be refereed within HTML document. To use it in TS @ViewChild('name-of-ref') refVariable: ElementRef;Now we can use refVariable. Get element using - refVariable.nativeElement.value;

// * because it's a structural directive
<p *ngIf="serverCreated; else noServer">Server was created!</p>
<ng-template #noServer>
    <p>No Server was created!</p>
</ng-template>

/* severCreated is a bool defined in component.ts
ng-template marks a portion with some tag, so that it can be used later
*/

// ngStyle derrivative, here it's binded with getColor method that's why inside []
<p [ngStyle]="{backgroundColor: getColor()}">{{ status }}</p>

// ngClass, it adds css class based on condition specified
<p [ngClass]="{online: isOnline, offline: !isOnline}">{{ status }}</p>

// ngFor
<p *ngFor="let item of items">{{ item }}</p>

You can also create nameOfComponent.model.ts file to store some typeScript class maybe like a data class which is getting instanced in the original component.ts

Install Augury Chrome Extension

Interface

  • ng g i habbitInterface
export interface Habit {
    id: number;
    title: string;
    count: number;
    streak?: boolean;    // ? means optional
}

// inside a class ts, HabbitService is just a class that
// does http calls and returns list of habbits

ngOnInit(): void {
    this.habbits = this.habitService.getHabits().pipe(map(habits => {
        return habits.map(habit => {
            habit.stream = habit.count > 5 : true : false;
            return habit;
        })
    }));
}

/* basically streak is computed property that is set
automatically */

Components & Data Binding

Send custom Data from parent to child

// parent.html
<app-server-element
  *ngFor="let serverElement of serverElements"
  [element]="serverElement"
></app-server-element>

// child.ts
export class ServerElementComponent implements OnInit {
  @Input() element: { type: string, name: string, content: string };

  constructor() { }
  ngOnInit(): void { }
}

// child.html
{{ element.name }}
  • Without @Input decorator the variable won't be accessible as property
  • You can set an alias instead of variable name by @Input('alias-instead-of-variable-name')

Get Event callback from child to parent

// child.ts
export class CockpitComponent implements OnInit {

  @Output() serverCreated = new EventEmitter<{ serverName: string, serverContent: string }>();
  @Output() blueprintCreated = new EventEmitter<{ blueprintName: string, blueprintContent: string }>();
  newServerName = '';
  newServerContent = '';

  constructor() { }
  ngOnInit(): void { }
  onAddServer() {    // called by button in child.html
    this.serverCreated.emit({ serverName: this.newServerName, serverContent: this.newServerContent });
  }
  onAddBlueprint() {    // called by button in child.html
    this.blueprintCreated.emit({ blueprintName: this.newServerName, blueprintContent: this.newServerContent });
  }
}

// parent.html
<app-cockpit
  (serverCreated)="onServerAdded($event)"
  (blueprintCreated)="onBlueprintAdded($event)"
></app-cockpit>
  • You can set an alias instead of variable name by @Output('alias-instead-of-variable-name')

View Encapsulation

CSS defined in a component say for p, then it gets applied for all p element However in Angular it gets applied to only that component. It's a great behaviour as it ensures that whichever css we apply gets applied to the same css.

How Angular does that, is it basically adds attribute to each element based on what component it belongs to like ng0, ng1 then it uses that as selector. There's also a shadow DOM method which is only supported in new browsers

// component.ts
@Component({
  selector: 'app-server-element',
  templateUrl: './server-element.component.html',
  styleUrls: ['./server-element.component.css'],
  encapsulation: ViewEncapsulation.Emulated    // use this line
})

/* encapsulation - Emulated is default behaviour
Native is same as Emulated but it uses shadow DOM method which is not supported
by older browsers so Native is not recommended.
Last, there's None which gives default encapsulation behaviour and attribute of
component wont get set. */

Content Hook

By default everything placed inside the custom-component-tag in HTML is lost

// some-component.html
...
<ng-content></ng-content>
...

This hook puts all the content placed inside while calling <some-component>....</some-component> to the ng-content place

Say this content has some #ref and you want to use it then just like @ViewChild there's @ContentChild @ContentChild('name-of-ref') refVariable: ElementRef;

Lifecycle

Directives

  • There are 2 types of directives - Attribute & Structural directives
  • Attribute directives changes the attribute like style and stuff, eg: ngStyle, ngClass
  • Structural directives changes the structure (can remove), eg: *ngIf, *ngFor
  • To create a new directive ng generate directive name-of-derrivate

Custom Highlight Attribute Directive

<p appBetterHighlight [defaultColor]="'yellow'" [highlightColor]="'red'">...</p>

import { Directive, ElementRef, HostBinding, HostListener, Input, OnInit, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appBetterHighlight]'
})
export class BetterHighlightDirective implements OnInit {
  @Input() defaultColor: string = 'transparent';
  @Input() highlightColor: string = 'blue';
  @HostBinding('style.backgroundColor') backgroundColor: string;

  constructor(private elRef: ElementRef, private renderer: Renderer2) { }
  ngOnInit() { }

  @HostListener('mouseenter') mouseEnter(eventData: Event) {
    this.backgroundColor = this.highlightColor;
  }
  @HostListener('mouseleave') mouseLeave(eventData: Event) {
    this.backgroundColor = this.defaultColor;
  }
}

Custom Structural Directive

<div *appUnless="condition">...</div>

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appUnless]'
})
export class UnlessDirective {
  @Input() set appUnless(condition: boolean) {
    if (!condition) {
      this.vcRef.createEmbeddedView(this.templateRef);
    } else {
      this.vcRef.clear();
    }
  }

  constructor(private templateRef: TemplateRef<any>, private vcRef: ViewContainerRef) { }
}

ngSwitch

<div [ngSwitch]="value">
  <p *ngSwitchCase="5">Value is 5</p>
  <p *ngSwitchCase="10">Value is 10</p>
  <p *ngSwitchDefault>Value is 10</p>
</div>

Custom Dropdown Directive - Attach CSS

import { Directive, HostBinding, HostListener } from '@angular/core';

@Directive({
  selector: '[appDropdown]'
})
export class DropdownDirective {
  @HostBinding('class.open') isOpen = false;

  @HostListener('click') toggleOpen() {
    this.isOpen = !this.isOpen;
  }
}

Services

  • Use ng generate service name-of-service
  • To use the new service in say your xyz.ts file add providers: [LoggingService] to @Component or whole module and then constructor(private loggingService: LoggingService) {} then you can simply call the service by this.loggingService.logStatusChange(accountStatus)
// LoggingService.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoggingService {
  logStatusChange(msg: string) { console.log(msg); }
}

This provider dependency injector is hierarchial in nature that means if we provide the service (using providers) to a component than that component and all it's child will get that service and that too of same instance, so data within service will be shared.

Routing

// app.module.ts

const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users/:id', component: UsersComponent },
  { path: 'servers', component: ServersComponent }
];

// Then in it's imports array add
RouterModule.forRoot(appRoutes)

// Finally in app.component.html add
<router-outlet></router-outlet>
// to the place where you want the router to show

To get route param, in the component.ts file of that route add private route: ActivatedRoute; to the constructor and then get param by this.route.snapshot.params['id'] Inside the ngOnInit

  • Then <a href="/servers">..</a> This works however what it does is it restarts the entire app instead of href use routerLink this will work just like that but doesnt restart app hence more fast
  • /path goes to rootpath/path whereas if it's path and say user is already at /path then it will go to /path/path so it basically will append.
  • ../path This will go back one from current path and then add
<ul class="nav nav-tabs">
  <li
    role="presentation"
    routerLinkActive="active"
    [routerLinkActiveOptions]="{ exact: true }"
  >
    <a routerLink="/">Home</a>
  </li>
  <li role="presentation" routerLinkActive="active">
    <a routerLink="/servers">Servers</a>
  </li>
  <li role="presentation" routerLinkActive="active">
    <a routerLink="/users">Users</a>
  </li>
</ul>

routerLinkActive add the active css class if that route is active and routerLinkActiveOptions exact makes sure that it is exact since / and /servers both has / in it but we only want home to be active in case of / so that's why exact is required.

  • To programmatically route somewhere
constructor(private router: Router) { }
onLoadServer() {
    this.router.navigate(['/servers']);
}

// for relative path 'path' instead of '/path' relativeTo needs to be specified
constructor(private router: Router, private route: ActivatedRoute) { }
onLoadServer() {
    this.router.navigate(['/servers'], {relativeTo: this.route});
}

To get params use this.route.snapshot.param['param_name'] inside ngOnInit however this might not work in a scenerio say there's a button on that component which routes to the same component but with different param. Angular will not reload the component since it knows that component is already there.
Add this in ngOnInit

paramsSubscription: Subscription;

ngOnInit() {
    this.id = this.route.snapshot.params['id'];
    this.paramsSubscription = this.route.params.subscribe((params: Params) => {
        this.id = params['id'];
    });
}

ngOnDestroy() {
    this.paramsSubscription.unsubscribe();
}

// this params.suscribe is something called observable which is called when
// the event is observed

Query Parameters

  • To set additional query params. Below will go to - /servers/5/edit?query1=1#loading
<a
    [routerLink]="['/servers', 5, 'edit']"
    [queryParams]="{query1: '1'}"
    fragment="loading">
    ...
</a>
this.router.navigate(['/servers', 5, 'edit'],
    {queryParams: {query1: '1'}, fragment: 'loading'});
  • To get the queryParams and framents just like snapshot & params.subscribe simillary do it for queryParams and fragments.

Parsed params and queryParams are always string to make them int add + in their initialization

Nested Routing

  • To add nested routing
const addRoutes: Routes = [
    { path: 'servers', component.ServersComponent, children: [
        { path: ':id', component: ServerComponent },
        { path: ':id/edit', component: EditServerComponent },
    ] }
]

// Inside ServersComponent.html add <router-outlet></router-outlet>

Redirecting and 404

const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'not-found', component: NotFound },
    { path: '**', redirectTo: '/not-found' }
];

Routing Module

  • If the size of router is too large usually create a new module ng g m AppRoutingModule
// AppRoutingModule.ts
const appRoutes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'users', component: UsersComponent, children: [
    { path: ':id/:name', component: UserComponent }
  ] },
  {
    path: 'servers',
    // canActivate: [AuthGuard],
    canActivateChild: [AuthGuard],
    component: ServersComponent,
    children: [
    { path: ':id', component: ServerComponent, resolve: {server: ServerResolver} },
    { path: ':id/edit', component: EditServerComponent, canDeactivate: [CanDeactivateGuard] }
  ] },
  // { path: 'not-found', component: PageNotFoundComponent },
  { path: 'not-found', component: ErrorPageComponent, data: {message: 'Page not found!'} },
  { path: '**', redirectTo: '/not-found' }
];

@NgModule({
  imports: [
    // RouterModule.forRoot(appRoutes, {useHash: true})
    RouterModule.forRoot(appRoutes)
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {

}

// In App.module.ts imports simply add AppRoutingModule

Route Guard

  • Here this is auth guard
// auth.service.ts
export class AuthService {
  loggedIn = false;

  isAuthenticated() {
    const promise = new Promise(
      (resolve, reject) => {
        setTimeout(() => {
          resolve(this.loggedIn);
        }, 800);
      }
    );
    return promise;
  }

  login() {
    this.loggedIn = true;
  }

  logout() {
    this.loggedIn = false;
  }
}

// auth-guard.service.ts
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
  CanActivateChild
} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';

import { AuthService } from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot,
              state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this.authService.isAuthenticated()
      .then(
        (authenticated: boolean) => {
          if (authenticated) {
            return true;
          } else {
            this.router.navigate(['/']);
          }
        }
      );
  }

  canActivateChild(route: ActivatedRouteSnapshot,
                   state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this.canActivate(route, state);
  }
}

// Then finally add guards to the route
{
    path: 'servers',
    // canActivate: [AuthGuard],
    canActivateChild: [AuthGuard],
    component: ServersComponent,
    children: [...]
}

canActivate has an array of guards which ensures that the component is guarded we are using CanActivateChild instead it will render the parent component but not child so it's like having CanActivate to all the child

Can Deactivate Guard

For example if user is submitting a form and he accidentally navigate to some other page then a prompt should say do you want to save changes.

// can-deactivate-guard.service.ts
import { Observable } from 'rxjs/Observable';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {

  canDeactivate(component: CanComponentDeactivate,
                currentRoute: ActivatedRouteSnapshot,
                currentState: RouterStateSnapshot,
                nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return component.canDeactivate();
  }
}

// edit-server.component.ts
export class EditServerComponent implements OnInit, CanComponentDeactivate {
  canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
    // return true if you want navigation guard to go off
    return confirm('Do you want to discard the changes?');
  }
}

// Then finally add guards to the route
{
  path: ':id/edit',
  component: EditServerComponent,
  canDeactivate: [CanDeactivateGuard]
}

Passing Static Data to Route

{
  path: 'not-found',
  component: ErrorPageComponent,
  data: {message: 'Page not found!'}
}

/* Now to the component you want to use that data
use snapshot.data['message'] again suscribe to it */

Hash Route

Routing works in local environment but to make it work in web server also need to ensure that the server returns index.html if there's a 404 error. Some older browser and server don't support that for this RouterModule.forRoot(appRoutes, {useHash: true})

Observable

Count after every interval

private firstObsSubscription: Subscription;

ngOnInit() {
    this.firstObsSubscription = interval(1000).subscribe(count => {
        console.log(count);
    });
}

ngOnDestroy(): void {
    this.firstObsSubscription.unsubscribe();
}

Custom Interval Observable

private firstObsSubscription: Subscription;

ngOnInit() {
    const customIntervalObservable = Observable.create(observer => {
        let count = 0;
        setInterval(() => {
            observer.next(count);
            count++;
        }, 1000);
    });

    this.firstObsSubscription = customIntervalObservable.subscribe(data => {
        console.log(data);
    });
}

ngOnDestroy(): void {
    this.firstObsSubscription.unsubscribe();
}
  • Just like next there's also complete() and error(new Error("New error"));
  • To handle error:-
this.firstObsSubscription = customIntervalObservable.subscribe(data => {
    console.log(data);
}, error => {
    console.log(error);
}, () => {
    console.log("Completed");
});

Transform data from Observable before using

  • Say in above interval count example, we want to have Round 1, Round 2, Round 3... instead of 0, 1, 2...
this.firstObsSubscription = customIntervalObservable
    .pipe(map((data: number) => {
        return 'Round: ' + (data + 1);
    })).subscribe(data => {
        console.log(data);
    }, error => {
        console.log(error);
    }, () => {
        console.log("Completed");
    });

/* Like map there's also filter and many more
https://www.learnrxjs.io/learn-rxjs/operators */

Handling Forms

  • Simple template driven approach
  • Make sure FormsModule is in imports array of app.module.ts
// In html, last 2 attributes are important
<form (ngSubmit)="onSubmit(f)" #f="ngForm">
    <input type="email" class="form-control" ngModel name="email">
</form>

// In component.ts
export class AppComponent {
    onSubmit(form: NgForm) {
        console.log(form.value.email);
    }
}

// Instead of passing f to onSubmit, we can also use it directly in ts

@ViewChild('f') signupForm: ngForm;

this.signupForm.value.email;

Validation Check

// Add attribute required or email to form input element
// then in form button
<button class="btn btn-primary" type="submit" [disabled]="!f.valid">Submit</button>

// .css of that component
input.ng-invalid.ng-touched {
    border: 1px solid red;
}

/* Now button won't be active unless form input is valid and red border will
appear on input if we leave it (touched dirty) in invalid state */

<input
    type="email"
    class="form-control"
    ngModel
    name="email"
    required
    email
    #email="ngModel">
<span class="help-block" *ngIf="!email.valid && email.touched">
    Please enter a valid email!
</span>

Default Value

// for a form element set ngModel
[ngModel]="'default value'"
// or
[ngModel]="someVariable"

Grouping

You can have a div with attribute ngModelGroup="groupName" then all the element inside it will be grouped as one. Now you can put it as ref and check validity of that group maybe.

Set Value

Say you want to put value to form (during maybe startup from API)

@ViewChild('f') signupForm: NgForm;

changeVal() {
    this.signupForm.setValue({
        username: "abc",
        email: "abc.xyz.com"
    });
}

// or to overwrite part of form
this.signupForm.form.patchValue({/* ... same as above ... */ });

Reset Form

this.signupForm.reset();

Pipes

Say you have a username which you want to display. You want the username to be uppercase while displaying but you don't want to change its value

{{ username | uppercase }}                 // ANKIT
{{ dateObj | date:"fullDate" }}            // Sunday, Aug 8, 2020
{{ username | uppercase | date }}          // Will apply Left to Right

Creating own pipe

  • ng generate pipe shorten
// shorten.pipe.ts
@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
    transform(value: any, limit: number) {
        if (value.length > limit)
            return value.substr(0, limit);
        return value;
    }
}

HTTP Requests

Add HTTPClientModule to imports of module.ts

constructor(private http: HttpClient) {}
onCreatePost(postData: { title: string; content: string}) {
    this.http.post('...', postData).subscribe(response => {
        console.log(response);
    });
    // No need to unsuscribe to these observables because it will never leak
}

Applying Headers & Other params

this.http.get<{ [key: string]: Post }>('...', {
    headers: new HTTPHeaders({...}),
    params: ...,
    responseType: 'json'
});

Usually you want certain header to be applied to many, so use interceptor

// auth-interceptor.service.ts
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';

export class AuthInterceptorService implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    console.log('Request is on its way');
    return next.handle(req);
  }
}

// module.ts
declarations: [AppComponent],
imports: [BrowserModule, FormsModule, HttpClientModule],
providers: [
  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptorService,
    multi: true
  }
],
bootstrap: [AppComponent]

Now interceptor will get called before every HTTP request and if you want to filter some behavior then do it inside interceptor you can check url and stuff

Angular Modules & Optimizing Angular Apps

  • Declarations hold declarations for components, pipes, directive...
  • Imports holds other modules dependency
  • Providers hold all the services we want to use
  • Bootrstrap holds what is starting point of app, it can have multiple starting point too (it's an array) for that you need to have multiple <app-root></app-root> in index.html

Every module works on it's own, they don't communicate with each other

  • Instead of BrowserModule import CommonModule to self made modules
  • Declarations of certain component, pipes... can be put on only 1 module multiple declarations are not allowed

Lazy Loading

Using multiple module is pre requisite. Idea is to download smaller bundle of the required module only unless user don't visit it.

// To route add
{
    path: 'recipes', loadChildren: () => import('./recipes/recipes.module')
        .then(m => m.RecipesModule)
}
// Don't add the RecipesModule to imports now, don't load it eagerly when loading
// lazily
// After lazy loading path of module lazy loaded in it's router becomes relative

Pre-load Lazy Loading

Preload all lazy loaded module before hand

// app.routing.module.ts
imports: [
    RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules})
],

Deploying

  • Constants for development and production in Environment file
import { environment } fromt '../../environments/environment';
// use environment.APIKey;

Angular will automatically swap dev and prod constants

  • ng build --prod

NgRX - State Management

  • npm i --save @ngrx/store

// shopping-list.actions.ts
import { Action } from '@ngrx/store';

export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export class AddIngredient implements Action {
    readonly type = ADD_INGREDIENT;

    constructor(public payload: Ingredient) {}
}

export type ShoppingListActions = AddIngredient // | RemoveIngredients;

// shopping-list.reducer.ts
import { Ingredient } from '../shared/ingredient.model';
import * as ShoppingListActions from './shopping-list.actions';

export interface State {
    ingredients: Ingredients[];
    ...
}

export interface AppState {
    shoppingList: State;
}

const initialState : State = {
    ingredients: [
        new Ingredient('Apples', 5),
        new Ingredient('Tomatoes', 10),
    ]
};

export function shoppingListReducer(state: State = initialState,
    action: ShoppingListActions.ShoppingListActions) {
    switch(action.type) {
        case ShoppingListActions.ADD_INGREDIENT:
            return {
                ...state,    // always have old state
                ingredients: [...state.ingredients, action.payload]
            };
        default:
            return state;
    }
}

Then in AppModule

import { StoreModule } from '@ngrx/store';

// Add to imports array - StoreModule.forRoot({shoppingList: shoppingListReducer})

Then to the component you want to use that store

// add to constructor
import * as fromShoppingList from './store/shopping-list.reducer';

constructor(
    private store: Store<fromShoppingList.AppState>
) {}

// Then in ngOnInit
this.ingredients = this.store.select('shoppingList');

// ingredients here is an observable
ingredients: Observable<{ ingredients: Ingredient[] }>;

// To fetch data from observable in componenet you can use async pipe
<a *ngFor="let ingredient of (ingredients | async).ingredients">
    {{ ingredient.name }} ({{ ingredient.amount }})
</a>
// Angular will subscribe to observable for us

Dispatch action at shopping-edit-component

import * as ShoppingListActions from './shopping-list.actions';
import * as fromShoppingList from './store/shopping-list.reducer';

// add to constructor
constructor(
    private store: Store<fromShoppingList.AppState>
) {}

this.store.dispatch(new ShoppingListActions.AddIngredients(...));

Angular Universal

Pre render so that user receive rendered pages. Pre rendering helps crawlers and SEO

  • ng add @nguniversal/express-engine --clientProject <identifier found under projects of angular.json>
  • npm i --save @nguniversal/module-map-ngfactory-loader
  • Then add ModuleMapLoaderModule to app.server.module.ts imports array
/* Now the project gets run at server, but the problem is some API are browser
only like LocalStorage so to handle that */
constructor(
    @Inject(PLATFORM_ID) private platformId;
) {}

// then on ngOnInit
if (isPlatformBrowser(this.platformId)) {
    ...
}