Quantcast
Channel: Andy Blog
Viewing all 97 articles
Browse latest View live

DIY tutorial - fence

$
0
0

Introduction

post 4x4,通常8 ft长,2ft underground, 6ft upground

rail/stringer 通常2x4或2x6,长度根据post间距而定,通常 6ft 或8ft

picket通常1x4或1x6,高度常见42 inch 或72 inch

screw尺寸常见2 1/2 inch,3 1/2 inch

修复fence panel/picket

  1. 购买材料

    2x4 hanger

    8x1 1/4” exterior wood screws

https://www.youtube.com/watch?v=7KVDJfGzVtE

修复lean fence

replace post

适合旧post根部已经彻底腐烂断掉

  1. 购买材料

    3袋concrete mix

    4x4 post。cut完可以用end cut preservative来涂层,保持干燥防止腐烂,可以涂2次

  2. 将篱笆稍微靠后一点,让出施工空间

  3. 移除旧post。在旧post根部往下挖,露出混凝土,沿着周边往下挖,洞口不要挖太大。

  4. 用crow bar和hammer往下一点点砸裂混凝土,直到木头周边的混凝土完全取出,

  5. 放入新post

  6. 倒入部分concrete、加水、搅匀,重复2-3次直到基本接近地面,期间不断检查post的level

  7. 等待1天等concrete完全凝固

https://www.youtube.com/watch?v=D9lOD_3IIfQ

https://www.youtube.com/watch?v=PeRcpepb0rc

https://www.youtube.com/watch?v=4zrFljGY5XA

user fence pole

Metal Fence Corner Post

https://www.youtube.com/watch?v=XYDIMP_q4kM

https://www.youtube.com/watch?v=Sd8LbN5gUMQ

mender

post根部腐烂(rotted)造成fence倾斜(lean)或者不稳(wobby)。

  1. 清理post根部周边,直到露出混凝土

  2. 在post和混凝土之间钉入mender,用锤子使劲砸进去,mendor尽可能多砸入一些(例如地面高度跟最下面横木平齐)

  3. 扶正post,使其与地面90度垂直,然后用螺丝将post与mnedor固定死。可以用exterior 2.5 inch screw (5mm x 70mm)

  4. 如果需要将横木跟mender固定,需要更长的螺丝3.5-4 inch

https://www.youtube.com/watch?v=gqiydHTZdGk

https://www.youtube.com/watch?v=U3rKPexoEcA

https://www.youtube.com/watch?v=Wc_YOukRjAE

FAQ

References

How to Install a Picket Fence

Learn How to Construct a Custom Fence and How to Build a Gate


ASP.NET Core Tutorial (3) TagHelper

$
0
0

Introduction

Tag Helper is server-side code to create and render HTML elements in Razor files. It is introduced in MVC 6. It is an alternative of HTML helper in MVC 5.

There are many built-in Tag Helpers for common tasks - such as creating forms, links, loading assets and more. We can also extend abstract TagHelper class to create customized TagHelper.

Usage of TagHelper

  1. Used as server-side markup in Razor view e.gl

    <bell-button></bell-button>
    
  2. Used as server-side attribute for HTML element to change standard HTML elements.

    e.g.

    Built-in TagHelper: LabelTagHelper

    in model:

     public class Movie
     {
         public int ID { get; set; }
         public string Title { get; set; }
         public DateTime ReleaseDate { get; set; }
         public string Genre { get; set; }
         public decimal Price { get; set; }
     }
    

    in Razor view:

    <label asp-for="Movie.Title"></label>
    

    It generates the following HTML:

    <label for="Movie_Title">Title</label>
    

    Here, asp-for attribute is available by the For property in the LabelTagHelper.

TagHelper vs HtmlHelper

Please note that Htmlhelper is C# code mixed with HTML inside Razor views and sometimes hard to read and maintain. Whereas, TagHelper is written in C# entirely and can be rendered and inserted to Razor views. Tag Helpers reduce the explicit transitions between HTML and C# in Razor views.

sample 1

  • HtmlHelper

    Suppose we have a simple label html helper:

      @Html.Label("FirstName", "First Name:", new {@class="caption"})
    

    Here, new {@class="caption"} is an anonymous object used to represent attributes. Because class is a reserved keyword in C#, we have to use the @ symbol to force C# to interpret “@class=” as a symbol (property name). Also, we lose intellisense. On the other hand, to a front-end designer (someone familiar with HTML/CSS/JavaScript and other client technologies but not familiar with C# and Razor), most of the line is foreign.

  • TagHelper equivalent

    using LabelTagHelper

    <label class="caption" asp-for="FirstName"></label>
    

    It generates

    <label class="caption" for="FirstName">First Name</label>
    

    We can find TagHelpers looks like standard HTML and is used as a markup in Razor view. It is friendly to front-end designer.

sample 2

  • HtmlHelpers Suppose we have the Form portion of the Views/Account/Register.cshtml Razor view generated from the legacy ASP.NET 4.5.x MVC template included with Visual Studio 2015.

    We can find most of the markup in the Register view is C#.

  • Tag Helpers equivalent:

    The markup is much cleaner and easier to read, edit, and maintain than the HTML Helpers approach.

Overall, TagHelper is server-side markup/attribute technique in contrast to Angular is client-side markup/attribute technique. Here, we suppose markup is dependent HTML tag and attribute is the attribute of a HTML tag.

Develop a TagHelper - demo 1: Email TagHelper

Basic

We wanna create a tag helper of email and use it in razor view like:

<email>Support</email>

We hope the server use our email tag helper to convert that markup into the following:

<a href="mailto:Support@contoso.com">Support@contoso.com</a>

Steps:

  1. Create new TagHelper class inherits .NET built-in agHelper class or implements ITagHelper interface. Here, we name it as EmailTagHelper

     using Microsoft.AspNetCore.Razor.TagHelpers;
     using System.Threading.Tasks;
    	
     namespace AuthoringTagHelpers.TagHelpers
     {
         public class EmailTagHelper : TagHelper
         {
             public override void Process(TagHelperContext context, TagHelperOutput output)
             {
                 output.TagName = "a";    // Replaces <email> with <a> tag
             }
         }
     }
    
  2. Import TagHelper assembly to Razor view. To make the EmailTagHelper class available to all our Razor views, add the addTagHelper directive to the Views/_ViewImports.cshtml file for .NET core. Views/_ViewImports.cshtml file makes the Tag Helper available to all view files in the Views directory and sub-directories.

     @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
     @addTagHelper *, AuthoringTagHelpers
    

    Another sample to import tag helpers:

    Here, @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers loads fundamental TagHelpers assembly. @addTagHelper *, AuthoringTagHelpers loads the customized TagHelper assembly. We can also use fully qualified name (FQN) like:

     @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
     @addTagHelper AuthoringTagHelpers.TagHelpers.EmailTagHelper, AuthoringTagHelpers
    

    Here, wildcard syntax (“*”) specifies that all Tag Helpers in the specified assembly (Microsoft.AspNetCore.Mvc.TagHelpers) will be available to every view file in the Views directory or sub-directory.

    Note that @removeTagHelper removes a Tag Helper that was previously added.

    Also, we can disable a Tag Helper at the element level with the Tag Helper opt-out character (“!”). e.g., Phone validation is disabled in the <span>. Here, the element and Tag Helper attributes are no longer displayed in a distinctive font.

    <!span asp-validation-for="Phone" class="text-danger"></!span>
    
  3. Add the TagHelper to Views/Home/Contact.cshtml razor view

     @{
         ViewData["Title"] = "Contact";
     }
     <h2>@ViewData["Title"].</h2>
     <h3>@ViewData["Message"]</h3>
    	
     <address>
         One Microsoft Way<br />
         Redmond, WA 98052<br />
         <abbr title="Phone">P:</abbr>
         425.555.0100
     </address>
    	
     <address>
         <strong>Support:</strong><email>Support</email><br />
         <strong>Marketing:</strong><email>Marketing</email>
     </address>
    

    Note <email>Support</email> portion

  4. run the app and we will get

    <a>Support</a>
     <a>Marketing</a>
    

    Please note that the anchor tag missing href and not clickable. We’ll fix it.

Improve v1

  1. Update EmailTagHelper with the following

     [HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)] 
     public class EmailTagHelper : TagHelper
     {
         private const string EmailDomain = "contoso.com";
    	
         // Can be passed via <email mail-to="..." />. 
         // Pascal case gets translated into lower-kebab-case.
         public string MailTo { get; set; }
    	
         public override void Process(TagHelperContext context, TagHelperOutput output)
         {
             output.TagName = "a";    // Replaces <email> with <a> tag
    	
             var address = MailTo + "@" + EmailDomain;
             output.Attributes.SetAttribute("href", "mailto:" + address);
             output.Content.SetContent(address);
         }
     }
    

    Here, we specify TagStructure=TagStructure.WithoutEndTag to make the tag support self-closing

  2. Update the markup in the Views/Home/Contact.cshtml:

     @{
         ViewData["Title"] = "Contact Copy";
     }
     <h2>@ViewData["Title"].</h2>
     <h3>@ViewData["Message"]</h3>
    	
     <address>
         One Microsoft Way Copy Version <br />
         Redmond, WA 98052-6399<br />
         <abbr title="Phone">P:</abbr>
         425.555.0100
     </address>
    	
     <address>
         <strong>Support:</strong><email mail-to="Support"></email><br />
         <strong>Marketing:</strong><email mail-to="Marketing"></email>
     </address>
    
  3. Run the app and it generates the correct links.

    <a href="Support@contoso.com">Support</a>
     <a href="Marketing@contoso.com">Marketing</a>
    

Improve v2

In this section, we’ll write an asynchronous email helper.

public class EmailTagHelper : TagHelper
{
    private const string EmailDomain = "contoso.com";
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "a";                                 // Replaces <email> with <a> tag
        var content = await output.GetChildContentAsync();
        var target = content.GetContent() + "@" + EmailDomain;
        output.Attributes.SetAttribute("href", "mailto:" + target);
        output.Content.SetContent(target);
    }
}

Develop a TagHelper - demo 2: bold TagHelper

  1. Create a BoldTagHelper class

    using Microsoft.AspNetCore.Razor.TagHelpers;

    namespace AuthoringTagHelpers.TagHelpers { [HtmlTargetElement(“bold”)] [HtmlTargetElement(Attributes = “bold”)] public class BoldTagHelper : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) { output.Attributes.RemoveAll(“bold”); output.PreContent.SetHtmlContent(“”); output.PostContent.SetHtmlContent(“”); } } }

    Please note:

    • Here [HtmlTargetElement(Attributes = "bold")] specifies that any HTML element that contains an HTML attribute named “bold” will match, and the Process() override method in the class will run.
    • Here [HtmlTargetElement("bold")] specified an HTML element named “bold” trigger Process() method.
    • Process method removes the “bold” attribute and surrounds the containing markup with .
  2. Import taghelper assembly to the view. see above

  3. Add a bold attribute to About.cshtml razor view.

     @{
         ViewData["Title"] = "About";
     }
     <h2>@ViewData["Title"].</h2>
     <h3>@ViewData["Message"]</h3>
    	
     <p bold>I am bold</p>
     <bold>I am bold too.</bold>
    

Develop a TagHelper - demo 3: Email TagHelper

Support model

  1. Create a model class

     using System;		
     namespace AuthoringTagHelpers.Models
     {
         public class WebsiteContext
         {
             public Version Version { get; set; }
             public int CopyrightYear { get; set; }
             public bool Approved { get; set; }
             public int TagsToShow { get; set; }
         }
     }
    
  2. Add TagHelper class WebsiteInformationTagHelper

     using System;
     using AuthoringTagHelpers.Models;
     using Microsoft.AspNetCore.Razor.TagHelpers;
    	
     namespace AuthoringTagHelpers.TagHelpers
     {
         [HtmlTargetElement("Website-Information")]
         public class WebsiteInformationTagHelper : TagHelper
         {
             public WebsiteContext Info { get; set; }
    	
           public override void Process(TagHelperContext context, TagHelperOutput output)
           {
              output.TagName = "section";
              output.Content.SetHtmlContent(
     $@"<ul><li><strong>Version:</strong> {Info.Version}</li>
     <li><strong>Copyright Year:</strong> {Info.CopyrightYear}</li>
     <li><strong>Approved:</strong> {Info.Approved}</li>
     <li><strong>Number of tags to show:</strong> {Info.TagsToShow}</li></ul>");
              output.TagMode = TagMode.StartTagAndEndTag;
           }
        }
     }
    

    Note:

    1. The TagHelper includes a property of WebsiteContext
    2. By convention, tag helper translates Pascal-cased C# class names (WebsiteInformation) and properties for tag helpers into lower kebab case (website-information). Here we specify the tag name is Website-Information
  3. Add the tag helper to razor view

     @using AuthoringTagHelpers.Models
     @{
         ViewData["Title"] = "About";
     }
     <h2>@ViewData["Title"].</h2>
     <h3>@ViewData["Message"]</h3>
    	
     <p bold>Use this area to provide additional information.</p>
    	
     <bold> Is this bold?</bold>
    	
     <h3> web site info </h3>
     <website-information info="new WebsiteContext {
                                         Version = new Version(1, 3),
                                         CopyrightYear = 1638,
                                         Approved = true,
                                         TagsToShow = 131 }" />
    

    or

    update the controller:

     public IActionResult Index(bool approved = false)
     {
         return View(new WebsiteContext
         {
             Approved = approved,
             CopyrightYear = 2015,
             Version = new Version(1, 3, 3, 7),
             TagsToShow = 20
         });
     }
    

    update the razor view:

     @using AuthoringTagHelpers.Models
     @model WebsiteContext
    	
     @{
         ViewData["Title"] = "Home Page";
     }
    	
     <div>
         <h3>Information about our website (outdated):</h3>
         <website-information info=@Model />
     </div>
    

Reference

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-2.1#tag-helpers-compared-to-html-helpers

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring?view=aspnetcore-2.1

ExpressVPN tutorial

$
0
0

Introduction

ExpressVPN is a virtual private network service. The software is marketed as a privacy and security tool that encrypts users’ web traffic and masks their IP addresses. It has released apps for Windows, macOS, iOS, Android, Linux

Installation

  1. select a subscription and make payment > get activation code

  2. download at here

  3. Install the app > reboot computer > start the app > set up

  4. enter activation code > click sign in

  5. sign in successfully > choose location e.g. HongKong

  6. click connect button

  7. now you are connected via HongKong > minize the app to let it run on the background

  8. to disconnect, just click the connect button

C# Delegate and Event Tutorial

$
0
0

Introduction

A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

Delegate type can be declared using the delegate keyword. Once a delegate is declared, delegate instance will refer and call those methods whose return type and parameter-list matches with the delegate declaration.

e.g.

public delegate int Calculate(int x, int y);

Any method from any accessible class or struct that matches the delegate type can be assigned to the delegate. The method can be either static or an instance method. This makes it possible to programmatically change method calls, and also plug new code into existing classes.

Features

  1. Delegates are type-safe, object-oriented and secure.

    Delegates are type-safe pointer of any method. Delegates can point to either static or instance methods.

  2. Delegates in C# are similar to the function pointer in C/C++, but delegates are fully object-oriented, and unlike C++ pointers to member functions, delegates encapsulate both an object instance and a method. It provides a way which tells which method is to be called when an event is triggered.

    For example, if you click an Button on a form (Windows Form application), the program would call a specific method. In simple words, it is a type that represents references to methods with a particular parameter list and return type and then calls the method in a program for execution when it is needed.

  3. Delegates are mainly used in implementing the call-back methods and events.

    A delegate is a solution for situations in which you want to pass methods around to other methods. Typically, we pass data as parameters to methods, however, in delegate we pass methods as parameters to methods. In this case, we can have a method to invoke other methods.

    They are used in these senarios. We do not know at compile time what this second methods is. That information is available only at runtime hence Delegates will be used to resolve these senarios.

  4. Delegates can be chained together; for example, two or more methods can be called on a single event.

  5. Delegate doesn’t care about the class of the object that it references. Delegates can also be used in “anonymous methods” invocation.

Steps

  1. Declare delegates. The definition looks like abstract method declarations.

    Syntax:

    [modifier] delegate [return_type] [delegate_name] ([parameter_list]);

    modifier: It is the required modifier which defines the access of delegate and it is optional to use.

    delegate: It is the keyword which is used to define the delegate.

    return_type: It is the type of value returned by the methods which the delegate will be going to call. It can be void. A method must have the same return type as the delegate.

    delegate_name: It is the user-defined name or identifier for the delegate.

    parameter_list: This contains the parameters which are required by the method when called through the delegate.

    e.g.

    public delegate int operation(int x, int y);

    When the C# compiler encounters this line, it defines a type derived from System.MulticastDelegate class, that also implements a method named Invoke that has exactly the same signature as the method described in the delegate declaration:

     public class operation : System.MulticastDelegate  
     {  
         public int Invoke(int x, int y);  
         // Other code  
     }  
    
  2. Instantiate delegates. After declaring a delegate, create a delegate object using new keyword and associate with a particular method.

    Syntax:

    [delegate_name] [instance_name] = new [delegate_name](calling_method_name);

    e.g.

     operation opt = new operation(Add);
        // here,
        // "operation" is delegate name. 
        // "opt" is instance_name
        // "Add" is the calling method, can be static method or instance method
    
  3. Make a method call to the delegate object and pass parameters to the delegated method, also receive the return value.

    e.g.

     var returnValue = opt(val1, val2);
    

Demo

using System; 
namespace GeeksForGeeks { 
      
// declare class "Geeks" 
class Geeks { 
      
// Declaring the delegates 
// Here return type and parameter type should  
// be same as the return type and parameter type 
// of the two methods 
// "addnum" and "subnum" are two delegate names 
public delegate void addnum(int a, int b); 
public delegate void subnum(int a, int b); 
      
    // method "sum" 
    public void sum(int a, int b) 
    { 
        Console.WriteLine("(100 + 40) = {0}", a + b); 
    } 
  
    // method "subtract" 
    public void subtract(int a, int b) 
    { 
        Console.WriteLine("(100 - 60) = {0}", a - b); 
    } 
  
// Main Method 
public static void Main(String []args) 
{ 
      
    // creating object "obj" of class "Geeks" 
    Geeks obj = new Geeks(); 
  
    // creating object of delegate, name as "del_obj1"  
    // for method "sum" and "del_obj2" for method "subtract"& 
    // pass the parameter as the two methods by class object "obj" 
    // instantiating the delegates 
    addnum del_obj1 = new addnum(obj.sum); 
    subnum del_obj2 = new subnum(obj.subtract); 
  
    // pass the values to the methods by delegate object 
    del_obj1(100, 40); 
    del_obj2(100, 60); 
  
    // These can be written as using 
    // "Invoke" method 
    // del_obj1.Invoke(100, 40); 
    // del_obj2.Invoke(100, 60); 
} 
} 
} 

Output:

(100 + 40) = 140
(100 - 60) = 40

In the above program, there are two delegates addnum and subnum. We are creating the object obj of the class Geeks because both the methods(addnum and subnum) are instance methods. So they need an object to call. If methods are static then there is no need to create the object of the class.

More

Multicasting

Multicasting of delegate is an extension of the normal delegate(Single Cast Delegate). It helps the user to point more than one method in a single call.

  1. Delegates are combined and when you call a delegate then a complete list of methods is called.
  2. All methods are called in First in First Out(FIFO) order.
  3. ‘+’ or ‘+=’ Operator is used to add the methods to delegates.
  4. ‘–’ or ‘-=’ Operator is used to remove the methods from the delegates list.
  5. multicasting of delegate should have a return type of Void otherwise it will throw a runtime exception. Also, the multicasting of delegate will return the value only from the last method added in the multicast. Although, the other methods will be executed successfully.

Demo:

// C# program to illustrate the  
// Multicasting of Delegates 
using System; 
  
class rectangle { 
      
// declaring delegate 
public delegate void rectDelegate(double height, 
                                  double width); 
  
    // "area" method 
    public void area(double height, double width) 
    { 
        Console.WriteLine("Area is: {0}", (width * height)); 
    } 
   
    // "perimeter" method 
    public void perimeter(double height, double width) 
    { 
        Console.WriteLine("Perimeter is: {0} ", 2 * (width + height)); 
    } 
   
   
// Main Method 
public static void Main(String []args) 
{ 
      
    // creating object of class  
    // "rectangle", named as "rect" 
    rectangle rect = new rectangle(); 
  
    // these two lines are normal calling 
    // of that two methods 
    // rect.area(6.3, 4.2); 
    // rect.perimeter(6.3, 4.2); 
  
    // creating delegate object, name as "rectdele" 
    // and pass the method as parameter by  
    // class object "rect" 
    rectDelegate rectdele = new rectDelegate(rect.area); 
      
    // also can be written as  
    // rectDelegate rectdele = rect.area; 
  
    // call 2nd method "perimeter" 
    // Multicasting 
    rectdele += rect.perimeter;  
  
    // pass the values in two method  
    // by using "Invoke" method 
    rectdele.Invoke(6.3, 4.2); 
    Console.WriteLine(); 
      
    // call the methods with  
    // different values 
    rectdele.Invoke(16.3, 10.3); 
} 
}  Output:

Area is: 26.46
Perimeter is: 21 

Area is: 167.89
Perimeter is: 53.2

event

Events enable a class or object to notify other classes or objects when something of interest occurs.

  • The class that sends (or raises) the event is called the publisher
  • The classes that receive (or handle) the event are called subscribers.

The event keyword in C# is used to declare an event in a publisher class. event in C# is a type of Delegate, which means that if one wants to use Event, then one must define delegate first.

e.g.

void Main()
{
	var publisher = new EventProgram();
	publisher.Test();
}

// Define other methods, classes and namespaces here
public delegate string MyDel(string str);
	
public class EventProgram {
  MyDel MyEvent;
  //event MyDel MyEvent; // that's the same
	
  public EventProgram() {
     this.MyEvent += new MyDel(this.WelcomeUser);
  }
  public string WelcomeUser(string username) {
     return "Welcome " + username;
  }
  public void Test() {
     EventProgram obj1 = new EventProgram();
     string result = obj1.MyEvent("Tutorials Point");
     Console.WriteLine(result);
  }
}

As we can see, use event or not is the same, event is a type of Delegate.

An Event declaration adds a layer of abstraction and protection on the delegate instance. This protection prevents clients of the delegate from resetting the delegate and its invocation list and only allows adding(+) or removing(-) targets from the invocation list.

A common senario is when a button is clicked in the UI. Events are very useful to create notifications.

e.g.

public class EventTest {     
    public delegate void Print(string val);     
    public event Print PrintEvent;   
    public EventTest()     {         
      this.PrintEvent += PrintData;        
      this.PrintEvent += PrintValue;     
      }
    public virtual void OnPrintEvent()     {         
      if (PrintEvent != null)             
        PrintEvent("Test");     
        }       
      private void PrintData(string s)     {         
        Console.WriteLine("PrintData" + s);     
        }       
      public void PrintValue(string s)     
      {         
        Console.WriteLine("PrintValue" + s); 
	  } 
    }
}

Delegate vs. Interface

Both delegates and interfaces enable a class designer to separate type declarations and implementation. An interface reference or a delegate can be used by an object that has no knowledge of the class that implements the interface or delegate method.

  • A given interface can be inherited and implemented by any class or struct.
  • A delegate can be created for a method on any class, as long as the method fits the method signature for the delegate.

Use a delegate in the following circumstances:

  • An eventing design pattern is used. It provides a way which tells which method is to be called when an event is triggered.
  • It is desirable to encapsulate a static method. Interface has to be applied to instances of class.
  • The caller has no need to access other properties, methods, or interfaces on the object implementing the method.
  • Easy composition is desired. i.e. multicasting
  • A class may need more than one implementation of the method.

Use an interface in the following circumstances:

  • There is a group of related methods that may be called.
  • A class only needs one implementation of the method.
  • The class using the interface will want to cast that interface to other interface or class types.
  • The method being implemented is linked to the type or identity of the class: for example, comparison methods.

一般来说,delegate 和 interface都可以实现行为定义与行为实现的分离,碰到问题时,应该使用delegate 还是 interface?

  1. comparasion table

  2. 如果行为的实现是基于对象的,也就是说,是对象自带的行为,用interface+class结构,会更清晰;如果是因为某个event动态触发的行为,用delegate+method,会更灵活。

    e.g. IComparable or the generic version, IComparable<T>. IComparable declares the CompareTo method, which returns an integer that specifies a less than, equal to, or greater than relationship between two objects of the same type. IComparable can be used as the basis of a sort algorithm. Although using a delegate comparison method as the basis of a sort algorithm would be valid, it is not ideal. Because the ability to compare belongs to the class and the comparison algorithm does not change at run time, a single-method interface is ideal.

    这个例子中,interface 与delegate 都可以使用,但是“比较”这个行为,通常是对象自身具备的一个行为,因此用interface更合适

  3. Delegates can, indeed, be seen as interfaces/contracts for a single method and are thus similar to interfaces. If we just need a simple anonymous method, using delegate is more brief and interface is kind of messy.

    e.g. use delegate

     var item = myList.FindBySelector(new SelectorDelegate(item => item.IsTheOne));
    

    vs. use interface

     Item item = myList.FindBySelector(new ISelector () 
     {
        @Override
        public boolean apply(Item item) 
        {
            return item.IsTheOne;
        }
     });
    

References

Delegates (C# Programming Guide)

When to Use Delegates Instead of Interfaces (C# Programming Guide)

Delegates And Interface Overview

to read

https://www.c-sharpcorner.com/UploadFile/84c85b/delegates-and-events-C-Sharp-net/

https://stackoverflow.com/questions/8694921/delegates-vs-interfaces-in-c-sharp

https://www.informit.com/articles/article.aspx?p=332881

https://www.pluralsight.com/guides/c-using-interfaces-and-delegates

https://www.c-sharpcorner.com/UploadFile/1c8574/delegates-interface-discussion/

https://codewithshadman.com/publish-subscribe-design-pattern-in-csharp/

Create Angular v2+ project (1) - introduction

$
0
0

Introduction

Angular is a framework for building client applications in HTML and either JavaScript or a language like TypeScript that compiles to JavaScript.

The framework consists of several libraries, some of them core and some optional. Typically, we build Angular applications by composing HTML templates in markup format.

Development steps:

  1. Build a project via cli
  2. Create component classes including view logic (.ts files, define properties) and they manage HTML templates(.html files) and component stylesheets(.css files)
  3. Add application logic in services such as HTTP requests, calculations. Call services from components
  4. Box components and services in modules, then import to the root module
  5. Configure routing and import to the root module
  6. Launch the app by bootstrapping the root module.
  7. Debug the project

Here is the outline of this tutorial:

  1. Fundamental knowledge
  2. Create a sample Angular project
  3. Create a component
  4. Create a model
  5. Create a service
  6. Create routing
  7. Add HTTP service
  8. Define module

Fundamentals

  1. Module

    Angular apps are modular and Angular has its own modularity system called NgModules. Angular app has at least one root module, typically named AppModule. Within module, we can define components, directives, services…

  2. Component

    Component controls a patch of screen and it is a class. We define application logic inside component and component supports the view.

  3. Template

    Template is the view of component. It is a form of HTML that tells Angular how to render the component.

  4. Metadata

    Metadata decorates Angular class and it tells Angular how to process a class. In TypeScript, we attach metadata by using a decorator. E.g. here’s some metadata for a Component:

     @Component({
       selector:    'app-hero-list',
       templateUrl: './hero-list.component.html',
       providers:  [ HeroService ],
       styleUrls: []
     })
     export class HeroListComponent implements OnInit {
     /* . . . */
     }
    
    • selector: CSS selector that tells Angular to use this component
    • templateUrl: module-relative address of this component’s HTML template
    • providers: array of dependency injection providers for services that the component requires.
    • styleUrls: relative address of stylesheets
  5. Two way data binding

    Data binding plays an important role in communication between a template and its component. Two way data binding represents the data value of input box in template syncs with the property of component automatically. Angular processes all data bindings once per JavaScript event cycle

Environment:

  1. Node.js v8+
  2. Visual Studio Code

Create a sample project via angular/cli

  1. Install node.js

  2. install angular/cli npm install -g @angular/cli

    or

    npm install -g @angular/cli@latest

    or

    npm install -g @angular/cli@8.0.6 (angular v5)

    npm install -g @angular/cli@1.7.4 (angular v5)

    npm update -g @angular/cli

  3. check version:

    ng --version

    or ng -v

    or check package.json

  4. create a new project ng new angular-demo

  5. enter angular-demo folder, ng serve or ng serve --port 6000, by default, the port is 4200

    use configuration option: ng serve --configuration=local

  6. open browser, http://localhost:4200

Create a component

  1. create a new component heroes: ng generate component heroes

    or

    ng generate component general-info --module app (app.module.ts)

  2. a new folder heroes will be created with components source code in it

    heros.component.ts

     import { Component, OnInit } from '@angular/core';
    	
     @Component({
       selector: 'app-heroes',
       templateUrl: './heroes.component.html',
       styleUrls: ['./heroes.component.css']
     })
     export class HeroesComponent implements OnInit {		
       constructor() { }
    	
       ngOnInit() {
       }		
     } 
    

    heroes.component.spec.ts

     import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    	
     import { HeroesComponent } from './heroes.component';
    	
     describe('HeroesComponent', () => {
       let component: HeroesComponent;
       let fixture: ComponentFixture<HeroesComponent>;
    	
       beforeEach(async(() => {
         TestBed.configureTestingModule({
           declarations: [ HeroesComponent ]
         })
         .compileComponents();
       }));
    	
       beforeEach(() => {
         fixture = TestBed.createComponent(HeroesComponent);
         component = fixture.componentInstance;
         fixture.detectChanges();
       });
    	
       it('should create', () => {
         expect(component).toBeTruthy();
       });
     }); 
    
  3. app.module.ts will be updated

     import { BrowserModule } from '@angular/platform-browser';
     import { NgModule } from '@angular/core';
    	
    
     import { AppComponent } from './app.component';
     import { HeroesComponent } from './heroes/heroes.component';
    	
    	
     @NgModule({
       declarations: [
         AppComponent,
         HeroesComponent
       ],
       imports: [
         BrowserModule
       ],
       providers: [],
       bootstrap: [AppComponent]
     })
     export class AppModule { } 
    

Create a new model

`ng generate class hero`

hero.ts

export class Hero {
  id: number;
  name: string;
}

Create a new service

  1. ng generate service services\hero

  2. A new folder services will be created with service source code in it

    hero.service.ts

     import { Injectable } from '@angular/core';
    	
     @Injectable()
     export class HeroService {		
       constructor() { }		
     } 
    

    hero.service.spec.ts

     import { TestBed, inject } from '@angular/core/testing';	
     import { HeroService } from './hero.service';
    	
     describe('HeroService', () => {
       beforeEach(() => {
         TestBed.configureTestingModule({
           providers: [HeroService]
         });
       });
    	
       it('should be created', inject([HeroService], (service: HeroService) => {
         expect(service).toBeTruthy();
       }));
     }); 
    

Create routing

  1. ng generate module app-routing --flat --module=app

    --flat puts the file in src/app instead of its own folder.
    --module=app tells the CLI to register it in the imports array of the AppModule.

  2. a app-routing.module.ts routing config file will be created and app.module.ts will be updated

    app-routing.module.ts

     import { NgModule } from '@angular/core';
     import { CommonModule } from '@angular/common';
    	
     @NgModule({
       imports: [
         CommonModule
       ],
       declarations: []
     })
     export class AppRoutingModule { } 
    

    app.module.ts

     ...
     import { AppRoutingModule } from './app-routing.module';
    	
     @NgModule({
       declarations: [
         AppComponent,
         ...
       ],
       imports: [
         ...
         AppRoutingModule
       ],
       providers: [],
       bootstrap: [AppComponent]
     })
     export class AppModule { } 
    

    Next, we will configure the router with Routes in the RouterModule

  3. First, import RouterModule, Routes. Also, remove the @NgModule.declarations array and CommonModule because we don’t declare components in a routing module .

    app-routing.module.ts

     import { NgModule }             from '@angular/core';
     import { RouterModule, Routes } from '@angular/router';
    	
     @NgModule({
       exports: [ RouterModule ]
     })
     export class AppRoutingModule {}
    
  4. Add routes

    A typical Angular Route has two properties:

    path: a string that matches the URL in the browser address bar.
    component: the component that the router should create when navigating to this route.

    e.g. we create route for HeroesComponent. We hope localhost:4200/heroes will invoke HeroesComponent

    app-routing.module.ts

     import { HeroesComponent }      from './heroes/heroes.component';
     ...
     const routes: Routes = [
       { path: 'heroes', component: HeroesComponent }
     ];
    
  5. Add more routes
create new components:
> `ng generate component hero-detail`
> `ng generate component dashboard`

update app-routing.module.ts

	import { DashboardComponent }   from './dashboard/dashboard.component';
	import { HeroDetailComponent }  from './hero-detail/hero-detail.component';
	...
	const routes: Routes = [
		...
		{ path: '', redirectTo: '/dashboard', pathMatch: 'full' }, // default route
		{ path: 'dashboard', component: DashboardComponent },
		{ path: 'detail/:id', component: HeroDetailComponent }, // parameterized route
	];	
  1. Then, initialize the router and start it listening for browser location changes.

    app-routing.module.ts @NgModule({ … imports: [ RouterModule.forRoot(routes) ], })

  2. Finally, add RouterOutlet in template pages. Open the AppComponent template replace the element with a element.

    app.component.html

     ...
     <h1></h1>
     <nav>
      			<a routerLink="/dashboard">Dashboard</a>
       	<a routerLink="/heroes">Heroes</a>
     </nav>
     <router-outlet></router-outlet>
     ...
    

    <router-outlet> tells the router where to display routed views. Please note that RouterOutlet is already available to the AppComponent because AppModule imports AppRoutingModule which exported RouterModule. <routerLink> is the selector for the RouterLink directive that turns user clicks into router navigations

  3. Grab parameter from router

    update heroes.component.html

     ...
     <ul class="heroes">
       <li *ngFor="let hero of heroes">
         <a routerLink="/detail/">
           <span class="badge"></span> 
         </a>
       </li>
     </ul>
     ...
    

    because detail/:id is routed to HeroDetailComponent, we need to parse data inside this component

    hero-detail.component.ts

     ...
     import { ActivatedRoute } from '@angular/router';
     import { Location } from '@angular/common';		
    	
     @Component({
       ...
     })
     export class HeroDetailComponent implements OnInit {
         constructor(
           private route: ActivatedRoute,
           private heroService: HeroService,
           private location: Location
         ) {}
         ngOnInit(): void {
             this.getHero();
         }
         getHero(): void {
             const id = +this.route.snapshot.paramMap.get('id'); // + can convert string to number
             this.heroService.getHero(id)
             .subscribe(hero => this.hero = hero);
         }
         goBack(): void {
             this.location.back();
         }
     }
    

Add HTTP services

HttpClient is Angular’s mechanism for communicating with a remote server over HTTP.

  1. Install the module by importing HttpClientModule to AppModule

    app.module.ts

     import { HttpClientModule } from "@angular/common/http";
     @NgModule({
       declarations: [
         ...
         // components list
       ],
       imports: [
         BrowserModule
         ...
         , HttpClientModule
       ],
     }
    
  2. Use Http service to handle Http requests

    After installing the module, the app will make requests to and receive responses from the HttpClient.

    hero.service.ts

     import { Injectable } from "@angular/core";
     import { HttpClient, HttpHeaders } from "@angular/common/http";
     import { Observable } from 'rxjs/Observable';
     import { of } from 'rxjs/observable/of';
    	
     @Injectable()
     export class HeroService {
         private heroesUrl = 'api/heroes';  // URL to web api
    
         constructor(
           private http: HttpClient,
           private messageService: MessageService) { }
    
         // get mock data
         //getHeroes(): Observable<Hero[]> {
         //  return of(HEROES);
         //}
    
         getHeroes (): Observable<Hero[]> {
           return this.http.get<Hero[]>(this.heroesUrl)
         }
     }
    

    All HttpClient methods return an RxJS Observable of something (Hero array).

    Please note

    1. for angular 6.x, install rxjs-compat package

      npm install --save rxjs-compat

  3. error handling

    If things go wrong when we’re getting data from a remote server. The HeroService.getHeroes() method should catch errors and do something appropriate.

    To catch errors, you “pipe” the observable result from http.get() through an RxJS catchError() operator. Then, extend the observable result with the .pipe() method and give it a catchError() operator

    hero.service.ts

     import { catchError, map, tap } from 'rxjs/operators';
     ...
     export class HeroService {
     ...
         getHeroes (): Observable<Hero[]> {
           return this.http.get<Hero[]>(this.heroesUrl)
             .pipe(
                 tap(heroes => this.log(`fetched heroes`)),
               	catchError(this.handleError('getHeroes', []))
             );
         }
         /**
          * Handle Http operation that failed. Let the app continue.
          * @param operation - name of the operation that failed
          * @param result - optional value to return as the observable result
          */
         private handleError<T> (operation = 'operation', result?: T) {
           return (error: any): Observable<T> => {
    		 
             // TODO: send the error to remote logging infrastructure
             console.error(error); // can log to other data repository
    		 
             // TODO: better job of transforming error for user consumption
             this.log(`${operation} failed: ${error.message}`); // display in the client
    		 
             // Let the app keep running by returning an empty result.
             return of(result as T);
           };
         }
     ...
     }
    

    Here, getHeroes() still returns an Observable<Hero[]> (“an observable of Hero array”)

  4. get data in component

    HeroService returns an Observable<Hero[]> and we need to subscribe it and render it in view component.

     ...
     import { HeroService } from '../hero.service';
     @Component({
       ...
     })
     export class HeroesComponent implements OnInit {
       heroes: Hero[];
    	 
       constructor(private heroService: HeroService) { }
    	 
       ngOnInit() {
         this.getHeroes();
       } 
    	
       getHeroes(): void {
         this.heroService.getHeroes()
             .subscribe(heroes => this.heroes = heroes);
       }
     }
     ... 
    
  5. Add hero update support

    update hero.service.ts

    /** PUT: update the hero on the server */ updateHero (hero: Hero): Observable { return this.http.put(this.heroesUrl, hero, httpOptions).pipe( tap(_ => this.log(`updated hero id=${hero.id}`)), catchError(this.handleError('updateHero')) ); }

    The HttpClient.put() method takes three parameters

    • the URL
    • the data to update (the modified hero in this case)
    • request options. e.g.

        const httpOptions = {
          headers: new HttpHeaders({ 'Content-Type': 'application/json' })
        }; 
      

    Then, call save() method in hero detail component

    hero-detail.component.html

    <button (click)="save()">save</button>
    

    hero-detail.component.ts

     save(): void {
        this.heroService.updateHero(this.hero)
          .subscribe(() => this.goBack());
      }
    
  6. Add hero add support

    update hero.service.ts

     /** POST: add a new hero to the server */
     addHero (hero: Hero): Observable<Hero> {
       return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
         tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
         catchError(this.handleError<Hero>('addHero'))
       );
     }
    

    Then, call add() method in hero detail component

    hero-detail.component.html

    <input #heroName />
     <button (click)="add(heroName.value); heroName.value=''">add</button>
    

    hero-detail.component.ts

     add(name: string): void {
       name = name.trim();
       if (!name) { return; }
       this.heroService.addHero({ name } as Hero)
         .subscribe(hero => {
           this.heroes.push(hero);
         });
     }
    
  7. Add hero deletion support

    update hero.service.ts

     /** DELETE: delete the hero from the server */
     deleteHero (hero: Hero | number): Observable<Hero> {
       const id = typeof hero === 'number' ? hero : hero.id;
       const url = `${this.heroesUrl}/${id}`;
    	
       return this.http.delete<Hero>(url, httpOptions).pipe(
         tap(_ => this.log(`deleted hero id=${id}`)),
         catchError(this.handleError<Hero>('deleteHero'))
       );
     }
    

    Then, call delete() method in hero list component

    hero-detail.component.html

    <ul class="heroes">
       <li *ngFor="let hero of heroes">
         <a routerLink="/detail/">
           <span class="badge"></span> 
         </a>
         <button class="delete" title="delete hero"
         (click)="delete(hero)">x</button>
       </li>
     </ul>
    

    heroes.component.ts

     delete(hero: Hero): void {
       this.heroes = this.heroes.filter(h => h !== hero);
       this.heroService.deleteHero(hero).subscribe();
     }
    
  8. Add hero search support

    update hero.service.ts

     /* GET heroes whose name contains search term */
     searchHeroes(term: string): Observable<Hero[]> {
       if (!term.trim()) {
         // if not search term, return empty hero array.
         return of([]);
       }
       return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
         tap(_ => this.log(`found heroes matching "${term}"`)),
         catchError(this.handleError<Hero[]>('searchHeroes', []))
       );
     }
    

    add a search component ng generate component hero-search

    update hero-search.component.html

    <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />		
     <ul>
         <li *ngFor="let hero of heroes$ | async">
           <a routerLink="/detail/">
    		    
           </a>
         </li>
     </ul>
    

    The $ is a convention that indicates heroes$ is an Observable, not an array.
    The *ngFor can’t do anything with an Observable. But there’s also a pipe character (|) followed by async, which identifies Angular’s AsyncPipe.
    The AsyncPipe subscribes to an Observable automatically so you won’t have to do so in the component class.

    update hero-search.component.ts

     ...
     import { Observable } from 'rxjs/Observable';
     import { Subject }    from 'rxjs/Subject';
     import { of }         from 'rxjs/observable/of';
     import {
        debounceTime, distinctUntilChanged, switchMap
      } from 'rxjs/operators';
     import { Hero } from '../hero';
     import { HeroService } from '../hero.service';
    	
     @Component({
       ...
     })
     export class HeroSearchComponent implements OnInit {
         heroes$: Observable<Hero[]>; // declare heroes$ as an Observable
         private searchTerms = new Subject<string>();
    		
         constructor(private heroService: HeroService) {}
    		
         // Push a search term into the observable stream.
         search(term: string): void {
             this.searchTerms.next(term);
         }
    		
         ngOnInit(): void {
             this.heroes$ = this.searchTerms.pipe(
             // wait 300ms after each keystroke before considering the term
             debounceTime(300),
    		
             // ignore new term if same as previous term
             distinctUntilChanged(),
    			
             // switch to new search observable each time the term changes
             switchMap((term: string) => this.heroService.searchHeroes(term)),
         );
         }
     }
    
    • A Subject is both a source of observable values and an Observable itself. You can subscribe to a Subject as you would any Observable.
    • You can also push values into that Observable by calling its next(value) method as the search() method does.
    • debounceTime(300) waits until the flow of new string events pauses for 300 milliseconds before passing along the latest string. You’ll never make requests more frequently than 300ms.
    • distinctUntilChanged ensures that a request is sent only if the filter text changed.
    • switchMap() calls the search service for each search term that makes it through debounce and distinctUntilChanged. It cancels and discards previous search observables, returning only the latest search service observable.

Define feature modules

We could import and declare all components in the root module. Or, we can define feature modules to group and encapsulate components, then import to the root module.

A typical root module

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  providers:    [],
  declarations: [ AppComponent ],
  exports:      [],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
  • declarations - the view classes that belong to this module. Angular has three kinds of view classes: components, directives, and pipes.
  • exports - the subset of declarations that should be visible and usable in the component templates of other modules.
  • imports - other modules whose exported classes are needed by component templates declared in this module.
  • providers - creators of services that this module contributes to the global collection of services; they become accessible in all parts of the app.
  • bootstrap - the main application view, called the root component, that hosts all other app views. Only the root module should set this bootstrap property.

Create a feature module

Import feature module to root module

Make release

Create a build: ng build

build in production: ng build --prod

if there are virtual directory stock: ng build --base-href /stock/ --prod

References

https://angular.io

Debug the project

  1. Install Debugger for Chrome in visual studio code

  2. Create .vscode/launch.json file to enable debugger

     {
         "version": "0.1.0",
         "configurations": [
             {
                 "name": "Launch localhost",
                 "type": "chrome",
                 "request": "launch",
                 "url": "http://localhost:4200",
                 "webRoot": "${workspaceFolder}/wwwroot"
             },
             {
                 "name": "Launch index.html (disable sourcemaps)",
                 "type": "chrome",
                 "request": "launch",
                 "sourceMaps": false,
                 "file": "${workspaceFolder}/index.html"
             },
         ]
     }
    

    Here, we specify visual studio to open a new Chrome window (request=launch) for debugging purpose.

  3. In visual studio code, we start the serve by Terminal > ng serve and add some debug breakpoints

  4. Then, click the Start debugging button in debug view. A chrome will be opened automatically, press f12 to open developer tools, then refresh the page.

Install lodash

npm install --save lodash

npm install --save @types/lodash

Then, in your .ts file:

import * as _ from "lodash";

Next, simply call _.<lodash_function>()

Install moment.js

npm install moment --save

npm install @types/moment --save

or

npm install moment-timezone --save (will install moment automatically) npm install @types/moment-timezone --save

in angular-cli.json (Angular 5+)

{
  ...
  "apps": [
     ...
     "scripts": [
        "../node_modules/moment/min/moment.min.js"
     ]
     ...
  ]
  ...
}

in angular.json (Angular 6+)

{
  "projects": {
    "Web": {
      ...
      "architect": {
        	...
            "scripts": []
          },
          ...
        },
        "serve": {
          ...
        },
        "test": {
        	...
            "styles": [
              "src/styles.scss",
              "./node_modules/bootstrap/dist/css/bootstrap.css"
            ],
            "scripts": [
              "../node_modules/moment/min/moment.min.js"
            ],
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ]
          }
        },
        ...
      }
    }
  },
  ...
}

in my-component.component.ts

import { Component } from '@angular/core';
import * as moment from 'moment';
import 'moment-timezone';

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
export class MyComponent {

  constructor() {
    let now = moment(); 
    console.log('hello world', now.format()); 
    console.log(now.add(7, 'days').format()); 
    
    let easternNow = moment().tz('America/New_York'); 
  }
}

Add scss support

  1. Open angular-cli.json, add styleExt, add styles

     {
       ...,
       "project": {
         "name": "website"
       },
       "apps": [
       		...,
         "styles": [
             "styles.css",
             "covalent-theme.scss"
         ]
       ],
       ...,
       "defaults": {
         "styleExt": "scss",
         "component": {}
       }
     }
    
  2. Add external style > open src\styles.css

    /* You can add global styles to this file, and also import other style files */ @import “~@angular/material/prebuilt-themes/indigo-pink.css”; @import ‘./assets/css/xxx.min.css’;

  3. Add boostrap:

    npm install --save bootstrap

    way1: add in global root - styles.css / styles.scss

     @import '../node_modules/bootstrap/dist/css/bootstrap.min.css'; 
    

    way2: add in .angular-cli.json or angular.json:

    "styles": [
       "src/styles.scss",
       "./node_modules/bootstrap/dist/css/bootstrap.css"
     ],
    

Create pipe

we can write a custom pipe by implementing PipeTransform interface

for example, a pipe to filter employee object via factId property

  1. write employee-fact-filter.pipe.ts

     import { PipeTransform, Pipe } from "@angular/core";
     import { Employee } from "../models/employee.model";
    	
     @Pipe({name: 'employeeFactFilter'})
     export class EmployeeFactFilterPipe implements PipeTransform{
         transform(value: Employee, factId : number) : Employee {
             if (factId == null) value.TblEmployeeFactRel = [];
             else value.TblEmployeeFactRel = value.TblEmployeeFactRel.filter(fact => fact.FactId == factId);
             return value;
         }
    	
     }
    

    Please note

    • A pipe is a class decorated with pipe metadata. the name must by camel case e.g. employeeFactFilter instead of separated by comma e.g. employee-fact-filter(wrong)
    • the transform method accepts an input value followed by optional parameters and returns the transformed value. There will be one additional argument to the transform method for each parameter passed to the pipe. Your pipe has one such parameter: the exponent.
  2. include the pipe in the declarations array of the AppModule, app.module.ts

     import { BrowserModule } from '@angular/platform-browser';
     import { NgModule } from '@angular/core';
     import { FormsModule } from '@angular/forms';
     import { HttpModule } from '@angular/http';
    	
     import { AppComponent } from './app.component';
     import { EmployeeFactFilterPipe } from './employee-fact-filter.pipe.ts';
    	
     @NgModule({
       declarations: [
         AppComponent,
         EmployeeFactFilterPipe
       ],
       imports: [
         BrowserModule,
         FormsModule,
         HttpModule
       ],
       providers: [],
       bootstrap: [AppComponent]
     })
     export class AppModule { }
    

    Please note if we choose to inject your pipe into a class, we must provide it in the providers array of your NgModule.

  3. In template, we use this custom pipe:

    <tr *ngFor=”let factRel of (employee | employeeFactFilter: factId).GetFactRelByDate(from, to)”> </tr>

If we use multiple parameters, it will be defined like

@Pipe({name: 'uselessPipe'})
export class uselessPipe implements PipeTransform {
  transform(value: string, before: string, after: string): string {
    let newStr = `${before} ${value} ${after}`;
    return newStr;
  }
}

call it like that:

Install material icon

npm install material-design-icons

install jquery

way 1, add new lib to global scope

npm install --save jquerynpm install popper.js --savenpm install bootstrap --save

angular.json

"architect": {
        "build": {
          ...,
            "scripts": [
			  "node_modules/jquery/dist/jquery.slim.js",
			  "node_modules/popper.js/dist/umd/popper.js",
			  "node_modules/bootstrap/dist/js/bootstrap.js"
              ...
            ]
          },
		"styles": [
		  "node_modules/bootstrap/dist/css/bootstrap.css",
		  "src/styles.css"
		],

in component:

declare var $: any;
ngOnInit() {
   $(document).ready(function() {
     alert('I am Called From jQuery');
   });
}

way2, add typings to global libraries

npm install –save jquery

npm install –save @types/jquery

in component:

import * as $ from 'jquery';

ngOnInit() {
   $(document).ready(function() {
     $('#my-button').click(doSomething());
 });
}

FAQ

Code scaffolding Run ng generate component component-name to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module.

Running unit tests Run ng test to execute the unit tests via Karma.

Running end-to-end tests Run ng e2e to execute the end-to-end tests via Protractor.

References

Angular Playground

Material Icons

Create Angular v2+ project (2) - typescript

$
0
0

Introduction

check version

New features

Pick vs Partial

Partial and Pick are mapped types. They are used to create a new type using part of original type.

e.g.

interface PartialTask {
  id: string,
  name: string,
  contacts: any[]
}

interface Task extends PartialTask{
  associatedJob: string,
  submissionDate: string,
  allocatedTime: number,
  expectedCompletion: string,
  assignee: string,
  invoiceNumber: string,
  invoiceDueDate: string,
  comment: string,
  taskAddress: string
  ...
  ... x 10
}

Here we have 2 versions of Task type.

Partial helps us create a clone type that mirrors a provided type(interface, class) with all properties:

type PartialTaskByPartial = Partial<Task>;
let taskObj : PartialTaskByPartial = null;

Here the new type PartialTaskByPartial contains all properties of Task class.

Pick is alternative to the above but gives us more powerful way. We can pick some of properties from original type to get a new type:

type PartialTaskByPick = Pick<Task, 'id' | 'associatedJob'>;
let partialTaskObj : PartialTaskByPick = null;

Here the new type PartialTaskByPick only contains 2 properties: id, associatedJob

Demo 1

In practice, Pick can help us create new objects with customized proproties from original types. e.g.

function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
    const copy = {} as Pick<T, K>;

    keys.forEach(key => copy[key] = obj[key]);

    return copy;
}

Then:

let originalObj = { "name": "someName", "age": 20, "date": "2015-01-03" };
let copy = pick(originalObj, "name", "age") as Test;
console.log(copy); // { name: "someName", "age": 20 }

Please note that here Pick help us achieve this purpose via new empty object {} and add properties and copy value from original object into it. It has better performance than delete a property from original object.

Demo 2

Pick is handy when you need to create a new type from an existing interface with only the specified keys, which is great but sometimes you need just the opposite. Like when you’re defining the return type for your API endpoint where you want to include everything except for one or two private fields.

So what you actually need is Omit. It is not built-in and we will create for that:

type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];  
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>; 

this is how to use:

type PartialTaskByOmit = Omit<Employee, 'id' | 'associatedJob'>;  
var partialTaskObj : PartialTaskByOmit ;

Here the new type PartialTaskByOmit contains all properties of Task class except id, associatedJob

References

https://medium.com/@curtistatewilkinson/typescript-2-1-and-the-power-of-pick-ff433f1e6fb

http://ideasintosoftware.com/typescript-advanced-tricks/

Create Angular v2+ project (3) - forms

$
0
0

Introduction

Angular Forms provide a framework support for two-way data binding, change tracking, validation and error handling.

There are two approaches to build forms in Angular.

  1. template-driven form
  2. reactive(model-driven) form

Template-driven form

Reactive forms is an Angular technique for creating forms in in Angular template syntax with form-specific directives such as ngForm, ngModel.

Steps

  1. Build an Angular form with a component and template. Then use ngModel to create two-way data bindings for reading and writing input control values
  2. Form validation
    1. Track form control’s state and validity with ngModel
    2. Provide visual feedback using custom CSS classes
    3. Show and hide validation errors to users
    4. Handle form submission with ngSubmit

build an Angular form with a component and template

  • add a model - Hero.ts

      export class Hero {		
        constructor(
          public id: number,
          public name: string,
          public power: string,
          public alterEgo?: string
        ) {  }		
      }
    
  • add form component - HeroForm.component.ts

      import { Component } from '@angular/core';
      import { Hero }    from '../hero';
    	
      @Component({
        selector: 'app-hero-form',
        templateUrl: './hero-form.component.html',
        styleUrls: ['./hero-form.component.css']
      })
      export class HeroFormComponent {
    	
        powers = ['Really Smart', 'Super Flexible',
                  'Super Hot', 'Weather Changer'];
    	
        model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
    	
        submitted = false;
    	
        onSubmit() { this.submitted = true; }
    	
        // TODO: Remove this when we're done
        get diagnostic() { return JSON.stringify(this.model); }
      }
    
  • hero-form.component.html

    <div class="container">
          <h1>Hero Form</h1>
    
          <!-- declare a template variable for the form. The variable `heroForm` is now a reference to the NgForm directive that governs the form as a whole. -->
          <form #heroForm="ngForm">
    
            <div class="form-group">
              <label for="name">Name</label>
              <input type="text" class="form-control" id="name"
                 required
                 [(ngModel)]="model.name" name="name">
            </div>
    	 
            <div class="form-group">
              <label for="alterEgo">Alter Ego</label>
        		<input type="text"  class="form-control" id="alterEgo"
               [(ngModel)]="model.alterEgo" name="alterEgo">
            </div>
    
          <div class="form-group">
            <label for="power">Hero Power</label>
            <select class="form-control"  id="power"
                    required
                    [(ngModel)]="model.power" name="power">
              <option *ngFor="let pow of powers" [value]="pow"></option>
            </select>
          </div>
    	 
            <button type="submit" class="btn btn-success">Submit</button>
    	 
          </form>
      </div>
    

    Please note:

    1. Angular automatically creates and attaches an NgForm directive to the <form> tag via #heroForm="ngForm". The NgForm directive supplements the form element with additional features. It holds the controls you created for the elements with an ngModel directive and name attribute, and monitors their properties, including their validity. It also has its own valid property which is true only if every contained control is valid.

    2. Angular creates FormControl instances and registers them with an NgForm directive that Angular attached to the <form> tag. Each FormControl is registered under the name you assigned to the name attribute.

  • update application’s root module - app.module.ts, import FormsModule

      import { NgModule }      from '@angular/core';
      import { BrowserModule } from '@angular/platform-browser';
      import { FormsModule }   from '@angular/forms'; // added
    	 
      import { AppComponent }  from './app.component';
      import { HeroFormComponent } from './hero-form/hero-form.component'; // added
    	 
      @NgModule({
        imports: [
          BrowserModule,
          FormsModule // added
        ],
        declarations: [
          AppComponent,
          HeroFormComponent // added
        ],
        providers: [],
        bootstrap: [ AppComponent ]
      })
      export class AppModule { }
    
  • Update application’s root component - app.component.html

    <app-hero-form></app-hero-form>
    

Form validation

Using ngModel in a form gives us not only two-way data binding, but also tells us if the user touched the control, if the value changed, or if the value became invalid. Specifically, it attaches additional classes in different states:

track form control’s state and validity with ngModel

e.g. hero-form.component.html

<input type="text" class="form-control" id="name"
  required
  [(ngModel)]="model.name" name="name"
  #spy>
<br>TODO: remove this: 

Here, we temporarily add a template reference variable named spy to the Name <input> tag and use it to display the input’s CSS classes.

  1. We look but don’t touch the textbox
  2. Click inside the name box, then click outside it.
  3. Add slashes to the end of the name.
  4. Erase the name.

We will get respectively:

Provide visual feedback using custom CSS classes

Next, we add css classes to implement visualized validation

forms.css

.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

Add this css to index.html

<link rel="stylesheet" href="assets/forms.css">

Then we got

Show and hide validation errors to users

We make some changes to hero-form.component.html

<label for="name">Name</label>
<input type="text" class="form-control" id="name"
       required
       [(ngModel)]="model.name" name="name"
       #name="ngModel">
<div [hidden]="name.valid || name.pristine"
     class="alert alert-danger">
  Name is required
</div>

Here, we add

  1. a template reference variable called name and gave it the value “ngModel”. It can access the input box’s Angular control from within the template.
  2. a “is required” error message in <div>, which you’ll display only if the control is invalid.

Handle form submission with ngSubmit

bind the form’s ngSubmit event property to the hero form component’s onSubmit() method:

hero-form.component.html

<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
	...
	<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
</form>

hero-form.component.ts

submitted = false;

onSubmit() { 
	// connect remote api to process submission
	...
	this.submitted = true; 
}

Reactive form

Reactive forms is an Angular technique for creating forms in a reactive style. With reactive forms, we create a tree of Angular form control objects in the component class and bind them to native form control elements in the component template.

Steps

  1. Build a reactive form with a component and template.
  2. Add a FormGroup
  3. Use FormBuilder to quickly build FormGroup
  4. Form validation
    1. Track form control’s state and validity with ngModel
    2. Provide visual feedback using custom CSS classes
    3. Show and hide validation errors to users
    4. Handle form submission with ngSubmit

Build a reactive form with a component and template.

  • Create data models: data-model.ts

      export class Hero {
        id = 0;
        name = '';
        addresses: Address[];
      }
    	
      export class Address {
        street = '';
        city   = '';
        state  = '';
        zip    = '';
      }
    	
      export const heroes: Hero[] = [
        {
          id: 1,
          name: 'Whirlwind',
          addresses: [
            {street: '123 Main',  city: 'Anywhere', state: 'CA',  zip: '94801'},
            {street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'},
          ]
        },
        {
          id: 2,
          name: 'Bombastic',
          addresses: [
            {street: '789 Elm',  city: 'Smallville', state: 'OH',  zip: '04501'},
          ]
        },
        {
          id: 3,
          name: 'Magneta',
          addresses: [ ]
        },
      ];
    	
      export const states = ['CA', 'MD', 'OH', 'VA'];
    
  • Create a reactive forms component hero-detail.component.ts

      import { FormControl } from '@angular/forms';
      export class HeroDetailComponent1 {
        name = new FormControl();
      }
    

    Here, FormControl is a directive that allows us to create and manage a FormControl instance. It tracks the value and validation status of an individual form control. It corresponds to an HTML form control such as an <input> or <select>.

  • Create the template hero-detail.component.html

    <h2>Hero Detail</h2>
      <h3><i>Just a FormControl</i></h3>
      <label class="center-block">Name:
        <input class="form-control" [formControl]="name">
      </label>
    

    Here, we associate the FormControl name in the component to the formControl in the template of element.

  • Update application’s root module - app.module.ts, import ReactiveFormsModule

      import { NgModule }            from '@angular/core';
      import { BrowserModule }       from '@angular/platform-browser';
      import { ReactiveFormsModule } from '@angular/forms';  // <-- #1 import module
    	
      import { AppComponent }        from './app.component';
      import { HeroDetailComponent } from './hero-detail/hero-detail.component';
    	
      @NgModule({
        declarations: [
          AppComponent,
          HeroDetailComponent,
        ],
        imports: [
          BrowserModule,
          ReactiveFormsModule // <-- #2 add to @NgModule imports
        ],
        bootstrap: [ AppComponent ]
      })
      export class AppModule { }
    
  • Update application’s root component app.component.html

    <div class="container">
        <h1>Reactive Forms</h1>
        <app-hero-detail></app-hero-detail>
      </div>
    

Add a FormGroup

If we have multiple FormControls, we register them within a parent FormGroup.

hero-detail.component.ts

import { Component }              from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
export class HeroDetailComponent2 {
  heroForm = new FormGroup ({
    name: new FormControl()
  });
}

hero-detail.component.html

<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm">
  <div class="form-group">
    <label class="center-block">Name:
      <input class="form-control" formControlName="name">
    </label>
  </div>
</form>

Here, formGroup is a reactive form directive that takes an existing FormGroup instance and associates it with <form> element.

Use FormBuilder to quickly build FormGroup

FormBuilder class helps us to build FormGroup easily.

hero-detail.component.ts

import { Component }              from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

export class HeroDetailComponent3 {
  heroForm: FormGroup; // <--- heroForm is of type FormGroup

  constructor(private fb: FormBuilder) { // <--- inject FormBuilder
    this.createForm();
  }

  createForm() {
    this.heroForm = this.fb.group({
      name: '', // <--- the FormControl called "name", initial value is an empty string
    });
  }
}

Form validation

hero-detail.component.ts

import { Component }                          from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
this.heroForm = this.fb.group({
  name: ['', Validators.required ],
});

Reactive forms vs template-driven forms

Reactive forms are synchronous (as you create controls from you code). In reactive forms, you create the entire form control tree in code. You can immediately update a value or drill down through the descendants of the parent form because all controls are always available.

template-driven forms are asynchronous (as it delegate task of creation of control) Template-driven forms delegate creation of their form controls to directives. To avoid “changed after checked” errors, these directives take more than one cycle to build the entire control tree. That means you must wait a tick before manipulating any of the controls from within the component class. In template driven from we write [NgModel] or [NgForm] (directives) that will take task of creation of your control on web page in htm and the creation is asynchronous.

Therefore, using template-driven form, if we try to access a form control in ngOninit(), the control may not accessible because the component just finished creating itself and subcomponents and may not complete rending views. Only in ngAfterViewInit(), we can confirm the views complete rendering and form controls are accessible. Therears, using reactive form, we can always find control where in Template driven from we cannot.

References

angular built-in validators

Create Angular v2+ project (4) - router

$
0
0

Introduction

The Angular Router enables navigation from one view to another view when user perform some tasks. Also, it can pass optional parameters along to the target view component.

There are below typical navigation senarios:

  • Enter a URL in the address bar and the browser navigates to a corresponding page.
  • User click links on the page and the browser navigates to a new page.
  • User click the browser’s back and forward buttons and the browser navigates backward and forward through the history of pages we’ve seen.

Angular Router uses the browser’s history.pushState for navigation. Thanks to pushState, we can make in-app URL paths look the way we want them to look, e.g. localhost:3000/crisis-center. The in-app URLs is different from server URLs and handled by browsers.

Outline

  • steps to create simple routing
  • refactor routing into modules
  • route guards
  • route resolvers

Steps

  1. the routed Angular application has a configured routes
  2. The hosting component has a RouterOutlet where it can display views produced by the router.
  3. the hosting component also has RouterLinks that users can click to navigate via the router.
  4. we can handle routing data or events in components corresponding to specific routes.

Here is Routing related terms in Angular:

Prepare root component

Due to Angular routing system uses HTML5 pushState, we must add a <base href> element to the app’s index.html. The browser uses the value to prefix relative URLs when referencing CSS files, scripts, and images.

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Web</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

<body>
  <app-root></app-root>
</body>

</html>

Set router configuration

creates route definitions, configures the router via the RouterModule.forRoot method, and adds the result to the AppModule’s imports array.

app/app.module.ts

import { NgModule } from '@angular/core';
import { NgModule }             from '@angular/core';
import { BrowserModule }        from '@angular/platform-browser';
import { FormsModule }          from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';	

import { AppComponent }          from './app.component';
import { CrisisListComponent }   from './crisis-list.component';
import { HeroListComponent }     from './hero-list.component';
...
const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'hero/:id',      component: HeroDetailComponent },
  {
    path: 'heroes',
    component: HeroListComponent,
    data: { title: 'Heroes List' }
  },
  { path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
	BrowserModule,
    FormsModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
    // other imports here
  ],
 declarations: [
    AppComponent,
    HeroListComponent,
    CrisisListComponent,
  ],
  bootstrap: [ AppComponent ]
  ...
})
export class AppModule { }

Please note:

  1. The appRoutes array of routes describes how to navigate. Pass it to the RouterModule.forRoot() method in the module imports to configure the router.
  2. Each Route maps a URL path to a component.
  3. :id in the second route is a token for a route parameter. such as /hero/42, “42” is the value of the id parameter
  4. data property in the third route is a place to store arbitrary data associated with this specific route. The data property is accessible within each activated route. Use it to store items such as page titles, breadcrumb text, and other read-only, static data. We use it as the resolve guard to retrieve dynamic data
  5. The empty path in the fourth route represents the default path for the application, the place to go when the path in the URL is empty
  6. ** path in the last route will be used if the requested URL doesn’t match any paths for routes defined earlier in the configuration. This is useful for displaying a 404 - Not Found page or redirecting to another route.
  7. Router uses first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. In the configuration above, routes with specific path are listed first, followed by an empty path route, that matches the default route. The wildcard route comes last because it matches every URL and should be selected only if no other routes are matched first.
  8. enableTracing option is only for debugging purpose and it outputs each router event that took place during each navigation lifecycle to the browser console. We set the enableTracing: true option in the object passed as the second argument to the RouterModule.forRoot() method.

Set routing endpoints

In the application host view(shell), we set up router-outlet and it is the navigation root. Typically, the root AppComponent is the application shell.

app.component.html

<div class="container">
    <router-outlet></router-outlet>
</div>

Set routing urls

Now we have routes configured and ready to render navigated views. Here, we use routerLink

app.component.html

<h1>Angular Router</h1>
<nav>
	<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
	<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

the host view looks like

The RouterLink directives on the anchor tags give the router control over those elements. Because navigation paths are fixed, so we just assign a string to the routerLink.

Had the navigation path been more dynamic, you could have bound to a template expression that returned an array of route link parameters (the link parameters array). The router resolves that array into a complete URL.

The RouterLinkActive directive on each anchor tag helps visually distinguish the anchor for the currently selected “active” route. The router adds the active CSS class to the element when the associated RouterLink becomes active. You can add this directive to the anchor or to its parent element.

hero-list.component.ts

<h2>HEROES</h2>
<ul class="items">
<li *ngFor="let hero of heroes$ | async"
  [class.selected]="hero.id === selectedId">
  <a [routerLink]="['/hero', hero.id]">
    <span class="badge"></span>
  </a>
</li>
</ul>

<button routerLink="/sidekicks">Go to sidekicks</button>

Process routing data in component

A routed Angular application has one singleton instance of the Router service.

After the end of each successful navigation lifecycle, the router builds a tree of ActivatedRoute objects that make up the current state of the router. We can access the data in the application

import { ActivatedRoute, Router } from "@angular/router";
...
@Component({
	...
})

export class HeroComponent{
	private id : number;
    constructor(protected route: ActivatedRoute
        , protected router: Router) {
		this.id = this.route.snapshot.paramMap.get('id'); // get id parameter via /hero/42
		// or
		//this.route.paramMap.pipe(
		//    switchMap((params: ParamMap) =>
		//      this.id = params.get('id');
		//  );
    }
	...
}

Activated route object includes the route path, parameters and other information:

![](/images/posts/20180829-router-1.png)

Important properties:

**paramMap** - an Observable that contains the required and optional parameters specific to the route. old name is `params`.

**queryParamMap** — an Observable that contains the query parameters available to all routes. old name is `queryParams`

Process routing events in component

During each navigation, Router service emits multiple navigation events through the Router.events property.

![](/images/posts/20180829-router-2.png)

These events are logged to the console when the enableTracing option is enabled. Because the events are Observable, we can filter() for events of interest and subscribe() to them.

import { ActivatedRoute, Router, NavigationEnd, NavigationStart } from '@angular/router';
...
@Component({
	...
})

export class HeroComponent{
    constructor(protected route: ActivatedRoute
        , protected router: Router) {
		 this.router.events.subscribe((event) => {
	      if (event instanceof NavigationStart) {
	        console.log('current url: ', this.router.url);
	        console.log('to url: ', event.url);
	      }
	      if (event instanceof NavigationEnd) {
	        console.log('current url: ', this.router.url);
	        console.log('to url: ', event.url);
	      }
	    })
	    }
	...
}

hero-detail.component.ts

gotoHeroes() {
  	this.router.navigate(['/heroes']);
	//this.router.navigate([['/hero', hero.id]]);
}

Refactor routing module

Create a file app/app-routing.module.ts to contain the routing module.

import { NgModule }              from '@angular/core';
import { RouterModule, Routes }  from '@angular/router';
 
import { CrisisListComponent }   from './crisis-list.component';
import { HeroListComponent }     from './hero-list.component';
import { PageNotFoundComponent } from './not-found.component';
 
const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',        component: HeroListComponent },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
 
@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

update the app.module.ts file, first importing the newly created AppRoutingModule from app-routing.module.ts, then replacing RouterModule.forRoot in the imports array with the AppRoutingModule.

import { NgModule }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
 
import { AppComponent }     from './app.component';
import { AppRoutingModule } from './app-routing.module';
 
import { CrisisListComponent }   from './crisis-list.component';
import { HeroListComponent }     from './hero-list.component';
import { PageNotFoundComponent } from './not-found.component';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    HeroListComponent,
    CrisisListComponent,
    PageNotFoundComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

child routing

create a sub routing config crisis-center-routing.module.ts:

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// some new components
import { CrisisCenterHomeComponent } from './crisis-center-home.component';
import { CrisisListComponent }       from './crisis-list.component';
import { CrisisCenterComponent }     from './crisis-center.component';
import { CrisisDetailComponent }     from './crisis-detail.component';

const crisisCenterRoutes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisCenterComponent,
    children: [
      {
        path: '',
        component: CrisisListComponent,
        children: [
          {
            path: ':id',
            component: CrisisDetailComponent
          },
          {
            path: '',
            component: CrisisCenterHomeComponent
          }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(crisisCenterRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class CrisisCenterRoutingModule { }

then, import this new routing module to app.module.ts

import { NgModule }       from '@angular/core';
import { CommonModule }   from '@angular/common';
import { FormsModule }    from '@angular/forms';

import { AppComponent }            from './app.component';
import { PageNotFoundComponent }   from './not-found.component';

import { AppRoutingModule }        from './app-routing.module';
import { HeroesModule }            from './heroes/heroes.module';
import { CrisisCenterRoutingModule }      from './crisis-center/crisis-center-routing.module';

import { DialogService }           from './dialog.service';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    HeroesModule,
    CrisisCenterRoutingModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    PageNotFoundComponent
  ],
  providers: [
    DialogService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

navigation, *.component.ts:

this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });

Route guards

At the moment, any user can navigate anywhere in the application anytime. But for below senarios, we have to add guards to the route configuration to implement them:

  • Perhaps the user is not authorized to navigate to the target component. - CanActivate
  • Maybe the user must login (authenticate) first. - CanActivate
  • Maybe you should fetch some data before you display the target component. - CanActivate
  • We might want to save pending changes before leaving a component. - CanDeactivate
  • We might ask the user if it’s OK to discard pending changes rather than save them. - CanDeactivate

Typically, a routing guard’s return value controls the router’s behavior. It can return an Observable or a Promise

  • If it returns true, the navigation process continues.
  • If it returns false, the navigation process stops and the user stays there.

Angular router supports multiple guard interfaces:

  • CanActivate to mediate navigation to a route. determines whether the component of a router is accessible
  • CanActivateChild to mediate navigation to a child route.
  • CanDeactivate to mediate navigation away from the current route. determines whether the component of a router is okay to leave away
  • Resolve to perform route data retrieval before route activation.
  • CanLoad to mediate navigation to a feature module loaded asynchronously.

CanActivate: requiring authentication

This interface can be used to restrict access to a router on the user identity - whether user can navigate into the router

  1. add a admin folder, add admin-specific components, routing config, module config

    structure:

     - src/app/admin
         |- admin.module.ts
         |- admin-routing.module.ts
         |- admin.component.ts
         |- admin-dahboard.component.ts
         |- admin-feature1.component.ts
         |- admin-feature2.component.ts
    
  2. some admin feature components:

    src/app/admin/admin.component.ts

     import { Component } from '@angular/core';
    			 
     @Component({
       template:  `
         <h3>ADMIN</h3>
         <nav>
           <a routerLink="./" routerLinkActive="active"
             [routerLinkActiveOptions]="{ exact: true }">Dashboard</a> | 
           <a routerLink="./feature1" routerLinkActive="active">Feature1</a> | 
           <a routerLink="./feature2" routerLinkActive="active">Feature2</a>
         </nav>
         <router-outlet></router-outlet>
       `
     })
     export class AdminComponent {
     }
    

    src/app/admin/admin-dashboard.component.ts

     import { Component } from '@angular/core';
    	
     @Component({
       template:  `
         <p>Dashboard</p>
       `
     })
     export class AdminDashboardComponent { }
    

    src/app/admin/admin-feature1.component.ts

     import { Component } from '@angular/core';
    	
     @Component({
       template:  `
         <p>Admin feature 1 </p>
       `
     })
     export class AdminFeature1Component { }
    
  3. create admin routing config

    src/app/admin/admin-routing.module.ts

     import { AdminComponent } from "./admin.component";
     import { Routes, RouterModule } from "@angular/router";
     import { AdminFeature1Component } from "./admin-feature1.component";
     import { AdminFeature2Component } from "./admin-feature2.component";
     import { AdminDashboardComponent } from "./admin-dashboard.component";
     import { NgModule } from "@angular/core";
    	
     const adminRoutes: Routes = [
         {
             path: 'admin',
             component: AdminComponent,
             children: [
                 {
                     path: '',
                     children: [
                         { path: 'feature1', component: AdminFeature1Component },
                         { path: 'feature2', component: AdminFeature2Component },
                         { path: '', component: AdminDashboardComponent }
                     ]
                 }
             ]
         }
     ];
    	
     @NgModule({
         imports: [
             RouterModule.forChild(adminRoutes)
         ],
         exports: [
             RouterModule
         ]
     })
     export class AdminRoutingModule { }
    
  4. create admin module config, importing admin routing config

    src/app/admin/admin.module.ts

     import { NgModule }       from '@angular/core';
     import { CommonModule }   from '@angular/common';
    	 
     import { AdminDashboardComponent }  from './admin-dashboard.component';
     ...
     import { AdminRoutingModule }       from './admin-routing.module';
    	 
     @NgModule({
       imports: [
         CommonModule,
         AdminRoutingModule
       ],
       declarations: [
         AdminComponent,
         AdminDashboardComponent,
         AdminFeature1Component,
         AdminFeature2Component
       ]
     })
     export class AdminModule {}
    

    import the AdminModule into app.module.ts and add it to the imports array to register the admin routes.

     import { NgModule }       from '@angular/core';
     import { CommonModule }   from '@angular/common';
     import { FormsModule }    from '@angular/forms';
    	
     import { AppComponent }            from './app.component';
     import { PageNotFoundComponent }   from './not-found.component';
    	
     import { AppRoutingModule }        from './app-routing.module';
     import { AdminModule }             from './admin/admin.module';
    	
     @NgModule({
       imports: [
         CommonModule,
         FormsModule,
         AdminModule,
         AppRoutingModule
       ],
       declarations: [
         AppComponent,
         PageNotFoundComponent
       ],
       providers: [
       ],
       bootstrap: [ AppComponent ]
     })
     export class AppModule { }
    
  5. Now we get

  6. create a guard service to restrict navigation access

    src/app/services/auth-guard.service.ts

     import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
     import { Injectable } from "@angular/core";
    	
     @Injectable()
     export class AuthGuardService implements CanActivate{
         canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot){
             console.log('you trying to access the router: ', state.url);
             return true;
         }
     }
    
  7. add the guard service to admin.module.ts as providers

     import { NgModule } from '@angular/core';
     import { CommonModule } from '@angular/common';
    	
     import { AdminDashboardComponent } from './admin-dashboard.component';
    	
     import { AdminRoutingModule } from './admin-routing.module';
     import { AdminFeature1Component } from './admin-feature1.component';
     import { AdminComponent } from './admin.component';
     import { AdminFeature2Component } from './admin-feature2.component';
     import { AuthGuardService } from '../services/auth-guard.service';
    	
     @NgModule({
         imports: [
             CommonModule,
             AdminRoutingModule
         ],
         declarations: [
             AdminComponent,
             AdminDashboardComponent,
             AdminFeature1Component,
             AdminFeature2Component
         ],
         providers:[AuthGuardService] // here
     })
     export class AdminModule { }
    
  8. add the guard service to admin-routing.module.ts

    src/app/admin/admin-routing.module.ts

     import { AdminComponent } from "./admin.component";
     import { Routes, RouterModule } from "@angular/router";
     import { AdminFeature1Component } from "./admin-feature1.component";
     import { AdminFeature2Component } from "./admin-feature2.component";
     import { AdminDashboardComponent } from "./admin-dashboard.component";
     import { NgModule } from "@angular/core";
     import { AuthGuardService } from "../services/auth-guard.service";
    	
     const adminRoutes: Routes = [
         {
             path: 'admin',
             component: AdminComponent,
             canActivate: [AuthGuardService], // here
             children: [
                 {
                     path: '',
                     children: [
                         { path: 'feature1', component: AdminFeature1Component },
                         { path: 'feature2', component: AdminFeature2Component },
                         { path: '', component: AdminDashboardComponent }
                     ]
                 }
             ]
         }
     ];
    	
     @NgModule({
         imports: [
             RouterModule.forChild(adminRoutes)
         ],
         exports: [
             RouterModule
         ]
     })
     export class AdminRoutingModule { }
    
  9. In real world, we can write a auth.service.ts to authenticate user. Then, auth-guard.service.ts call this service to guard the navigation

    e.g. src/app/auth.service.ts

     import { Injectable } from '@angular/core';		
     import { Observable, of } from 'rxjs';
     import { tap, delay } from 'rxjs/operators';
    	
     @Injectable()
     export class AuthService {
       isLoggedIn = false;
    	
       // store the URL so we can redirect after logging in
       redirectUrl: string;
    	
       login(): Observable<boolean> {
         return of(true).pipe(
           delay(1000),
           tap(val => this.isLoggedIn = true)
         );
       }
    	
       logout(): void {
         this.isLoggedIn = false;
       }
     }
    

    e.g. auth-guard.service.ts

     import { Injectable }       from '@angular/core';
     import {
       CanActivate, Router,
       ActivatedRouteSnapshot,
       RouterStateSnapshot
     }                           from '@angular/router';
     import { AuthService }      from './auth.service';
    	
     @Injectable()
     export class AuthGuardService implements CanActivate {
       constructor(private authService: AuthService, private router: Router) {}
    	
       canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
         let url: string = state.url;
    	
         return this.checkLogin(url);
       }
    	
       checkLogin(url: string): boolean {
         if (this.authService.isLoggedIn) { return true; }
    	
         // Store the attempted URL for redirecting
         this.authService.redirectUrl = url;
    	
         // Navigate to the login page with extras
         this.router.navigate(['/login']);
         return false;
       }
     }
    

CanDeactivate: handling unsaved changes

this interface can be used to determine whether user can navigate away the current route

Here, we wanna add a deactivate guard to feature1 router

  • way1, declare one guard per component:

    src\app\services\can-feature1-deactivate-guard.service.ts

      import { Injectable } from "@angular/core";
      import { AdminFeature1Component } from "../admin/admin-feature1.component";
      import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
      import { Observable } from "rxjs";
    	
      @Injectable()
      export class CanFeature1DeactivateGuard implements CanDeactivate<AdminFeature1Component> {
    	
        canDeactivate(
          component: AdminFeature1Component,
          route: ActivatedRouteSnapshot,
          state: RouterStateSnapshot
        ): Observable<boolean> | boolean {    
          console.log('you are leaving router - ', state.url);
          return true;
        }
      }
    

    update admin.module.ts, add this guard as provider

      import { NgModule } from '@angular/core';
      import { CommonModule } from '@angular/common';
      ...
      import { CanFeature1DeactivateGuard } from '../services/can-feature1-deactivate-guard.service';
    	
      @NgModule({
          imports: [
              CommonModule,
              AdminRoutingModule
          ],
          declarations: [
              AdminComponent,
              AdminDashboardComponent,
              AdminFeature1Component,
              AdminFeature2Component
          ],
          providers:[AuthGuardService, CanFeature1DeactivateGuard] // here
      })
    
      export class AdminModule { }
    

    update admin-routing.module.ts, add this guard to monitor the router

      import { NgModule } from "@angular/core";
      ...
      import { CanFeature1DeactivateGuard } from "../services/can-feature1-deactivate-guard.service";
    	
      const adminRoutes: Routes = [
          {
              path: 'admin',
              component: AdminComponent,
              canActivate: [AuthGuardService],
              children: [
                  {
                      path: '',
                      children: [
                          { path: 'feature1', component: AdminFeature1Component, canDeactivate: [CanFeature1DeactivateGuard] }, //here
                          { path: 'feature2', component: AdminFeature2Component },
                          { path: '', component: AdminDashboardComponent }
                      ]
                  }
              ]
          }
      ];
    
      @NgModule({
          imports: [
              RouterModule.forChild(adminRoutes)
          ],
          exports: [
              RouterModule
          ]
      })
      export class AdminRoutingModule { }
    

    using this method, we have to define guard for each component.

  • way2, create reusable guard. Using approach, we can just define a interface for all components and a guard based on this interface. Any router needs guard service can implement this interface with specific logic, then add a routing config.

    1. declare a generic component deactivate guard interface, src\components\can-component-deactivate.component.ts

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

      or

       import { Observable } from "rxjs";
       import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
      		
       export interface CanComponentDeactivate {
           canDeactivate: (currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot) => Observable<boolean> | Promise<boolean> | boolean;
       }
      
    2. create a reusable deactivate guard, src\services\can-deactivate-guard.service.ts

       import { Injectable } from "@angular/core";
       import { CanDeactivate } from "@angular/router";
       import { CanComponentDeactivate } from "../components/can-component-deactivate.component";
      		
       @Injectable()
       export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
         canDeactivate(component: CanComponentDeactivate) {
           return component.canDeactivate ? component.canDeactivate() : true;
         }
       }
      

      or

       import { Injectable } from "@angular/core";
       import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
       import { CanComponentDeactivate } from "../components/can-component-deactivate.component";
      		
       @Injectable()
       export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
         canDeactivate(component: CanComponentDeactivate, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot) {
           return component.canDeactivate ? component.canDeactivate(currentRoute, currentState) : true;
         }
       }
      

    Next, component which needs deactivate guard implements the interface

      import { Component } from '@angular/core';
      import { CanComponentDeactivate } from '../components/can-component-deactivate.component';
      import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    	
      @Component({
        template:  `
          <p>Admin feature 2 </p>
        `
      })
      export class AdminFeature2Component implements CanComponentDeactivate {
          canDeactivate(currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot) {
              console.log('you are leaving router - ', currentState.url);
              return true;
          };
      }
    

    Finally, add the reusable deactivate guard to routing config, module config

    admin-routing.module.ts

      import { AdminFeature1Component } from "./admin-feature1.component";
      import { AdminFeature2Component } from "./admin-feature2.component";
      ...
      const adminRoutes: Routes = [
          {
              path: 'admin',
              component: AdminComponent,
              canActivate: [AuthGuardService],
              children: [
                  {
                      path: '',
                      children: [
                          { path: 'feature1', component: AdminFeature1Component},
                          { path: 'feature2', component: AdminFeature2Component, canDeactivate:[CanDeactivateGuard] }, //here
                          { path: '', component: AdminDashboardComponent }
                      ]
                  }
              ]
          }
      ];
    

    admin.module.ts

      import { NgModule } from '@angular/core';
      import { CommonModule } from '@angular/common';
      ...
      @NgModule({
          imports: [
              CommonModule,
              AdminRoutingModule
          ],
          declarations: [
              AdminComponent,
              AdminDashboardComponent,
              AdminFeature1Component,
              AdminFeature2Component
          ],
          providers:[AuthGuardService, CanDeactivateGuard]
      })
      export class AdminModule { }
    	
      @NgModule({
          imports: [
              RouterModule.forChild(adminRoutes)
          ],
          exports: [
              RouterModule
          ]
      })
      export class AdminRoutingModule { }
    

Route resolvers

Resolves can be used to fetch data before navigating. This is an advanced Angular feature.

For instance, we have a component to display a list of items and we can click an item to view details. In the list view, we can create a resolver to determine whether an item exists or not.

import { Injectable }             from '@angular/core';
import { Router, Resolve, RouterStateSnapshot,
         ActivatedRouteSnapshot } from '@angular/router';
import { Observable }             from 'rxjs';
import { map, take }              from 'rxjs/operators';

import { Item }  from './item.model'; 
import { ItemService }  from './item.service';
 
@Injectable()
export class ItemDetailResolver implements Resolve<Item> {
  constructor(private cs: ItemService, private router: Router) {}
 
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Item> {
    let id = route.paramMap.get('id');
 
    return this.cs.getItemDetails(id).pipe(
      take(1),
      map(item => {
        if (item) {
          return item;
        } else { // id not found
          this.router.navigate(['/dashboard']);
          return null;
        }
      })
    );
  }
}

add the resolver to module config

import { ItemDetailResolver }   from './item-detail-resolver.service';

@NgModule({
  imports: [
    RouterModule.forChild(ItemRoutes)
  ],
  exports: [
    RouterModule
  ],
  providers: [
    ItemDetailResolver //here
  ]
})
export class ItemModule { }

FAQ

  1. [routerLink] vs. routerLink

    When we use brackets, it means we’re passing a bindable property (a variable).

    <a [routerLink]="routerLinkVariable"></a>
    
     <a [routerLink]="['employee-detail', employeeId]"></a>
    
     <a [routerLink]="['/update-employee', employeeId, 'site']"><i class="normal material-icons cls-material-icon">edit</i></a>
    

    So this variable (routerLinkVariable) could be defined inside our class and it should have a value like below:

     export class myComponent {
     public routerLinkVariable = "/home"; // the value of the variable is string!
    

    Where as without brackets we’re passing string only and we can’t change it, it’s hard coded and it’ll be like that throughout our app.

    <a routerLink="/home"></a>
    	
     <a routerLink="/employee-detail/"></a>
    
     <a routerLink="/update-employee//site"><i class="normal material-icons cls-material-icon">edit</i></a>
    

    ex1:

    <a [routerLink]="['/update-employee/1001/level', {'key1' : 'value1', redirectUrl: '/employee-detail/1001'}]"><i class="normal material-icons cls-material-icon">edit</i></a>
    

    will be translated to

    <a _ngcontent-c3="" ng-reflect-router-link="/update-employee/1001/level," href="/update-employee/130438/level;key1=value1;redirectUrl=%2Femployee-detail-compact%2F1001"><i _ngcontent-c3="" class="normal material-icons cls-material-icon">edit</i></a>
    

    ex2:

    <a [routerLink]="['/update-employee', employeeId, 'site']"><i
           class="normal material-icons cls-material-icon">edit</i></a>
    

    will be translated to

    <a _ngcontent-c3="" ng-reflect-router-link="/update-employee,1001,level" href="/update-employee/1001/level"><i _ngcontent-c3="" class="normal material-icons cls-material-icon">edit</i></a>
    

References

Routing & Navigation

https://alligator.io/angular/route-resolvers/


Create Angular v2+ project (5) - rxjs & observable

$
0
0

Introduction

Asynchronous programming is an important technique to create web applications. It allows units of work to run separately from the primary application thread and makes main application responsive.

RxJS is a library for reactive programming using Observables. It help us create asynchronous or callback-based applications quicker and easier. Angular 2+ uses RxJS to implement asynchronous operations.

Observables in rxjs

Observable provides support for passing messages between publishers and subscribers in our application. It helps us solve event handling, asynchronous programming issues.

Observable defines a subscriber function to publish events/values to consumers(observers) subscribe to it.

An Observable instance begins publishing values only when someone subscribes to it.

We subscribe by calling the subscribe() method of the instance, passing an observer object to receive the notifications.

Observer is a handler for receiving observable notifications implements the Observer interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:

  • next() Required. A handler for each delivered value. Called after execution starts. It defines the actual handler logic.
  • error() Optional. A handler for an error notification. An error halts execution of the observable instance.
  • complete() Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete.

Observer is event handler and receives event data published by an observable as a stream.

Observables vs Observers vs Subscriptions

  • An observable is a function that produces a stream of values to an observer over time.
  • When you subscribe to an observable, you are an observer.
  • An observable can have multiple observers.

Steps to use observable/observer

  1. create an Observable instance

     // Use the Observable constructor to create an observable instance
     const sequence = new Observable();	
    

    or

     // define a observable instance. It emits values in a sequence to subscribers(consumers)
     const sequence = Observable.of(...items); // angular 5+
     const sequence = of(...items); // angular 6+
    

    or

     //Converts its argument to an Observable instance. This method is commonly used to convert an array to an observable.
     const sequence = Observable.from(iterable)
    

    or

    Observable.create(function subscribe(observer) { observer.next(item1); observer.next(item2); observer.next(itemx); observer.complete(); })

  2. we define a subscriber function inside this instance. this function accepts observer object and put into a list

     const sequence = new Observable((observer) => {
           // synchronously deliver 1, 2, and 3, then complete
           observer.next(1);
           observer.next(2);
           observer.next(3);
           observer.complete();
     }
    

    equivalent to

     const sequence = Observable.of(1, 2, 3);
    
  3. a consumer(observer) calls the subscribe() method of the observable instance. Then pass event handlers

     const sequenceSubscription1 = sequence.subscribe({
         // event handlers
         next() { ... }
         error() { ... }
     });
    
     const sequenceSubscription2 = sequence.subscribe({
         // event handlers
         next() { ... }
     });
    

    Subscriber function received an observer object, the observer object defines next(), or error()/complete() methods.

  4. The observable publish events as a stream and pass values to observers’ next() method

Broadcasting/multicasting

Typically, a typical observable creates a new, independent execution for each subscribed observer. When an observer subscribes, the observable wires up a separate event handler and delivers values to that observer.

If we want each subscription of observer(consumer) get the same value, we need multicasting technique. Multicasting is the practice of broadcasting to a list of multiple subscribers in a single execution with the same event data.

We make some changes in above steps. When we subscribe observer to the observable, we add observers to an array(list):

function multicastSequenceSubscriber() {
  const seq = [1, 2, 3];
  // Keep track of each observer (one for every active subscription)
  const observers = [];

  // Return the subscriber function (runs when subscribe()
  // function is invoked)
  return (observer) => {
    observers.push(observer);
    return {
      unsubscribe() {
        // Remove from the observers array so it's no longer notified
        observers.splice(observers.indexOf(observer), 1);
      }
    };
  };
}

// Create a new Observable that will deliver the above sequence
const multicastSequence = new Observable(multicastSequenceSubscriber);

// multiple observers subscribe 
multicastSequence.subscribe({
  next(num) { console.log('1st subscribe: ' + num); },
  complete() { console.log('1st sequence finished.'); }
});
multicastSequence.subscribe({
    next(num) { console.log('2nd subscribe: ' + num); },
    complete() { console.log('2nd sequence finished.'); }
  });

RxJS

Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. It assumes all events published as a stream and listenable, e.g. keystrokes, an HTTP response, or an interval timer.

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. RxJS provides an implementation of the Observable type. The library also provides utility functions for creating and working with observables. These utility functions can be used for:

  • Converting existing code for async operations into observables
  • Iterating through the values in a stream
  • Mapping values to different types
  • Filtering streams
  • Composing multiple streams

Create observables

RxJS offers a number of functions that can be used to create new observables. These functions help us create observables from events, timers, promises, and so on.

  • Create an observable from a promise

      import { fromPromise } from 'rxjs/observable/fromPromise';
    	
      // Create an Observable from a promise
      const data = fromPromise(fetch('/api/endpoint'));
      // Subscribe to begin listening for async result
      data.subscribe({
       next(response) { console.log(response); },
       error(err) { console.error('Error: ' + err); },
       complete() { console.log('Completed'); }
      });
    
  • Create an observable from a counter

      import { interval } from 'rxjs/observable/interval';
    	
      // Create an Observable that will publish a value on an interval
      const secondsCounter = interval(1000);
      // Subscribe to the observable
      secondsCounter.subscribe(n =>
        console.log(`It's been ${n} seconds since subscribing!`));
    
  • Create an observable from an event

      import { fromEvent } from 'rxjs/observable/fromEvent';
    	
      const el = document.getElementById('my-element');
    	
      // Create an Observable that will publish mouse movements
      const mouseMoves = fromEvent(el, 'mousemove');
    	
      // Subscribe to start listening for mouse-move events
      const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
        // define the event handling logic - Log coords of mouse movements
        console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
    	
        // When the mouse is over the upper-left of the screen,
        // unsubscribe to stop listening for mouse movements
        if (evt.clientX < 40 && evt.clientY < 40) {
          subscription.unsubscribe();
        }
      });
    
  • Create an observable that creates an AJAX request

      import { ajax } from 'rxjs/observable/dom/ajax';
    	
      // Create an Observable that will create an AJAX request
      const apiData = ajax('/api/data');
      // Subscribe to create the request
      apiData.subscribe(res => console.log(res.status, res.response));
    

Operators

Operators are functions that build on the observables foundation to enable sophisticated manipulation of collections. e.g. map(), filter(), concat(), and flatMap(). An operator observes the source observable’s emitted values, transforms them, and returns a new observable of those transformed values.

import { map } from 'rxjs/operators';

Observable.of(1, 2, 3).map((val: number) => val * val).subscribe(x => console.log(x))

RxJS provides many operators (over 150 of them).

e.g. catchError operator that lets us handle known errors from the events of observable. It helps us catch this error and supply a default value, then our stream continues to process values rather than erroring out.

import { ajax } from 'rxjs/observable/dom/ajax';
import { map, catchError } from 'rxjs/operators';
// Return "response" from the API. If an error happens,
// return an empty array.
const apiData = ajax('/api/data')
	.pipe(
	  map(res => {
	    if (!res.response) {
	      throw new Error('Value expected!');
	    }
	    return res.response;
	  }),
  	catchError(err => Observable.of([]))
)
.subscribe({
  next(x) { console.log('data: ', x); },
  error(err) { console.log('errors already caught... will not run'); }
});

Pipe

pipes to link operators together and allows us to combine multiple operator functions into a single function.

import { pipe } from 'rxjs/util/pipe';
import { filter, map } from 'rxjs/operators';

Observable.of(1, 2, 3, 4, 5)
.pipe(
  filter(n => n % 2),
  map(n => n * n)
)
.subscribe(x => console.log(x));

Subject in rxjs

Subject is a practice of publisher-subscriber model in RxJS. It allows us to define our own observable and observer.

  • Observable is an object allows us to emit/publish an event. It has all the Observable operators, and we can subscribe to him.
  • Observer is an object allows us to subscribe an observable.
  • Subject is both an Observable and Observer allows us to both publish and subscribe.

Steps to use Subject:

  1. create a Subject instance of component:

     const subject = new Subject<data_type/event_type>(); 
    

    datatype can be boolean, string, number…

  2. Subject is observable and it means he has all the operators (map, filter, etc. ) and we can subscribe to him.

     subject.subscribe(val => console.log(`First observer ${val}`));
    

    or

     subject.map(value => `Observer one ${value}`).subscribe(value => {
       console.log(value);
     });
    

    here, we subsribe to the subject object. When the subject object changes, the console logs.

  3. Subject is observer and it listens to observable with next(), error(), and the complete() methods. Here is the Subject object methods:

     subject.next(event.target.value);
    

    When we call the next() method of Subject object, it publish the value of event and every subscriber will get this value.

    We can also trigger error() and complete() of Subject object.

In typical senario, we have the source Observable and many observers, and multiple observers share the same Observable execution.

Subject demo

We will have a textbox. when we enter something inside the textbox, it debounces x seconds and display the result.

  1. template: ng2.component.html
<input type=”text” placeholder=”Enter message” (keyup)=”keyup($event)” [(ngModel)]=”message”>
  1. component: ng2.component.ts

     import { Component, OnInit, OnDestroy } from '@angular/core';
     import { Subject } from 'rxjs';
     import { NgModel } from '@angular/forms';
    
     @Component({
       selector: 'app-ng2',
     
       styleUrls: ['./ng2.component.scss']
     })
     export class Ng2Component implements OnInit, OnDestroy {
    	
       constructor() { }
    
    public message : string;
       // define a Subject object as observer and it publish string as event objects
       public messageSubject = new Subject<string>();
    	
       ngOnInit() {
         // we have some observers subscribe to this subject object
         this.messageSubject.debounceTime(1000).subscribe(value => {
           console.log('I waited 1 seconds ', value);
         })
         this.messageSubject.debounceTime(2000).subscribe(value => {
           console.log('I waited 2 seconds ', value);
         })
         this.messageSubject.debounceTime(3000).subscribe(value => this.welcome(value))
       }
    	
       ngOnDestroy() {
         this.messageSubject.unsubscribe();
       }
    	
       keyup($event) : void{
          // it is publishing this value to all the subscribers that have already subscribed to this message
          this.messageSubject.next(this.message);
       }
    	
       welcome(value : string) : void{
         console.log('welcome ', value);
       }
     }
    
  2. run the demo. enter something.

BehaviorSubject vs. ReplaySubject vs. AsyncSubject

All of these derives from Subject and used for multicasting Observables. But they behaves slightly differently.

  1. BehaviorSubject - It needs an initial value as it always return a value on subscription even if it hasn’t received a next(). At any point, we can retrieve the last value of the subject in a non-observable code using the getValue() method or value property.

     export declare class BehaviorSubject<T> extends Subject<T> {
         private _value;
         constructor(_value: T);
         readonly value: T;
         getValue(): T;
         next(value: T): void;
     }
    

    e.g.

     import * as Rx from "rxjs";
    	
     const subject = new Rx.BehaviorSubject();
    	
     // subscriber 1
     subject.subscribe((data) => {
         console.log('Subscriber A:', data);
     });
    	
     subject.next(Math.random());
     subject.next(Math.random());
    	
     console.log(subject.value)
    	
     // output
     // Subscriber A: 0.24957144215097515
     // Subscriber A: 0.8751123892486292
     // Subscriber A: 0.1901322109907977
     // 0.1901322109907977
    

    We can also create BehaviorSubjects with an initial value:

     import * as Rx from "rxjs";
    	
     const subject = new Rx.BehaviorSubject(Math.random());
    	
     // subscriber 1
     subject.subscribe((data) => {
         console.log('Subscriber A:', data);
     });
    	
     // output
     // Subscriber A: 0.24957144215097515
    

    Note that BehaviorSubject always sends the current(latest) value to observers.

  2. ReplaySubject can send “old” values to new subscribers. It has the extra characteristic that it can record a part of the observable execution and store multiple old values and “replay” them to new subscribers.

    When creating the ReplaySubject we can specify how much values we want to store and for how long we want to store them. e.g. “I want to store the last 2 values, that have been executed in the last second prior to a new subscription”

     import * as Rx from "rxjs";
    	
     const subject = new Rx.ReplaySubject(2);
    	
     // subscriber 1
     subject.subscribe((data) => {
         console.log('Subscriber A:', data);
     });
    	
     subject.next(Math.random())
     subject.next(Math.random())
     subject.next(Math.random())
    	
     // subscriber 2
     subject.subscribe((data) => {
         console.log('Subscriber B:', data);
     });
    	
     // Subscriber A: 0.3541746356538569
     // Subscriber A: 0.12137498878080955
     // Subscriber A: 0.531935186034298
     // Subscriber B: 0.12137498878080955
     // Subscriber B: 0.531935186034298
    

    Note that when We start subscribing with Subscriber B. The ReplaySubject object stores 2 values and it will immediately emit last 2 values to Subscriber B and Subscriber B will log those.

    We can also specify for how long we wanna to store values in the replay subject:

     `const subject = new Rx.ReplaySubject(2, 100);`
    

    Here, we only want to store the last 2 values, but no longer than a 100 ms

  3. AsyncSubject

    While the BehaviorSubject and ReplaySubject both store values, BehaviorSubject stores the lastest value and ReplaySubject stores recent values, the AsyncSubject works a bit different. It emits the last value of the Observable execution is sent to its subscribers, and only when the execution completes.

     import * as Rx from "rxjs";
    	
     const subject = new Rx.AsyncSubject();
    	
     // subscriber 1
     subject.subscribe((data) => {
         console.log('Subscriber A:', data);
     });
    	
     subject.next(Math.random())
     subject.next(Math.random())
     subject.next(Math.random())
    	
     // subscriber 2
     subject.subscribe((data) => {
         console.log('Subscriber B:', data);
     });
    	
     subject.next(Math.random());
     subject.complete(); // only the complete() method triggers the response of observers
    	
     // Subscriber A: 0.4447275989704571
     // Subscriber B: 0.4447275989704571
    

    Note that the AsyncSubject object emits values multiple times. But only the emitt when it completes can lead subscribers to respond.

Observables in Angular

Angular makes use of observables as an interface to handle a variety of common asynchronous operations. e.g.

  • The EventEmitter class extends Observable, specifically Subject
  • The HTTP module uses observables to handle AJAX requests and responses.
  • The Router and Forms modules use observables to listen for and respond to user-input events.

Event emitter

EventEmitteris actually a utility for us to define events within a component. EventEmitter objects can emit an event object via emit() method. Essentially, EventEmitter object is a publisher(observable). When it invokes emit() method, it passes the emitted event object to the next() method of any subscribers(observer).

source code:

```
export declare class EventEmitter<T> extends Subject<T> {
    __isAsync: boolean;
    constructor(isAsync?: boolean);
    emit(value?: T): void;
    subscribe(generatorOrNext?: any, error?: any, complete?: any): any;
}	
```

Steps:

  1. define a component and EventEmitter object in it. Usually, we define an EventEmitter object with @Output() decorator.

     @Component({
       selector: 'zippy',
       template: `
       <div class="zippy">
         <div (click)="toggle()">Toggle</div>
         <div [hidden]="!visible">
           <ng-content></ng-content>
         </div>
       </div>`})
    	 
     export class ZippyComponent {
       visible = true;
       @Output() open = new EventEmitter<any>();
       @Output() close = new EventEmitter<any>();
    	 
       toggle() {
         this.visible = !this.visible;
         if (this.visible) {
           this.open.emit(null);
         } else {
           this.close.emit(null);
         }
       }
     }
    
  2. In parent component, reference this component

    <zippy (open)=”onOpen($event)” (close)=”onClose($event)”></zippy>

  3. in the parent component .ts file, define two methods onOpen($event), onClose($event) to respond to these events.

HTTP

HttpClient returns observables from HTTP method calls. For instance, http.get(‘/api’) returns an observable. we can subscribe to it to get result.

Async pipe

template:

<div><code>observable|async</code>: Time: </div>

Router

The ActivatedRoute is an injected router service that makes use of observables to get information about a route path and parameters. e.g.:

import { ActivatedRoute } from '@angular/router';
 
@Component({
  ...
})
export class Routable2Component implements OnInit {
  constructor(private activatedRoute: ActivatedRoute) {}
 
  ngOnInit() {
    this.activatedRoute.url
      .subscribe(url => console.log('The URL changed to: ' + url));
  }
}

Reactive forms

Reactive forms have properties that use observables to monitor form control values. The FormControl properties valueChanges and statusChanges contain observables that raise change events.

We can subscribe to an observable form-control property. Then, whenever the form control changes, it triggers the logic of subscribers. e.g.:

import { FormGroup } from '@angular/forms';
 
@Component({
  ...
})
export class MyComponent implements OnInit {
  nameChangeLog: string[] = [];
  heroForm: FormGroup;
 
  ngOnInit() {
    this.logNameChange();
  }
  logNameChange() {
    const nameControl = this.heroForm.get('name');
    nameControl.valueChanges.forEach(
      (value: string) => this.nameChangeLog.push(value)
    );
  }
}

HttpInterceptor

HttpInterceptor was introduced with Angular 4.3. It provides a way to intercept HTTP requests and responses to transform or handle them before passing them along.

  1. create a class to implement HttpInterceptor interface

    interface HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> }

    Most interceptors transform the outgoing request before passing it to the next interceptor in the chain, by calling next.handle(transformedReq). An interceptor may transform the response event stream as well, by applying additional RxJS operators on the stream returned by next.handle().

    More rarely, an interceptor may handle the request entirely, and compose a new event stream instead of invoking next.handle(). This is an acceptable behavior, but keep in mind that further interceptors will be skipped entirely.

    e.g.

     import { Injectable } from "@angular/core";
     import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
     import { Observable } from "rxjs";
     import { finalize } from "rxjs/operators";
     import { LoaderService } from '../services/loader.service';
     @Injectable()
     export class LoaderInterceptor implements HttpInterceptor {
         constructor(public loaderService: LoaderService) { }
         intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
             this.loaderService.show();
             return next.handle(req).pipe(
                 finalize(() => this.loaderService.hide())
             );
         }
     }
    
  2. Add HttpInterceptor to app.module.ts

     providers: [
         ...
         { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }
     ]
    

References

reactivex Subject

Angular RxJS

Angular observables

rxjs operations

Create Angular v2+ project (6) - service

$
0
0

Create singleton service

There are two ways to make a service a singleton in Angular:

  1. Declare that the service should be provided in the application root. This is the preferred way and will create the uniqueroot service instance.

    src/app/user.service.ts

     import { Injectable } from '@angular/core';
    	
     @Injectable({
       providedIn: 'root',
     })
     export class UserService {
     }
    

    In component, import it and add it to the component’s constructor

     import { UserService } from '../user.service';
    	
     @Component({
       selector: 'sample',
       templateUrl: '../sample.component.html',
       styleUrls: ['../sample.component.scss']
     })
     export class SampleComponent implements OnInit {		
         constructor(private userService: UserService) { }
         ...
     }
    

    On the other hand, to use a service as a new instance in each component (per instance per component, not singleton), we need to add service as a Provider to our component:

     import { Service } from '../service';
    	
     @Component({
       selector: 'sample',
       templateUrl: '../sample.component.html',
       styleUrls: ['../sample.component.scss'],
       providers: [Service]
     })
     export class SampleComponent implements OnInit {		
         constructor(private userService: UserService) { }
         ...
     }
    
  2. Include the service in the AppModule or in a module that is only imported by the AppModule.

    app.module.ts

     @NgModule({
       declarations: [
         AppComponent,
         NotFoundComponent,
         SearchComponent,
         ...
    	    
       ],
       imports: [
         BrowserModule,
         FormsModule,
         HttpClientModule,
         ...
       ],
       providers: [
         UserService,
         ...
       ],
       bootstrap: [AppComponent],
       entryComponents: [
         ...
       ]
     })
     export class AppModule { }
    

References

Create Angular v2+ project (7) - Angular life cycle

$
0
0

Introduction

Each component has a lifecycle managed by Angular.

Generally, Angular creates a component and its children, set up data and listens to changes, renders it and children. Then, keep checking it when its data-bound properties change, and destroys it before removing it from the DOM.

Fundamental steps are:

constructor() build the component 
-> ngOnChanges()/ngDoCheck() listen changes of ngModel 
-> ngOnInit() load huge data 
-> ngAfterContentInit()/ngAfterContentChecked() listens changes of component's child-content 
-> ngAfterViewInit()/ngAfterViewChecked() checks component's child-views

Angular application is a tree of components. Each component has an associated view. The global sequence of a component is:

  1. constructor
    1. parent constructor
    2. child constructor
  2. ngOnChanges() init input properties
  3. ngOnInit() init data - view generation started
    1. parent ngOnInit()
    2. child ngOnInit()
  4. ngDoCheck() keep checking input properties’ changes
    1. check parent
    2. check child
  5. ngAfterContentInit() update content of component
    1. parent ngOnInit()
    2. child ngOnInit()
  6. ngAfterContentChecked()
  7. ngAfterViewInit() update view of component - view generation completed
    1. parent ngAfterViewInit()
    2. child ngAfterViewInit()
  8. ngAfterViewChecked()
  9. destroy component

Lifecycle hook of a component

here is the sequence

Hooks for the component

  • constructor: This is invoked when Angular creates a component or directive by calling new on the class.
  • ngOnChanges - runs whenever change happens: Invoked every time there is a change in one of the input properties of the component.
  • ngOnInit - runs once: Invoked when given component has been created. This hook is only called once after the first ngOnChanges. It’s the place to perform complex data initializations shortly after construction. ngOnInit() is called after ngOnChanges() was called the first time. ngOnChanges() is called every time inputs are updated by change detection
  • ngDoCheck - run whenever change detected: Invoked when the change detector of the given component is invoked. It allows us to implement our own change detection algorithm for the given component.

Hooks for the components children

  • ngAfterContentInit: Invoked after Angular performs any content projection into the components view. Invoked each time after each sub-component added to the component.
  • ngAfterContentChecked: Invoked each time the content of the given component has been checked by the change detection mechanism of Angular.
  • ngAfterViewInit - runs when new sub-views have been rendered: Invoked when the component’s view has been fully initialized. ngAfterViewInit() is called after the view is initially rendered. @ViewChild() depends on it and we can access children member in this step.
  • ngAfterViewChecked: Invoked each time the view of the given component has been checked by the change detection mechanism of Angular.

Hooks for the component

  • ngOnDestroy - runs once: This method will be invoked once just before Angular destroys the component. Use this hook to unsubscribe observables and detach event handlers to avoid memory leaks.

Example

component structure

composite-panel.component.html

<app-general-composite-info [model]="model" [isEditing]="isEditing" #generalCompositeInfo>
</app-general-composite-info>

<div class="row">
    ...
</div>

composite-panel.component.ts

@Component({
    selector: 'app-composite-info-panel',
    templateUrl: './composite-info-panel.component.html',
    styleUrls: ['./info.component.scss']
})
export class CompositeInfoPanelComponent implements OnInit {
    private _model: Employee = new Employee();
    @Input() set model(val: Employee) {
        this.loadEmployee(val);
    }
    get model(): Employee {
        return this._model;
    }

    @ViewChild('generalCompositeInfo') generalCompositeInfo: GeneralCompositeInfoComponent;

    constructor() {
    }

    ngOnInit(): void {
        ...
    }
}

composite.component.html

<form #compositeForm="ngForm">
    <app-basic-info [model]="model" #basicInfo></app-basic-info>
    <app-general-info [model]="model.TblEmployeeFactRel" #generalInfo></app-general-info>
</form>

composite.component.ts

@Component({
    selector: 'app-composite-info',
    templateUrl: './composite-info.component.html',
    styleUrls: ['./composite-info.component.scss']
})
export class CompositeInfoComponent implements OnInit {
    private _model: Employee = new Employee(); // class statement
    @Input() set model(val: Employee) {
        this.loadEmployee(val);
    }
    get model(): Employee {
        // this._model.TblEmployeeFactRel = this.generalInfo.model; //todo
        return this._model;
    }

    @ViewChild('basicInfo') basicInfo: BasicInfoComponent;
    @ViewChild('generalInfo') generalInfo: GeneralInfoComponent;

    constructor() {
    }

    ngOnInit(): void {
        ...
    }
}

Sequence ##:

First loading:

  1. composite panel parent component constructor

  2. composite panel parent component instance variable

  3. composite child component constructor

  4. composite child component instance variable

  5. grandchild component constructor

  6. grandchild component instance variable

  7. class statement

  8. composite panel parent component Input() properties ngOnInit()

  9. composite child component Input() ngOnInit()

  10. basic grandchild component Input() ngOnInit()

  11. general grandchild component Input() ngOnInit()

Later change/event handling:

  1. composite component Input()

  2. basic component Input()

  3. general component Input()

FAQ

ngOnInit vs ngAfterViewInit

ngOnInit is a life cycle hook called by Angular to indicate that Angular is done creating the component.

ngAfterViewInit is also a lifecycle hook that is called after a component’s view has been fully generated.

Usually, we initialize data in ngOnInit() for display purpose; process a view via ViewChild() in ngAfterViewInit() i.e. add event handler(s). If we

@Component({
    selector: 'sample',
    templateUrl: './sample.component.html'
})
export class SampleComponent implements OnInit, AfterViewInit {

    @ViewChild(SubComponent) sub: SubComponent;

	constructor(){
		// step 1 angular start create the component and sub component
	}

	ngOnInit(){
		// step 2 angular complete creating the component the sub components
		// we init or load view data...
		// angular start rendering the view and sub views
	}
       
    ngAfterViewInit() {
		// step 3 angular complete rendering the view and sub views
		// we process view components
        this.sub.doSomething();
		// angular update or process views...
		// angular throws ExpressionChangedAfterItHasBeenCheckedError exception if finds view data changes at this moment
		// we can add setTimeout(() => {}) to defer the data changes if we need to modify view data.

    }
}

ngOnChanges() vs. value changes of ngDoCheck()

ngOnChanges() = value changes of ngDoCheck()

ngDoCheck() is called very often, on each change detection run, we you should normally avoid to use it to avoid performance problems. It will detect the changes/mouseleft on any element, content or view change behavior.

ngAfterContentChecked() vs. ngAfterViewChecked()

AfterContentInit() and AfterContentChecked() hooks on child component instance (AfterContentInit is called only during first check)

  • Angular ran change detection for the projected content (ng-content).
  • they are called after components external content has been initialized(AfterContentInit) or checked (AfterContentChecked).
  • Use it if you need to query projected elements using @ContentChildren decorator.

e.g.

child component

@Component({
  selector: 'app-child',
  template: '<input [(ngModel)]="hero">'
})
export class ChildComponent {
  hero = 'Magneta';
}

parent component:

@Component({
  selector: 'app-parent',
  template: `
    <div>-- projected content begins --</div>
      <ng-content></ng-content>
    <div>-- projected content ends --</div>
	`
})
export class ParentComponent implements AfterContentChecked, AfterContentInit {
  // Query for a CONTENT child of type `ChildComponent`
  @ContentChild(ChildComponent) contentChild: ChildComponent;

  constructor() {
    this.logIt('AfterContent constructor');
  }

  ngAfterContentInit() {
    // contentChild is set after the content has been initialized
    this.logIt('AfterContentInit');
  }

  ngAfterContentChecked() {
      this.logIt('AfterContentChecked');
  }

  private logIt(method: string) {
    let child = this.contentChild;
    let message = `${method}: ${child ? child.hero : 'no'} child content`;
    console.log(message);
  }
  // ...
}

AfterViewInit() and AfterViewChecked() hooks on child component instance (AfterViewInit is called only during first check)

  • Angular ran change detection for the view content.
  • Use it if you need to query view elements using @ViewChildren decorator.
  • they are called after the component view and its child views has been initialized(AfterViewInit) or checked (AfterViewChecked)
  • ngAfterViewChecked is called after the bindings of the view children are checked (it is related to the view only).

e.g.

child component

@Component({
  selector: 'app-child-view',
  template: '<input [(ngModel)]="hero">'
})
export class ChildViewComponent {
  hero = 'Magneta';
}

parent component

@Component({
  selector: 'parent-view',
  template: `
    <div>-- child view begins --</div>
      <app-child-view></app-child-view>
    <div>-- child view ends --</div>`
   + `
    <p *ngIf="comment" class="comment">
      
    </p>
  `
})
export class ParentComponent implements  AfterViewChecked, AfterViewInit {
  // Query for a VIEW child of type `ChildViewComponent`
  @ViewChild(ChildViewComponent) viewChild: ChildViewComponent;

  constructor() {
    this.logIt('AfterView constructor');
  }

  ngAfterViewInit() {
    // viewChild is set after the view has been initialized
    this.logIt('AfterViewInit');
  }

  ngAfterViewChecked() {
      this.logIt('AfterViewChecked');
  }

  private logIt(method: string) {
    let child = this.viewChild;
    let message = `${method}: ${child ? child.hero : 'no'} child view`;
    console.log(message);
  }
  // ...
}

“Expression has changed after it was checked” Error

This issue is only happens in Angular development mode.

Angular 2+ has verification loops and runs change detection for each component within each loop.

ngOnInit, OnChanges and ngDoCheck lifecycle loop ngAfterContentInit() + ngAfterContentChecked() lifecycle loop ngAfterViewInit lifecycle hook

In each loop, Angular verifies the beginning value(old value) and ending value(new value) of each property and will throw ExpressionChangedAfterItHasBeenCheckedError error if the values are different.

The problem is that model value is changed by the child after the parent has determined and “rendered” this.

e.g. we have two components

parent component

@Component({
    selector: 'a-comp',
    template: `
        <span></span>``
        <b-comp [text]="text"></b-comp>
    `
})
export class AComponent implements OnInit {
    name = 'I am A component';
    text = 'A message for the child component`;
	constructor() {}

    ngOnInit() {
    }
}

child component:

@Component({
    selector: 'b-comp',
    template: `
        <span></span>
    `
})
export class BComponent implements OnInit {
    @Input() text;

    constructor(private parent: AComponent) {}

    ngOnInit() {
        this.parent.text = 'updated text';
    }
}

In order to keep proper databindings, Angular uses change detection technique.

  1. Angular checks A component.
  2. Angular evaluates text to A message for the child component and passes it down to the B component.
  3. Angular evaluates name to I am A component
  4. Angular updates the DOM of A with these values and puts the evaluated values to the oldValues of view in A component

    view.oldValues[0] = ‘A message for the child component’; view.oldValues[1] = ‘A message for the child component’;

  5. Angular pass values of text and name to child B component
  6. Angular runs verification lifecyle loops to check values. If value changes, it throws the error ExpressionChangedAfterItHasBeenCheckedError

Solutions:

  1. asynchronous property update

     export class BComponent {
         name = 'I am B component';
         @Input() text;
    	
         constructor(private parent: AppComponent) {}
    	
         ngOnInit() {
             setTimeout(() => {
                 this.parent.text = 'updated text';
             });
         }
    	
         ngAfterViewInit() {
             setTimeout(() => {
                 this.parent.name = 'updated name';
             });
         }
     }
    

    here, the setTimeout() function schedules a macrotask then will be executed in the following VM turn.

    or if the property is observable

     this.parent.text$
     .pipe(
       delay(0)
     )
     .subscribe(
     () => this.parent.text = 'updated text';
     )
    
  2. forcing additional change detection cycle.

    force another change detection cycle for the parent A component between the first one and the verification phase. And the best place to do it is inside the ngAfterViewInit() lifecycle hook as it’s triggered when change detection for all child components have been performed and so they all had possibility to update parent components property:

     export class AppComponent {
         name = 'I am A component';
         text = 'A message for the child component';
    	
         constructor(private cd: ChangeDetectorRef) {
         }
    	
         ngAfterViewInit() {
             this.cd.detectChanges();
         }
     }
    

Best practice

Here is a model of typical angular component.

It has below behavior:

  • the model can be used for adding/updating/viewing purpose. Because it supports add, it has a built-in not-null model.
  • the component contains a list of items and a selected item (i.e. model).
  • the component accepts multiple listParams via Input() or Routing and the change of each param will reload the list
  • the model property is Input() so that the component can be used for view/edit purpose
  • reloading the list will re-select/set model
  • set the model property will re-select/set the ‘model’

Therefore, we need listen to the changes of the list and model property. Because any changes of them will trigger to reselect/set the model, we use combineLatest() to listen to them. Also, we need to keep the last value of list and model property, we have to set two BehaviorSubject objects i.e. item$, list$ to always keep last value and listen.

param1 change -> load list -> reselect item from the list -> set model

param2 change -> load list -> reselect item from the list -> set model

model change -> reselect item from the list -> set model

Models

export class Component{
	private item$ = BehaviorSubject(null);
	private list$ = BehaviorSubject([]);
	@Input() model(model){ if (model != null) this.item$.next(model.item) }
	@Input() listParam1(val){ if (val != null) this.loadList(val) }
	@Input() listParam2(val){ if (val != null) this.loadList(val) }
	ngAfterViewInit() {
		combineLatest(this.list$, this.item$)
		  .subscribe(([list, item]) => {
		    this.selectItem(item);
		  })
	}
	loadlist() {list$.next(list);}
	selectItem(item){
		if (list contains item) model = item;
		else model = null;
	}
}

Principle

  1. Each entrance to external should cause the change of $item or $list.

  2. If the component included in parent component, parent shouldn’t call the same entrance triggered by multiple different events. per entrance per parent.

    If there are multiple events in parent invoke the same entrance, each event will trigger the change of $item or $list and will reselect the item from the list followed by setting the model. That means each event of parent might change the model of component. It is very likely some of events are caused by other children components in parent. In this case, we might have to consider the event sequence in parent in order to ensure some actions go first and some go later. As we know, it is very hard to arrange the execution or subscribe sequence of events. It is very hard to maintain.

  3. if a parent pre-rendering method() depends on a child component, we cannot use *ngIf to the child component. Angular might throw ExpressionChangedAfterItHasBeenCheckedError error

    e.g.

    parent.component.html

    <div *ngIf="!isLoading">
    
         <!-- child component -->
         <app-fact-info [model]="model" [isEditing]="isEditing" #factInfo>
         </app-fact-info>
    	
         <button type="submit" class="btn btn-primary btn-lg cls-form-control-button cls-form-control-button-enabled"
             [disabled]="!isValid()"
             [ngClass]="{'cls-form-control-button-enabled': isValid(), 'cls-form-control-button-disabled': !isValid()}"
             (click)="update()">Update</button>
     </div>
    

    parent.component.ts

     @ViewChild('factInfo') factInfo: FactInfoComponent;
     public isValid(): boolean {
         return this.factInfo != null && this.factInfo.isValid();
     }
    

    It might throws ExpressionChangedAfterItHasBeenCheckedError error. The reason is, Angular keep evaluates the isValid() method after ngOnInit() in ngOnChanges(). When parent is rendering the page in ngAfterViewInit() step and call the method, the return value of isValid() is null because the child component not exists yet. Afterwards, the child was created and appears, the return value of isValid() might different from the previous one. Both actions happen in ngAfterViewInit() step and triggers the error.

    fixed v1. we always create but hide the child component.

    parent.component.html

    <div [hidden]="isLoading">    	
    
         <!-- child component -->
         <app-fact-info [model]="model" [isEditing]="isEditing" #factInfo>
         </app-fact-info>
    	
         <button type="submit" class="btn btn-primary btn-lg cls-form-control-button cls-form-control-button-enabled"
             [disabled]="!isValid()"
             [ngClass]="{'cls-form-control-button-enabled': isValid(), 'cls-form-control-button-disabled': !isValid()}"
             (click)="update()">Update</button>
     </div>
    

Complete code:

atomic.component.html, please note that we use ItemId as the model value

<select [items]="list" bindLabel="Name" bindValue="ItemId" [(ngModel)]="model.ItemId" required
    name="item" #item="ngModel" (change)="selectItem($event)" placeholder="Select"
    type="text">
</select>

atomic.component.ts

@Component({
  selector: 'app-atomic',
  templateUrl: './atomic.component.html',
  styleUrls: ['./atomic.component.scss'],
  providers: []
})
export class AtomicComponent implements OnInit, AfterViewInit, OnDestroy{
	private _model = new Model();
	private item$ : BehaviorSubject<number> = new BehaviorSubject(null); // for listening event purpose, can be item or itemId
	@Input() set model(val : any){
		if (val != null){
			//this._model = val; // optional because we already pass `ItemId` to parent/external in `selectItem()` method, pls keep in mind the parent/external always use the `model` property of this component to grab data, don't use the data passed from parent/external
			this.item$.next(val);
		}
	}
	get model() {
		return this._model;
	}

	@Output() modelChanged = new EventEmitter<Model>();
	
	private list = [];
	private list$ = new BehaviorSubject([]);
	
	private _listParam1 : any;
	@Input() set listParam1(val : any) // set only, uni-direction
	{ 
		if (val != null) { 
			this._listParam1 = val;
			loadListByParam1(val); 
		} 
	}
	
	constructor( ) {		
	}

	ngOnInit() {
		// load huge data if required
	}
	
	ngAfterViewInit(): void {
		// 
		combineLatest(this.list$, this.item$)
		  .subscribe(([list, item]) => {
		    this.selectItem(item);
		  })
	}
	
	list = [];
	list$ = new BehaviorSubject([]);
	loadListByParam1(param1 : any)
	{
		// load list
		if (param1 == null) {
		  this.list = [];
		  this.list$.next(this.list);
		}
		else {
		  this.list = [];
		  this.service.getListByParam1(param1)
		    .pipe(map(elements => elements.map(element => new Item(element))))
		    .subscribe(elements => {
		        this.list = elements;
		      	this.list$.next(this.list);
		    })
		}
	}
	
	public selectItem(item: any): void {
		if (item != null && item.ItemId != null) { // use `ItemId` to determine if we need to set value of model because ItemId is `ngModel`. use `Item` if we use Item as `ngModel`
			// if list contains item, set _model = item, else set _model = null
			if (this.list.find(i => i.Id == item.Id)) {
				this._model.ItemId = item.Id;
				//this._model = item; // if we use `ItemId` as `ngModel`. This line might trigger infinite change event because it cause model changed!!!, therefore, the `set model()` was re-called and then call `selectItem()` over and over again. we can also use `this._model.Id = new Item(item)` to replace
			}
			else {
				this._model.ItemId = null;
				// or this._model = new Model();
				//this._model = null; // don't use. if we use `ItemId` as `ngModel`. This line might always set model as null if we pass model as a reference from the parent 
			}
		}
		// emit selected item
		this.modelChanged.emit(this._model);
		// this method is an entrance and must cause change of $item
		if (this.item$.getValue() != item) this.item$.next(item); 
	}
	ngOnDestroy(): void {
		if (this.item$ != null) this.item$.unsubscribe();
		if (this.list$ != null) this.list$.unsubscribe();
	}
}

Please note if we pass item value to the component when the component is initializing. we should wrapp everthing into setTimeout() in selectItem(item) {} like

selectItem(item : any) : void{
  setTimeout(() => { // select an item until the view is fully rendered
    // if list contains item, set _model = item, else set _model = null
    ...
  });
}

the reason is: during the child component is initializing and if the parent automatically pass value simultaneously to it, the child component will then call selectItem() immediately during the step of ngAfterViewInit. But the selectItem() method will change the value of _model. As we know, in ngAfterViewInit step and in dev environment, if Angular finds the model value was changed, it will throw ExpressionChangedAfterItHasBeenCheckedError exception. Therefore, we wrap everything into setTimeout() and holds the change until Angular fully loaded and checked the view.

pass model value from external/parent

this.atomicComponent.model = latestData;

逐层传递,每层有自己独立的model,外面传过来的值,只用来设置自己的model,这个model也将被传到外面。尽管外面传值时候可能有nest,每个component设置自己的model不考虑nest,只设好自己的值

References

https://angular.io/guide/lifecycle-hooks

https://medium.com/@zizzamia/the-secret-life-cycle-of-components-ee180a9a42bb

https://codecraft.tv/courses/angular/components/lifecycle-hooks/

https://www.c-sharpcorner.com/article/life-cycle-of-angular-components/

https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

https://blog.angular-university.io/angular-debugging/

Blazor tutorial 1 - Introduction

$
0
0

Introduction

Blazor is a Single Page Application framework. It is open source and powered by .NET Foundation. Blazor allows us to develop front-end web applications using C#, HTML, razor templates. We use blazor to build components and compose those components into pages which we can use inside our applications. Blazor can run on a server or directly on a client.

Razor is an ASP.NET programming syntax used to create dynamic web pages using C# or VB.NET languages. Razor was developed in 2010. Razon syntax is essentially template markup syntax. We use razor to create UI components of Blazor web apps.

Blazor doen’t require any plugin installed in the client’s browser. It runs in the browser by utilizing WebAssembly. Also, It can run in the server side as well.

Blazor was released as a part of .NET Core 3

There are 2 hosting models: server-side blazor and Web Assembly.

Blazor Server side

Blazor server-side pre-renders HTML content before it is sent to the client’s browser. Also, Blazor server-side apps will work on older browsers (such as Internet Explorer 11) as there is no requirement for Web Assembly, only HTML and JavaScript. As the code executes on the server, it is also possible to debug our .NET code in Visual Studio.

However, Blazor server-side sets up an in-memory session for the current client and uses SignalR to communicate between the .NET running on the server and the client’s browser. All memory and CPU usage comes at a cost to the server, for all users. It also means that the client is tied to the server that first served it, so doesn’t work with load-balancing.

Once the initial page has been rendered and sent to the browser, blazor.server.js file hooks into user interaction events in the browser so it can mediate between the user and the server. For example, if a rendered element has an @onclick event registered, blazor.server.js will hook into its JavaScript onclick event and then use its SignalR connection to send that event to the server and execute the relevant .NET code.

WebAssembly(WASM)

WebAssembly is an instruction set that can interprete higher language like C# into machine binary code then execute. It is similar to CIL(common intermediate language).

WebAssembly runs on the client, inside the browser. Blazor can work offline, when the network connection to the server lost, the client app can continue to function. Also, it can run as Progressive Web App(PWA), which means the client can choose to install out app onto their device and run whatever they wish without any network access at all.

However, blazor.webassembly file has to bootstrap the client application. It downloads all required .NET DLL assemblies and it makes the start-up time slower than server-side(DLLs are cached by the browser, making subsequent start-up time faster)

Demo

  1. Create a new project >

  2. 2 types of projects:

    server model: 1 project will be created.

    client model:

    if check “ASP.NET Core Hosted”, 3 projects will be created: Client, Server, Shared, the reason is backend project was created.

    if not check, only 1 project created.

  3. Create razor component

    right click > razor component > xxx.razor file

    at compile time, each razor component is built into a .NET class. The class inlcudes common UI elements like state, rendering logic, lifecycle methods, and event handlers.

    Razor component file can mix HTML and C#

    use @() to add C# statement inline with HTML.

    use @code directive to add multiple statements, enclosed by parenthese

    use @functions section to the template for methods and properties

    use @page directive to identify a compnent as a page, also, can be used to specify a route.

Reference

Blazor Tutorial - Build your first Blazor app - server-side blazor

Build a web app with Blazor

ASP.NET Core Blazor

Project structure for Blazor apps

Blazor Interview Questions

Blazor tutorial 2 - Telerik UI

$
0
0

Installation

method 1: use msi installation

it enables vs to create Telerik Blazor application

method 2: install Telerik Blazor extension, then create Telerik Blazor application

After installation, you got

method 2: use nuget to install libs to existing project; then config Startup.cs

  1. add ui components: download zip package at https://www.telerik.com/account/downloads

    unzip > vs > manage nuget package source > point to the unzip folder; or simply set as https://nuget.telerik.com/nuget

    select Telerik.UI.for.Blazor

  2. add css

    if server model > Pages > _Host.cshtml > add

    <link rel="stylesheet" href="_content/Telerik.UI.for.Blazor.Trial/css/kendo-theme-bootstrap/all.css" />
    
     <script src="_content/Telerik.UI.for.Blazor.Trial/js/telerik-blazor.js" defer></script>
    

    if client(webassembly) model > modify wwwroot > index.razor

  3. add reference

    if server model > _imports.razor

     @using Telerik.Blazor
     @using Telerik.Blazor.Components
    
  4. if server model > modify Startup.cs or Program.cs

     builder.Services.AddRazorPages();
     builder.Services.AddServerSideBlazor();
    
     builder.Services.AddTelerikBlazor();
    
     builder.Services.AddSingleton<WeatherForecastService>();
    
  5. modify MainLayout.razor > wrap all content into <TelerikRootComponent>

Reference

Telerik UI for Blazor video

blazor-ui demos

blazor-ui docs

Blazor tutorial 2 - Razor

$
0
0

Syntax

Razor is one of the view engines supported since ASP.NET MVC. Razor allows us to write a mix of HTML and C#/Basic.

Declare Variables

Declare a variable in a code block enclosed in brackets and then use those variables inside HTML with @ symbol.

e.g.

@{ 
    string str = "";

    if(1 > 0)
    {
        str = "Hello World!";
    }
}	
<p>@str</p>

Inline expressions

@variableName

e.g.

<h1>@DateTime.Now.ToShortDateString()</h1>

Multi statement code block

@{ ... }

e.g.

@{
    var date = DateTime.Now.ToShortDateString();
    var message = "Hello World";
}

<h2>Today's date is: @date </h2>
<h3>@message</h3>

display text

@:text

or

<text></text>

e.g.

@{
    var date = DateTime.Now.ToShortDateString();
    string message = "Hello World!";
    @:Today's date is: @date <br />
    @message                               
}

if-else condition

e.g.

@if(DateTime.IsLeapYear(DateTime.Now.Year) )
{
    @DateTime.Now.Year @:is a leap year.
}
else { 
    @DateTime.Now.Year @:is not a leap year.
}

for loop

e.g.

@for (int i = 0; i < 5; i++) { 
    @i.ToString() <br />
}

Model

Use @model to use model object anywhere in the view.

e.g.

@model Student

<h2>Student Detail:</h2>
<ul>s
    <li>Student Id: @Model.StudentId</li>
    <li>Student Name: @Model.StudentName</li>
    <li>Age: @Model.Age</li>
</ul>

Razor pages

@page directive

@page is used to declare the page as router

Reference

Razor Syntax

Compare Razor Pages to ASP.NET MVC

Introduction to ASP.NET Web Programming Using the Razor Syntax (C#)

Multiple Models in Single View in MVC

Blazor tutorial 3 - Razor Page


Blazor tutorial 4 - Telerik UI

$
0
0

Installation

method 1: use msi installation

it enables vs to create Telerik Blazor application

method 2: install Telerik Blazor extension, then create Telerik Blazor application

After installation, you got

method 2: use nuget to install libs to existing project; then config Startup.cs

  1. add ui components: download zip package at https://www.telerik.com/account/downloads

    unzip > vs > manage nuget package source > point to the unzip folder; or simply set as https://nuget.telerik.com/nuget

    select Telerik.UI.for.Blazor

  2. add css

    if server model > Pages > _Host.cshtml > add

    <link rel="stylesheet" href="_content/Telerik.UI.for.Blazor.Trial/css/kendo-theme-bootstrap/all.css" />
    
     <script src="_content/Telerik.UI.for.Blazor.Trial/js/telerik-blazor.js" defer></script>
    

    if client(webassembly) model > modify wwwroot > index.razor

  3. add reference

    if server model > _imports.razor

     @using Telerik.Blazor
     @using Telerik.Blazor.Components
    
  4. if server model > modify Startup.cs or Program.cs

     builder.Services.AddRazorPages();
     builder.Services.AddServerSideBlazor();
    
     builder.Services.AddTelerikBlazor();
    
     builder.Services.AddSingleton<WeatherForecastService>();
    
  5. modify MainLayout.razor > wrap all content into <TelerikRootComponent>

Reference

Telerik UI for Blazor video

blazor-ui demos

blazor-ui docs

Blazor tutorial 5 - Identity

$
0
0

Introduction

ASP.NET Core Identity is a membership system. It allows us to create, read, update and delete users. It supports user registration, authentication, authorization, password recovery, two-factor authentication. It also supports external login providers like Microsoft, Facebook, Google and etc.

Add identity

right click blazor web project > add new scafforded item > identity

Then identity scaffold was generated under Areas/Identity folder

Add identity migration support:

Package Manager Console > enter command > generate tables

Add-Migration IdentitySupport

Update-Database

How to use Identity

Blazor server app provides AuthenticationStateProvider service that helps us to find-out user’s authentication state data from HttpContext.User.

This service is used by CascadingAuthenticationState and AuthorizeView component to get the authentication states.

In the code of building component UI, we should use the 2 encapsulated components instead of directly use AuthenticationStateProvider because component isn’t notified automatically if the underlying authentication state data changes.

AuthenticationStateProvider service

The AuthenticationStateProvider service is a built-in service in the Blazor server app that helps you to obtain the authentication state data from HttpContext.User.

@page "/"  
@using Microsoft.AspNetCore.Components.Authorization  
@inject AuthenticationStateProvider AuthenticationStateProvider  

<p>
    @userAuthenticated  
</p>
  
@code{  
    string userAuthenticated;  
    protected override async Task OnInitializedAsync()  
    {  
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();  
        var user = authState.User;  
        if (user.Identity.IsAuthenticated)  
        {  
            userAuthenticated = $"{ user.Identity.Name} is authenticated.";  
        }  
        else  
        {  
            userAuthenticated = "The user is NOT authenticated.";  
        }  
    }  
}  

<AuthorizeView> component

In Blazor we use AuthorizeView component to show or hide UI elements depending on whether the user is authorized to see it.

<AuthorizeView>
    <Authorized>
        This content is displayed only if the user is Authorized
    </Authorized>
    <NotAuthorized>
        This content is displayed if the user is Not Authorized
    </NotAuthorized>
</AuthorizeView>

This component supports both role-based and policy-based authorization.

For role-based authorization, use the Roles parameter.

<AuthorizeView Roles="administrator, manager">
    <p>Displayed if the logged in user is in administrator or manager role</p>
</AuthorizeView>

For policy-based authorization, use the Policy parameter:.

<AuthorizeView Policy="admin-policy">
    <p>Displayed if the logged in user staisfies admin-policy</p>
</AuthorizeView>

<CascadingAuthenticationState> component

it is used to wrap up this entire component. It can share the Authentication state within the complete application.

e.g.

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

[Authorize] attribute and <AuthorizeRouteView> component

  1. We use [Authorize] attribute to protect routable components (i.e components with @page directive). We reach these components via the router and authorization is performed while being routed to these components.

    AuthorizeView component is used to protect parts of a page of child components [Authorize] attribute is used for the complete routable components, not the part

     e.g. AccountPage.razor
    	
     @page "/accountpage"
     @page "/accountpage/{actionparameter}/{idparameter:int}"
     @attribute [Authorize]
     @inherits AccountComponentBase
    	
     ...
     @code {
         ...
     }
    
  2. [Authorize] attribute also supports role-based and policy-based authorization. For role-based authorization, use the Roles parameter:

     @page "/"
     @attribute [Authorize(Roles = "administrator, manager")]
    	
     <p>Only users in administrator or manager role are allowed access</p>
    

    For policy-based authorization, use the Policy parameter:

     @page "/"
     @attribute [Authorize(Policy = "admin-policy")]
    	
     <p>Only users who satisfy admin-policy are allowed access</p>
    
  3. When use [Authorize] attribute, we must also use <AuthorizeRouteView> component to replace component

    <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

    to

    <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

Customize unauthorized content

If access is not allowed, “Not authorized” is displayed by default. However, we can customize this unauthorized content.

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <p>Sorry, you're not authorized to reach this page.</p>
                    <p>You may need to log in as a different user.</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

Get Authentication and authorization state data

Use Cascading AuthenticationState parameter to get the state data.

e.g.

public class EditEmployeeBase : ComponentBase
{
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    [Inject]
    public NavigationManager NavigationManager { get; set; }

    protected async override Task OnInitializedAsync()
    {
        var authenticationState = await authenticationStateTask;

        if (!authenticationState.User.Identity.IsAuthenticated)
        {
            string returnUrl = WebUtility.UrlEncode($"/editEmployee/{Id}");
            NavigationManager.NavigateTo($"/identity/account/login?returnUrl={returnUrl}");
        }

        // rest of the code
    }
}

Check if authenticated user is in a specific role

if (authenticationState.User.IsInRole("Administrator"))
{
    // Execute Admin logic
}

Check if authenticated user satisfies a specific policy

public class EditEmployeeBase : ComponentBase
{
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    [Inject]
    private IAuthorizationService AuthorizationService { get; set; }

    protected async override Task OnInitializedAsync()
    {
        var user = (await authenticationStateTask).User;

        if ((await AuthorizationService.AuthorizeAsync(user, "admin-policy"))
        .Succeeded)
        {
            // Execute code specific to admin-policy
        }
    }
}

Reference

ASP.NET core identity setup in blazor application

Viewing all 97 articles
Browse latest View live