Category: Angular

TILT-Typed Reactive Forms and arrays–part 26

I want now to pass to typed reactive forms ( with Angular 14 )- what is good is that are typed , i.e. I can see errors if some property is not correct.

The code previously was

profileForm = this.fb.group({
    url: [''],
    publicTILTS: this.fb.array([])
  });

Now the code looks like

profileForm =  new FormGroup({
    url: new FormControl(''),
    publicTILTS: new FormArray([new FormControl(new TILT())])
  });

And you may ask – what is the difference ? The difference is type inferring from generics. For example , the constructor for FormControl ( after several steps, since it is a interface)

new <T = any>(value: FormControlState<T> | T  //more code

So the above definition is , actually, this one:

profileForm =  new FormGroup({
    url: new FormControl<string>(''),
    publicTILTS: new FormArray<FormControl<TILT|null>>([new FormControl<TILT>(new TILT())])
  });

Now the other modifications are

  1. Get rid of formArray

  2. typed data in HTML :
    <div  *ngFor="let item of profileForm.value.publicTILTS; let i = index;let f=first"> 
  3. modify the string url It was hardcoded:
  tap(it => this.profileForm.controls['url'].setValue(it)),

Now it is typed patchValue that has a Partial – typed!

tap(it => this.profileForm.patchValue({url:it}))

Just technical, the relevant definitions are

patchValue(value: ɵFormGroupValue<TControl>, options?: {
        onlySelf?: boolean;
        emitEvent?: boolean;
    }): void;

export declare type ɵFormGroupValue<T extends {
    [K in keyof T]?: AbstractControl<any>;
}> = ɵTypedOrUntyped<T, Partial<{
    [K in keyof T]: ɵValue<T[K]>;
}>, {
    [key: string]: any;
}>;
  1. Modified the array – patchValue does not work with arrays, so setControl to the rescue

Now it is

//this do not work - do not modify the array length, but the contents
// this.profileForm.patchValue({publicTILTS: [...it]});
//setControl<K extends string & keyof TControl>
this.profileForm.setControl("publicTILTS",new FormArray(it.map(a=>new FormControl(a))));        

Again it is typed , even if it seems a string- I have put a comment above with the definition – see keyof ? It will raise an error at compile time!

For the end user, the site is THE SAME. However, the code now is typed – and , yes, this is a great deal for programmer!

TILT-Passing to IAsyncEnumerable instead of array–part 25

When tansmitting an array of data to an application , usually the application transfers all data – or in chunks – if it is a large numbers ( page 1, page 2 and so on). Both are having drawbacks

  • all data from start means that the time to see the data increases with the number of data
  • paging the data means to show the user the number of pages and an action from the user to go further to the next page.

What if I can use IASyncEnumerable to push data one by one to the GUI ?

So – those are the steps for the backend (.NET ) and frontend( Angular ) to pass the transfer from array to one by one ( IASyncEnumerable)

Backend

I have a function that loads the data from database and transforms into an array . Also caches the data

private async Task<TILT_Note_Table[]?> LatestTILTs(string urlPart, int numberTILTS){
    if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
    {
        return result;
    }
    //find id from urlPart - if not found , return null
    //caches the data and returns the array
}

Also a controller that sends the data

 public async Task<ActionResult<TILT_Note_Table[]?>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
{
    var data = await publicTILTS.LatestTILTs(urlPart,numberTILTS);
    if (data== null)
    {
        return new NotFoundObjectResult($"cannot find {urlPart}");
    }
    return data;
}

Also some tests that verifies that when I post a new tilt, the numbers is 1

Step 1: transform from array to IASyncEnumerable

transformation of the main function

 private async Task<IAsyncEnumerable<TILT_Note_Table>?> privateLatestTILTs(string urlPart, int numberTILTS)
        {
            if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
            {
                return result.ToAsyncEnumerable();// modification here
            }
    //find id from urlPart - if not found , return null
    //caches the data and returns the array.ToAsyncEnumerable();
        }

transformation of the controller- just add IAsyncEnumerable

 public async Task<ActionResult<IAsyncEnumerable<TILT_Note_Table[]>?>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
    {
        var data = await publicTILTS.LatestTILTs(urlPart,numberTILTS);
            return new NotFoundObjectResult($"cannot find {urlPart}");
        }
        return Ok(data);
    }

Also for the tests you can add .ToArrayAsync()

Step 2: Get rid of the Task

So now the obtaining of TILTS looks like this

private async IAsyncEnumerable<TILT_Note_Table> privateLatestTILTs(string urlPart, int numberTILTS)
{
    if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
    {
        //why I cant return result.ToAsyncEnumerable() ?
        await foreach (var item in result.ToAsyncEnumerable())
        {
            await Task.Delay(1000);
            yield return item;
        }
    }
    //same with retrieving data
    //when sending back data, we have to send one by one , as for the caching

The controller looks pretty much the same

[HttpGet("{urlPart}/{numberTILTS}")]
public ActionResult<IAsyncEnumerable<TILT_Note_Table>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
{
    var data =  publicTILTS.LatestTILTs(urlPart,numberTILTS);
    
    if (data== null)
    {
        return new NotFoundObjectResult($"cannot find {urlPart}");
    }

    return Ok(data);
}

And this is the modification for the backend . If you want to see in action , open your browser to https://tiltwebapp.azurewebsites.net/api/PublicTILTs/LatestTILTs/ignatandrei/100000 and see TILTS how they come one by one

Frontend

In Angular I have obtained the whole array at once .

 public getTilts(id:string, nr:number): Observable<TILT[]>{
    return this.http.get<TILT[]>(this.baseUrl+'PublicTILTs/LatestTILTs/'+id + '/'+nr)
    .pipe(
      tap(it=>console.log('received',it)),
      map(arr=>arr.map(it=>new TILT(it)))
    )
    ;
 }

Now we are obtaining one by one – how to let the page knows that it has an array instead of modifying also the page ?

( Yes, the display could be modified to accumulate – but I want minimal modifications to the page)

To obtain one by one we display the fetch

 //https://gist.github.com/markotny/d21ef4e1af3d6ea5332b948c9c9987e5
  //https://medium.com/@markotny97/streaming-iasyncenumerable-to-rxjs-front-end-8eb5323ca282
  public fromFetchStream<T>(input: RequestInfo, init?: RequestInit): Observable<T> {
    return new Observable<T>(observer => {
      const controller = new AbortController();
      fetch(input, { ...init, signal: controller.signal })
        .then(async response => {
          const reader = response.body?.getReader();
          if (!reader) {
            throw new Error('Failed to read response');
          }
          const decoder = new JsonStreamDecoder();
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            if (!value) continue;
            decoder.decodeChunk<T>(value, item => observer.next(item));
          }
          observer.complete();
          reader.releaseLock();
        })
        .catch(err => observer.error(err));
      return () => controller.abort();
    });
  }

And for modifying the function to have an array, instead of just one, RXJS scan to the rescue

 public getTilts(id:string, nr:number): Observable<TILT[]>{
    return this.fromFetchStream<TILT>(this.baseUrl+'PublicTILTs/LatestTILTs/'+id + '/'+nr)
    .pipe(
      tap(it=>console.log('received',it)),
      map(it=>new TILT(it)),
      scan((acc,value)=>[...acc, value], [] as TILT[])
    );  
}

You can see the final result ( with 1 sec delay between tilts, to be visible ) here http://tiltwebapp.azurewebsites.net/AngTilt/tilt/public/ignatandrei

TILT–Details for programmers- part 24

I have organized the About in order to show more details. See https://tiltwebapp.azurewebsites.net/AngTilt/

Zero and the most important, the date when the CI was done

First , Licences – .NET Core and Angular . Usefull to know.

Second, Info about Versions – Repo and history – UI and JSON – mostly for making managers happy .

Third, Automation – Swagger and Blockly Automation – in order for others to try how to interact.

Fourth , Info about deployment – HealthCheck and info about deployment Environment – user, environment, error – for SRE .

And to have something new, this is the map of the API’s

obtained with the NetCoreUsefullEndpoints ( https://tiltwebapp.azurewebsites.net/api/usefull/graph/text ) – and digraph rendering https://dreampuf.github.io/GraphvizOnline/

Tools Used

Visual Studio

Visual Studio Code

https://github.com/ignatandrei/RSCG_AMS

https://github.com/ignatandrei/NetCoreUsefullEndpoints/

https://github.com/ignatandrei/blocklyAutomation/

https://github.com/domaindrivendev/Swashbuckle.AspNetCore

TILT–Some improvements-part 23

I wanted to have a share on TILTs, instead of a clipboard copy. Discovered that browser navigator has a native share – https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share .

Implementation very simple ( not so good, )

private share(str:string): boolean{
if ('share' in navigator) {
    navigator
    .share({
    title: 'TILT!',
    text: str,
    url: environment.url + 'AngTilt/tilt/public/'+this.profileForm.controls['url'].value
    })
    .then(() => {
    console.log('Callback after sharing');
    })
    .catch(console.error);
    return true;
} else {
    return false;
}
}
  1. Added .NET Tools to have data about the outdated and more
feat: dot net tools
e0fd82f
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "dotnet-project-licenses": {
      "version": "2.4.0",
      "commands": [
        "dotnet-project-licenses"
      ]
    },
    "dotnetthx": {
      "version": "0.2.0",
      "commands": [
        "dotnet-thx"
      ]
    },
    "powershell": {
      "version": "7.2.6",
      "commands": [
        "pwsh"
      ]
    },
    "dotnet-depends": {
      "version": "0.6.1",
      "commands": [
        "dotnet-depends"
      ]
    },
    "dotnet-outdated-tool": {
      "version": "4.1.0",
      "commands": [
        "dotnet-outdated"
      ]
    },
    "run-script": {
      "version": "0.4.0",
      "commands": [
        "r"
      ]
    }
  }
}

Tools used

Visual Studio

Visual Studio Code

CLI

TILT-Angular 14 and small updates-part 20

It is always good to have the latest version of NuGet packages – if it does not break the application, of course. Easy task when you have some automated test -and Visual Studio is doing this for you.

However, to update an Angular application where the version is fixed – a nightmare. I will do in a full time.

Also, being deployed to Azure, a few clicks on AppInsights and you have monitoring the application without instrumenting the code.

Also, added a feature to the application – you can add now a link to your TILT. This small modification went also when to display the TILT – keep remembering that any modification that you will do to the model will reflect in more than 1 place in the GUI.

Also, modifying the interface – show first the TILTs in a list, rather than in a calendar format.

Now the Angular 14 is on market – see https://blog.angular.io/angular-v14-is-now-available-391a6db736af. I was impressed by Title and NgMOdel onPush

So – update the cli ( npm update -g @angular/cli ) and crating a new app (ng new ) and adding Angular (ng add @angular/material)

So now it is time to bring the code to

  1. Fix the versions

  2. Add the code from the Ang13 app

  3. Add the title

For 1:

Fix the versions in package.json

Delete the npm_modules

npm i

For 2: I use WinMerge with a custom filter to exclude node_modules

Added missing npm packages from the previous application

Fix again the versions

For 3:

This is the code for TILT:

import { Injectable, NgModule } from '@angular/core';
import {
  RouterModule,
  RouterStateSnapshot,
  Routes,
  TitleStrategy,
} from '@angular/router';
import { LoginUrlGuard } from './login-url.guard';
import { LoginUrlComponent } from './login-url/login-url.component';
import { MyTiltComponent } from './my-tilt/my-tilt.component';
import { OnePublicTiltComponent } from './one-public-tilt/one-public-tilt.component';
import { PublicTiltsComponent } from './public-tilts/public-tilts.component';
import { TiltMainComponent } from './tilt-main/tilt-main.component';

const routes: Routes = [
  {
    path: 'tilt/public',
    component: PublicTiltsComponent,
    title: 'List of public tilts',
  },
  { path: 'tilt/public/:id', component: OnePublicTiltComponent },
  { path: '', redirectTo: '/tilt/public', pathMatch: 'full' },
  {
    path: 'tilt/my',
    component: MyTiltComponent,
    canActivate: [LoginUrlGuard],
    title: 'My tilts',
  },
  {
    path: 'loginURL',
    component: LoginUrlComponent,
    title: 'Login to add a tilt',
  },
];

@Injectable()
export class TemplatePageTitleStrategy extends TitleStrategy {
  override updateTitle(routerState: RouterStateSnapshot) {
    const title = this.buildTitle(routerState);
    if (title !== undefined) {
      document.title = `TILT! - ${title}`;
    } else {
      var arr = routerState.url.split('/');
      if(arr.length==0)
        document.title = `TILT! - AAA`;
      else
        document.title = `TILT! - tilts for ` + arr[arr.length-1];
    }
  }
}

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [{provide: TitleStrategy,  useClass: TemplatePageTitleStrategy}],
})
export class AppRoutingModule {}

Tools used

npm

ng

Visual Studio Code

WinMerge

Windows terminal + Powershell to run IDE, Angular, .NET Core

I work at http://github.com/ignatandrei/BlocklyAutomation – and every time I need to run Angular , Visual Studio Code IDE and .NET Core run ( and Visual Studio  sometimes  – it is more web based)

Also, sometimes I need to run in a container – to install globally something that I do not want to have in my PC.

So  I have 1 windows terminal command to run those :

First one

wt new-tab   –title RootSource –suppressApplicationTitle -p “Windows PowerShell” -d . cmd /k “cd src && code . && powershell” ;split-pane  –title Angular –suppressApplicationTitle -V -p “Windows PowerShell” -d . cmd /k “cd src &&  npm run start –watch && powershell” ;split-pane  –title LocalAPI_NetCore –suppressApplicationTitle -V -p “Windows PowerShell” -d . cmd /k “cd src/Local/LocalAPI/LocalAPI && dotnet watch run”

As you see , I have

code .

to run VSCode,

npm run start –watch

to run Angular and

dotnet watch run

to run dotnet.

Second one:

wt new-tab -p “Windows PowerShell” -d . ;split-pane -p “Windows PowerShell” -d . cmd /k “cd src && devcontainer open .”

The second it just works with devcontainer  – if you are interested in sources, see https://github.com/ignatandrei/BlocklyAutomation/tree/main/src/.devcontainer 

With those, I can start my developer environment fast ( how fast, that depends on my PC)

Developer choices Web => Windows application

So I have an Angular application – basically, a macro for your WebAPI – see https://ignatandrei.github.io/BlocklyAutomation/ . I want to transform into an Windows Application and have also deployed to Windows Store.

Attempt 1 : embed as a resource a .NET Core WebAPP and deploy to Windows Store. Not really –  the validation part of Store realizes this trick

Attempt2 : Electron – must rewrite window.prompt : https://github.com/electron/electron/issues/472

Attempt3 : Let’s see Blazor – https://github.com/dotnet/MobileBlazorBindings/  – however , it has just WPF, not UWP .

Attempt3: WebView2 –  https://github.com/MicrosoftEdge/WebView2Samples . It has an UWP. However, I realize that it does not have redirect … so routes in Angular will be difficult – if not intercepting 404 and serving the same index.html.

Attempt4: PWA : https://docs.microsoft.com/en-us/windows/uwp/publish/pwa/overview – yes, but my app it was Angular 12. Pass to 13 ( and force some unresolved dependencies ) take me the rest of the  day .

So I am at PWA now.

And Angular with https://www.pwabuilder.com/ is pretty impresive

What I do not have yet: ASP.NET Core downloading as Windows Store application with PWA and Controllers …

UNIQUE ID inside an Angular Component

If you use an id for a HTML element inside your angular component and then use

document.getElementById

to retrieve it, bear in mind that someone can have multiple instances of your component on the same page. So here is a simple trick to deal with

1. In the HTML component put

<div [id]=”‘htmlOutput’+myId”></div>

2. In the ts file put

export class MyComponent{

static id:number=0;

myId:number=0;

constructor(/*arguments*/){

this.myId=++DisplayBlocklyComponent.id;

}

}

and for retrieving the HTML element

document.getElementById(‘htmlOutput’+this.myId)

That is all;

Integrating SPA ( Angular, React, Vue) with .NET Core for production

If you have a .NET Core WebAPI application ( that only serves data ) and  a SPA application ( Angular, Vue,  React) you can have just one application following this 2 steps:

1.  In .NET Core add in Startup.cs ( with using Microsoft.AspNetCore.Builder from Microsoft.AspNetCore.StaticFiles )

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
             {
                

            // If you know the endpoints, put it here

           //endpoints.MapFallbackToFile(“dbodepartment/{**slug}”,”/index.html”);
                         
                 endpoints.MapControllers();
                 endpoints.MapFallbackToFile(“{**slug}”, “/index.html”);
});

2. Compile the SPA to index.html, deploy in wwwroot with the js and css

( yes, you should modify the endpoint for data when compiling to / –  for Angular see my previous post , http://msprogrammer.serviciipeweb.ro/2020/09/14/angular-base-href-for-web-desktop-mobile-and-paths-for-services/ )

Angular base href for web, desktop, mobile ( and paths for services)

My current stack is .NET Core (backend) + Angular ( frontend) ( For  database, it is usual SqlServer – but I use Sqlite, Mongo, local storage ….)

With Angular I can have Mobile application ( by Cordova ) and Windows Desktop ( with Electron)

But the BaseHref it is a PIA …. even for Web, if you host into a virtual directory .

So this is my setup in 2 steps. Maybe could be somehow easy, but… I did not discover something better yet.

Step 1: Create a favicon.svg into the assets folder

Step 2: Instead of

<base href=”/”>

put

<!– <base href=”/”> –>

<script>

var isCordovaApp = !!window.cordova;

var isWindowApp = !!window.MSApp;

if (isCordovaApp) {

        hrefApp = “.”;

      }

if (isWindowApp) {

        hrefApp = “./”;

      }

if (!(isCordovaApp || isWindowApp)) {

var baseHref = window.location.href.split(“/”);

while (true) {

          baseHref.pop();

          hrefApp = baseHref.join(“/”) + “/”;

// window.alert(hrefApp);

var request = new XMLHttpRequest();

          request.open(“GET”, hrefApp + “favicon.svg”, false);           request.send(null);

if (request.status === 200) {

if(request.responseText.indexOf(“meta name=”)<1){

              console.log(`found ${hrefApp} `);

break;

            }

          }

        }

      }

    document.write(‘<base href=”‘ + hrefApp+ ‘” />’);

</script>

And this is all!

For correct path of services , I do this

For each service , I construct with

constructor(@(Inject)(APP_BASE_HREF) baseHref: string, private client: HttpClient)

this.baseUrl = environment.webAPIUrl + baseHref ;

and  when I call a function

const url = this.baseUrl+’api/WeatherForecast;

In the envoronment.ts I have

export const environment = {

  production: false,

  webAPIUrl: ‘http://localhost:5000’

};

and in the environment.prod.ts I do have

export const environment = {

  production: true,

  webAPIUrl: ”

};

One more thing: to inject APP_BASE_HREF , I must have into app.modue.ts

providers: [{

    provide: APP_BASE_HREF,

    useFactory: (s: PlatformLocation) => s.getBaseHrefFromDOM(),

    deps: [PlatformLocation]

  }

That’s all! ( yes , I know it is a bit complicated….)

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.