A.K.A. — The Mountains of Angular Front-end Development

preface

I mainly share my technical thinking since I have been doing front-end business for more than 3 years, and the theme content is probably around the things we are familiar with, such as: Componential development, state management, Dialog, Angular services, etc. The framework is mainly Angular, but there will also be some comparisons with React code organization.

The technical thinking part belongs to some personal understanding, everyone should have their own understanding and thinking about technology, expect to reach consensus with everyone, everyone can communicate any ideas or views in the comments. Disclaimer – Some personal understandings are involved in the sharing, such as the contrast with React philosophy and thinking, which only represents a personal perspective on technology.

Theme directory

1. Technical system

2. Componentization

Angular services

4. State management

5. Pop-up layer

Technology system

Because our company is a ToB business, which focuses on the front end and needs to deal with complex interactions, complex state management and multi-team development management, the overall technical system is still complex, but it is also interesting and challenging for developers.

It’s not easy to say, it’s not easy to say.

CDK Component Development Kit is an official Angular Component Development Kit, the bible of front-end Component Development.

Personal advice:

  1. First, you must be solid in the basics such as JavaScript and the Angular framework, and then you must be proficient in one or two of the latter.
  2. In addition, in the process of business development, we should dare to go into technical details, get familiar with the technical points and structural design on the technical chain based on business practice, and put forward improvement suggestions for the imperfect parts.
  3. Cherish such business opportunities, let their own technology to the depth of the direction of extension.

componentization

component

Component is the basic unit of front-end interaction logic encapsulation, which can be divided into component library, business component library and business component according to the degree of reuse.

Our development is probably the same, writing business components and basic components, and extracting the common parts of business components into the basic components as the basic component library or basic business component library, so as to maximize code reuse and unified style interaction.

One of the difficulties in our current development is that experience is needed to determine what is reusable, component libraries business component libraries have been packaged, whether the underlying component libraries meet business requirements, and so on.

Nature of the component

Components, as basic logical units, are essentially DOM structure design and interaction Control (View and Control in MVC). From this perspective, the three frameworks are almost the same, and the components we are talking about are usually framework components. Although browsers support native Web Components, there are still few Component libraries encapsulated based on Web Components. On the one hand, Web Components lag behind the Component features of Angular, React and other frameworks. On the other hand, the Angular/React/Vue ecosystem is mature. The only disadvantage is that the component libraries are not universal, but the company can choose a unified stack, so this problem is not so prominent.

Angular components/directives

Angular components come in two forms: components and directives. In a nutshell, a component provides a wrapper around a DOM structure that performs logical processing. Directives usually provide only logical wrapping and need to be attached to a host HTML element.

Directives. Angular directives are not allowed to provide templates. They need to be bound to a host that performs additional logical processing on host elements.

If you’ve ever written a new component library based on Angular, you should know that there are three ways to implement a basic component: global services, components, and directives. The first consideration in component design should be component partitioning, directives, and services.

For example, the Tooltip module in the Angular component library provides a global service and a directive that is easier to use because it interacts with existing HTML elements. The tooltip function is enabled by binding to the existing HTML element.

The tooltip module code is too complex, so the following is a simple instruction thyStopPropagation to illustrate the features of the instruction:

Its function is to prevent bubbling, and the shape of the component is basically the same, you can get the host element (DOM node) in the constructor, and then listen to the event, prevent the default bubbling behavior of the event, and can specify the type of event to prevent bubbling through the parameter.

As you can see, instructions are purely logical reuse, which is a good way to encapsulate code.

React has only components. If you want to implement the thyStopPropagation directive in Angular, how to encapsulate and use it?

Component governance

In most cases the component is managed by the framework, our developers are usually write components in accordance with relevant provisions of the framework of the way, and then use the component is actually to framework to perform component logic and framework for management of most of the components, such as components to create, destroy, as well as other statements periodic function, the management of the component tree component state update, The component corresponding DOM maintenance, we are now writing front-end is more advanced code, because the framework does so much for us.

In cases where we need to create our own components (Angular), such as in the pop-up scenario, we need to manage the components ourselves by creating component instances manually, but this is not completely out of the framework, and to some extent it is managed by the framework. For example, the viewContainerRef object that developers use to create components dynamically is the Angular component container. Component instances are associated with the framework’s component tree through the viewContainerRef object.

Base component tree:

Create component D dynamically:

The diagram above illustrates the relationship between dynamically created components and the Angular component tree. Note that this is only the relationship between components and the tree. The DOM structure of components is arbitrary.

This part of componentization concludes with an understanding of componentization and how components are organized within the Angular framework.

Presents the service

Angular services are a feature that sets other frameworks apart. I’m not talking about services per se, but a combination of dependency injection, Rxjs, and so on that bridge components, state-sharing between applications, messaging, and an important way to organize code.

Application scenarios

Angular State Management: Angular state management technologies are implemented on a service basis.

② Singleton service: ThyDialog, ThyPopover, etc., is a combination of functions and data states. For example, if an instance of the ThyDialog service can open a pop-up, then the instance state of the pop-up is maintained in the service. With a maximum of three popovers open, you need to maintain a list of instance states in the service.

③ Service configuration within components: Such usually corresponding to our business, such as a Wiki products need to implement a event or state transfer across components, layer upon layer pass parameters or layers of throwing events are too much trouble, so a plan out naturally, through a service storage Shared state, or in the service based on Rxjs do a notice stream, Implement communication across components.

Among them, [③ intra-component configuration service] is a double-edged sword. Using services to solve cross-component communication problems is a sharp tool, but there are also problems of abuse or non-standard use:

  1. It needs to be clear whether the service is a singleton or an intra-component configuration provider

  2. Configuration providers within components need to consider the hierarchy of configuration

  3. When configuring providers within components, you need to consider whether service instances can be obtained in various usage scenarios of corresponding components

  4. Pay attention to state maintenance in the service (maintaining data state rigorously)

  5. Be careful to unsubscribe for data streams

  6. Note the di lead-ins in the service, such as the service on which the service depends.

Speculative point

Services are considered a good way to organize business logic in Angular, but features like services are despised in functional programming because they do not conform to the idea of pure functions. Local state can be stored in services, so methods in services are no longer pure, and this can be the beginning of a bad code taste.

Another point is that the communication between components adds a layer of services, which makes the application data flow and communication direction between components less intuitive. Therefore, I also think that services are a good thing, but you need to think clearly before using them.

React Scenario

React does not have the concept of a service. The core of React is a component. Other concepts are designed around components.

This is also the basis of React. I’m not very good at it, but here’s a show off:

Parent-child communication – parameter transfer, event callback

Context – a context used to share state between hierarchical components. React uses this a lot to implement component property penetration.

③ State management – There is no doubt that state management is a common cross-component communication solution in React

④ Custom event – use custom event to realize the message communication problem under the non-nested component structure, define an event source, a component binding event handler function, a component triggering event according to the behavior, the idea is about this.

React advocates pure functions, so services are not allowed in its design. Services can store local state, which violates the concept of pure functions. This is code or mode guidance, but it doesn’t mean that React developers can’t use services-like things (dependency injection and services have been introduced in React, but they’re not destined to be adopted by the mainstream).

Service Limitations

When developing applications using the Angular framework, do you encounter scenarios where services are unavailable or difficult to use?

(1) Component injection scenario – a common problem encountered by beginners is that a service can be configured within a component. Because of the dependency injection context, this service can only be injected into the component and its children. Siblings or parent components (and global services) cannot be obtained through constructor injection. The construction of this implicit relationship has its flexibility, but also increases code complexity, which I think is one of the limitations of service usage.

(2) tools function scene – everyone knows service only by for instance constructor injection way, the tool function scenario use of the service is in trouble, this limitation often encountered in the editor, Wiki editors of some configuration parameters (read-only, the default font size) context service access or components, Sometimes it needs to be used in a component, sometimes it needs to be used in a utility function, and when it needs to be used in a utility function, it is more difficult to obtain it in a utility function.

Here’s how to get the Angular service from a utility function. Here’s how to get the Angular service from a Wiki editor:

The core of this tool is that the injector has been assigned a dependency injection object when the editor is initialized. The injector’s dependency injection context is the context of the editor component. With this injector object, you can do whatever you want in tool functions, just like opening and leaving.

The above example used injector to get the Wiki editor context service. In addition to this, injector has also used the Injector to get some global Angular services. For example, I wanted to open a shortcut menu in tool function handling. So I could have gotten the global ThyPopover service through Injector and executed its open function to open the menu.

State sharing scheme based on Weakmap

The injector object has been used to bridge the gap between tool functions and Angular services. The injector object has been used to bridge the gap between tool functions and Angular services. The injector object has been used to bridge the gap between tool functions and Angular services. It is called: [State sharing scheme based on Weakmap].

Sharing data in an application could theoretically be done by storing it in a global variable, which can then be used in utility functions and Angular components (back to the original way). However, the problem with this is that data can interact with each other in multiple editors. WeakMap can solve this problem. You can specify the key of WeakMap as an editor application object, so that the data of each editor will play the role of isolation. WeakMap has many applications in Wiki editor:

It can be seen from the definition of Weakmap in the above figure that Weakmap is actually a singleton. The key type is designated as Editor, which can isolate multiple Editor data. This data design scheme is very simple and practical.

This concludes the section on Angular services. Angular services act as a bridge to the Angular framework, greatly enhancing the organization and code design capabilities of the Angular framework.

State management

State management is also a personal pain for me, because I did not understand the function of state management until I came to Worktile. Therefore, here is the core of my understanding of state management, and of course, I will introduce our application scenarios based on the state management form in PingCode: Global data, business data, micro front-end data, etc.

It’s worth noting that no state management is perfectly reasonable, and strictly speaking it doesn’t even count as a technique, because state management is a way of organizing data, and it’s not necessary. The essence of state management is to standardize the use of data, including data initialization, modification, page update, which belongs to the application technical scheme.

State management thought

The core of state management is the idea of management. It provides a unified path for data modification and standardizes data modification. Data modification itself can be recorded and processed uniformly.

State management is not necessary for common applications, but with the development of React and the popularity of open source libraries related to state management, state management has gradually become a trend. Of course, the idea of state management has its own progress, so now people usually process data according to the idea of state management.

The basic theory

The simplest state management consists of two concepts: immutable data and Action.

Immutable – Data cannot be modified. If modified, only a new object can be created, representing the library: Immutable.

(2) Action – Describes data modification, which can also be understood as adding a type identifier for each data modification.

Redux, the granddaddy of state management, has some more complex concepts, which I’ll cover more here, but just focus on those two.

Redux Dev Tool

This is a help developers to monitor state management data browser plug-in, written the React have used commonly, this is a very good debugging tools, it can record every time of the application of data modification, also can compare the data before and after the modification of data changes, can effectively help the developers to troubleshoot problems, combing the complex data revision process, I think that’s one of the reasons for Redux’s success is that the tools are so good that developers enjoy the real convenience of learning so many concepts.

Thought extending

As an excellent data management idea, state management is also useful in our rich text editor, and in the rich text editor, the design and control of data modification is just needed, which is the basis of collaborative editing and Redos/Undos.

The following is an illustration of the data of a picture node:

[
  {
    type: 'image',
    url: 'https://altas.pingcode.com/xxx',
    align: 'center'
  }
]
Copy the code

Now use the interface to adjust the alignment to the left, the new data is as follows:

[
  {
    type: 'image',
    url: 'https://altas.pingcode.com/xxx',
    align: 'left'
  }
]
Copy the code

So let’s think about how does this happen? And how to deal with co-editing and Undo scenarios?

The easy way:

In the case of Undo, the state of the previous one is directly restored. In the case of collaborative editing, the full data needs to be sent to the collaborative party

Methods of control:

  • Unified data modification methods
  • Design a data operation type for each type of data modification

This corresponds to the Transforms module in Slate, which Transforms changes to data structures into a base type of Action (similar to the Action concept). In this way, similar to state management, I can log changes to data. Accurately know the information about this data modification, including the type of data modification and the data before and after the change (corresponding to the Operation object in Slate).

Implement cooperative and Undo operations based on this Operation object:

  • The Undo Operation only needs to be reversed (insert_text corresponds to remove_text, which can be reversed by performing an Operation).
  • In collaboration, only the Operation needs to be sent to the partner, and the partner can realize data synchronization based on the information of the Operation.

This is my analogy, and it doesn’t matter if you don’t quite understand it.

frameworks

I think the state management technique can be roughly divided into three parts: specification modification, change notification, and framework binding.

Frame binding is a stage I extracted. My understanding is that state management itself and it and frame binding should be two layers. Theoretically, the specification modification and modification notification in state management can be framework-independent, and only the frame binding layer is related to frame.

Because the state data ultimately needs to be displayed on the interface, frame binding is the combination of the framework’s data update mechanism, informing the framework of the data changes, and driving the interface refresh within the framework’s rendering mechanism.

The core mechanism for Angular interface updates is change detection, which is convenient if the component is not in OnPush mode and the interface is automatically updated whenever the data in the Store is updated. But if the component is in OnPush mode, then the component needs to subscribe to updates in the Store, manually executing markForCheck or detectChanges to drive the interface refresh.

OnPush is a way for Angular to optimize the performance of components. Component OnPush means that change detection for components and their children is performed only when references to component parameters are updated. Change detection is skipped if parameters are unchanged. React useMemo.

In React, setState is used to drive component updates. Therefore, React may encapsulate the logic from state management data to component state update based on higher-order components, thus driving interface refresh during data update.

Basic form of status management

Here I want to compare the basic usage and presentation of @Tethys/Store, Redux and Mobx.

@tethys/ Store Our state management library, which is a very flexible way to store and use data as Angular services, using global injection as global state or local injection as component state. The modification and storage of data are organized.

Redux completely follows pure functions and is the most react-like state, but the code is more discrete.

Redux is a library that combines Redux, Thunk, and Sagas. Asynchronous requests are not allowed in Redux, but HTTP requests for data must be asynchronous. Therefore, a layer is proposed to handle asynchronous requests, and then call synchronous data modification after asynchronous requests are completed. Because redux is too discrete, actions, denies, etc., are defined in independent files. Therefore, DVA encapsulates this case so that state definition, synchronous modification, and asynchronous modification are encapsulated in one Model. Multiple Models can be defined for different business modules.

Mobx may be a one-step solution, storing business data as a Store and providing data modification methods on the Store.

Our Store (@tethys/ Store) is morphologically similar to Mobx’s, regardless of pure function, convenient is done, done.

In a sense, Redux is playing a big game. It comes up with a whole set of ideas, a whole set of incredibly complicated and perverted processes to constrain how your data works, and then it provides you with a great debugging tool. Then React caught fire. Then there are easier ways to manage state, such as Dva and Mobx.

I’m not really sensitive to these things, so I’m fine with Redux, and I’m fine with @tethys/ Store. It’s just a way to structure code, as long as the team is on the same page.

PingCode Front-end status data

This is an analysis of a typical scenario for state management in our PingCode product: global data, application data management (encapsulated in the business component library for logical reuse), complex state management solutions.

① Global data

AppRootContext – Personal information, global configuration information management, singleton mode

GlobalUsersStore – Manages global user information in singleton mode

The GlobalUsersStore data acquisition process is as follows:

  • The Portal application obtains initialized data through the API
  • Store the initialization data in the Window object
  • The child application reads the data or objects in the window during constructor injection

Portal is the base application in the PingCode product, corresponding to the base application in the micro-front-end architecture we use, which is based on Angular’s own ngX-Planet for those interested.

This code is in the business component library: Styx /module.ts

UseFactory is used to configure the service provider to ensure that the service is singleton for the micro-front-end pattern sub-application.

UseFactory is defined as follows:

As you can see, the technique behind it is very simple, which is to attach the fetched data to the Window object.

This is through a business component library into these global data, because in order to achieve more applications sharing, the stored value relative to the traditional way, the data is mounted on the window object, feeling technology to return by the way is not recommended, are probably better we use this way is temperate, not abuse.

Application data Management

PilotStore – this is a typical data management scenario in our application. First of all, it has certain commonality in various sub-applications, with consistent API (query, search and collection) and similar interface. Therefore, there is certain encapsulation of Pilot in the business component library, including data encapsulation and component encapsulation.

Pilot is our internal abstraction of a Pilot concept, code each application body, for example: project management product Pilot represents the project, Wiki product Pilot represents the knowledge base.

There is a consensus that business component encapsulation is harder because developers need to balance those that can be written dead and those that can’t be written dead and that need to be as simple as possible to use.

Specific to the Pilot data management scenario, each sub-application interface interaction and data structure are basically the same, except the API structure, which is as follows:

{{sub_domain https://}. Pingcode.com/api/wiki/pilot left https:// sub_domain}. Pingcode.com/api/ {applicationName} / pilotCopy the code

There are two approaches for different business components of urls:

  • The child application passes a Url when it uses this block
  • The business component library reads the configuration of the child application and automatically concatenates this Url

The two methods are similar, there is no essential difference, because each child application initializers will add a global parameter StyxConfig instance, including the application identity name, so in the Pilot encapsulation process directly read this identity, so we use the second scheme.

PingCode front-end state management technology

Angular services and Rxjs are basically the core technologies that make up PingCode’s front-end state management. They are used in components to get service instances (stores) through dependency injection and update pushes from subscribing to Store data (Rxjs streams). The Store itself also provides snapshots that can be accessed directly without subscription if you only need to read them once.

The specific implementation code has been completely open source, here is not to do details, interested can refer to:

Github Storage: github.com/tethys-org/…

Address: document tethys – org. Making. IO/store/guide…

Complex state management

There is a relatively complex logic in Wiki, that is, the synchronization of page data update, which always had problems in the past. After a reconstruction last year, there are few problems now. We adopt the bus mode: the overall idea and implementation are very intuitive and concise, and here is a brief introduction for your reference:

Modify origin: Page data modification sources, including title, content, and publisher.

Page Event Bus: A global bus for page data modification, based on publish and subscribe mode.

Sync Target: The store corresponding to the change to be synchronized, that is, the part of the page where the data change is to be synchronized, which can be infinite.

The page-event-bus receives the notification of the modification event and forwards it to the subscriber. If you need to subscribe to the page-event-bus, you only need to subscribe to the Page-event-bus. The overall relationship is: many to 1, 1 to many again.

For this previously wrote an article on technology: zhuanlan.zhihu.com/p/339607139, but at the end of the core ideas should be above said bus mode.

Pop-up layer

The pop-up layer is what I particularly want to talk about and what I am most familiar with. I feel that all the interactions in our PingCode product are pop-ups.

Pop-up layer interaction

Pop-up layer interaction is ubiquitous in PingCode. Most of the interaction is based on the pop-up layer, such as Wiki page details pop-up box, page editing, Project work item details, Portal expansion sidebar, content selection in pop-up details, Pilot switching search in the application, The pop-up layer is important, as are reminders of operation success, failure, mention selection, association selection, etc.

From another point of view, pop up layer is out of routing interaction is another kind of form, it can be done from the current routing interaction, is one of the important features, single-page application interaction from routing means it can be across application, across module interaction of a kind of form, for example, we can open another application in an application page for details.

Pop-up layer technique

Most of the application pop-up layer technologies in the Angular framework are implemented based on CDK Overlay and CDK Portals.

Overlay is responsible for the overall structure and interaction processing of the pop-up layer. Protals is mainly an encapsulation of Angular dynamically created components. Because the pop-up layer is based on dynamically created components, the declaration cycle of the pop-up layer components is controlled to some extent. Also, components that are popped up through overlays break out of the original document structure in the DOM.

The Angular framework’s popup layer is a paradigm for framework design. It works beautifully with frameworks and developers use it as if it were code.

Usage scenarios

① Dialog – Open the Wiki page details

code

② Popover – Open the sorting menu

code

Structure analysis

CDK Overlay struct – Dialog

CDK Overlay structure-popover

The popover popover layer and dialog popover layer share the same container because they are both implemented based on overlay

② Bootstrap pop-up layer structure – Modal

Multiple pop-up layers need to accumulate z-index. The new pop-up layer is +50 based on the z-index of the previous pop-up layer, which is maintained at the bottom of bootstrap.

③ Ant construction – Modal

③ Ant structure – Popover

The Ant pop-up layer is structured like the CDK Overlay, and the implementation is abstracted out of the component library: rC-Dialog, but not nearly as encapsulated as the CDK Overlay.

Pop-up layer independence

As mentioned earlier in The pop-up layer, Angular’s pop-up layer is more independent.

Component creation and destruction are maintained by the developer and are to some extent out of the Control of the Angular framework. Shows that Angular’s pop-up layer is component-independent.

This component represents a pop-up layer component that is not created when the application is initialized, but when it needs to be popped. This is different from Ant Design. Here’s a quick look at how React uses Modal and Popover.

Modal using

Modal prompt

Popover use

React is usually used as a component, while Angular is used as a service. I personally think it’s more elegant to use Modal as a service. Of course Modal also provides a global invocation of a service. However, the usage scenarios are limited (info, success, error, etc.), and Ant Desgin’s use of popovers is organized in the form of component wraps. In this case, popovers are similar to virtual components. Interactively enhance the included DOM (similar to what Angular directives do).

② DOM independence – Compared with Bootstrap structure, Overlay implementation of the pop-up layer in DOM structure is separated from the original document structure, layout is not affected by the pop-up source.

The DOM structure of Bootstrap is theoretically restricted by the layout. For example, because of the layout or scrollbar, the overflow: Hidden style may be set in the page, so the pop-up layer will be affected. Of course, Bootstrap should also have a solution to this problem.

Position strategy, scrolling strategy – The origin of position strategy and scrolling strategy is probably the product of [② DOM independence].

Location policy – The latest CDK code contains only global and flexible location policies. Global is relatively simple and corresponds to the global pop-up Dialog scenario (DOM should be independent in this scenario, because the pop-up box position is fixed globally). Flexible is more complex. Its position should be based on the relative position of the existing DOM. The core of flexible is to solve the popup problem based on the location source, because the popup layer DOM is removed from the original document structure. Therefore, the pop-up position needs to be calculated based on the original position, and flexible corresponds to the code encapsulation of this scenario.

Scrolling strategy – Because the pop-up layer is DOM independent, the pop-up layer does not automatically follow when the page scrolls by default, which is essentially different from Bootstrap. So Overlay is designed specifically to handle this scenario with a scrolling strategy. You can configure a scrolling strategy: scroll follow, scroll block, scroll close.

The Angular CDK Overlay is a complete solution. It removes the POPup layer DOM structure from the original document, loosens the coupling between the popup layer component DOM and the original document structure, and then designs a set of solutions to the problems caused by this structure. In addition to Dialog, Popover pop-up layer scenario, its application also includes Select components, Tootip components, Dropdown components, etc., to achieve the underlying technology of the great unity, this is too strong.

The Art of Design

In fact, the essence of Overlay is code design, including DOM structure design, component structure design, instance Ref structure design, scene strategy design, style design, etc.

In our practice, a pop-up layer solution based on overlays is generally more elegant, less problematic, and easier to write loosely-coupled code. The structural independence of the DOM removes the dependency on the original document layout, and its use as a service allows for more flexibility in the invocation of the pop-up layer, making it a perfect design.

conclusion

I have shared some things that are not systematic, which is my humble opinion. I hope I can give you some thoughts or resonance.

Generally speaking, WE still hope that in the actual development process, in addition to paying attention to the use of the basic framework (library), but also to think more about some thinking, more to see some excellent open source library architecture, trace the root of the problem, these architecture, library for their own use, rather than falling into the framework or logic of the mire.

At present, front-end development is no longer as simple as pure JavaScript and HTML era, and the current technical system is generally very complex. For example, our company has self-developed component library, business component library, state management and micro front-end, which poses certain challenges to every front-end developer in the company. When a defect occurs in a product, the chain of problems can be long, but this is also an opportunity. The more complex the scenario, the more the developer can extend the technical depth.

I the deepest impression is I just learn to React before it no matter how to don’t understand state management is zha, interview is often well, come to our company in order to be truly understand, I think one of the most important point is that in the actual development didn’t really match the scene, when encountered are available, without any scenario, natural essence can’t understand.